dplyr::case_when搭配lubridate使用Inf的报错解决方法
问题背景
需要实现一个返回x年前日期的自定义函数,规则如下:
- 传入参数为无穷值
Inf时,返回预设特殊日期1800-01-01 - 传入普通数值时,返回当前日期减去对应年数的结果
最初基于dplyr和lubridate编写的代码如下:
foo <- function(years_ago) { case_when( years_ago == Inf ~ as.Date("1800-01-01"), TRUE ~ Sys.Date() - years(years_ago) ) }
调用foo(1)可以正常返回1年前的日期,但调用foo(Inf)时会抛出错误:argument is not interpretable as logical。
核心原因:
dplyr::case_when()的运行逻辑是先计算完所有分支右侧的全部表达式,再根据左侧的条件匹配对应位置的返回值,不会因为某条条件提前命中就跳过其他分支的计算。传入Inf时,即使第一个条件已经匹配,R还是会计算TRUE分支的years(Inf),这个无效运算直接触发了报错,和条件判断本身的逻辑无关。
可行方案
不需要切换到base R实现,tidyverse框架下有两种简单的改法:
方案1:最小改动适配原有代码
只需要在TRUE分支里把Inf临时替换成合法的数值占位即可——因为Inf对应的位置最终会取第一个分支的返回值,占位值的计算结果根本不会被用到,完全不影响最终输出:
foo <- function(years_ago) { case_when( years_ago == Inf ~ as.Date("1800-01-01"), TRUE ~ Sys.Date() - years(replace(years_ago, is.infinite(years_ago), 0)) ) }
这个写法完全保留了原来case_when的代码结构,改动量最小,同时支持标量和向量输入。
方案2:提前拦截特殊值(鲁棒性更强)
在进入通用计算逻辑前先处理特殊值,从根源上避免无效运算,适配更复杂的输入场景:
foo <- function(years_ago) { res <- rep(as.Date("1800-01-01"), length(years_ago)) normal_pos <- which(is.finite(years_ago)) res[normal_pos] <- Sys.Date() - years(years_ago[normal_pos]) if (length(years_ago) == 1) res <- res[[1]] res }
效果验证
两种改法都可以通过所有测试场景:
foo(1) # 正常返回1年前的日期 foo(Inf) # 正常返回"1800-01-01",无报错 foo(c(1, 3, Inf, 10)) # 支持向量输入,对应位置返回正确结果
内容的提问来源于stack exchange,提问作者Tom Wagstaff
相关产品推荐
相关产品推荐

