Reading and writing CSV and Parquet

Polars · Essentials · 12 min

Getting data in with the right types, and saving it in a fast typed format, is the first step of every real job.

Polars has eager readers that return a DataFrame: pl.read_csv, pl.read_parquet, pl.read_json, pl.read_ndjson, pl.read_excel and pl.read_database. Writers mirror them: df.write_csv, df.write_parquet, df.write_json.

Useful read_csv options:

  • separator=";", has_header=False
  • columns=["a", "b"] to read only some columns
  • schema_overrides={"zip": pl.String} to stop codes with leading zeros becoming integers
  • try_parse_dates=True to turn ISO date strings into Date/Datetime
  • null_values=["", "NA", "-"] for extra missing markers

In Polars 2, read_csv is literally scan_csv(...).collect(), so it runs on the same multi-threaded engine as lazy queries, and it accepts a list of files or a glob such as "data/*.csv".

Parquet is the format to prefer: it is columnar, compressed and stores the schema, so reads are fast, exact and need no type guessing. pl.read_parquet(path, columns=[...]) reads only the columns you ask for. When you will reread data, convert CSV to Parquet once.

For files bigger than memory, or when you only need part of a file, use the lazy scan_* functions instead; they come up in the lazy lessons.

Example

import polars as pl

with open("orders.csv", "w") as f:
    f.write("id;day;zip;qty\n1;2026-03-01;02134;2\n2;2026-03-02;10001;NA\n")

df = pl.read_csv(
    "orders.csv",
    separator=";",
    schema_overrides={"zip": pl.String},   # keep the leading zero
    null_values=["NA"],
    try_parse_dates=True,
)
print(df.schema)
# Schema({'id': Int64, 'day': Date, 'zip': String, 'qty': Int64})

df.write_parquet("orders.parquet")
back = pl.read_parquet("orders.parquet", columns=["zip", "qty"])
print(back.rows())   # [('02134', 2), ('10001', None)]

Remember

  • schema_overrides, null_values and try_parse_dates fix most CSV typing issues
  • Parquet keeps the schema exactly and reads only requested columns
  • Eager read_* loads everything; lazy scan_* is for big or partial reads

Watch out for

  • Letting ID or postal-code columns be inferred as integers and losing leading zeros.

Check yourself

  1. Why is Parquet usually better than CSV for data you load repeatedly?

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

Official docs: docs.pola.rs/user-guide/io/csv/