Python Polars 速查表(基于我们的 O'Reilly 书籍)

Hacker News Top 工具

摘要

此速查表提供了在 Python 中使用 Polars 库的快速参考,涵盖安装、数据结构以及数据分析和转换的方法。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/08/18 15:59

# Python Polars:权威速查表 来源:https://opensource.posit.co/resources/cheatsheets/polars/ Polars(https://pola.rs/)是一个用于快速、高效地转换、分析和可视化数据的库,提供富有表现力的DataFrame API。该库由Ritchie Vink于2020年首次发布。 从终端安装Polars及其所有可选依赖项: ``` uv pip install "polars[all]" ``` 在Python中导入Polars,并确认已安装的Polars及其依赖项版本: ``` import polars as pl pl.show_versions() ``` Polars查询通常包括读取数据、转换数据和将结果写回。一个完整的查询通常是一连串的方法调用: ``` fruit = pl.read_csv("fruit.csv") fruit.filter( (pl.col("weight") > 1000) & pl.col("is_round") ).write_parquet("fruit.parquet") ``` 在本速查表中,`df`表示`DataFrame`,`lf`表示`LazyFrame`,`o`表示与`df`组合的第二个DataFrame,`e`表示任意表达式。因此`e.abs()`表示“在表达式上调用`.abs()`”,例如`pl.col("x").abs()`。 ## 数据结构 Polars的所有数据都存储在Series或DataFrame中。 | 结构 | 描述 | |------|------| | `Series` | 一维结构。存储相同数据类型的值序列。 | | `DataFrame` | 二维结构。具有行和列。由一个或多个长度相同的Series组成。 | | `LazyFrame` | 类似DataFrame但不包含实际数据。是生成DataFrame的蓝图。 | 与pandas不同,Polars的DataFrame没有行索引,其API更倾向于不可变性和方法链式调用,而非就地修改。 - 通过传递名称和值序列创建Series: ``` series = pl.Series("sales", [150.00, 300.00, 250.00]) ``` - 从列字典创建DataFrame,每个值可以是Series或普通Python序列。也可以使用任何`pl.read_*()`函数从文件创建: ``` df = pl.DataFrame({ "sales": series, "id": [41, 42, 43] }) ``` - 由于没有行索引,需要时可以显式添加一列作为索引。 - 将DataFrame转换为LazyFrame。或者直接使用任何`pl.scan_*()`函数从LazyFrame开始。 ## 及早API与惰性API 及早API立即执行,而惰性API首先构建优化的查询计划。优化器会自动应用谓词下推(尽早过滤)和投影下推(丢弃未使用的列)。 通过`.lazy()`和`.collect()`在两种表示之间转换:`.lazy()`将DataFrame转换为LazyFrame,`.collect()`执行LazyFrame并返回DataFrame。 - 将DataFrame转换为LazyFrame,并执行LazyFrame获取DataFrame: ``` lf = df.lazy() df = lf.collect() ``` - 使用流式引擎处理超出内存的数据集: ``` lf.collect(engine="streaming") ``` - 打印优化后的查询计划文本或可视化图形,查看优化器的决策: ``` lf.explain() lf.show_graph() ``` - 执行查询并返回各节点计时信息,了解时间实际消耗在何处。 ## 数据类型 Polars实现了大部分Apache Arrow内存规范,这是一种用于扁平和层次数据的高效列式格式。 | 分组 | 类型 | 说明 | |------|------|------| | 数值型 | `Decimal` | 128位,带精度和标度 | | | `Float32` | 范围 ±3.4×10³⁸ | | | `Float64` | 范围 ±1.8×10³⁰⁸ | | | `Int8` | 范围 ±128 | | | `Int16` | 范围 ±32,768 | | | `Int32` | 范围 ±2.1×10⁹ | | | `Int64` | 范围 ±9.2×10¹⁸ | | | `Int128` | 范围 ±3.4×10³⁸ | | | `UInt8` | 范围 0–255 | | | `UInt16` | 范围 0–65,535 | | | `UInt32` | 范围 0–4.3×10⁹ | | | `UInt64` | 范围 0–1.8×10¹⁹ | | 时间型 | `Date` | 自Unix纪元以来的天数 | | | `Datetime` | 自纪元以来的微秒数 | | | `Duration` | 时间长度/差值 | | | `Time` | 一天中的时间 | | 嵌套型 | `Array` | 固定长度序列 | | | `List` | 可变长度序列 | | | `Struct` | 具有多个命名字段的结构 | | 字符串型 | `String` | UTF-8文本,可变长度 | | | `Categorical` | 字符串字典 | | | `Enum` | 固定字符串字典 | | 其他 | `Boolean` | 真/假 | | | `Binary` | 原始字节 | | | `Null` | 表示空值/无值 | ### 检查类型 - 获取列名和数据类型的字典,或仅获取数据类型列表: - 每列打印一行,包括数据类型,对于宽DataFrame(直接打印难以阅读)很有用: - 计算每列的汇总统计,包括空值数量: - 报告DataFrame的内存大小,使用指定单位。 ### 类型转换 - 将列转换为其他数据类型。默认转换是严格的,不匹配的值会引发错误: ``` df.select(pl.col("id").cast(pl.UInt64)) ``` - 传递`strict=False`进行宽松转换。溢出目标类型的值将变为空值: ``` df.select(pl.col("id").cast(pl.Int8, strict=False)) ``` ## 读写数据 Polars有四大类输入输出函数,具体使用取决于您是采用及早还是惰性方式: - `read_*()`将数据读入DataFrame。 - `scan_*()`创建LazyFrame,推迟实际读取直到collect时。 - `write_*()`将DataFrame写入磁盘或云存储。 - `sink_*()`将数据流式传输到磁盘或云存储,无需全部加载到内存。 并非所有格式都支持所有四种操作: | 格式 | read | scan | write | sink | |------|------|------|-------|------| | Avro | ✓ | ✓ | ✓ | ✓ | | 剪贴板 | ✓ | | ✓ | | | CSV | ✓ | ✓ | ✓ | ✓ | | 数据库 | ✓ | | ✓ | | | Delta Lake | ✓ | ✓ | ✓ | ✓ | | Excel / ODS | ✓ | | ✓ | | | Iceberg | ✓ | ✓ | ✓ | | | IPC / Feather | ✓ | ✓ | ✓ | ✓ | | JSON | ✓ | | ✓ | | | NDJSON | ✓ | ✓ | ✓ | ✓ | | Parquet | ✓ | ✓ | ✓ | ✓ | | PyArrow Dataset | ✓ | | | | 许多函数接受的通用关键字参数包括`schema_overrides`、`n_rows`、`row_index_name`、`storage_options`和`compression`。 - 通过传递带通配符模式的URI扫描云存储文件,并使用`storage_options`提供凭证和区域设置: ``` pl.scan_parquet( "s3://bucket/*.parquet", storage_options={"aws_region": "us-east-2"} ) ``` - 将查询直接流式传输到分区Parquet数据集,为键列的每个唯一值写入一个目录: ``` lf.sink_parquet(pl.PartitionBy("out/", key="x")) ``` ## 数据转换 ### 选择列 根据名称、数据类型或位置保留列。 - 按名称选择列: - 选择表达式的结果,以便在输出时转换列: ``` df.select(pl.col("x") * 2) ``` - 使用关键字参数为表达式结果命名,生成新列: ``` df.select(doubled=pl.col("x") * 2) ``` - 选择名称匹配正则表达式的列。模式必须以`^`开头,`$`结尾: ``` df.select(pl.col("^.*_color$")) ``` - 选择所有列: 使用列选择器获得更灵活的选择。它们可以使用集合运算符`|`、`&`、`-`、`^`和`~`组合。 - 导入选择器模块,然后按数据类型或名称模式选择列。另见`cs.string()`、`cs.contains()`和`cs.first()`: ``` import polars.selectors as cs df.select(cs.numeric()) df.select(cs.starts_with("val")) ``` - 删除列而非保留。传递`strict=False`以便忽略不存在的名称而非引发错误: ``` df.drop("a", "y", strict=False) ``` ### 创建列 新列添加到现有列的右侧。 - 添加由表达式计算的新列,使用关键字参数命名: ``` df.with_columns(new=pl.col("a") + 1) ``` - 通过生成同名表达式替换现有列。此处列`a`中的空值被替换为零: ``` df.with_columns(pl.col("a").fill_null(0)) ``` - 添加每行具有相同字面值的列: ``` df.with_columns(ones=pl.lit(1)) ``` - 添加行索引列。使用`offset`从非零值开始计数: ``` df.with_row_index(name="id", offset=1) ``` ### 过滤行 根据一列或多列的值保留行。 - 通过传递布尔列名进行过滤: - 使用单个表达式过滤: ``` df.filter(pl.col("x") > 5) ``` - 传递多个表达式以逻辑与组合。也可以显式使用`&`,此时每个比较需要自己的括号: ``` df.filter(pl.col("valid"), pl.col("x") > 5) df.filter(pl.col("valid") & (pl.col("x") > 5)) ``` - 使用`|`进行逻辑或: ``` df.filter(pl.col("valid") | (pl.col("x") > 5)) ``` - 使用关键字参数约束,这是测试相等性并将结果与与组合的简写: ``` df.filter(valid=True, x=5) ``` - 仅保留没有缺失值的行,或限制检查特定列: ``` df.drop_nulls() df.drop_nulls("x") ``` - 删除重复行。使用`subset`决定哪些列定义重复,使用`keep`选择哪个重复项保留: ``` df.unique(subset=["x"], keep="first") ``` ### 切片和采样行 根据位置保留行。 - 保留前几行或后几行。默认均为五行: - 通过提供偏移量和长度保留连续切片。此处保留第三行到第七行: - 保留每第*n*行: - 随机采样行。使用`with_replacement=True`允许同一行被多次抽取,或使用`fraction`按比例采样而非固定数量: ``` df.sample(10) df.sample(10, with_replacement=True) df.sample(fraction=0.2) ``` ### 排序行 根据一列或多列的值重新排序行。 - 按单列排序(默认升序),或多列依次排序: ``` df.sort("x") df.sort("x", "y") ``` - 将空值移到末尾而非开头: ``` df.sort("x", nulls_last=True) ``` - 反转顺序。按多列排序时,传递布尔列表设置每列方向: ``` df.sort("x", descending=True) df.sort("x", "y", descending=[False, True]) ``` - 按表达式结果而非列值排序,例如计算的比率或列表长度: ``` df.sort(pl.col("x") / pl.col("y")) df.sort(pl.col("l").list.len()) ``` - 仅保留按列排序的前*k*个最大或最小行,这比先排序整个表再切片更高效: ``` df.top_k(5, by="score") df.bottom_k(5, by="score") ``` ### 重塑数据 在宽格式和长格式之间转换。 - 通过将一列或多列的值转换为行使DataFrame变长,保留`index`列作为标识符: ``` df.unpivot(on=["c"], index="id") ``` - 通过将一列的值转换为新列使DataFrame变宽。如果`on`和`index`的组合不唯一,请提供`aggregate_function`决定如何合并冲突: ``` df.pivot(on="c", index="id", values="x") df.pivot(on="c", index="id", values="x", aggregate_function="sum") ``` - 展开列表列,使每个元素占据一行,重复其他列: - 展开结构列,使每个字段成为自己的列: - 交换行和列。使用`include_header=True`保留原始列名作为一列: ``` df.transpose(include_header=True) ``` - 将DataFrame拆分为较小DataFrame的列表,每个对应给定列的每个唯一值。 ### 汇总和聚合 分割. 应用. 合并。 - 按一列或多列将DataFrame分组。这会给你一个`GroupBy`对象,然后进行聚合: ``` dfg = df.group_by("x") dfg = df.group_by("x", "y") ``` - 对每个组应用现成的汇总。计算每组的行数、获取每组的前几行或计算每组每列的平均值: ``` dfg.len() dfg.head(2) dfg.mean() ``` - 当没有内置聚合函数适用时,对每个组应用自定义函数。 - 使用`agg()`完全控制聚合。传递不带聚合方法的表达式会将值收集到列表中,使用关键字参数命名结果可以为新列提供合理的名称: ``` dfg.agg(...) dfg.agg(pl.col("y")) dfg.agg(avg=pl.col("y").mean()) ``` - 使用窗口表达式与`over()`将聚合添加为原始DataFrame的新列,而不折叠行: ``` df.with_columns(avg=pl.col("y").mean().over("x")) ``` - 按时间值或索引而非类别分组。`group_by_dynamic()`创建固定持续时间的窗口,`group_by`在此基础上添加常规分组: ``` df.group_by_dynamic("timestamp", every="1h", group_by="store") ``` - 使用`rolling()`创建随每行移动而非固定步长的窗口。计算每店七天滚动销售总额: ``` df.rolling(index_column="date", period="7d", group_by="store").agg( pl.col("sales").sum() ) ``` - 创建常规时间序列中缺失的行,确保每个间隔都有表示: ``` df.upsample( time_column="date", every="1d", group_by="store", maintain_order=True ) ``` - 跨列聚合而非沿列聚合。水平函数在每行内组合多列: ``` df.select(pl.sum_horizontal(cs.numeric())) df.select(pl.any_horizontal(cs.boolean())) ``` ### 连接和连接 将多个DataFrame组合为一个。 - 在共享键上连接两个DataFrame。默认是内连接,仅保留两侧匹配的行。 - 使用`how`选择不同的连接策略。左连接保留`df`的每一行: ``` df.join(o, on="key", how="left") ``` - 当键在每个DataFrame中名称不同时,显式命名两侧: ``` df.join(o, left_on="a", right_on="b") ``` - 全外连接保留两侧的所有行。添加`coalesce=True`将两个键列合并为一个: ``` df.join(o, on="key", how="full", coalesce=True) ``` - 过滤连接仅返回`df`的列,并将`o`纯粹用作过滤器。半连接保留有匹配的`df`行,反连接保留没有匹配的行: ``` df.join(o, on="key", how="semi") df.join(o, on="key", how="anti") ``` - 交叉连接生成两个DataFrame的笛卡尔积,因此不需要键。 - 在最近匹配而非精确匹配上连接,这是对齐两个时间序列的常用方式。使用`by`先精确匹配某些列: ``` df.join_asof(o, on="ts", by="i") ``` - 在不等式或其他非等值连接的任意谓词上连接: ``` df.join_where(o, pl.col("a") >= pl.col("b")) ``` `df.join()`的常用关键字参数包括`left_on`、`right_on`、`coalesce`、`join_nulls`、`suffix`和`validate`,其中`validate`接受`"m:m"`、`"m:1"`、`"1:m"`和`"1:1"`。 - 将DataFrame堆叠在彼此之上,这需要列匹配: - 或者将DataFrame并排放置,或者取它们的列并集,用空值填充空隙: ``` pl.concat([df, o], how="horizontal") pl.concat([df, o], how="diagonal") ``` - 使用宽松策略强制转换不匹配的数据类型,而非引发错误: ``` pl.concat([df, o], how="vertical_relaxed") ``` - 使用另一个DataFrame的非空值更新`df`中的值,在键上匹配行: ``` df.update(o, on="id", how="left") ``` ## 表达式

相似文章