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:

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 0x7f72bef9bf40>
[
  [
    1.1,
    2.2,
    3.3
  ],
  [],
  [
    4.4,
    5.5
  ]
]
ak.from_arrow(pa_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
----
#: [[1,2,3,3,4,...,719,719,720,720,721]]
Name: [["Bulbasaur","Ivysaur","Venusaur","VenusaurMega Venusaur","Charmander",...,"Diancie","DiancieMega Diancie","HoopaHoopa Confined","HoopaHoopa Unbound","Volcanion"]]
Type 1: [["Grass","Grass","Grass","Grass","Fire",...,"Rock","Rock","Psychic","Psychic","Fire"]]
Type 2: [["Poison","Poison","Poison","Poison","",...,"Fairy","Fairy","Ghost","Dark","Water"]]
Total: [[318,405,525,625,309,...,600,700,600,680,600]]
HP: [[45,60,80,80,39,...,50,50,80,80,80]]
Attack: [[49,62,82,100,52,...,100,160,110,160,110]]
Defense: [[49,63,83,123,43,...,150,110,60,60,120]]
Sp. Atk: [[65,80,100,122,60,...,100,160,150,170,130]]
Sp. Def: [[65,80,100,120,50,...,150,110,130,130,90]]
...

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
[{'#': 1, Name: 'Bulbasaur', 'Type 1': 'Grass', 'Type 2': 'Poison', ...},
 {'#': 2, Name: 'Ivysaur', 'Type 1': 'Grass', 'Type 2': 'Poison', ...},
 {'#': 3, Name: 'Venusaur', 'Type 1': 'Grass', 'Type 2': 'Poison', ...},
 {'#': 3, Name: 'VenusaurMega Venusaur', 'Type 1': 'Grass', 'Type 2': ..., ...},
 {'#': 4, Name: 'Charmander', 'Type 1': 'Fire', 'Type 2': '', Total: 309, ...},
 {'#': 5, Name: 'Charmeleon', 'Type 1': 'Fire', 'Type 2': '', Total: 405, ...},
 {'#': 6, Name: 'Charizard', 'Type 1': 'Fire', 'Type 2': 'Flying', ...},
 {'#': 6, Name: 'CharizardMega Charizard X', 'Type 1': 'Fire', ...},
 {'#': 6, Name: 'CharizardMega Charizard Y', 'Type 1': 'Fire', ...},
 {'#': 7, Name: 'Squirtle', 'Type 1': 'Water', 'Type 2': '', Total: 314, ...},
 ...,
 {'#': 715, Name: 'Noivern', 'Type 1': 'Flying', 'Type 2': 'Dragon', ...},
 {'#': 716, Name: 'Xerneas', 'Type 1': 'Fairy', 'Type 2': '', Total: 680, ...},
 {'#': 717, Name: 'Yveltal', 'Type 1': 'Dark', 'Type 2': 'Flying', ...},
 {'#': 718, Name: 'Zygarde50% Forme', 'Type 1': 'Dragon', 'Type 2': ..., ...},
 {'#': 719, Name: 'Diancie', 'Type 1': 'Rock', 'Type 2': 'Fairy', ...},
 {'#': 719, Name: 'DiancieMega Diancie', 'Type 1': 'Rock', 'Type 2': ..., ...},
 {'#': 720, Name: 'HoopaHoopa Confined', 'Type 1': 'Psychic', ...},
 {'#': 720, Name: 'HoopaHoopa Unbound', 'Type 1': 'Psychic', ...},
 {'#': 721, Name: 'Volcanion', 'Type 1': 'Fire', 'Type 2': 'Water', ...}]
--------------------------------------------------------------------------------
type: 800 * {
    "#": ?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
}

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

ak.type(array)
ArrayType(RecordType([OptionType(NumpyType('int64')), OptionType(ListType(NumpyType('uint8', parameters={'__array__': 'char'}), parameters={'__array__': 'string'})), OptionType(ListType(NumpyType('uint8', parameters={'__array__': 'char'}), parameters={'__array__': 'string'})), OptionType(ListType(NumpyType('uint8', parameters={'__array__': 'char'}), parameters={'__array__': 'string'})), OptionType(NumpyType('int64')), OptionType(NumpyType('int64')), OptionType(NumpyType('int64')), OptionType(NumpyType('int64')), OptionType(NumpyType('int64')), OptionType(NumpyType('int64')), OptionType(NumpyType('int64')), OptionType(NumpyType('int64')), OptionType(NumpyType('bool'))], ['#', 'Name', 'Type 1', 'Type 2', 'Total', 'HP', 'Attack', 'Defense', 'Sp. Atk', 'Sp. Def', 'Speed', 'Generation', 'Legendary']), 800, None)
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
[-15,
 5,
 10,
 20,
 90,
 80,
 10,
 30,
 -40,
 -40,
 ...,
 30,
 36,
 36,
 -21,
 -50,
 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

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
[{x: 1.1, y: [1]},
 {x: 2.2, y: [1, 2]},
 {x: 3.3, y: [1, 2, 3]}]
------------------------
type: 3 * {
    x: float64,
    y: var * int64
}
pa_array = ak.to_arrow(ak_array)
pa_array
<awkward._connect.pyarrow.extn_types.AwkwardArrowArray object at 0x7f72bef9fa80>
-- is_valid: all not null
-- child 0 type: extension<awkward<AwkwardArrowType>>
  [
    1.1,
    2.2,
    3.3
  ]
-- child 1 type: extension<awkward<AwkwardArrowType>>
  [
    [
      1
    ],
    [
      1,
      2
    ],
    [
      1,
      2,
      3
    ]
  ]
type(pa_array)
awkward._connect.pyarrow.extn_types.AwkwardArrowArray
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: extension<awkward<AwkwardArrowType>>
y: extension<awkward<AwkwardArrowType>>
----
x: [1.1,2.2,3.3]
y: [[1],[1,2],[1,2,3]]

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: extension<awkward<AwkwardArrowType>>
y: extension<awkward<AwkwardArrowType>>
----
x: [[1.1,2.2,3.3]]
y: [[[1],[1,2],[1,2,3]]]

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

pa_table[0]
<pyarrow.lib.ChunkedArray object at 0x7f72bee0a8e0>
[
  [
    1.1,
    2.2,
    3.3
  ]
]
pa_table[1]
<pyarrow.lib.ChunkedArray object at 0x7f72bee0a3e0>
[
  [
    [
      1
    ],
    [
      1,
      2
    ],
    [
      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.lib.Array, pyarrow.lib.ChunkedArray, pyarrow.lib.RecordBatch, and pyarrow.lib.Table, but ak.to_arrow() only returns pyarrow.lib.Array.

For instance, when writing to an IPC stream, Arrow requires pyarrow.lib.RecordBatch, 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
[{x: 1.1, y: [1]},
 {x: 2.2, y: [1, 2]},
 {x: 3.3, y: [1, 2, 3]}]
------------------------
type: 3 * {
    x: float64,
    y: var * int64
}
first_batch = pa.RecordBatch.from_arrays(
    [
        ak.to_arrow(ak_array.x),
        ak.to_arrow(ak_array.y),
    ],
    ["x", "y"],
)
first_batch.schema
x: extension<awkward<AwkwardArrowType>>
y: extension<awkward<AwkwardArrowType>>
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\x80\x04\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\xf0\x02\x00\x00\x04\x00\x00\x00*\xfd\xff\xff\x00\x00\x01\x15\x18\x00\x00\x00L\x01\x00\x00\x08\x00\x00\x00\x14\x00\x00\x00\x01\x00\x00\x00T\x01\x00\x00\x01\x00\x00\x00y\x00\x00\x00\x02\x00\x00\x00<\x00\x00\x00\x04\x00\x00\x00\xe8\xfc\xff\xff\x14\x00\x00\x00\x04\x00\x00\x00\x07\x00\x00\x00awkward\x00\x14\x00\x00\x00ARROW:extension:name\x00\x00\x00\x00\x1c\xfd\xff\xff\xc8\x00\x00\x00\x04\x00\x00\x00\xb9\x00\x00\x00{"mask_type": null, "node_type": "ListOffsetArray", "mask_parameters": null, "node_parameters": {}, "record_is_tuple": null, "record_is_scalar": false, "is_nonnullable_nulltype": false}\x00\x00\x00\x18\x00\x00\x00ARROW:extension:metadata\x00\x00\x00\x00\x04\x00\x06\x00\x04\x00\x00\x00\x00\x00\x12\x00\x18\x00\x08\x00\x00\x00\x07\x00\x0c\x00\x00\x00\x10\x00\x14\x00\x12\x00\x00\x00\x00\x00\x00\x02\x14\x00\x00\x00L\x01\x00\x00\x08\x00\x00\x00\x14\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00item\x00\x00\x00\x00\x02\x00\x00\x00<\x00\x00\x00\x04\x00\x00\x00X\xfe\xff\xff\x14\x00\x00\x00\x04\x00\x00\x00\x07\x00\x00\x00awkward\x00\x14\x00\x00\x00ARROW:extension:name\x00\x00\x00\x00\x8c\xfe\xff\xff\xc4\x00\x00\x00\x04\x00\x00\x00\xb4\x00\x00\x00{"mask_type": null, "node_type": "NumpyArray", "mask_parameters": null, "node_parameters": {}, "record_is_tuple": null, "record_is_scalar": false, "is_nonnullable_nulltype": false}\x00\x00\x00\x00\x18\x00\x00\x00ARROW:extension:metadata\x00\x00\x00\x00\x08\x00\x0e\x00\x08\x00\x07\x00\x08\x00\x00\x00\x00\x00\x00\x01@\x00\x00\x00\x00\x00\x12\x00\x18\x00\x08\x00\x06\x00\x07\x00\x0c\x00\x00\x00\x10\x00\x14\x00\x12\x00\x00\x00\x00\x00\x01\x03\x14\x00\x00\x00L\x01\x00\x00\x08\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00x\x00\x00\x00\x02\x00\x00\x00D\x00\x00\x00\x04\x00\x00\x00\xcc\xff\xff\xff\x14\x00\x00\x00\x04\x00\x00\x00\x07\x00\x00\x00awkward\x00\x14\x00\x00\x00ARROW:extension:name\x00\x00\x00\x00\x08\x00\x0c\x00\x04\x00\x08\x00\x08\x00\x00\x00\xc4\x00\x00\x00\x04\x00\x00\x00\xb4\x00\x00\x00{"mask_type": null, "node_type": "NumpyArray", "mask_parameters": null, "node_parameters": {}, "record_is_tuple": null, "record_is_scalar": false, "is_nonnullable_nulltype": false}\x00\x00\x00\x00\x18\x00\x00\x00ARROW:extension:metadata\x00\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 record batches (yielded by the pyarrow.lib.RecordBatchStreamReader reader) to ak.from_arrow():

reader = pa.ipc.open_stream(sink.getvalue())
reader.schema
x: extension<awkward<AwkwardArrowType>>
y: extension<awkward<AwkwardArrowType>>
for batch in reader:
    print(repr(ak.from_arrow(batch)))
<Array [{x: 1.1, y: [1]}, ..., {x: 3.3, ...}] type='3 * {x: ?float64, y: op...'>
<Array [{x: 1.1, y: [1]}, ..., {x: 3.3, ...}] type='3 * {x: ?float64, y: op...'>
<Array [{x: 1.1, y: [1]}, ..., {x: 3.3, ...}] type='3 * {x: ?float64, y: op...'>
<Array [{x: 1.1, y: [1]}, ..., {x: 3.3, ...}] type='3 * {x: ?float64, y: op...'>
<Array [{x: 1.1, y: [1]}, ..., {x: 3.3, ...}] type='3 * {x: ?float64, y: op...'>
<Array [{x: 1.1, y: [1]}, ..., {x: 3.3, ...}] type='3 * {x: ?float64, y: op...'>

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 pyarrow.lib.Table, but ak.to_arrow() only returns pyarrow.lib.Array.

For instance, when writing to a Feather file, Arrow requires pyarrow.lib.Table, 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
[{x: 1.1, y: [1]},
 {x: 2.2, y: [1, 2]},
 {x: 3.3, y: [1, 2, 3]}]
------------------------
type: 3 * {
    x: float64,
    y: var * int64
}
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: extension<awkward<AwkwardArrowType>>
y: extension<awkward<AwkwardArrowType>>
----
x: [[1.1,2.2,3.3]]
y: [[[1],[1,2],[1,2,3]]]
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: extension<awkward<AwkwardArrowType>>
y: extension<awkward<AwkwardArrowType>>
----
x: [[1.1,2.2,3.3]]
y: [[[1],[1,2],[1,2,3]]]
type(from_feather)
pyarrow.lib.Table
ak.from_arrow(from_feather)
[{x: 1.1, y: [1]},
 {x: 2.2, y: [1, 2]},
 {x: 3.3, y: [1, 2, 3]}]
--------------------------
type: 3 * {
    x: ?float64,
    y: option[var * int64]
}

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
[{x: 1.1, y: [1]},
 {x: 2.2, y: [1, 2]},
 {x: 3.3, y: [1, 2, 3]}]
------------------------
type: 3 * {
    x: float64,
    y: var * int64
}
ak.to_parquet(ak_array, "/tmp/example.parquet")
<pyarrow._parquet.FileMetaData object at 0x7f72bf6b18a0>
  created_by: parquet-cpp-arrow version 17.0.0
  num_columns: 2
  num_rows: 3
  num_row_groups: 1
  format_version: 2.6
  serialized_size: 0

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")
[{x: 1.1, y: [1]},
 {x: 2.2, y: [1, 2]},
 {x: 3.3, y: [1, 2, 3]}]
------------------------
type: 3 * {
    x: float64,
    y: var * int64
}

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"])
[{x: 1.1},
 {x: 2.2},
 {x: 3.3}]
--------------
type: 3 * {
    x: float64
}

Doesn’t read column "y".