Python Polars 速查表(基于 O'Reilly 官方书籍浓缩)

查看原文 HN 讨论

文章摘要

这是 Posit(原 RStudio)开源资源站上新增的一份 Polars 速查表,作者 Jeroen Janssens 和 Thijs Nieuwdorp 正是 O’Reilly 出版的《Python Polars: The Definitive Guide》的两位作者。他们在 HN 自述里说,花了几周把近 500 页的书「有损压缩」成两页速查表,除 PDF 之外还提供了无障碍的 HTML 版本,全站内容也是开源的(托管在 posit-dev/open-source-website 仓库)。Polars 是 Ritchie Vink 在 2020 年首次发布的库,安装用 uv pip install "polars[all]"pl.show_versions() 可以确认依赖版本。速查表通篇约定:df 是 DataFrame,lf 是 LazyFrame,o 是要与 df 合并的第二个 DataFrame,e 表示任意表达式。

数据结构与惰性 API。 三种结构:Series(一维、同类型序列)、DataFrame(二维、多个等长 Series)、LazyFrame(外形像 DataFrame 但不持有数据,是生成 DataFrame 的蓝图)。与 pandas 不同,Polars 的 DataFrame 没有行索引,API 偏向不可变和方法链,需要索引时用 df.with_row_index("id") 显式加一列。eager API 立即执行,lazy API 先构建优化后的查询计划,优化器会自动做谓词下推(尽早过滤)和投影下推(丢掉从未用到的列)。.lazy().collect() 在两种表示间往返;lf.collect(engine="streaming") 用流式引擎做核外处理,从而处理比内存更大的数据集;lf.explain() 打印优化后的计划,lf.show_graph() 画成图,lf.profile() 返回每个节点的耗时。

数据类型。 Polars 实现了 Apache Arrow 内存规范的大部分。数值类有 Decimal(128 位,带 precision 和 scale)、Float32/64、Int8 到 Int128、UInt8 到 UInt64;时间类有 Date(Unix epoch 起的天数)、Datetime(微秒)、Duration、Time;嵌套类有 Array(定长)、List(变长)、Struct(带名字的多字段);字符串类有 String(UTF-8 变长)、Categorical(字符串字典)、Enum(固定字符串字典);其他有 Boolean、Binary、Null。检视类型用 df.schema / df.dtypes / df.glimpse()(宽表时每列一行,比直接打印可读)/ df.describe() / df.estimated_size("mb")。转换用 .cast(),默认严格,strict=False 时溢出值变成 null 而不报错。

读写 I/O 的四个家族。 read_*() 读进 DataFrame,scan_*() 建 LazyFrame 并延迟到 collect 时才真正读,write_*() 写到磁盘或云存储,sink_*() 流式写出而不全量驻留内存。不是每种格式都支持全部四种:CSV、Delta Lake、IPC/Feather、NDJSON、Parquet 四种全支持;Iceberg 支持 scan/write/sink 但没有 read;Avro、Clipboard、Database、Excel/ODS、JSON 只有 read 和 write;PyArrow Dataset 只有 scan。常用关键字参数有 schema_overridesn_rowsrow_index_namestorage_optionscompression。扫云上文件可以直接给带通配符的 URI 加 storage_options 传凭据和 region;lf.sink_parquet(pl.PartitionBy("out/", key="x")) 可以按键值流式写成分区 Parquet 数据集。

转换数据。 选列:df.select("a","b")df.select(pl.col("x") * 2)、用关键字参数给结果命名 df.select(doubled=pl.col("x") * 2)、正则选列(模式必须以 ^ 开头 $ 结尾)、pl.all();更灵活的是列选择器 import polars.selectors as cscs.numeric()cs.starts_with("val")cs.string()cs.contains()cs.first(),并且可以用 |&-^~ 这些集合运算符组合;丢列用 df.drop("a","y", strict=False)。造列:with_columns(new=...) 加到最右侧,同名表达式则替换原列,pl.lit(1) 加常量列,with_row_index(name="id", offset=1)。过滤行:df.filter("valid") 直接传布尔列名,多个表达式默认按 AND 组合,也可显式用 &(每个比较各自加括号)、| 表示 OR,还支持 df.filter(valid=True, x=5) 这种关键字约束简写;drop_nulls()unique(subset=["x"], keep="first")。切片抽样:head()/tail() 默认 5、slice(2, 5)gather_every(2)sample(10, with_replacement=True)sample(fraction=0.2)。排序:sort 支持多列、nulls_last=Truedescending 传布尔列表逐列指定方向、按表达式排序,以及比「全排再切」更省的 top_k(5, by="score") / bottom_k。变形:unpivot(宽变长)、pivot(长变宽,若 on 与 index 组合不唯一需给 aggregate_function)、explode(列表列展开成行)、unnest(struct 字段展开成列)、transpose(include_header=True)partition_by("group")

汇总聚合。 「Split. Apply. Combine.」——group_by 得到 GroupBy 对象后可用现成汇总 len() / head(2) / mean(),自定义用 map_groups(...),完全控制用 agg(...)(不带聚合方法的表达式会把值收集成列表,关键字参数命名结果);窗口表达式 over() 可以把聚合作为新列加回原 DataFrame 而不折叠行数;时间维度上有 group_by_dynamic("timestamp", every="1h", group_by="store") 做定长窗口、rolling(index_column="date", period="7d", group_by="store") 做逐行滑动窗口、upsample(...) 补齐规整时间序列缺失的行;横向聚合有 pl.sum_horizontal(cs.numeric())pl.any_horizontal(cs.boolean())

连接与拼接。 df.join(o, on="key") 默认 inner,how 可选 left、full(配 coalesce=True 合并键列)、semi(保留有匹配的 df 行)、anti(保留没匹配的)、cross(笛卡尔积,无需键);键名不同用 left_on/right_onjoin_asof(o, on="ts", by="i") 做最近邻匹配,是对齐两条时间序列的常规做法;join_where(o, pl.col("a") >= pl.col("b")) 支持不等值/任意谓词连接;validate 接受 "m:m""m:1""1:m""1:1"。拼接用 pl.concathow 可为默认的垂直、horizontaldiagonal(取列的并集、空缺填 null)、vertical_relaxed(放宽类型强转而不报错);df.update(o, on="id", how="left") 用另一个 DataFrame 的非空值更新。

表达式系统。 速查表给了一个「表达式」的正式定义:表达式是一棵描述如何构造一个或多个 Series 的操作树——Series 是同类型数组(可以是列也可以独立存在),操作树可以是单个、线性或有分支的,「描述」意味着它是被动的配方、需要函数来执行,「构造」意味着输出可能是内部的而非新列,「一个或多个」意味着单个表达式可以产出多个 Series。起点只有三种:某列(pl.col("name"))、所有列(pl.col("*") 等价于 pl.all())、字面量(pl.lit("ok")),另有 pl.arange(0, 5)(stop 不含)和 pl.date_range / pl.date_ranges(单数产生一个范围,复数产生每行一个范围的列)。算术、比较、布尔运算每个运算符都有等价方法(add/sub/mul/truediv/floordiv/pow/mod/dotlt/le/eq/ge/gt/neand_/or_/not_/xor,因为 and/or/not 是 Python 关键字所以带下划线),方便保持方法链不断。注意 Python 里的链式比较不可用,必须写成 (pl.col("x") > 0) & (pl.col("x") < 10)。条件表达式用 when().then()...otherwise(),条件按顺序求值、先匹配者胜,所以最具体的条件要放前面。

表达式方法按命名空间归类。 数学与取整:abs/sign/exp/cbrt/sqrt/log/log10/log1p/三角与双曲及其反函数/degrees/radians/ceil/floor/round/clip/cut/qcut。缺失值——Polars 明确区分 null(缺失)与 NaN(0/0 这类未定义运算得到的浮点数),两者由不同方法处理:fill_nan/fill_null/is_finite/is_infinite/is_nan/is_null/drop_nans/drop_nulls/flatten/reshape/explode/implode。位移、累计与滑动:backward_fill/forward_fill/interpolate/shift/cum_count/cum_sum/cum_max/cum_min/diff/pct_change/ewm_mean/ewm_std/ewm_var/rolling_max/rolling_min/rolling_mean/rolling_median/rolling_std/rolling_var/rolling_map。排序排名:sort/sort_by/arg_sort/shuffle/reverse/rank/is_duplicated/is_unique/is_first_distinct/is_last_distinct。统计:max/min/mean/nan_max/nan_min/median/std/var/entropy/kurtosis/skew/product/quantile/sum/arg_max/arg_min/first/last/get/mode。计数与唯一值这里有个容易踩的区别:len() 计所有行含 null,count() 只计非空值,null_count() 计 null;另有 n_unique/approx_n_unique/arg_unique/unique/unique_counts/value_counts/head/tail/limit/bottom_k/top_k/gather/gather_every/sample/slice/arg_true/replace/search_sorted。类型专属命名空间:arr(定长数组)与 list(变长,list.len/get/sort/join("-")/contains)、cat(Categorical 从数据推断类别并按字典序排,Enum 预先固定并按声明顺序排)、dtmonth/replace/strftime/convert_time_zone("UTC")/total_seconds)、str(UTF-8,长度和切片按字符不按字节:contains/split/to_uppercase/to_datetime/extract/strip_chars)、structfield/rename_fields/with_fields)、binbase64_decode/hex_encode)、nameprefix/to_lowercase 控制输出列名)、meta(写插件时用的内省:output_name/is_regex/has_multiple_outputs)。

呈现与可视化。 排版用 Great Tables:GT(df) 起手,链 tab_stub/cols_label/tab_header/fmt_number/fmt_nanoplot/data_color。绘图内置方法基于 Altair,走 plot 命名空间(df.plot.scatter(x=..., y=..., color=...));Plotnine、Plotly、hvPlot、Seaborn、Matplotlib 都能直接吃 Polars DataFrame,实在不行先 df.to_pandas()。最后一节是 Polars Cloud:用 pc.ComputeContext(workspace=..., cpus=4, memory=16, cluster_size=32) 描述算力,然后 lf.remote(ctx).execute().await_result() 把 LazyFrame 跑在自己环境里的集群上。

HN 评论精华

这条帖子 197 分、41 条评论。讨论几乎没停留在速查表本身,而是迅速跑成了三场语言/工具之争:R 的 tidyverse 与 data.table 对 pandas/Polars 的人机工程学优势、「不如直接用 DuckDB 写 SQL」派的反攻,以及对 pl.col("...") 这十个字符仪式感的集体吐槽(并给出了几种真正好用的缩写法)。