How to convert to/from Arrow and Parquet

The Apache Arrow data format is very similar to Awkward Array’s, but they’re not exactly the same. As such, arrays can usually be shared without copying, but not always.

The Apache Parquet file format has strong connections to Arrow with a large overlap in available tools, and while it’s also a columnar format like Awkward and Arrow, it is implemented in a different way, which emphasizes compact storage over random access.

import awkward as ak
import pyarrow as pa
import pyarrow.csv
import urllib.request

From Arrow to Awkward

The function for Arrow → Awkward conversion is ak.from_arrow.

The argument to this function can be any of the following types from the pyarrow library:

  • pyarrow.lib.Array

  • pyarrow.lib.ChunkedArray

  • pyarrow.lib.RecordBatch

  • pyarrow.lib.Table

and they are converted into non-partitioned, non-virtual Awkward Arrays. (Any disjoint chunks in the Arrow array are concatenated.)

pa_array = pa.array([[1.1, 2.2, 3.3], [], [4.4, 5.5]])
pa_array
<pyarrow.lib.ListArray object at 0x7f765e263940>
[
  [
    1.1,
    2.2,
    3.3
  ],
  [],
  [
    4.4,
    5.5
  ]
]
ak.from_arrow(pa_array)
<Array [[1.1, 2.2, 3.3], [], [4.4, 5.5]] type='3 * var * ?float64'>

Here is an example of an Arrow Table, derived from CSV. (Printing a table shows its field types.)

pokemon = urllib.request.urlopen("https://gist.githubusercontent.com/armgilles/194bcff35001e7eb53a2a8b441e8b2c6/raw/92200bc0a673d5ce2110aaad4544ed6c4010f687/pokemon.csv")
table = pyarrow.csv.read_csv(pokemon)
table
pyarrow.Table
#: int64
Name: string
Type 1: string
Type 2: string
Total: int64
HP: int64
Attack: int64
Defense: int64
Sp. Atk: int64
Sp. Def: int64
Speed: int64
Generation: int64
Legendary: bool

Awkward Array doesn’t make a deep distinction between “arrays” and “tables” the way Arrow does: the Awkward equivalent of an Arrow table is just an Awkward Array of record type.

array = ak.from_arrow(table)
array
<Array [{'#': 1, ... Legendary: True}] type='800 * {"#": ?int64, "Name": option[...'>

The Awkward equivalent of Arrow’s schemas is ak.type.

ak.type(array)
800 * {"#": ?int64, "Name": option[string], "Type 1": option[string], "Type 2": option[string], "Total": ?int64, "HP": ?int64, "Attack": ?int64, "Defense": ?int64, "Sp. Atk": ?int64, "Sp. Def": ?int64, "Speed": ?int64, "Generation": ?int64, "Legendary": ?bool}
ak.to_list(array[0])
{'#': 1,
 'Name': 'Bulbasaur',
 'Type 1': 'Grass',
 'Type 2': 'Poison',
 'Total': 318,
 'HP': 45,
 'Attack': 49,
 'Defense': 49,
 'Sp. Atk': 65,
 'Sp. Def': 65,
 'Speed': 45,
 'Generation': 1,
 'Legendary': False}

This array is ready for data analysis.

array[array.Legendary].Attack - array[array.Legendary].Defense
<Array [-15, 5, 10, 20, ... 50, 50, 100, -10] type='65 * ?int64'>

From Awkward to Arrow

The function for Awkward → Arrow conversion is ak.to_arrow. This function always returns

  • pyarrow.lib.Array

type.

ak_array = ak.Array([{"x": 1.1, "y": [1]}, {"x": 2.2, "y": [1, 2]}, {"x": 3.3, "y": [1, 2, 3]}])
ak_array
<Array [{x: 1.1, y: [1]}, ... y: [1, 2, 3]}] type='3 * {"x": float64, "y": var *...'>
pa_array = ak.to_arrow(ak_array)
pa_array
<pyarrow.lib.StructArray object at 0x7f765e293d60>
-- is_valid: all not null
-- child 0 type: double
  [
    1.1,
    2.2,
    3.3
  ]
-- child 1 type: large_list<item: int64 not null>
  [
    [
      1
    ],
    [
      1,
      2
    ],
    [
      1,
      2,
      3
    ]
  ]
type(pa_array)
pyarrow.lib.StructArray
isinstance(pa_array, pa.lib.Array)
True

If you need pyarrow.lib.RecordBatch, you can build this using pyarrow:

