Structured arrays

NumPy · Advanced · 12 min

Reading binary files, sensors and C data, or keeping heterogeneous records compact, calls for structured dtypes.

A structured dtype describes a record made of named fields, each with its own type, like a C struct:

dt = np.dtype([("id", "i4"), ("name", "U8"), ("temp", "f8")])

An array of that dtype stores records back to back. a["temp"] returns a view of one field across all records (shape stays the same, dtype becomes float64), and a[0] returns one record. You can assign to fields (a["temp"] += 1), sort by them with np.sort(a, order="temp"), and filter with masks built from fields.

The layout is explicit: dt.itemsize and dt.fields show size and byte offsets. Pass align=True to pad fields like a C compiler would. This makes structured arrays the right tool when bytes come from somewhere with a fixed format: np.frombuffer(raw_bytes, dtype=dt) or np.fromfile(path, dtype=dt) parses a binary file of records with no loop at all. Prefix types with < or > to fix byte order, e.g. ">u2" for big-endian network data.

Fields can be sub-arrays (("pos", "f4", (3,))) or nested structures.

When should you not use them? For analysis of tabular data, pandas is far more convenient: structured arrays have no group-by, joins or missing-value handling, and arithmetic across records is awkward because ufuncs mostly don't operate on structured dtypes directly. Note also that a field view over interleaved records is strided, so operations on one field don't get the cache efficiency of a separate contiguous column. np.rec.array offers attribute access (r.temp) but is mostly legacy.

Example

import numpy as np

dt = np.dtype([("id", "i4"), ("name", "U8"), ("temp", "f8")])
a = np.array([(1, "oslo", 3.5), (2, "lima", 19.0), (3, "cairo", 28.2)], dtype=dt)

print(a["name"])                   # ['oslo' 'lima' 'cairo']
print(a[1])                        # (2, 'lima', 19.0)
a["temp"] += 1.0                   # field view, in place
print(a[a["temp"] > 10]["id"])     # [2 3]
print(np.sort(a, order="temp")["name"])   # coldest first

print(dt.itemsize, dt.fields["temp"][1])   # 44 36  (bytes, offset)

# parse packed big-endian binary records: uint16 id, float32 value
rec = np.dtype([("id", ">u2"), ("value", ">f4")])
raw = bytes.fromhex("0001 40490fdb 0002 402df854".replace(" ", ""))
r = np.frombuffer(raw, dtype=rec)
print(r["id"], r["value"].round(4))  # [1 2] [3.1416 2.7183]

Remember

  • A structured dtype lists named fields with their own types and offsets
  • a['field'] is a view; sort with order= and filter with field masks
  • frombuffer/fromfile with a structured dtype parses binary records without loops
  • For tabular analysis prefer pandas

Watch out for

  • Using structured arrays as a DataFrame substitute and fighting missing group-by, join and NA support.

Check yourself

  1. `a` is a structured array with fields 'x' (f8) and 'y' (f8). What is `a['x']`?

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

Official docs: numpy.org/doc/stable/user/basics.rec.html