【问题标题】:A function that returns several vectors in the mutate section在 mutate 部分返回多个向量的函数
【发布时间】:2021-08-18 19:58:27
【问题描述】:

这是一个令我困惑的问题。编写函数以在返回多个向量的变异部分中使用的最简单(也是最优雅)的方法是什么。

我举个例子。假设我有这样的功能。

f1 = function(x, n){
  y1 = rep(NA, length(x))
  y2 = rep(NA, length(x))
  y3 = rep(NA, length(x))
  y4 = rep(NA, length(x))
  for(i in (n+1):(length(x)-n)){
    idx = (i-n):(i+n)
    y1[i] = sin(mean(x[idx])/max(x[idx]))
    y2[i] = cos(mean(x[idx])/max(x[idx]))
    y3[i] = tan(mean(x[idx])/max(x[idx]))
    y4[i] = 1/tan(mean(x[idx])/max(x[idx]))
  }
  data.frame(
    y1 = y1,
    y2 = y2,
    y3 = y3,
    y4 = y4
  )
}

请不要分析它的数学意义,这只是一个例子。 如您所见,此函数接受一个向量并返回四个相同长度的向量。 如果我想在突变部分使用这个函数,该函数将被调用四次。不幸的是,输入向量很长,需要很长时间。

这是一个例子。

n = 10000
df = tibble(
  key = rep(c("a", "b", "c", "d"), n),
  val = rep(rnorm(n), 4)
)

f1test = function(df) df %>% 
  group_by(key) %>% 
  mutate(
    y1 = f1(val, 100) %>% pull(y1),
    y2 = f1(val, 100) %>% pull(y2),
    y3 = f1(val, 100) %>% pull(y3),
    y4 = f1(val, 100) %>% pull(y4)
  )
f1test(df)

在寻找解决方案时,我有一个稍微不同的想法,即一次返回所有四个向量,然后以某种方式将它们分开。 所以我创建了第二个示例函数,它执行相同的计算,只是返回结果的方式不同。

f2 = function(x, n){
  ret = rep(NA, length(x))
  for(i in (n+1):(length(x)-n)){
    idx = (i-n):(i+n)
    ret[i] = paste(
      sin(mean(x[idx])/max(x[idx])),
      cos(mean(x[idx])/max(x[idx])),
      tan(mean(x[idx])/max(x[idx])),
      1/tan(mean(x[idx])/max(x[idx])), sep = ";")
  }
  ret
}

使用此类函数可能如下所示:

f2test = function(df) df %>% 
  group_by(key) %>% 
  mutate(ret = f2(val, 100)) %>% 
  separate(ret, paste0("y", 1:4), sep=";", convert = TRUE)  
f2test(df)

您可以立即看到后者应该更快。确实是 n = 1000,f2 的版本大约快 2 倍。

对于 n = 10000,它快四倍。

现在我的问题。有谁知道更好(更优雅)的方法来解决这个问题?

【问题讨论】:

    标签: r dplyr


    【解决方案1】:

    无论你如何形成你的函数,都不需要计算四次

    mutate(
        y1 = f1(val, 100) %>% pull(y1),
        y2 = f1(val, 100) %>% pull(y2),
        y3 = f1(val, 100) %>% pull(y3),
        y4 = f1(val, 100) %>% pull(y4)
      )
    

    你可以mutate(没有赋值)返回一个框架的东西:

    f1 <- function(x) data.frame(mysin = sin(x), mycos = cos(x), mytan = tan(x))
    mtcars %>%
      mutate(f1(cyl)) %>%
      head
    #                    mpg cyl disp  hp drat    wt  qsec vs am gear carb   mysin   mycos  mytan
    # Mazda RX4         21.0   6  160 110 3.90 2.620 16.46  0  1    4    4 -0.2794  0.9602 -0.291
    # Mazda RX4 Wag     21.0   6  160 110 3.90 2.875 17.02  0  1    4    4 -0.2794  0.9602 -0.291
    # Datsun 710        22.8   4  108  93 3.85 2.320 18.61  1  1    4    1 -0.7568 -0.6536  1.158
    # Hornet 4 Drive    21.4   6  258 110 3.08 3.215 19.44  1  0    3    1 -0.2794  0.9602 -0.291
    # Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2  0.9894 -0.1455 -6.800
    # Valiant           18.1   6  225 105 2.76 3.460 20.22  1  0    3    1 -0.2794  0.9602 -0.291
    

    如果你想在过程中分配名称,那么你可以这样做

    mtcars %>%
      mutate(setNames(f1(cyl), c("A","B","C"))) %>%
      head
    #                    mpg cyl disp  hp drat    wt  qsec vs am gear carb       A       B      C
    # Mazda RX4         21.0   6  160 110 3.90 2.620 16.46  0  1    4    4 -0.2794  0.9602 -0.291
    # Mazda RX4 Wag     21.0   6  160 110 3.90 2.875 17.02  0  1    4    4 -0.2794  0.9602 -0.291
    # Datsun 710        22.8   4  108  93 3.85 2.320 18.61  1  1    4    1 -0.7568 -0.6536  1.158
    # Hornet 4 Drive    21.4   6  258 110 3.08 3.215 19.44  1  0    3    1 -0.2794  0.9602 -0.291
    # Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2  0.9894 -0.1455 -6.800
    # Valiant           18.1   6  225 105 2.76 3.460 20.22  1  0    3    1 -0.2794  0.9602 -0.291
    

    与您的f1 相比,这显然是一种简化,我的观点是您可以在返回值中本地使用所有列,而无需弄清楚如何将它们分开。

    这可能会使你的其他功能

    f1test = function(df) df %>% 
      group_by(key) %>% 
      mutate(f1(val, 100))
    

    (有或没有setNames)。

    【讨论】:

    • 是的!这是我一直在寻找的解决方案。 df %&gt;% group_by(key) %&gt;% mutate(f1(val, 100)) 简洁大方!非常感谢。
    【解决方案2】:

    我们可以使用split 并使其更快`

    library(purrr)
    map_dfr(split(df$val, df$key), f1, n = 15, .id = 'key')
    

    或使用rbindlist

    library(data.table)
    rbindlist(lapply(split(df$val, df$key), f1, n = 15), idcol = 'key')
    

    基准测试

    #OP's function
    > system.time(f1test(df))
       user  system elapsed 
      3.589   0.170   3.748 
    > system.time(map_dfr(split(df$val, df$key), f1, n = 15, .id = 'key'))
       user  system elapsed 
      0.606   0.029   0.635 
    > system.time(rbindlist(lapply(split(df$val, df$key), f1, n = 15), idcol = 'key'))
       user  system elapsed 
      0.591   0.024   0.614 
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-31
      • 2017-01-13
      相关资源
      最近更新 更多