【问题标题】:Call data.frame columns inside of R functions?在 R 函数中调用 data.frame 列?
【发布时间】:2015-07-22 13:12:50
【问题描述】:

这样做的正确方法是什么?

在给定一系列输入的情况下,我有一个单独工作的函数,我想通过逐行循环数据来在大型数据集而不是奇异值上使用此函数。我试图更新函数以调用 data.frame 列而不是向量值,但没有成功。

一个简单的例子是:

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

如果这是重复的,请提前道歉。我一直在非常彻底地搜索 *,希望能找到一个现有的例子。

【问题讨论】:

  • 基本算术函数是矢量化的。您不需要dplyrlapply 来添加BMI 列,只需data$BMI = data$weight / data$height^2
  • 如果你想对一个函数取一个数据框并添加一个BMI列然后返回修改后的数据框,你可以按列号data[, 2] / data[, 3]^2引用列,引用名称@ 987654325@。对于这两种方法,您可以让用户为函数输入可选参数,以指定要使用的列索引或引用的列名称。
  • @Gregor 但是不要那样做,对吧?传递data.frame似乎有点浪费。只需编写一个函数myfun 用于构造列并将其与data$mynewcol <- with(data,myfun(weight,height,other_col)) 一起使用
  • @Frank 是的,但是我试图回答一般问题而不是具体情况。 OP 似乎想知道如何处理函数内的数据和列,但选择了一个示例,其中这不是一个 应该 做的。回顾示例,答案是字符串列名、索引或 NSE。

标签: r function apply


【解决方案1】:

提供这个答案,因为我无法在 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)

【讨论】:

    【解决方案2】:

    一般来说,函数不应该知道比他们需要知道的更多。如果您编写一个需要 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
    

    【讨论】:

      【解决方案3】:

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

      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,则可能值得保留此函数,但由于它是基础 R 中的单行代码,您可能不需要它。

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

      请注意,使用存储在变量中的列名意味着您不能使用$。这是我们通过使事情更加程序化所付出的代价,并且为此类应用程序形成一个好习惯。见fortunes::fortune(343):

      大多数 R 初学者迟早会被这个太方便的快捷方式所困扰。作为一个 R 新手,想想 R 作为你的银行账户:过度使用 $-extraction 会导致不良后果。最好是 尽早养成 '[[' 和 '[' 的习惯。

      -- Peter Ehlers(关于 $-extraction 的使用) R 帮助(2013 年 3 月)

      对于像 dplyr 这样更高级的用法,您不必引用列名等(并且可以评估表达式),lazyeval 包使事情变得相对轻松,并且具有非常漂亮的小插曲。

      基函数with可以用来做一些惰性求值,例如,

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

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

      【讨论】: