在 R 函数中调用 data.frame 列?

Call data.frame columns inside of R functions?

正确的做法是什么?

我有一个函数,在给定一系列输入的情况下,它自己可以很好地工作,我想在大型数据集上使用这个函数,而不是通过逐行循环数据来处理奇异值。我尝试更新函数以调用 data.frame 列而不是向量值,但没有成功。

一个简单的例子是:

假设我有一个包含 4 列的 date.frame,data$id、data$height、data$weight、data$gender。我想编写一个函数来遍历每一行(使用应用)并计算 BMI (kg/m^2)。我知道使用 dplyr 很容易做到,但我想学习如何在不求助于外部包的情况下做到这一点,但找不到如何正确引用函数中的列的明确答案。

如有重复请提前致歉。我一直在非常彻底地搜索 Whosebug,希望找到一个现有的例子。

我想这就是您要找的。从功能上引用数据框列的最简单方法是使用带引号的列名。原则上,你做的是这个

data[, "weight"] / data[, "height"]^2

但在函数内部您可能希望让用户指定高度或体重列的名称不同,因此您可以编写函数

add_bmi = function(data, height_col = "height", weight_col = "weight") {
    data$bmi = data[, weight_col] / data[, height_col]
    return(data)
}

此函数将假设要使用的列默认命名为 "height" 和 "weight",但用户可以根据需要指定其他名称。您可以改用列索引来执行类似的解决方案,但使用名称往往更容易调试。

这么简单的功能很少有用。如果您正在计算大量数据集的 BMI,也许值得保留此函数,但由于它是 base R 中的单行函数,您可能不需要它。

my_data$BMI = with(my_data, weight / height^2)

需要注意的是,使用存储在变量中的列名意味着您不能使用 $。这是我们通过让事情变得更加程序化而付出的代价,对于这样的应用程序养成一个好习惯是一个好习惯。见 fortunes::fortune(343):

Sooner or later most R beginners are bitten by this all too convenient shortcut. As an R newbie, think of R as your bank account: overuse of $-extraction can lead to undesirable consequences. It's best to acquire the '[[' and '[' habit early.

-- Peter Ehlers (about the use of $-extraction) R-help (March 2013)

对于像 dplyr 这样的更高级的用法,你不需要引用列名等(并且可以计算表达式),lazyeval 包使事情相对轻松并且有非常好的小插曲.

基函数with可以用来做一些懒惰的评估,例如

with(mtcars, plot(disp, mpg))
# sometimes with is nice
plot(mtcars$disp, mtcars$mpg)

with 最好以交互方式和简单的脚本使用。如果您开始编写程序化生产代码(例如,您自己的 R 包),避免非标准评估会更安全。例如,请参阅 ?subset 中的警告,这是另一个使用非标准评估的基本 R 函数。

一般来说,函数不应该知道比它们需要知道的更多的东西。如果您编写一个需要 data.frame 的函数,而在 data.frame 中提供输入数据并不是必需的,那么您会使您的函数比需要的更具限制性。

该函数的正确写法如下:

bmi <- function(height,weight) weight/height^2;

这将允许您根据身高值向量和体重值向量计算 BMI 值向量,因为 /^ 都是向量化运算。所以,例如,如果你有两个松散的身高和体重向量,那么你可以这样称呼它:

set.seed(1);
N <- 5;
height <- rnorm(N,1.7,0.2);
weight <- rnorm(N,65,4);
BMI <- bmi(height,weight);
height; weight; BMI;
## [1] 1.574709 1.736729 1.532874 2.019056 1.765902
## [1] 61.71813 66.94972 67.95330 67.30313 63.77845
## [1] 24.88926 22.19652 28.91995 16.50967 20.45224

如果您的输入包含在 data.frame 中,您将能够这样做:

set.seed(2);
N <- 5;
df <- data.frame(id=1:N, height=rnorm(N,1.7,0.2), weight=rnorm(N,65,4), gender=sample(c('M','F'),N,replace=T) );
df$BMI <- bmi(df$height,df$weight);
df;
##   id   height   weight gender      BMI
## 1  1 1.520617 65.52968      F 28.33990
## 2  2 1.736970 67.83182      M 22.48272
## 3  3 2.017569 64.04121      F 15.73268
## 4  4 1.473925 72.93790      M 33.57396
## 5  5 1.683950 64.44485      M 22.72637

提供这个答案是因为我无法在 SO 上找到它并且我的头撞在墙上试图弄清楚为什么我的 R 包中的函数假设我的新列是一个对象而不是 data.frame列。

如果函数接受 data.frame 并且在函数中添加和转换附加列,方法如下:

example_func <- function(df) {
  # To add a new column
  df[["New.Column"]] <- value
  
  # To get the ith value of that column
  df[[i, "New.Column"]]

  # To subset set the df using some conditional logic on that column
  df[df[["New.Column"]]==value]

  # To sort on that column
  setorderv(df, "New.Column", -1)
}

注意这需要 library(devtools)