a result as columns, and the frames built on them - #3
Merged
Merged
Conversation
A result is rows to iterate and columns to hand to something else. The rows were there already. This is the columns: `__arrow_c_stream__` on `Result`, and `to_arrow`, `to_pandas`, `to_polars` and `record_batches` written on top of it. The interface is the PyCapsule one, so the capsule is the whole of it and the four methods are conveniences. `pyarrow.table(result)` and `polars.DataFrame(result)` read a result with nothing here doing anything, which is the point of a protocol every reader implements. `to_pandas` asks for Arrow-backed dtypes, which is what pandas 3 wants anyway and what keeps a string column from becoming a column of Python strings on the way in. `QueryResult` carries no column types, only values, so the type of a column is inferred from what is in it. One type to a column, because that is what Arrow holds. Integers beside floats widen to floats, which is the only mixture that is not refused: anything else says which two types it mixed and at which row. Nulls do not decide anything and a column of nothing but nulls is Arrow's null type, which is also what an empty result's columns are. Nodes, rels and paths go across as structs, and a path is a struct of two lists. A year-month duration goes as a month-day-nano interval with the days and nanoseconds zero, because pyarrow has no array class for the year-month one and raises a KeyError on the type id. A time with an offset is refused, because Arrow has no type for one. Batches are 65,536 rows. The copy runs with the GIL released. On this machine 300,000 rows across three columns take 44 ms as Arrow against 67 ms as Python objects, and a single integer column takes 13.8 ms against 44.5 ms, which is where the difference actually lives: a Python object per cell is the cost, and not the query. Table names are borrowed rather than cloned per row, which took the node column from 27 ms to 17.5 ms over 300,000 rows. 33 tests, skipped when pyarrow is not installed, and the pandas and polars ones skip on their own. The wheel still depends on nothing.
20 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A result is rows to iterate and columns to hand to something else. The rows were there already. This is the columns:
__arrow_c_stream__onResult, andto_arrow,to_pandas,to_polarsandrecord_batcheswritten on top of it.The interface is the PyCapsule one, so the capsule is the whole of it and the four methods are conveniences.
pyarrow.table(result)andpolars.DataFrame(result)read a result with nothing here doing anything, which is the point of a protocol every reader implements.to_pandasasks for Arrow-backed dtypes, which is what pandas 3 wants anyway and what keeps a string column from becoming a column of Python strings on the way in. A requested schema is accepted and ignored, which the protocol allows and which is honest: the result is what it is.The types
QueryResultcarries no column types, only values, so the type of a column is inferred from what is in it. One type to a column, because that is what Arrow holds.int64float64stringboolnulllist<item>structof its fieldsdate32time64[ns]timestamp[ns]timestamp[ns, tz=+05:30]duration[ns]month_day_nano_intervalstruct<table, offset>struct<table, src, dst, ord>struct<nodes: list, rels: list>Integers beside floats widen to floats, which is the only mixture that is not refused: anything else says which two types it mixed and at which row. Nulls do not decide anything and a column of nothing but nulls is Arrow's null type, which is also what an empty result's columns are, so an empty result still has its column names.
Two of these are choices rather than mappings. A year-month duration goes as a month-day-nano interval with the days and nanoseconds zero, because pyarrow has no array class for the year-month one and raises
KeyError: 21on the type id, and neither pandas nor polars accepts any interval type at all. A time with an offset is refused, because Arrow has no type for one.The numbers
Batches are 65,536 rows and the copy runs with the GIL released. On this machine:
That is where the difference actually lives: a Python object per cell is the cost, and not the query. Table names are borrowed rather than cloned per row, which took the node column from 27 ms to 17.5 ms over the same 300,000 rows. The GIL test hands the copy to a thread and spins the main one, and saw 535,456 turns in 83 ms.
Tests
33 of them, skipped as a file when pyarrow is not installed, and the pandas and polars ones skip on their own. Every type above, nulls in every container, the widening and the refusals, the capsule and the protocol both, two batches over 70,000 rows, Arrow and objects agreeing on the same statement, and the GIL. The wheel still depends on nothing:
arrowis asked for withdefault-features = false, features = ["ffi"], which is the C Data Interface and none of the readers and writers.Closes nothing on its own. Ticks the
to_arrowandto_pandasline of tamnd/zu#168.