@MaximeRivest: dpyr 有用户了! https://github.com/maximerivest/dpyr…
摘要
dpyr 是一个新的 Python 库,复制了 R 的 dplyr 数据操作函数,提供与 Polars 或 DuckDB 后端的方法链、IDE 自动补全,并且语义经过 dplyr 验证。
查看缓存全文
缓存时间: 2026/08/25 14:06
dpyr 有用户了! https://t.co/PnW9m58LyN https://t.co/s6jddTm95F
maximerivest/dpyr
来源: https://github.com/maximerivest/dpyr
dpyr
dplyr for Python。
Tidyverse 的动词——filter、mutate、group_by、summarize、连接、across、tidyselect——作为 Python 方法链实现,在 polars (https://pola.rs) 或 duckdb (https://duckdb.org) 上执行,并具备真正的 IDE 自动补全功能,其语义经与 dplyr 自身验证。
pip install dpyr
# 或:uv add dpyr
from dpyr import read, col, n, desc
starwars = read("starwars.parquet")
# read() 接受任何表格形式的数据:
# .parquet/.csv/.arrow/.db 文件路径、字典、
# polars/pandas 数据框、arrow 表格、
# Hugging Face 数据集、numpy/torch/jax 数组。
( starwars
.filter(col.height > 180, col.mass < 100)
.mutate(bmi = col.mass / (col.height / 100) ** 2)
.group_by(col.species)
.summarize(
n = n(),
mean_bmi = col.bmi.mean(),
)
.arrange(desc(col.mean_bmi))
)
在笔记本中执行它,你会立即看到结果。若列名拼错,你会在该行得到错误提示,并附有“你是否想说的是…”的建议。将同样的代码包装在一个管道中,并仅在末尾调用 .collect(),整个链式调用就会作为一个融合查询运行,并具备谓词下推能力。这种组合——模式优先、数据惰性、显示优先——是其核心设计。
两个后端,同一种语义
import duckdb
from dpyr import read
df = read({"x": [1, 2, 3], "g": ["a", "a", "b"]})
# polars 引擎
con = duckdb.connect("warehouse.db")
tbl = read(con, "events")
# SQL 下推
相同的链式调用在两个引擎上产生相同的结果——这是通过一个 Hypothesis 模糊测试器来强制保证的,该测试器在两个引擎上运行随机的动词链并逐位比较结果;同时也通过针对真实 dplyr 的差异测试来保证:tests/specs/ 中的每个规范都由 dplyr(通过 oracle/run_specs.R)执行以生成一个已提交的黄金 parquet 文件,然后在两个后端上通过 dpyr 重放。当 R 和引擎之间存在真实差异时,决策会记录在 docs/SEMANTICS.md 中,而非听之任之。
你熟悉的 dplyr | dplyr | dpyr |
|—|—|
| filter(df, height > 180) | df.filter(col.height > 180) |
| mutate(df, bmi = mass / h^2) | df.mutate(bmi = col.mass / col.h ** 2) |
| summarise(df, n = n(), m = mean(x, na.rm = TRUE)) | df.summarize(n = n(), m = col.x.mean()) |
| arrange(df, desc(mass)) | df.arrange(desc(col.mass)) |
| select(df, name, starts_with("h")) | df.select(col.name, starts_with("h")) |
| select(df, -mass) | df.select(-col.mass) |
| across(where(is.numeric), mean) | across(where(is_numeric), "mean") |
| left_join(a, b, by = "k") | a.left_join(b, on = col.k) |
| pivot_longer(df, x:y) | df.pivot_longer([col.x, col.y]) |
| if_else(), case_when(), n_distinct() | if_else(), case_when(), .n_unique() |
| lag(), lead(), row_number(), min_rank() | lag(), lead(), row_number(), min_rank() |
| cumsum(), dense_rank(), percent_rank() | cum_sum(), dense_rank(), percent_rank() |
| slice_min(x, n), slice_max(x, n) (保持 ties) | slice_min(col.x, n), slice_max(col.x, n) |
| separate(), unite(), relocate() | separate(), unite(), relocate() |
| coalesce(), replace_na() | coalesce(), replace_na() |
分组的 mutate/filter 按组进行窗口计算,summarize 剥离一级分组,连接默认使用 .x/.y 后缀并匹配 NA 值——这些都是刻意保留的 dplyr 行为。
真正有效的自动补全
df.c.height—— 基于数据框的代理:列名从实时模式补全,返回的表达式是有类型的(对数值调用.mean(),对字符串调用.str_detect();在字符串列上调用.mean()会立即在构建时引发错误)。df.filter(lambda c: c.height > 180)—— lambda 风格实现相同效果。dpyr stubgen data/*.parquet -o schemas.py—— 生成类型化的模式模块,以便在任何 IDE 中静态进行补全和类型检查。
数据库是目的地,而不仅仅是来源
db = read("warehouse.db")
# 目录对象:db.tables, db.orders
gold = db.orders.group_by(col.region).summarize(rev = col.amount.sum())
gold.to_table("gold_revenue")
# CREATE TABLE AS ,完全在引擎内执行
gold.to_view("gold_live")
# 将惰性计划保存为命名视图
gold.write("gold.parquet")
# 在引擎内执行 COPY(通过扩展分派)
mem = read({"region": ["east"], "target": [1000.0]})
gold.inner_join(mem, on = col.region)
# 内存中的数据框通过 arrow 自动桥接到 duckdb
# (零拷贝)
默认交互式,需要时可惰性执行
df.persist()
# 检查点:现在具体化(duckdb:临时表)
df.lazy()
# 此数据框永不隐式执行
dpyr.options.interactive = False
# 全局关闭交互模式,用于生产管道
结果通过计划哈希进行缓存,因此在笔记本中重新显示数据框时永远不会重新计算它。
文档
完整指南请访问 maximerivest.github.io/dpyr (https://maximerivest.github.io/dpyr/) —— 入门、分组数据、连接、窗口函数、列操作、数据整形、表达式与自动补全,以及后端指南(连接和操作 polars 与 duckdb)。
项目文档
| 文档 | 内容 |
|---|---|
| docs/DESIGN.md | API 设计、具体化模型、自动补全策略、架构 |
| docs/SEMANTICS.md | R、polars 和 duckdb 之间所有刻意做出的不同决策 |
| docs/TESTING.md | dplyr 作为预言机的黄金测试、后端一致性模糊测试、Hypothesis 属性 |
| docs/ROADMAP.md | 1.0 版本已发布内容及后续计划 |
许可证
MIT © Maxime Rivest
相似文章
@MaximeRivest: 我用了大约3个提示,让Fable将dplyr移植到Python,以polars/duckdb为后端。我尚未完成对移植的评估,但它……
Maxime Rivest 宣布将 dplyr 移植到 Python,使用 polars/duckdb 作为后端,并评价说移植很扎实。
@MaximeRivest: DSPy 3.4 候选版本已发布,我们正从 litellm 迁移。我一直困扰于 dspy 导入速度慢以及……
DSPy 3.4 候选版本已宣布,正从 litellm 迁移以提升导入速度并减少依赖。社区成员如 Drew Breunig 对改进表示赞赏。
@isaacbmiller1:DSPy 3.2.0 发布!亮点速览:- dspy.RLM 在解析、工具执行与故障恢复方面的改进……
DSPy 3.2.0 优化了 dspy.RLM 的解析、工具执行与故障恢复,并持续推进与 LiteLLM 的解耦。
@lateinteraction: 天哪,我们真是太幸运了,能有@isaacbmiller1作为首席维护者!另外,哇,我花了比我愿意承认的还长的时间才…
DSPy 3.3.0 已发布,其特色是用于 GEPA 优化代码和提示词的 dspy.Flex,以及原生支持并行工具调用的 dspy.ReActV2,同时对首席维护者 Isaac Miller 表示赞赏。
从 uv 迁移到 PDM
开发者分享将 Python 项目从 uv 迁移到 PDM 的经验,强调 PDM 纯 Python 代码库、刚发布的 2.26.8 版带来的“相对时间依赖冷却”功能,以及更强大的项目管理特性。