Chapter 02

Series 与 DataFrame

掌握 Polars 的两大核心数据结构、丰富的数据类型系统、从各种来源创建与读取数据,以及统一的 null 缺失值表示。

2.1 Series:一维数组

Series 是一列类型统一的具名数据。你很少单独创建它,但理解它有助于理解 DataFrame。

import polars as pl

s = pl.Series("age", [30, 25, 35])
print(s.dtype)      # Int64
print(s.sum())      # 90
print(s.mean())     # 30.0
print(s.name)       # "age"

2.2 数据类型(dtype)

Polars 的类型系统直接对应 Arrow 类型,比 pandas 更精确、更丰富。

Int8 / Int16 / Int32 / Int64
有符号整数,位宽不同。默认整数字面量推断为 Int64。选小位宽可省内存。
UInt8 / UInt16 / UInt32 / UInt64
无符号整数,用于计数、索引等非负场景。
Float32 / Float64
浮点数。默认浮点字面量推断为 Float64。缺失值用 null 表示,而非 pandas 的 NaN(Polars 里 NaNnull 是两回事)。
Utf8(String)
字符串类型,底层是 Arrow 变长 UTF-8。比 pandas 的 object 数组高效得多,是 Polars 处理文本快的关键。
Boolean
布尔类型,用位图存储,过滤/掩码运算极快。
Categorical
类别类型:把重复字符串编码为整数 + 字典。适合低基数(取值种类少)的列,如"城市""性别",省内存且分组更快。
Date / Datetime / Duration / Time
时间相关类型。Datetime 可带时区与精度(ms/us/ns),Duration 表示时间差。详见第 8 章。
List / Struct
嵌套类型:List 是变长同类型数组(一个单元格里装多个值),Struct 是命名字段的组合(类似一行 JSON)。详见第 9 章。

2.3 创建 DataFrame

# 方式 1:从字典(列 → 值列表)
df = pl.DataFrame({
    "id": [1, 2, 3],
    "price": [9.9, 19.9, 29.9],
})

# 方式 2:显式指定 schema(列名 + dtype)
df = pl.DataFrame(
    {"id": [1, 2], "tag": ["a", "b"]},
    schema={"id": pl.UInt32, "tag": pl.Categorical},
)

# 方式 3:从行(list of dict)
df = pl.DataFrame([
    {"id": 1, "name": "Alice"},
    {"id": 2, "name": "Bob"},
])

2.4 读取文件

# CSV
df = pl.read_csv("data.csv")

# Parquet(列式格式,强烈推荐用于中间存储)
df = pl.read_parquet("data.parquet")

# JSON / NDJSON
df = pl.read_json("data.json")
df = pl.read_ndjson("data.ndjson")

# 带参数:指定分隔符、类型、只读部分列
df = pl.read_csv(
    "data.csv",
    separator=";",
    columns=["id", "price"],   # 只读这两列
    schema_overrides={"id": pl.UInt32},
)
read_ vs scan_

每个 read_xxx 都有个惰性版 scan_xxx(如 scan_csvscan_parquet)。scan_ 返回 LazyFrame,不立即读入内存,配合优化器只读需要的列和行——处理大文件时永远优先用 scan_。第 7 章详解。

2.5 检查 DataFrame

print(df.shape)      # (行数, 列数),如 (1000, 5)
print(df.columns)    # ['id', 'price', ...]
print(df.dtypes)     # [Int64, Float64, ...]
print(df.schema)     # {'id': Int64, 'price': Float64}

df.head(5)             # 前 5 行
df.describe()          # 各列统计摘要(count/mean/std/min/max...)
df.estimated_size("mb")  # 估算内存占用
schema 是什么

schema 是"列名 → dtype"的有序映射,是 DataFrame 的类型契约。在 Lazy 模式下,Polars 在真正执行前就能通过 schema 推断出每一步的结果类型,从而提前发现类型错误并做优化。

2.6 null 缺失值

Polars 用统一的 null 位图(validity bitmap)表示缺失,任何类型都能有 null,无需像 pandas 那样把整型列升级成浮点来塞 NaN

df = pl.DataFrame({"x": [1, None, 3]})

df.null_count()               # 每列的 null 数量
df.select(pl.col("x").is_null())      # 布尔列
df.drop_nulls()               # 删除含 null 的行
df.fill_null(0)               # 用 0 填充
df.fill_null(strategy="forward") # 前向填充
null ≠ NaN

在 Polars 里 null(缺失)和 NaN(非数,浮点特殊值)是两个不同概念。is_null() 只匹配缺失,is_nan() 只匹配 NaN。这与 pandas 混用 NaN 表示缺失的做法不同,迁移时务必注意。

2.7 写出文件

df.write_csv("out.csv")
df.write_parquet("out.parquet", compression="zstd")
df.write_ndjson("out.ndjson")

2.8 小结与下一步