数据分析【免费下载链接】dplyrdplyr: A grammar of data manipulation项目地址https://gitcode.com/gh_mirrors/dp/dplyr点击查看免费下载导读本文以 dplyr 仓库中的快照文件 tests/testthat/_snaps/filter.md 为核心系统剖析filter()与filter_out()两个核心行筛选动词的参数约束、底层求值管线与高质量错误信息设计。快照文件不是普通文档而是 testthat 框架在测试运行中自动生成并校验的预期输出档案逐字记录了真实运行时产生的警告与错误信息。读完本文你将掌握快照测试文件的阅读与复现方法、一列矩阵弃用警告的来龙去脉issue #6091、多列矩阵与高维数组的强约束、类型/尺寸/求值三类错误的构造原理、across()在filter()中已废弃的替代方案if_any()/if_all()以及.by、.preserve、by拼写检查等参数校验的源码级实现。快照文件在 dplyr 质量保障体系中的角色testthat 的快照测试expect_snapshot()是一种契约式测试第一次运行时测试框架把真实输出写入_snaps/目录下的 Markdown 文件之后的每次运行都会将当前输出与快照逐字比对任何差异都会导致测试失败。因此快照文件实际是运行时真实行为的精确副本具有比普通文档更高的可信度。在 dplyr 中tests/testthat/_snaps/filter.md 对应 tests/testthat/test-filter.R 中的一组expect_snapshot()用例二者通过测试名一一对应。快照内容覆盖六大主题快照测试块触发代码关注点filter() and filter_out() allow matrices with 1 column with a deprecation warning (#6091)matrix(c(TRUE, FALSE), nrow 2)一列矩阵的弃用警告filter() and filter_out() disallow matrices with 1 columnmatrix(TRUE, nrow 3, ncol 2)多列矩阵拒绝filter() and filter_out() disallow arrays with 2 dimensionsarray(TRUE, dim c(3, 1, 1))高维数组拒绝filter() gives useful error messages13 组错误场景错误信息设计Using data frames in filter() is defunct (#7758)across()/tibble()数据框结果的废弃filter() doesnt allow data frames with missing or empty names (#6758)无名/NA 名列名列名校验.by相关 4 个测试块.by、.preserve、by参数校验快照文件是测试产物理论上由测试自动生成阅读时应以 tests/testthat/test-filter.R 中对应的测试块为源头快照为结果。二者配合即可精确复现每一条警告与错误。filter() 与 filter_out() 的语义与底层求值管线一对互补的筛选动词在 dplyr 中filter()保留满足条件的行filter_out()则丢弃满足条件的行。二者共享同一套参数结构见 R/filter.Rfilter - function(.data, ..., .by NULL, .preserve FALSE) filter_out - function(.data, ..., .by NULL, .preserve FALSE)...数据掩码data-masking表达式每个表达式都必须返回逻辑向量。多个表达式用逗号分隔时按组合若想按|组合需显式用when_any()包裹。.by临时分组列实现按组计算条件、输出保持原类的瞬态分组。.preserve仅对分组数据框有意义控制筛选后分组结构是否按结果重算。值得强调的语义filter()与filter_out()都把NA当作FALSE处理。也就是说filter()会同时丢掉FALSE与NA而filter_out()会同时保留FALSE与NA。由此保证filter(data, 条件)与filter_out(data, 条件)的结果互斥且并集恰好覆盖原数据的每一行一次这一不变量在 tests/testthat/test-filter.R 的filter() and filter_out() are complements测试中显式验证。当你的意图是保留某类行时用filter()当意图是剔除某类行时用filter_out()可避免!/!运算符以及| is.na(col)式显式 NA 补偿的繁琐写法。R 层调用链filter()与filter_out()是泛型函数最终都汇入filter_impl()R/filter.R核心管线为filter_impl() ├── dplyr_quosures(...) # 将 ... 捕获为 quosure ├── check_filter(dots) # 检出命名输入 误用为 ├── compute_by(.by, data) # 解析 .by 分组 └── filter_rows() ├── filter_expand() # 展开 across()/pick() └── filter_eval() └── mask$eval_all_filter() # C 层求值其中filter_rows()R/filter.R中的invert - verb filter_out揭示了filter_out()的本质完全复用filter()的求值逻辑仅在 C 层对最终 keep 向量取反见 src/filter.cpp。这一设计保证两个动词的错误信息、性能与行为完全对称。C 层求值与归约真正的求值发生在 src/filter.cpp 的dplyr_mask_eval_all_filter()中。其核心流程对每个分组初始化一个全TRUE的归约向量p_reduced逐个用rlang::eval_tidy()求值条件表达式检查结果尺寸结果长度必须等于组内行数n或为标量1否则触发filter_incompatible_size错误用filter_is_valid_lgl()校验类型见下文矩阵约束通过filter_lgl_reduce()以逻辑归约标量结果会广播到整组src/filter.cpp最后若invert为真即filter_out()对 keep 向量整体取反。这个管线同时解释了快照中多条错误的出处类型错误由stop_filter_incompatible_type()src/filter.cpp抛出尺寸错误由stop_filter_incompatible_size()src/filter.cpp抛出二者分别携带result、size、expected_size、index等结构化数据再由 R 侧 R/filter.R 的filter_bullets.dplyr:::filter_incompatible_type与filter_bullets.dplyr:::filter_incompatible_size方法渲染成人类可读的报错文案。一列矩阵以弃用警告放行#6091快照文件的第一块记录了 dplyr 1.1.0 起对单列矩阵的弃用策略out - filter(df, matrix(c(TRUE, FALSE), nrow 2)) Warning: Using one column matrices in filter() or filter_out() was deprecated in dplyr 1.1.0. i Please use one dimensional logical vectors instead.对应的测试块位于 tests/testthat/test-filter.R。从源码看这一行为由 src/filter.cpp 的filter_is_valid_lgl()控制无dim属性的纯逻辑向量直接合法1 维数组dimensionality 1合法且注释说明R 中很多操作会自然产生一维数组2 维且p_dim[1] 1的单列矩阵合法但通过signal_filter_one_column_matrix()发出一条dplyr:::signal_filter_one_column_matrix条件R 侧由 R/filter.R 的warn_filter_one_column_matrix()用lifecycle::deprecate_warn()转换为弃用警告并注明请改用一维逻辑向量。快照中还体现了两个实现细节分组数据框只警告一次filter_is_valid_lgl()的first参数只对第一个分组触发信号const bool first i 0src/filter.cpp因此分组筛选不会对每个组重复刷警告。警告不影响结果单列矩阵仍按逻辑向量正常参与归约测试断言out的筛选结果与预期一致filter保留第 1 行、filter_out保留第 2 行。多列矩阵与高维数组类型强约束对于超过一列的矩阵快照记录的是硬错误而非警告filter(df, matrix(TRUE, nrow 3, ncol 2)) Error in filter(): i In argument: matrix(TRUE, nrow 3, ncol 2). Caused by error: ! ..1 must be a logical vector, not a logical matrix.三维数组array(TRUE, dim c(3, 1, 1))同样被拒绝..1must be a logical vector, not a logical array。从 src/filter.cpp 可以看到完整的判定矩阵输入形态dim 属性处理纯逻辑向量无合法1 维逻辑数组c(n)合法单列矩阵c(n, 1)合法 弃用警告多列矩阵c(n, m1)报错logical matrix高维数组c(..., 2)报错logical array注意这里的错误信息同样走In argument:上下文头即哪个参数出了问题再由Caused by error:给出根本原因。这种外层定位参数、内层说明原因的两级结构正是 dplyr 错误信息体系的统一风格其上下文来源是 R/conditions.R 的local_error_context()/new_error_context()——它只保存表达式本身而非求值结果避免跨过 dplyr 动词生命周期后继续携带昂贵环境见注释 #7649。高质量错误信息13 种场景逐一拆解快照中最大的测试块filter() gives useful error messages记录了 13 组错误场景对应 tests/testthat/test-filter.R。这些错误覆盖了几乎所有filter 用错的方式是理解 dplyr 错误设计的最佳教材。类型错误必须返回逻辑向量filter(group_by(iris, Species), 1:n()) Error in filter(): i In argument: 1:n(). i In group 1: Species setosa. Caused by error: ! ..1 must be a logical vector, not an integer vector.要点在于分组数据框的错误信息会额外附上当前分组标签In group 1: Species setosa帮助用户在大型分组数据中定位是哪一组出了问题。这一组上下文由 R/conditions.R 的cnd_bullet_combine_details()负责追加未分组时则只有In argument:一行。..1是 C 层对第 1 个条件的内部占位名index从 1 开始编号见 src/filter.cppR 侧最终渲染为..1、..2等。尺寸错误长度必须是组内行数或 1filter(group_by(iris, Species), c(TRUE, FALSE)) i In argument: c(TRUE, FALSE). i In group 1: Species setosa. Caused by error: ! ..1 must be of size 50 or 1, not size 2.这里 50 是 iris 按Species分组后每组 50 行or 1表示允许标量广播。对应的 rowwise 数据框则要求每组恰好 1 行filter(rowwise(iris, Species), c(TRUE, FALSE)) i In row 1. Caused by error: ! ..1 must be of size 1, not size 2.rowwise 的错误上下文从In group 1:变为In row 1.。尺寸校验发生在 C 的 src/filter.cppres_size既不是组内行数n也不是1时立即报错且先校验尺寸再校验类型保证一个表达式至多报一次错。求值错误对象不存在filter(mtcars, _x) Error in filter(): i In argument: _x. Caused by error: ! object _x not found分组版本会追加i In group 1: cyl 4.。这类错误由rlang::eval_tidy()原样抛出dplyr 通过withCallingHandlers()R/filter.R捕获后仅补充In argument:上下文绝不吞掉底层原因——这正是错误透传设计任何用户表达式中的错误包括stop({)这种花括号消息都会原样保留。命名输入与的经典陷阱filter(mtcars, x 1) Error in filter(): ! We detected a named input. i This usually means that youve used instead of . i Did you mean x 1?这个提示由check_filter()在求值之前完成R/filter.R它遍历捕获的 quosure只要发现命名输入且表达式不是逻辑常量就立即中止并给出你是不是把当成了的建议。因此快照中的filter(mtcars, y 2, z 3)会准确指认z 3而filter(mtcars, TRUE, x 1)即便前面有合法条件也会被拦截。非数据源对象指向正确的替代函数filter(ts(1:10)) Error in filter(): ! Incompatible data source. x .data is a ts object, not a data source. i Did you want to use stats::filter()?时间序列对象不是数据框dplyr 的filter()无法处理错误信息直接引导用户改用 R 基础包的stats::filter()——一个简洁且贴心的走错门指引。错误信息的两级结构与测试技巧快照中的(expect_error(...))双层括号是 testthat 的常见技巧内层expect_error()把错误包装为普通返回值外层再交给expect_snapshot()序列化从而让快照同时记录错误的完整结构error/rlang_error与渲染文本。复现这些错误时直接在 R 会话中运行对应表达式即可看到与快照一致的输出。数据框作为筛选条件已废弃请用 if_any() / if_all()快照第 5 块记录了across()在filter()中的使用已被标记为 defunct#7758filter(df, across(everything(), ~ .x 0)) Error in filter(): i In argument: across(everything(), ~.x 0). Caused by error: ! ..1 must be a logical vector, not a tbl_df/tbl/data.frame object. i If you used across() to generate this data frame, please use if_any() or if_all() instead.关键在最后一行提示across()会返回数据框而filter()的每个条件必须是一维逻辑向量二者从类型上就不兼容。历史上用户习惯用across()表达所有列满足某条件但正确做法是改用if_any()相当于|组合或if_all()相当于组合df | filter(if_all(starts_with(x), ~ .x 0)) # 等价于 x1 0 x2 0 df | filter(if_any(starts_with(x), ~ .x 0)) # 等价于 x1 0 | x2 0这一等价关系在 tests/testthat/test-filter.R 中通过直接与/|写法对比来验证。值得注意的是快照中这一错误在普通数据框、分组数据框In group 1: x 1.、rowwise 数据框In row 1.三种形态下都会触发说明该约束与分组模式无关直接传入tibble(x 0, y 0)这样的逻辑数据框也同样被拒绝。R 侧负责追加这段建议的正是 R/filter.R 的filter_bullets.dplyr:::filter_incompatible_type当错误对象本身是 data.frame 时会在类型错误后附上if_any() / if_all()的指引。由此可以推断该提示的设计目标是一次性终结在 filter 里用 across这一历史反模式。缺失或空列名的数据框拒绝变换快照第 6 块记录了列名损坏的数据框无法筛选#6758filter(df1) # 列名为 或 NA Error in filter(): ! Cant transform a data frame with NA or names.对应的测试用new_data_frame(set_names(list(1), ))构造空名列名、set_names(list(1), NA_character_)构造缺失列名tests/testthat/test-filter.R。该约束出现在变换数据框阶段Cant transform...因为 dplyr 的行切片机制依赖有效的列名来重建数据框结构。对于filter_out()同样生效可见这是filter()/filter_out()共用的基础设施级校验。.by、.preserve 与 by 拼写参数级防御快照最后四个测试块围绕分组参数展开均在进入数据求值之前由 R/filter.R 的入口校验函数拦截。.by与.preserve互斥filter(df, .by x, .preserve TRUE) Error in filter(): ! Cant supply both .by and .preserve.实现位于check_not_both_by_and_preserve()R/filter.R只要.by非空且.preserve不是FALSE立即报错。逻辑上.by是一次性临时分组、结果还原为原始类而.preserve是保留既有分组结构二者语义本就冲突。.by与既有分组结构冲突filter(gdf, .by x) # gdf 是分组数据框 Error in filter(): ! Cant supply .by when .data is a grouped data frame.分组数据框与 rowwise 数据框都已带有隐式分组信息再叠加.by会产生歧义因此分别报出grouped data frame与rowwise data frame两类专属错误。by拼写检查#6647filter(df, by x) Error in filter(): ! Cant specify an argument named by in this verb. i Did you mean to use .by instead?这是 dplyr 经典的防呆设计check_by_typo()R/by.R把by缺少点号当作普通命名参数捕获发现后提示用户真正的参数是.by。该检查同样作用于filter()与filter_out()两个动词。快照驱动的回归保障如何复现与验证快照测试的价值在于把错误信息当作 API 的一部分来守护——错误文本的任何意外变化都会导致测试失败从而防止错误信息在迭代中悄然劣化。要亲自复现本文讨论的所有行为可以# 在 R 会话中仓库根目录加载开发版 dplyr 后执行 df - tibble(x 1:2) filter(df, matrix(c(TRUE, FALSE), nrow 2)) # 弃用警告 filter(tibble(x 1:3), matrix(TRUE, nrow 3, ncol 2)) # 类型错误 # 运行整个 filter 测试集含快照比对 devtools::test(filter filter)若想精确对比快照文本可打开 tests/testthat/_snaps/filter.md将其与 tests/testthat/test-filter.R 中的expect_snapshot()块逐段对应阅读每个---分隔符对应一个独立的expect_snapshot()调用。小结从一列矩阵的温和弃用到多列矩阵、高维数组、数据框条件的硬性拒绝从类型、尺寸、求值三类错误的定位 原因两级结构到.by/.preserve/by拼写的参数级防御——dplyr 的filter()与filter_out()展现了一套层次分明、意图清晰的错误处理哲学在入口拦截可静态发现的错误命名输入、参数冲突在求值期用结构化条件错误精确描述问题类型、尺寸、分组上下文并对历史反模式给出明确的迁移指引if_any()/if_all()、一维逻辑向量。而_snaps/filter.md这份快照档案则确保这套高质量错误信息在后续版本演进中始终稳定如一成为 dplyr数据操作语法可信赖的一部分。赞分享数据分析【免费下载链接】dplyrdplyr: A grammar of data manipulation项目地址https://gitcode.com/gh_mirrors/dp/dplyr点击查看免费下载相关推荐dplyr arrange() 排序机制深度解析错误消息、Locale 校验与测试快照实战dplyr arrange 排序机制深度解析错误消息、Locale 校验与测试快照实战 arrange 是 dplyr 中按列值重排行序的核心动词其底层实现数据分析dplyr case_when() 与 replace_when() 输入校验与错误信息机制全解析基于快照测试dplyr case_when 与 replace_when 输入校验与错误信息机制全解析基于快照测试 case_when 是 dplyr 中最常用的向量数据分析dplyr pick() 函数深度解析错误行为快照、宏展开机制与实战模式dplyr pick 函数深度解析错误行为快照、宏展开机制与实战模式 pick 是 dplyr 中用于在 mutate 、 summarise 、 filte数据分析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考