PyArrow Python
Produce schema-explicit columnar code whose logical object, batch boundary,
null behavior, memory lifetime, and materialization point are deliberate.
Boundary
Use this skill when the implementation directly uses pyarrow or must expose an
Arrow-compatible boundary. Do not introduce PyArrow merely to perform a small
Python-list or pandas transformation. Keep storage-format questions in scope
only when PyArrow reads or writes them. Arrow Flight servers, Acero internals,
and non-Python implementations require their own guidance.
Classify the object first
| Object | Meaning | Use it for |
|---|
DataType / Field / Schema | Immutable type and named-field contracts, including nullability and metadata. | Pinning a boundary before constructing or scanning data. |
Scalar | One typed value, possibly null. | Kernel arguments and scalar results. |
Array | One contiguous typed column backed by buffers. | A single physical column segment. |
ChunkedArray | One logical column made of zero or more same-typed arrays. | Table columns and multi-source results; chunk count is not row count. |
RecordBatch | Equal-length arrays under one schema. | Bounded transport or processing batches. |
Table | A logical table whose columns may be chunked. | Materialized tabular results that fit the required memory boundary. |
RecordBatchReader | A schema plus a consumable stream of batches. | Streaming interchange when the consumer can process batches once. |
Dataset | A logical collection of fragments with a unified schema. | Discovering and querying multi-file or partitioned data. |
Scanner | A dataset scan with projection, filter, and batching options bound. | Deferred dataset execution and bounded batch iteration. |
Arrays and schemas are immutable. A Table is materialized but may be
physically chunked; combine_chunks() can allocate and is not routine cleanup.
A Dataset describes sources, while a Scanner describes the read. Calling
to_table() materializes all selected rows; to_batches() preserves a batch
boundary. Read the object and memory model when
chunking, buffers, ownership, dictionaries, nested types, or zero-copy claims
matter.
Ordered workflow
- Recover the boundary contract: input object, output object, schema, field
nullability, row order if required, batch size, and ownership lifetime.
- Choose
Array, RecordBatch, Table, RecordBatchReader, Dataset, or
Scanner from that contract; do not default every job to Table.
- Pin types where empty input, identifiers, timestamps, decimals, nested data,
dictionary encoding, or cross-language interchange make inference unsafe.
- Push dataset projection and predicates into the scan. Materialize only at a
consumer that requires all rows.
- Use
pyarrow.compute kernels for Arrow data. Do not convert to Python rows
or pandas solely to perform a supported kernel operation.
- Test schema equality separately from value equality, and include empty,
null, multi-chunk, and multi-batch inputs.
- Inspect the installed API before using a drifting option or claiming a
zero-copy, pushdown, or memory property.
Choose by required result
| Required result | Use | Critical condition |
|---|
| One typed column | pa.array(..., type=...) | Conversion and null policy are explicit. |
| One bounded set of equal-length columns | pa.record_batch(..., schema=...) | All arrays conform to the same schema and length. |
| One logical materialized table | pa.table(..., schema=...) | Total selected result fits the boundary. |
| Consumable batches with a common schema | RecordBatchReader | The consumer accepts a one-pass stream. |
| Multi-file discovery and pruning | pyarrow.dataset.dataset | Format, filesystem, partitioning, and schema are known or inspected. |
| Deferred filtered/projected scan | dataset.scanner(...) | Use dataset expressions such as ds.field, not eager masks. |
| Elementwise, vector, aggregate, or selection work | pyarrow.compute | Choose kernel null semantics and checked/safe behavior deliberately. |
| Portable Arrow stream/file serialization | pyarrow.ipc | Choose stream for sequential batches, file for random-access footer metadata. |
| Analytical columnar storage | Parquet APIs or Dataset writer | Schema, partition layout, row groups, overwrite policy, and reader compatibility are explicit. |
Read the operation map before choosing construction,
compute, dataset, serialization, or conversion APIs.
Canonical typed scan
from collections.abc import Iterator
from pathlib import Path
import pyarrow as pa
import pyarrow.dataset as ds
ORDER_SCHEMA = pa.schema(
[
pa.field("order_id", pa.uint64(), nullable=False),
pa.field("country", pa.string(), nullable=False),
pa.field("amount", pa.decimal128(18, 2), nullable=True),
]
)
def scan_orders(root: Path, country: str) -> Iterator[pa.RecordBatch]:
dataset = ds.dataset(
root,
format="parquet",
schema=ORDER_SCHEMA,
partitioning="hive",
)
scanner = dataset.scanner(
columns=["order_id", "country", "amount"],
filter=ds.field("country") == country,
batch_size=65_536,
)
yield from scanner.to_batches()
This returns bounded batches rather than concatenating the whole dataset. The
explicit schema prevents empty or heterogeneous fragments from silently
changing the boundary. Whether the filter is pruned by partitions/statistics or
evaluated after reading depends on the fragments and expression; verify a
performance claim instead of inferring it from the code.
High-risk rules
Types, nulls, and chunks
- Treat field nullability as a contract. A nullable type does not mean nulls
have been checked for domain validity.
- Do not assume
nullable=False validates constructed values. PyArrow table
construction can attach a non-nullable field to an array that still contains
nulls. Build typed arrays, reject a nonzero null_count for required fields,
then construct the batch or table under the exact schema.
- Use safe casts by default. Permit truncation, overflow, temporal-unit loss, or
invalid UTF-8 only when the contract names that loss and a test proves it.
- Arrow null and floating
NaN are distinct. Choose kernel options explicitly
when either affects filtering, aggregation, or equality.
- Compute functions commonly propagate nulls; Boolean kernels also have Kleene
variants. Select the truth table required by the domain.
- Do not assume one chunk. Test with a multi-chunk
Table, and call
combine_chunks() only for an API that requires contiguous buffers or after
measurement justifies the allocation.
- Dictionary, list, large-list, struct, map, decimal, and timestamp types carry
semantics that Python containers do not preserve automatically. Pin the exact
Arrow type at external boundaries.
Datasets and materialization
- Use dataset
Expression objects for scan filters and projections. A Python
function or eager Boolean array cannot provide file/row-group pruning.
- Include partition columns in the declared schema and specify partitioning
when directory names encode values. Do not infer a partition convention from
one path example.
to_table() loads the selected result; use to_batches() or a
RecordBatchReader when total size is not proven safe.
- Preserve scanner/reader lifetime until consumption completes. Do not return a
view over memory whose producer or foreign owner is already gone.
- “Zero-copy” is conditional on type, layout, alignment, mutability, and the
source/consumer protocol. Verify actual buffers or document the allowed copy;
never promise zero-copy from an API name alone.
Conversion and storage
Conversions to pandas, NumPy, or Python objects can allocate, change nullable
representations, or lose nested/dictionary metadata. State the consumer and
test round trips only for properties the contract requires. For Parquet or IPC,
test the persisted schema and representative values after reopening the
artifact; an in-memory pre-write assertion is insufficient. Run the installed-API inspector, read version and
API grounding, and use testing Arrow
boundaries.
Completion gate
Do not declare completion until the public object type and consumption model
match the caller; schema names, order, types, nullability, metadata requirements,
and time-zone/decimal semantics are asserted; empty, null, multi-chunk, and
multi-batch cases pass; dataset projection/filtering occurs before
materialization where required; conversions and writes are reopened or
round-tripped; and every copy, allocation, ordering, and pushdown claim is
measured or qualified. Report skipped checks and their consequence.
References