pa_batch = pa.RecordBatch.from_arrays([
    ak.to_arrow(ak_array.x),
    ak.to_arrow(ak_array.y),
], ["x", "y"])
pa_batch
pyarrow.RecordBatch
x: double
y: large_list<item: int64 not null>
  child 0, item: int64 not null

If you need pyarrow.lib.Table, you can build this using pyarrow:

pa_table = pa.Table.from_batches([pa_batch])
pa_table
pyarrow.Table
x: double
y: large_list<item: int64 not null>
  child 0, item: int64 not null

The columns of this Table are pa.lib.ChunkedArray instances:

pa_table[0]
<pyarrow.lib.ChunkedArray object at 0x7f765e291c20>
[
  [
    1.1,
    2.2,
    3.3
  ]
]
pa_table[1]
<pyarrow.lib.ChunkedArray object at 0x7f765c5d4450>
[
  [
    [
      1
    ],
    [
      1,
      2
    ],
    [
      1,
      2,
      3
    ]
  ]
]

Arrow Tables are closely aligned with Pandas DataFrames, so

pa_table.to_pandas()
x y
0 1.1 [1]
1 2.2 [1, 2]
2 3.3 [1, 2, 3]

shares memory as much as is possible, which can be faster than constructing Pandas directly.

Reading/writing data streams and random access files

Arrow has several methods for interfacing to data streams and disk-bound files, see the official documentation for instructions.

When following those instructions, remember that ak.from_arrow can accept pyarrow Arrays, ChunkedArrays, RecordBatches, and Tables, but ak.to_arrow only returns Arrow Arrays.

For instance, when writing to an IPC stream, Arrow requires RecordBatches, so you need to build them:

ak_array = ak.Array([{"x": 1.1, "y": [1]}, {"x": 2.2, "y": [1, 2]}, {"x": 3.3, "y": [1, 2, 3]}])
ak_array
<Array [{x: 1.1, y: [1]}, ... y: [1, 2, 3]}] type='3 * {"x": float64, "y": var *...'>
first_batch = pa.RecordBatch.from_arrays([
    ak.to_arrow(ak_array.x),
    ak.to_arrow(ak_array.y),
], ["x", "y"])
first_batch.schema
x: double
y: large_list<item: int64 not null>
  child 0, item: int64 not null
sink = pa.BufferOutputStream()
writer = pa.ipc.new_stream(sink, first_batch.schema)

writer.write_batch(first_batch)

for i in range(5):
    next_batch = pa.RecordBatch.from_arrays([
        ak.to_arrow(ak_array.x),
        ak.to_arrow(ak_array.y),
    ], ["x", "y"])
    
    writer.write_batch(next_batch)

