如何在data.table中基于其他列应用函数创建新列?
这个问题我太熟悉了!你遇到的核心问题是你的自定义函数是针对单个标量值写的,而data.table会把整列(向量)直接传入函数,这就导致if(a %in% c(4,6))只会判断向量的第一个元素,然后把同一个结果套用到所有行,这就是警告和第5行结果错误的原因。下面给你几个高效的解决方案:
1. 把函数改造成向量化版本
直接用ifelse替换普通if,让函数能处理向量输入,每个元素都会被单独判断:
simple_func_vec <- function(a, b){ ifelse(a %in% c(4,6), a*b, b/a) } # 调用测试 dt[, newval := simple_func_vec(cyl, disp)] head(dt)
运行后第5行cyl=8的newval会变成360/8=45,完全符合预期,警告也会消失。
2. 用Vectorize()快速包装原函数
如果你不想修改原函数的代码,可以用R内置的Vectorize()把标量函数转换成支持向量的版本:
vec_simple_func <- Vectorize(simple_func) dt[, newval := vec_simple_func(cyl, disp)] head(dt)
这个方法相当于逐元素调用你的原函数,适合快速改造现有代码,但大数据量下性能不如直接写向量化函数,所以优先推荐第一种方法。
3. 直接用data.table的条件赋值语法
其实你根本不需要单独定义函数,data.table支持在j中直接写条件逻辑,代码更简洁,性能也更好:
# 用base的ifelse dt[, newval := ifelse(cyl %in% c(4,6), cyl*disp, disp/cyl)] # 或者用data.table专属的fifelse(速度更快) dt[, newval := fifelse(cyl %in% c(4,6), cyl*disp, disp/cyl)]
fifelse是data.table专门优化的向量化条件函数,比base包的ifelse效率更高,在处理大表时优势明显。
补充:为什么.SDcols没用?
.SDcols是用来指定要传入.SD(数据集子集)的列,它本身不负责逐行计算。如果你之前尝试用.SD,可能写了类似dt[, newval := simple_func(cyl, disp), .SDcols = c("cyl", "disp")],但这和直接调用函数没区别,因为.SDcols只是筛选列,不会改变函数的执行方式。
如果非要用.SD逐行处理,需要结合by = .I(逐行分组),但这种方法效率极低,大数据量下会非常慢,绝对不推荐:
# 不推荐!逐行分组性能极差 dt[, newval := simple_func(cyl, disp), by = .I]
以上几种方法里,优先推荐方法1或方法3,既高效又符合data.table的设计理念。
内容的提问来源于stack exchange,提问作者Gautam

