Polars Structs: How to Pack and Unpack Multiple Columns

3D isometric illustration of a robotic assembly line packing three separate colored data columns into a single Struct crate, and unpacking a crate back into columns.

In Pandas, you are stuck with flat columns. In Polars, you can put columns inside other columns.

This is called a Struct (structure). It’s like having a Dictionary inside a DataFrame cell, but it’s strongly typed and incredibly fast.

Why use Structs?

Imagine you have x and y coordinates. Instead of managing two separate columns, you can pack them into a single point column.

1. Packing Columns (pl.struct)

import polars as pl

df = pl.DataFrame({
    "x": [1, 2, 3],
    "y": [10, 20, 30],
    "id": ["a", "b", "c"]
})

# Combine 'x' and 'y' into a 'point' struct
df_packed = df.select(
    "id",
    pl.struct(["x", "y"]).alias("point")
)
print(df_packed)

Output:

shape: (3, 2)
┌─────┬───────────┐
│ id  ┆ point     │
│ --- ┆ ---       │
│ str ┆ struct[2] │
╞═════╪═══════════╡
│ a   ┆ {1,10}    │
│ b   ┆ {2,20}    │
│ c   ┆ {3,30}    │
└─────┴───────────┘

2. Accessing Fields (struct.field)

You can run math on the fields inside the struct without unpacking them!

# Multiply x by 2 inside the struct
df_packed.select(
    pl.col("point").struct.field("x") * 2
)

Output:

shape: (3, 1)
┌─────┐
│ x   │
│ --- │
│ i64 │
╞═════╡
│ 2   │
│ 4   │
│ 6   │
└─────┘

3. Unpacking (unnest)

When you’re done, you can expand them back into columns.

df_unpacked = df_packed.unnest("point")
print(df_unpacked)

Output:

shape: (3, 3)
┌─────┬─────┬─────┐
│ id  ┆ x   ┆ y   │
│ --- ┆ --- ┆ --- │
│ str ┆ i64 ┆ i64 │
╞═════╪═════╪═════╡
│ a   ┆ 1   ┆ 10  │
│ b   ┆ 2   ┆ 20  │
│ c   ┆ 3   ┆ 30  │
└─────┴─────┴─────┘

This is crucial for handling complex JSON data or keeping related metrics tied together during a groupby. Let’s see how this pattern handles a real-world data pipeline payload.

4. Native JSON Parsing Into Structs

Instead of using slow Python loops (json.loads) which completely drop your performance back down to row-by-row speeds, Polars can natively decode complex JSON strings straight into a typed Struct column.

import polars as pl

# Mocking a raw API response dataset
df_json = pl.DataFrame({
    "user_id": [101, 102],
    "raw_meta": [
        '{"browser": "Chrome", "os": "Linux"}', 
        '{"browser": "Safari", "os": "iOS"}'
    ]
})

# 1. Explicitly define the target schema for your JSON structure
json_schema = pl.Struct([
    pl.Field("browser", pl.String), 
    pl.Field("os", pl.String)
])

# 2. Parse natively and unnest instantly for downstream consumption
df_processed = df_json.with_columns(
    pl.col("raw_meta").str.json_decode(dtype=json_schema)
).unnest("raw_meta")

print(df_processed)

Output:

shape: (2, 3)
┌═════════┬═════════┬═══════┐
│ user_id ┆ browser ┆ os    │
│ ---     ┆ ---     ┆ ---   │
│ i64     ┆ str     ┆ str   │
╞═════════╪═════════╪═══════╡
│ 101     ┆ Chrome  ┆ Linux │
│ 102     ┆ Safari  ┆ iOS   │
└═════════┴═════════┴═══════┘

Key Takeaways

  • Polars allows for packed columns called Structs, enabling complex data management within DataFrames.
  • Structs are efficient for combining related data like coordinates into a single column, enhancing performance.
  • You can access fields within a struct for calculations without unpacking, streamlining data processing.
  • Polars supports unnesting Structs, making it easy to expand data back into separate columns when needed.
  • Native JSON parsing in Polars decodes complex strings directly into Structs, improving performance over traditional methods.

Similar Posts

Leave a Reply