writer.close()
bytes(sink.getvalue())
b'\xff\xff\xff\xff\xe8\x00\x00\x00\x10\x00\x00\x00\x00\x00\n\x00\x0c\x00\x06\x00\x05\x00\x08\x00\n\x00\x00\x00\x00\x01\x04\x00\x0c\x00\x00\x00\x08\x00\x08\x00\x00\x00\x04\x00\x08\x00\x00\x00\x04\x00\x00\x00\x02\x00\x00\x00\x8c\x00\x00\x00\x04\x00\x00\x00\x8c\xff\xff\xff\x00\x00\x01\x15\x14\x00\x00\x00\x1c\x00\x00\x00\x04\x00\x00\x00\x01\x00\x00\x00$\x00\x00\x00\x01\x00\x00\x00y\x00\x00\x00\x04\x00\x04\x00\x04\x00\x00\x00\x10\x00\x14\x00\x08\x00\x00\x00\x07\x00\x0c\x00\x00\x00\x10\x00\x10\x00\x00\x00\x00\x00\x00\x02\x10\x00\x00\x00 \x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00item\x00\x00\x00\x00\x08\x00\x0c\x00\x08\x00\x07\x00\x08\x00\x00\x00\x00\x00\x00\x01@\x00\x00\x00\x10\x00\x14\x00\x08\x00\x06\x00\x07\x00\x0c\x00\x00\x00\x10\x00\x10\x00\x00\x00\x00\x00\x01\x03\x10\x00\x00\x00\x18\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00x\x00\x06\x00\x08\x00\x06\x00\x06\x00\x00\x00\x00\x00\x02\x00\xff\xff\xff\xff\xe8\x00\x00\x00\x14\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x16\x00\x06\x00\x05\x00\x08\x00\x0c\x00\x0c\x00\x00\x00\x00\x03\x04\x00\x18\x00\x00\x00h\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\x00\x18\x00\x0c\x00\x04\x00\x08\x00\n\x00\x00\x00|\x00\x00\x00\x10\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x008\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x008\x00\x00\x00\x00\x00\x00\x000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x9a\x99\x99\x99\x99\x99\xf1?\x9a\x99\x99\x99\x99\x99\x01@ffffff\n@\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xe8\x00\x00\x00\x14\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x16\x00\x06\x00\x05\x00\x08\x00\x0c\x00\x0c\x00\x00\x00\x00\x03\x04\x00\x18\x00\x00\x00h\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\x00\x18\x00\x0c\x00\x04\x00\x08\x00\n\x00\x00\x00|\x00\x00\x00\x10\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x008\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x008\x00\x00\x00\x00\x00\x00\x000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x9a\x99\x99\x99\x99\x99\xf1?\x9a\x99\x99\x99\x99\x99\x01@ffffff\n@\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xe8\x00\x00\x00\x14\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x16\x00\x06\x00\x05\x00\x08\x00\x0c\x00\x0c\x00\x00\x00\x00\x03\x04\x00\x18\x00\x00\x00h\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\x00\x18\x00\x0c\x00\x04\x00\x08\x00\n\x00\x00\x00|\x00\x00\x00\x10\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x008\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x008\x00\x00\x00\x00\x00\x00\x000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x9a\x99\x99\x99\x99\x99\xf1?\x9a\x99\x99\x99\x99\x99\x01@ffffff\n@\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xe8\x00\x00\x00\x14\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x16\x00\x06\x00\x05\x00\x08\x00\x0c\x00\x0c\x00\x00\x00\x00\x03\x04\x00\x18\x00\x00\x00h\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\x00\x18\x00\x0c\x00\x04\x00\x08\x00\n\x00\x00\x00|\x00\x00\x00\x10\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x008\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x008\x00\x00\x00\x00\x00\x00\x000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x9a\x99\x99\x99\x99\x99\xf1?\x9a\x99\x99\x99\x99\x99\x01@ffffff\n@\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xe8\x00\x00\x00\x14\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x16\x00\x06\x00\x05\x00\x08\x00\x0c\x00\x0c\x00\x00\x00\x00\x03\x04\x00\x18\x00\x00\x00h\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\x00\x18\x00\x0c\x00\x04\x00\x08\x00\n\x00\x00\x00|\x00\x00\x00\x10\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x008\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x008\x00\x00\x00\x00\x00\x00\x000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x9a\x99\x99\x99\x99\x99\xf1?\x9a\x99\x99\x99\x99\x99\x01@ffffff\n@\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xe8\x00\x00\x00\x14\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x16\x00\x06\x00\x05\x00\x08\x00\x0c\x00\x0c\x00\x00\x00\x00\x03\x04\x00\x18\x00\x00\x00h\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\x00\x18\x00\x0c\x00\x04\x00\x08\x00\n\x00\x00\x00|\x00\x00\x00\x10\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x008\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x008\x00\x00\x00\x00\x00\x00\x000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x9a\x99\x99\x99\x99\x99\xf1?\x9a\x99\x99\x99\x99\x99\x01@ffffff\n@\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\x00'

But when reading them back, we can just pass the RecordBatches (yielded by the RecordBatchStreamReader reader), we can just pass them to ak.from_arrow:

reader = pa.ipc.open_stream(sink.getvalue())
reader.schema
x: double
y: large_list<item: int64 not null>
  child 0, item: int64 not null
for batch in reader:
    print(repr(ak.from_arrow(batch)))
<Array [{x: 1.1, y: [1]}, ... y: [1, 2, 3]}] type='3 * {"x": ?float64, "y": opti...'>
<Array [{x: 1.1, y: [1]}, ... y: [1, 2, 3]}] type='3 * {"x": ?float64, "y": opti...'>
<Array [{x: 1.1, y: [1]}, ... y: [1, 2, 3]}] type='3 * {"x": ?float64, "y": opti...'>
<Array [{x: 1.1, y: [1]}, ... y: [1, 2, 3]}] type='3 * {"x": ?float64, "y": opti...'>
<Array [{x: 1.1, y: [1]}, ... y: [1, 2, 3]}] type='3 * {"x": ?float64, "y": opti...'>
<Array [{x: 1.1, y: [1]}, ... y: [1, 2, 3]}] type='3 * {"x": ?float64, "y": opti...'>

