Converting to and from pandas and NumPy

PyArrow · Essentials · 12 min

Most PyArrow work starts or ends in pandas or NumPy, and copying large data by accident is a common cause of slow code.

pa.Table.from_pandas(df) converts a pandas DataFrame into a Table; table.to_pandas() goes back. A non-default pandas index is stored as a column plus schema metadata, so it round-trips. Pass preserve_index=False to drop it.

Type mapping is mostly natural, with a few rules:

  • Arrow integer columns with nulls become float64 in pandas by default (NaN for null), because NumPy ints have no null. Use types_mapper=pd.ArrowDtype to get Arrow-backed pandas columns that keep exact types and nulls with no conversion.
  • Arrow strings become Python object or pandas string columns.
  • Timestamps keep their unit and time zone.

For NumPy, pa.array(np_array) and arr.to_numpy() are zero-copy for numeric data without nulls. With nulls, or for strings, a copy is needed, so pass zero_copy_only=False. A zero-copy result is read-only, because Arrow memory is immutable.

pa.Table.from_pandas uses multiple threads, and conversions of large numeric columns are fast. Converting in a loop, row by row, is the slow path; always convert whole columns or tables. In pandas 2 and 3, pd.read_parquet(..., dtype_backend="pyarrow") and engine="pyarrow" use PyArrow underneath.

Example

import numpy as np
import pandas as pd
import pyarrow as pa

df = pd.DataFrame({"n": [1, 2, 3], "s": ["x", "y", None]})
t = pa.Table.from_pandas(df, preserve_index=False)
print(t.schema.types)   # [DataType(int64), DataType(large_string)]  (pandas 3 str)

with_null = pa.table({"n": pa.array([1, None, 3])})
print(with_null.to_pandas()["n"].dtype)                          # float64
print(with_null.to_pandas(types_mapper=pd.ArrowDtype)["n"].dtype)  # int64[pyarrow]

x = np.arange(5, dtype=np.int64)
arr = pa.array(x)                         # zero-copy from NumPy
back = arr.to_numpy()                     # zero-copy, read-only view
print(back.flags.writeable)               # False
nulls = pa.array([1, None, 3])
try:
    nulls.to_numpy()                      # zero_copy_only=True by default
except pa.ArrowInvalid:
    print("nulls need a copy")
print(nulls.to_numpy(zero_copy_only=False))   # [ 1. nan  3.]

Remember

  • Integer columns with nulls become float64 in pandas unless you use ArrowDtype
  • NumPy conversion is zero-copy only for numeric data without nulls
  • Convert whole tables or columns, never row by row

Watch out for

  • Writing into an array returned by zero-copy to_numpy(); it is read-only.

Check yourself

  1. You convert an Arrow int64 column containing nulls with to_pandas(). What dtype do you get by default?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: arrow.apache.org/docs/python/pandas.html