如何在处理 r 中超过 500 万个观测值的数据框时加快迭代速度?

Posted

技术标签:

【中文标题】如何在处理 r 中超过 500 万个观测值的数据框时加快迭代速度?【英文标题】:How to speed up iteration while working on a data-frame with over 5 million observations in r? 【发布时间】:2019-07-06 12:32:53 【问题描述】:

我正在尝试为数百万个观察值中的 7 个以上变量生成值,而当我编写一个 for 循环来实现这一点时,它需要很长时间。下面是我想要实现的一个例子。在这种情况下,它的速度很快,因为它只有几千个观察值:

# Load dplyr


library(tidyverse)
set.seed(50)

df <- data_frame(SlNo = 1:2000,
                 Scenario = rep(c(1, 2, 3, 4),500),
                 A = round(rnorm(2000, 11, 6)),
                 B = round(rnorm(2000, 15, 4))) %>%
      arrange(Scenario) 

#splitting data-frame to add multiple rows in the data-frame

df<- df %>% split(f = .$Scenario) %>%
  map_dfr(~bind_rows(tibble(Scenario = 0), .x)) 

#observations for certain variables in the newly added rows have specific values

df <- df %>% mutate(C = if_else(Scenario != 0, 0, 4),
                    E = if_else(Scenario != 0, 0, 6))

for(i in 2:nrow(df)) 

df$C[i] <- if_else(df$Scenario[i] != 0, (1-0.5) * df$C[i-1] + 3 + 2 + df$B[i] + df$E[i-1],
              df$C[i])
df$E[i] <- if_else(df$Scenario[i] != 0, df$C[i] + df$B[i] - 50, df$E[i])




df

# A tibble: 2,004 x 6
   Scenario  SlNo     A     B     C      E
      <dbl> <int> <dbl> <dbl> <dbl>  <dbl>
 1        0    NA    NA    NA   4     6   
 2        1     1    14    19  32     1   
 3        1     5     1    13  35    -2   
 4        1     9    17    20  40.5  10.5 
 5        1    13     8     7  42.8  -0.25
 6        1    17    10    16  42.1   8.12
 7        1    21     9    12  46.2   8.19
 8        1    25    14    18  54.3  22.3 
 9        1    29    14    15  69.4  34.4 
10        1    33     4    17  91.1  58.1 
# ... with 1,994 more rows

我想在处理更大的数据帧时快速产生类似的结果。我很感激这方面的任何帮助。提前谢谢你!

【问题讨论】:

你能用文字写出你的目标吗?我认为它可以更快地思考解决方案,而不是试图从你的代码中找出答案。您是否也看过使用 data.table 并用更快的方法替换 for 循环? 我正在处理的数据框包含数百万行和 7 个变量,我需要从中迭代计算三个变量的值。在此示例中,有 4 个场景,编写代码来计算 var 的值。每个场景的 C & E。每个场景的 C & E 的第一次观察被分配一个特定的值。这些变量中每个变量的每个后续观察值取决于同一变量的先前值,即 C[i] 值取决于 C[i-1] 和 E[i-1]。当应用于具有超过 5M obs 的 DF 时,为实现这一点而编写的 for 循环非常慢。 这看起来只是代数,虽然很复杂。如果你把它写出来,我敢打赌你可以想出只使用 cumsumlag 的东西。 如果你的公式也用文字写出来会更容易。 【参考方案1】:

tidyverse 你可以像这样使用purrr::accumulate

library(tidyverse)
set.seed(50)

df <- data.frame(SlNo = 1:2000,
                 Scenario = rep(c(1, 2, 3, 4),500),
                 A = round(rnorm(2000, 11, 6)),
                 B = round(rnorm(2000, 15, 4))) %>%
  arrange(Scenario)

df %>%
  nest(data = B) %>%
  group_by(Scenario) %>%
  mutate(new = accumulate(data, 
                          .init = tibble(C = 4, E = 6),
                          ~ tibble(C = (1 -0.5)* .x$C + 5 + .y$B + .x$E,
                                   E = 0.5 * .x$C + 5 + .x$E + 2 * .y$B - 50
                                   )
                          )[-1]
         ) %>% ungroup %>%
  unnest_wider(data) %>%
  unnest_wider(new)

#> # A tibble: 2,000 x 6
#>     SlNo Scenario     A     B     C     E
#>    <int>    <dbl> <dbl> <dbl> <dbl> <dbl>
#>  1     1        1    14    19  32    1   
#>  2     5        1     1    13  35   -2   
#>  3     9        1    17    20  40.5 10.5 
#>  4    13        1     8     7  42.8 -0.25
#>  5    17        1    10    16  42.1  8.12
#>  6    21        1     9    12  46.2  8.19
#>  7    25        1    14    18  54.3 22.3 
#>  8    29        1    14    15  69.4 34.4 
#>  9    33        1     4    17  91.1 58.1 
#> 10    37        1    13    15 124.  88.7 
#> # ... with 1,990 more rows

由reprex package (v2.0.0) 于 2021-07-05 创建

【讨论】:

【参考方案2】:

如果您不想转换到 data.tabledtplyr,这在弄清楚如何使 cumsumlag 适应您需要的输出时可能会很棘手,您可以将循环调整为并行运行,这里是代码示例:

#install.packages("foreach")
#install.packages("doParallel")

# Loading libraries

library(foreach)
library(doParallel)
library(tidyverse)
set.seed(50)

df <- data_frame(SlNo = 1:2000,
                 Scenario = rep(c(1, 2, 3, 4),500),
                 A = round(rnorm(2000, 11, 6)),
                 B = round(rnorm(2000, 15, 4))) %>%
      arrange(Scenario) 

#splitting data-frame to add multiple rows in the data-frame

df<- df %>% split(f = .$Scenario) %>%
  map_dfr(~bind_rows(tibble(Scenario = 0), .x)) 

#observations for certain variables in the newly added rows have specific values

df <- df %>% mutate(C = if_else(Scenario != 0, 0, 4),
                    E = if_else(Scenario != 0, 0, 6))


# Setting up the cores
n.cores <- parallel::detectCores() - 1
my.cluster <- parallel::makeCluster(
        n.cores, 
        type = "PSOCK",
        .packages="dplyr"
)
doParallel::registerDoParallel(cl = my.cluster)

# Run the foreach loop in parallel
foreach(
        i = 2:nrow(df2), 
        .combine = 'rbind'
) %dopar% 
        df$C[i] <- if_else(df$Scenario[i] != 0, (1-0.5) * df$C[i-1] + 3 + 2 + df$B[i] + df$E[i-1],
                           df$C[i])
        df$E[i] <- if_else(df$Scenario[i] != 0, df$C[i] + df$B[i] - 50, df$E[i])

df
# stop the cluster
parallel::stopCluster(cl = my.cluster)

这应该会显着加快您的代码速度。但是,在较大的数据集上,并行执行的时间差异并不明显,对于较小的数据集,它实际上可能需要更多的时间来执行。

【讨论】:

以上是关于如何在处理 r 中超过 500 万个观测值的数据框时加快迭代速度?的主要内容,如果未能解决你的问题,请参考以下文章

在 R 中:创建一个仅包含连续观测值的数据框和一个指示序列号的变量

按行名合并 R 中超过 2 个数据框

在 R 中估计具有数百万个观测值和数千个变量的 OLS 模型

如何使用具有超过 2^31 个观测值的 biglm

对于表中的每个观测值,根据纬度和经度 (R) 计算 x 米内的表中其他观测值的数量

使用 R 的高频数据的多重协方差矩阵