Reading/writing the Feather file format

Feather is a lightweight file format that puts Arrow Tables in disk-bound files, see the official documentation for instructions.

When following those instructions, remember that ak.from_arrow can accept Arrow Tables, but ak.to_arrow only returns Arrow Arrays.

For instance, when writing to a Feather file, Arrow requires Tables, so you need to build them:

ak_array = ak.Array([{"x": 1.1, "y": [1]}, {"x": 2.2, "y": [1, 2]}, {"x": 3.3, "y": [1, 2, 3]}])
ak_array
<Array [{x: 1.1, y: [1]}, ... y: [1, 2, 3]}] type='3 * {"x": float64, "y": var *...'>
pa_batch = pa.RecordBatch.from_arrays([
    ak.to_arrow(ak_array.x),
    ak.to_arrow(ak_array.y),
], ["x", "y"])

pa_table = pa.Table.from_batches([pa_batch])
pa_table
pyarrow.Table
x: double
y: large_list<item: int64 not null>
  child 0, item: int64 not null
import pyarrow.feather

pyarrow.feather.write_feather(pa_table, "/tmp/example.feather")

But when reading them back, we can just pass the Arrow Table to ak.from_arrow.

from_feather = pyarrow.feather.read_table("/tmp/example.feather")
from_feather
pyarrow.Table
x: double
y: large_list<item: int64 not null>
  child 0, item: int64 not null
type(from_feather)
pyarrow.lib.Table
ak.from_arrow(from_feather)
<Array [{x: 1.1, y: [1]}, ... y: [1, 2, 3]}] type='3 * {"x": ?float64, "y": opti...'>

Reading/writing the Parquet file format

With data converted to and from Arrow, it can then be saved and loaded from Parquet files. Arrow’s official Parquet documentation provides instructions for converting Arrow to and from Parquet, but Parquet is a sufficiently important file format that Awkward has specialized functions for it.

The ak.to_parquet function writes Awkward Arrays as Parquet files. It has relatively few options.

ak_array = ak.Array([{"x": 1.1, "y": [1]}, {"x": 2.2, "y": [1, 2]}, {"x": 3.3, "y": [1, 2, 3]}])
ak_array
<Array [{x: 1.1, y: [1]}, ... y: [1, 2, 3]}] type='3 * {"x": float64, "y": var *...'>
ak.to_parquet(ak_array, "/tmp/example.parquet")

The ak.from_parquet function reads Parquet files as Awkward Arrays, with quite a few more options. Basic usage just gives you the Awkward Array back.

ak.from_parquet("/tmp/example.parquet")
<Array [{x: 1.1, y: [1]}, ... y: [1, 2, 3]}] type='3 * {"x": float64, "y": var *...'>

(Note that the type is not exactly the same: all fields are nullable—potentially have missing data—in Parquet, so the numbers become numbers-or-None after passing through Parquet.)

Since the data in a Parquet file may be huge, there are columns and row_groups options to read back only part of the file.

  • Parquet’s “columns” correspond to Awkward’s record “fields,” though Parquet columns cannot be nested.

  • Parquet’s “row groups” are ranges of contiguous elements, such as “1000-2000”. They correspond to Awkward’s “partitioning.” Neither Parquet row groups nor Awkward partitions can be nested.

For instance, the expression

ak.from_parquet("/tmp/example.parquet", columns=["x"])
<Array [{x: 1.1}, {x: 2.2}, {x: 3.3}] type='3 * {"x": float64}'>

Doesn’t read column "y".

To take advantage of this, you might need to “explode” nested records when writing so that they become top-level columns.

ak_array = ak.Array([
    [{"x": 1.1, "y": [1]}, {"x": 2.2, "y": [1, 2]}, {"x": 3.3, "y": [1, 2, 3]}],
    [],
    [{"x": 4.4, "y": [1, 2, 3, 4]}, {"x": 5.5, "y": [1, 2, 3, 4, 5]}],
])
ak_array
<Array [[{x: 1.1, y: [1]}, ... 2, 3, 4, 5]}]] type='3 * var * {"x": float64, "y"...'>
ak.to_parquet(ak_array, "/tmp/example-exploded.parquet", explode_records=True)

(At the time of writing, this array can’t be written by pyarrow without exploding records anyway.)

