Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Query CSV and write Parquet

Use a lazy CSV scan when Polars should filter, derive or aggregate file-backed data. A direct Parquet sink writes the query’s output without first returning a complete dataframe to Plenty.

This example creates a small input file so it can run by itself. With real data, begin at scan_csv. The examples need the dataframe engine; run them in an empty directory: the new output methods refuse to overwrite existing files.

import std.dataframe as df

def main() -> Result[(), df.DataFrameError | AllocError | IoError]:
    readings = df.frame([
        df.column_str("station", ["001", "002", "001"]?)?,
        df.column_f64("reading", [10.0, 20.0, 30.0]?)?
    ]?)?
    readings.write_csv("readings.csv")?
    query = df.scan_csv("readings.csv", schema_overrides=Some([
        df.Field("station", df.DType.String),
        df.Field("reading", df.DType.F64)
    ]?))?
    warm = query.filter(df.col("reading")?.gt(df.lit(15.0)?)?)?
    warm.sink_parquet("warm.parquet")?
    result = df.scan_parquet("warm.parquet")?.collect_streaming()?
    print(result.values_str("station")?)?
    print(result.values_f64("reading")?)?
    Ok(())
["002", "001"]
[20.0, 30.0]

The String override keeps leading zeros in station identifiers. CSV contains text rather than a typed schema. For unfamiliar files, inspect query.schema()?. With infer_rows=0, unspecified columns stay strings, so you can validate them and choose explicit casts. The default samples 100 rows; infer_rows=-1 scans the full file for inference.

Missing values and text

A quoted empty string and an unquoted empty field have different meanings. The default writer/reader preserves that distinction. When copying a frame through CSV, pass its schema to preserve column types too.

import std.dataframe as df

def main() -> Result[(), df.DataFrameError | AllocError | IoError]:
    notes: list[Option[str]] = [Some(""), Nothing, Some("雪, café")]?
    data = df.frame([df.column_str_nullable("note", notes)?]?)?
    data.write_csv("notes.csv", separator=";")?
    restored = df.scan_csv(
        "notes.csv",
        separator=";",
        schema_overrides=Some(data.schema()?)
    )?.collect_streaming()?
    print(restored.values_str_nullable("note")?)?
    Ok(())
[Option[str].Some(""), Option[str].Nothing, Option[str].Some("雪, café")]

A custom null token is available through write_csv(null_value="NA") and scan_csv(null_value=Some("NA")). Use it only when that token cannot be a real value: even a quoted "NA" is treated as null on reading.

Streaming execution does not imply a memory bound. collect_streaming retains all final rows, and sorts, groups, joins or windows may buffer data. Prefer sink_parquet when the desired result is a file rather than a Plenty dataframe. A failed write can leave a partial new file; existing files are never overwritten by these two new output methods. See the I/O contracts for options, error timing and execution limits.

Continue with Application: a daily sales report to combine file scans, cleaning, dates, joins and exports in a complete program.