【问题标题】:Applying a custom function repeatedly to same dataframe using purrr使用 purrr 将自定义函数重复应用于同一数据帧
【发布时间】:2019-09-15 19:40:04
【问题描述】:

假设我有一个如下的数据框:

df <- data.frame(
  alpha = 0:20,
  beta = 30:50,
  gamma = 100:120
)

我有一个创建新列的自定义函数。 (注意,我的实际函数要复杂很多,没有自定义函数是无法向量化的,所以请忽略这里转换的实质。)例如:

newfun <- function(var = NULL) {
  newname <- paste0(var, "NEW")
  df[[newname]] <- df[[var]]/100
  return(df)
}

我想在数据集的许多列上重复应用它并“构建”数据集。当我执行以下操作时会发生这种情况:

df <- newfun("alpha") 
df <- newfun("beta") 
df <- newfun("gamma")

显然这是多余的,是map 的一个例子。但是当我执行以下操作时,我会返回一个数据框列表,这不是我想要的:

df <- data.frame(
  alpha = 0:20,
  beta = 30:50,
  gamma = 100:120
)
out <- c("alpha", "beta", "gamma") %>%
      map(function(x) newfun(x)) 

如何遍历列名向量并查看重复应用于同一数据帧的更改?

【问题讨论】:

    标签: r apply tidyverse purrr


    【解决方案1】:

    编写函数以在其范围之外找到一些df 既冒险又会咬你,尤其是当你看到类似的内容时:

    df[['a']] <- 2
    # Error in df[["a"]] <- 2 : object of type 'closure' is not subsettable
    

    当它找不到名为df您的变量,而是找到名为df基本函数 时,您将收到此错误。这一发现的两个寓意:

    1. 虽然我承认自己使用df,但将变量命名为与 R 函数相同的名称通常是一种不好的做法(尤其是从基础函数中);和
    2. 范围违规是草率的,会导致工作流程无法重现,并且通常难以解决问题或更改。

    为了解决这个问题,并且由于您的函数依赖于知道旧/新变量名称是什么或应该是什么,我认为 pmap 或 base R Map 可能会更好。此外,我建议您在函数之外命名新变量,使其成为“仅数据”。

    myfunc <- function(x) x/100
    setNames(lapply(dat[,cols], myfunc), paste0("new", cols))
    # $newalpha
    #  [1] 0.00 0.01 0.02 0.03 0.04 0.05 0.06 0.07 0.08 0.09 0.10 0.11 0.12 0.13 0.14 0.15 0.16 0.17
    # [19] 0.18 0.19 0.20
    # $newbeta
    #  [1] 0.30 0.31 0.32 0.33 0.34 0.35 0.36 0.37 0.38 0.39 0.40 0.41 0.42 0.43 0.44 0.45 0.46 0.47
    # [19] 0.48 0.49 0.50
    # $newgamma
    #  [1] 1.00 1.01 1.02 1.03 1.04 1.05 1.06 1.07 1.08 1.09 1.10 1.11 1.12 1.13 1.14 1.15 1.16 1.17
    # [19] 1.18 1.19 1.20
    

    从这里开始,我们只需要对它进行列绑定 (cbind):

    cbind(dat, setNames(lapply(dat[,cols], myfunc), paste0("new", cols)))
    #    alpha beta gamma newalpha newbeta newgamma
    # 1      0   30   100     0.00    0.30     1.00
    # 2      1   31   101     0.01    0.31     1.01
    # 3      2   32   102     0.02    0.32     1.02
    # 4      3   33   103     0.03    0.33     1.03
    # 5      4   34   104     0.04    0.34     1.04
    # ...
    

    特别注意:如果您打算迭代地(重复地)执行此操作,那么迭代地向帧添加行通常是不好的;虽然我知道这对于添加行来说是个坏主意,但我怀疑(目前没有证据)对列做同样的事情也是不好的。因此,如果您经常这样做,请考虑使用do.call(cbind, c(list(dat), ...)),其中... 是要添加的内容列表。这导致对cbind 的一次调用,因此只有原始dat 的一个内存副本。 (相比之下,迭代调用 *bind 函数会在每次传递时生成一个完整的副本,扩展性很差。)

    additions <- lapply(1:3, function(i) setNames(lapply(dat[,cols], myfunc), paste0("new", i, cols)))
    str(additions)
    # List of 3
    #  $ :List of 3
    #   ..$ new1alpha: num [1:21] 0 0.01 0.02 0.03 0.04 0.05 0.06 0.07 0.08 0.09 ...
    #   ..$ new1beta : num [1:21] 0.3 0.31 0.32 0.33 0.34 0.35 0.36 0.37 0.38 0.39 ...
    #   ..$ new1gamma: num [1:21] 1 1.01 1.02 1.03 1.04 1.05 1.06 1.07 1.08 1.09 ...
    #  $ :List of 3
    #   ..$ new2alpha: num [1:21] 0 0.01 0.02 0.03 0.04 0.05 0.06 0.07 0.08 0.09 ...
    #   ..$ new2beta : num [1:21] 0.3 0.31 0.32 0.33 0.34 0.35 0.36 0.37 0.38 0.39 ...
    #   ..$ new2gamma: num [1:21] 1 1.01 1.02 1.03 1.04 1.05 1.06 1.07 1.08 1.09 ...
    #  $ :List of 3
    #   ..$ new3alpha: num [1:21] 0 0.01 0.02 0.03 0.04 0.05 0.06 0.07 0.08 0.09 ...
    #   ..$ new3beta : num [1:21] 0.3 0.31 0.32 0.33 0.34 0.35 0.36 0.37 0.38 0.39 ...
    #   ..$ new3gamma: num [1:21] 1 1.01 1.02 1.03 1.04 1.05 1.06 1.07 1.08 1.09 ...
    
    do.call(cbind, c(list(dat), additions))
    #    alpha beta gamma new1alpha new1beta new1gamma new2alpha new2beta new2gamma new3alpha new3beta new3gamma
    # 1      0   30   100      0.00     0.30      1.00      0.00     0.30      1.00      0.00     0.30      1.00
    # 2      1   31   101      0.01     0.31      1.01      0.01     0.31      1.01      0.01     0.31      1.01
    # 3      2   32   102      0.02     0.32      1.02      0.02     0.32      1.02      0.02     0.32      1.02
    # 4      3   33   103      0.03     0.33      1.03      0.03     0.33      1.03      0.03     0.33      1.03
    # 5      4   34   104      0.04     0.34      1.04      0.04     0.34      1.04      0.04     0.34      1.04
    # 6      5   35   105      0.05     0.35      1.05      0.05     0.35      1.05      0.05     0.35      1.05
    # ...
    

    【讨论】:

      【解决方案2】:

      另一种方法是将您的函数更改为仅返回一个向量:

      newfun2 <- function(var = NULL) {
        df[[var]] / 100
      }
      
      newfun2('alpha')
      # [1] 0.00 0.01 0.02 0.03 0.04 0.05 0.06 0.07 0.08 0.09 0.10 0.11 0.12 0.13
      #[15] 0.14 0.15 0.16 0.17 0.18 0.19 0.20
      

      然后,使用 base,您可以使用 lapply() 循环遍历要执行的函数列表:

      cols <- c("alpha", "beta", "gamma")
      
      df[, paste0(cols, 'NEW')] <- lapply(cols, newfun2)
      #or 
      #df[, paste0(cols, 'NEW')] <- purrr::map(cols, newfun2)
      df
      
         alpha beta gamma alphaNEW betaNEW gammaNEW
      1      0   30   100     0.00    0.30     1.00
      2      1   31   101     0.01    0.31     1.01
      3      2   32   102     0.02    0.32     1.02
      4      3   33   103     0.03    0.33     1.03
      5      4   34   104     0.04    0.34     1.04
      6      5   35   105     0.05    0.35     1.05
      7      6   36   106     0.06    0.36     1.06
      8      7   37   107     0.07    0.37     1.07
      9      8   38   108     0.08    0.38     1.08
      10     9   39   109     0.09    0.39     1.09
      11    10   40   110     0.10    0.40     1.10
      12    11   41   111     0.11    0.41     1.11
      13    12   42   112     0.12    0.42     1.12
      14    13   43   113     0.13    0.43     1.13
      15    14   44   114     0.14    0.44     1.14
      16    15   45   115     0.15    0.45     1.15
      17    16   46   116     0.16    0.46     1.16
      18    17   47   117     0.17    0.47     1.17
      19    18   48   118     0.18    0.48     1.18
      20    19   49   119     0.19    0.49     1.19
      21    20   50   120     0.20    0.50     1.20
      

      【讨论】:

        【解决方案3】:

        根据您编写函数的方式,将 newfun 的结果分配给 df 的 for 循环反复运行良好。

        vars <- names(df)
        
        for (i  in vars){
          df <- newfun(i)
        }
        df
        #    alpha beta gamma alphaNEW betaNEW gammaNEW
        # 1      0   30   100     0.00    0.30     1.00
        # 2      1   31   101     0.01    0.31     1.01
        # 3      2   32   102     0.02    0.32     1.02
        # 4      3   33   103     0.03    0.33     1.03
        # 5      4   34   104     0.04    0.34     1.04
        # 6      5   35   105     0.05    0.35     1.05
        # 7      6   36   106     0.06    0.36     1.06
        # 8      7   37   107     0.07    0.37     1.07
        # 9      8   38   108     0.08    0.38     1.08
        # 10     9   39   109     0.09    0.39     1.09
        # 11    10   40   110     0.10    0.40     1.10
        # 12    11   41   111     0.11    0.41     1.11
        # 13    12   42   112     0.12    0.42     1.12
        # 14    13   43   113     0.13    0.43     1.13
        # 15    14   44   114     0.14    0.44     1.14
        # 16    15   45   115     0.15    0.45     1.15
        # 17    16   46   116     0.16    0.46     1.16
        # 18    17   47   117     0.17    0.47     1.17
        # 19    18   48   118     0.18    0.48     1.18
        # 20    19   49   119     0.19    0.49     1.19
        # 21    20   50   120     0.20    0.50     1.20
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-01-01
          • 2023-03-06
          • 1970-01-01
          • 2020-03-08
          • 2019-04-20
          • 2018-12-20
          • 2017-07-19
          • 2017-01-28
          相关资源
          最近更新 更多