exploded = ak.from_parquet("/tmp/example-exploded.parquet")
exploded
<Array [{x: [1.1, 2.2, 3.3], ... 3, 4, 5]]}] type='3 * {"x": var * float64, "y":...'>

The data type is different:

ak.type(ak_array)
3 * var * {"x": float64, "y": var * int64}
ak.type(exploded)
3 * {"x": var * float64, "y": var * var * int64}

but can be reconciled with ak.zip:

ak.type(ak.zip({"x": exploded.x, "y": exploded.y}))
3 * var * var * {"x": float64, "y": int64}

(apart from the option due to passing through Parquet).

Lazy Parquet file reading

The reason for limiting the columns or row_groups is to reduce the amount of data that needs to be read. These parameters of the ak.from_parquet function are helpful if you know in advance which columns and row groups you need, but reading can also be triggered by use with lazy=True.

Evaluating the following line only reads the file’s metadata, none of the columns or row groups:

ak_lazy = ak.from_parquet("/tmp/example.parquet", lazy=True)

We can see this by peeking at the array’s internal representation:

ak_lazy.layout
<RecordArray length="3">
    <field index="0" key="x">
        <VirtualArray cache_key="ak.from_parquet:0:col:x[0]">
            <ArrayGenerator f="<awkward.operations.convert._LazyDatasetGenerator object at 0x7f765e273790>" args="(0, ('x',), 3, {
    "class": "NumpyArray",
    "itemsize": 8,
    "format": "d",
    "primitive": "float64",
    "form_key": "col:x"
}, <ArrayCache mapping="{}"/>, 'ak.from_parquet:0')">
                <length>3</length>
                <form>
                    {
                        "class": "NumpyArray",
                        "itemsize": 8,
                        "format": "d",
                        "primitive": "float64",
                        "form_key": "col:x"
                    }
                </form>
            </ArrayGenerator>
            <ArrayCache mapping="{}"/>
        </VirtualArray>
    </field>
    <field index="1" key="y">
        <VirtualArray cache_key="ak.from_parquet:0:lst:y[0]">
            <ArrayGenerator f="<awkward.operations.convert._LazyDatasetGenerator object at 0x7f765e273790>" args="(0, ('y',), 3, {
    "class": "ListOffsetArray64",
    "offsets": "i64",
    "content": {
        "class": "NumpyArray",
        "itemsize": 8,
        "format": "l",
        "primitive": "int64",
        "form_key": "col:y.list.item"
    },
    "form_key": "lst:y"
}, <ArrayCache mapping="{}"/>, 'ak.from_parquet:0')">
                <length>3</length>
                <form>
                    {
                        "class": "ListOffsetArray64",
                        "offsets": "i64",
                        "content": {
                            "class": "NumpyArray",
                            "itemsize": 8,
                            "format": "l",
                            "primitive": "int64",
                            "form_key": "col:y.list.item"
                        },
                        "form_key": "lst:y"
                    }
                </form>
            </ArrayGenerator>
            <ArrayCache mapping="{}"/>
        </VirtualArray>
    </field>
</RecordArray>

Both fields are VirtualArray nodes, which delay evaluation of an ArrayGenerator.

The array also has an attached cache, which we can query to see that nothing has been read:

ak_lazy.caches
({},)

But when we actually access a field, the data are read from disk:

ak_lazy.x
<Array [1.1, 2.2, 3.3] type='3 * float64'>
ak_lazy.caches
({'ak.from_parquet:0:col:x[0]': <NumpyArray format="d" shape="3" data="1.1 2.2 3.3" at="0x7f762d816040"/>},)
ak_lazy.y
<Array [[1], [1, 2], [1, 2, 3]] type='3 * var * int64'>
ak_lazy.caches
({'ak.from_parquet:0:col:x[0]': <NumpyArray format="d" shape="3" data="1.1 2.2 3.3" at="0x7f762d816040"/>, 'ak.from_parquet:0:lst:y[0]': <ListOffsetArray64>
     <offsets><Index64 i="[0 1 3 6]" offset="0" length="4" at="0x7f762e816140"/></offsets>
     <content><NumpyArray format="l" shape="6" data="1 1 2 1 2 3" at="0x7f762e816080"/></content>
 </ListOffsetArray64>},)

A custom lazy_cache can be supplied: the default is a non-evicting Python dict attached to the output ak.Array but may be any Mapping.

Furthermore, a lazy_cache_key can be supplied to ensure uniqueness of cache keys across processes (the default is unique in process).