Skip to content

Commit 1bc0929

Browse files
committed
Update pyarrow.Table docs and tests
Signed-off-by: Andrew Stein <steinlink@gmail.com>
1 parent f736ce9 commit 1bc0929

5 files changed

Lines changed: 102 additions & 41 deletions

File tree

docs/md/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
- [Overview](./explanation/python.md)
5555
- [Installation](./how_to/python/installation.md)
5656
- [Loading data into a `Table`](./how_to/python/table.md)
57+
- [`pandas`, `polars` and `pyarrow` integration](./how_to/python/table_data.md)
5758
- [Callbacks and events](./how_to/python/callbacks.md)
5859
- [Multithreading](./how_to/python/multithreading.md)
5960
- [Hosting a WebSocket server](./how_to/python/websocket.md)
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# DataFrame and Arrow Compatibility
2+
3+
`perspective-python` accepts a `Table` constructor argument from any of the
4+
common Python columnar data libraries. In all three cases, `perspective.table`
5+
(and `Table.update()`) consume the input directly — there is no need to
6+
serialize to Apache Arrow IPC bytes yourself. However, note is
7+
still the most efficient way to bulk load data into `Table`.
8+
9+
## PyArrow
10+
11+
```python
12+
import pyarrow as pa
13+
import perspective
14+
15+
arrow_table = pa.table({
16+
"int": pa.array([1, 2, 3], type=pa.int64()),
17+
"float": pa.array([1.5, 2.5, 3.5], type=pa.float64()),
18+
"string": pa.array(["a", "b", "c"], type=pa.string()),
19+
})
20+
21+
table = perspective.table(arrow_table)
22+
```
23+
24+
The same applies to `Table.update()`:
25+
26+
```python
27+
table.update(arrow_table)
28+
```
29+
30+
If you have Arrow data already in IPC format (e.g. read from disk, received
31+
over the wire, or produced by another tool), pass the raw `bytes` directly —
32+
both stream and file formats are auto-detected:
33+
34+
```python
35+
with open("data.arrow", "rb") as f:
36+
table = perspective.table(f.read())
37+
```
38+
39+
## Polars
40+
41+
```python
42+
import polars as pl
43+
import perspective
44+
45+
df = pl.DataFrame({
46+
"a": [1, 2, 3, 4, 5],
47+
"b": ["x", "y", "z", "x", "y"],
48+
})
49+
50+
table = perspective.table(df)
51+
```
52+
53+
Internally, the `DataFrame` is converted to a `pyarrow.Table` before
54+
ingestion, so Polars columns inherit the Arrow type mapping above.
55+
56+
See also Perspective [Virtual Server support for `polars.DataFrame`](./virtual_server/polars.md)
57+
58+
## Pandas
59+
60+
`pandas.DataFrame` is supported via `pyarrow.Table.from_pandas`, which
61+
dictates behavior including type support — see the
62+
[pyarrow pandas docs](https://arrow.apache.org/docs/python/pandas.html) for
63+
details on which pandas dtypes round-trip cleanly.
64+
65+
```python
66+
from datetime import date, datetime
67+
import numpy as np
68+
import pandas as pd
69+
import perspective
70+
71+
data = pd.DataFrame({
72+
"int": np.arange(100),
73+
"float": [i * 1.5 for i in range(100)],
74+
"bool": [True for i in range(100)],
75+
"date": [date.today() for i in range(100)],
76+
"datetime": [datetime.now() for i in range(100)],
77+
"string": [str(i) for i in range(100)],
78+
})
79+
80+
table = perspective.table(data, index="float")
81+
```

rust/perspective-python/perspective/tests/conftest.py

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -109,26 +109,17 @@ def make_arrow(names, data, types=None, legacy=False):
109109

110110
@staticmethod
111111
def make_arrow_from_pandas(df, schema=None, legacy=False):
112-
"""Create an arrow binary from a Pandas dataframe.
112+
"""Create a pyarrow Table from a Pandas dataframe.
113113
114114
Args:
115115
df (:obj:`pandas.DataFrame`)
116116
schema (:obj:`pyarrow.Schema`)
117-
legacy (bool): if True, use legacy IPC format (pre-pyarrow 0.15). Defaults to False.
117+
legacy (bool): unused; retained for backwards compatibility.
118118
119119
Returns:
120-
bytes : a bytes object containing the arrow-serialized output.
120+
pyarrow.Table
121121
"""
122-
stream = pa.BufferOutputStream()
123-
table = pa.Table.from_pandas(df, schema=schema)
124-
125-
writer = pa.RecordBatchStreamWriter(
126-
stream, table.schema
127-
)
128-
129-
writer.write_table(table)
130-
writer.close()
131-
return stream.getvalue().to_pybytes()
122+
return pa.Table.from_pandas(df, schema=schema)
132123

133124
@staticmethod
134125
def make_dictionary_arrow(names, data, types=None, legacy=False):

rust/perspective-python/perspective/tests/table/test_table_arrow.py

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,8 @@
1515
import pandas as pd
1616
from perspective.tests.conftest import Util
1717
import pyarrow as pa
18-
import pyarrow.ipc as ipc
1918
from datetime import date, datetime
2019
import perspective as psp
21-
import io
2220

2321

2422
client = psp.Server().new_local_client()
@@ -49,14 +47,23 @@
4947

5048
ALL_INTEGERS_TABLE = pa.Table.from_pydict(ALL_INTEGERS_DATA)
5149

52-
bytes_io = io.BytesIO()
53-
with ipc.new_stream(bytes_io, ALL_INTEGERS_TABLE.schema) as stream:
54-
stream.write_table(ALL_INTEGERS_TABLE)
55-
ALL_INTEGERS_ARROW = bytes_io.getvalue()
5650

5751
class TestTableArrow(object):
5852
def test_table_with_integer_types(self):
59-
tbl = Table(ALL_INTEGERS_ARROW)
53+
tbl = Table(ALL_INTEGERS_TABLE)
54+
assert tbl.size() == 3
55+
assert tbl.schema() == {
56+
"int8": "integer",
57+
"int16": "integer",
58+
"int32": "integer",
59+
"int64": "integer",
60+
"uint8": "integer",
61+
"uint16": "integer",
62+
"uint32": "integer",
63+
"uint64": "integer",
64+
"float32": "float",
65+
"float64": "float",
66+
}
6067
for k, values in ALL_INTEGERS_DATA.items():
6168
v = tbl.view(filter=[[k, "==", values[0].as_py()]])
6269
assert len(v.to_json()) == 1
@@ -481,17 +488,7 @@ def test_table_arrow_loads_arrow_from_df_with_nan(self):
481488

482489
assert arrow_table["a"].null_count == 4
483490

484-
# write arrow to stream
485-
stream = pa.BufferOutputStream()
486-
writer = pa.RecordBatchStreamWriter(
487-
stream, arrow_table.schema
488-
)
489-
writer.write_table(arrow_table)
490-
writer.close()
491-
arrow = stream.getvalue().to_pybytes()
492-
493-
# load
494-
tbl = Table(arrow)
491+
tbl = Table(arrow_table)
495492
assert tbl.size() == 8
496493

497494
# check types

rust/perspective-python/perspective/tests/table/test_update.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,7 @@ def test_update_with_missing_or_null_values(self):
3232

3333
arrow_table = pa.Table.from_pandas(data, preserve_index=False)
3434

35-
# write arrow to stream
36-
stream = pa.BufferOutputStream()
37-
writer = pa.RecordBatchStreamWriter(
38-
stream, arrow_table.schema
39-
)
40-
writer.write_table(arrow_table)
41-
writer.close()
42-
arrow = stream.getvalue().to_pybytes()
43-
44-
tbl.update(arrow)
35+
tbl.update(arrow_table)
4536
assert tbl.size() == 2
4637
assert tbl.view().to_records() == [{"a": "1", "b": ""}, {"a": "3", "b": "4"}]
4738

0 commit comments

Comments
 (0)