【问题标题】:Faster For loop?更快的 For 循环?
【发布时间】:2015-09-14 15:37:06
【问题描述】:

我有这段代码,我在其中循环了 250,000 个项目。 以下是代码中的步骤: 1. 给定产品的子集数据 2. 将数据与月份数据框合并(左)。 3. 用该特定产品名称替换空产品名称 4. 将销售额的 NA 值替换为 0

这是两个产品的示例数据集。 数据:

data2 <- data.frame(product_no = c("A", "A", "A", "B","B","B"), 
                           sales = c(200, 130, 221, 310,109, 98), month = c(1, 4, 5, 8,1, 12), stringsAsFactors=FALSE)

month_unique <- as.data.frame(seq(1,12, by=1))
colnames(month_unique)[colnames(month_unique)=="seq(1, 12, by = 1)"] <- "month"

代码:

unique_product <- unique(data2$product_no)
data3 <- data.frame()

process_time <- Sys.time()
for (i in 1:length(unique_product)){
  step1 <- subset(data2, product_no==unique_product[i])
  step2 <- merge(month_unique,step1, by="month", all.x = TRUE)
  step2$product_no <- unique_product[i]
  step2[is.na(step2)] <- 0
  data3 <- rbind(data3, step2)
}
Sys.time() - process_time

预期结果:

data3

有更快的方法吗?

谢谢。

【问题讨论】:

  • 首先,您将列名从 productId 更改为 product_no (在您的可复制版本中应该只是 product_no 吗?)其次,您要做的就是确保data2 中没有出现的月份有 0 个值? (month_unique 中是否有额外的数据?
  • 是的,对不起。我已经编辑到 product_no。现在应该跑过去了。在可重现的版本中,它应该只是 product_no。实际上是的,我要做的就是确保在 data2 中的值为零的月份有零值。 month_unique 中没有额外的数据。

标签: r loops for-loop


【解决方案1】:

您可以使用 expand.grid 创建月份和 product_no 的所有组合,然后将 NA 替换为 0。

library(tidyr)

combinations <- expand.grid(month = 1:12,
                            product_no = unique(data2$product_no),
                            stringsAsFactors = FALSE)

result <- merge(combinations, data2, all.x = TRUE)
result <- replace_na(result, list(sales = 0))

请注意,我使用的是 tidyr 包中的 replace_na 函数,但您也可以这样做

result$sales[is.na(result$sales)] <- 0

你也可以在 dplyr 中使用left_join 函数,它通常比merge 更快。在 dplyr 中,函数通常(尽管不一定)与 %&gt;% 链接在一起:

library(dplyr)

result <- combinations %>%
    left_join(data2) %>%
    replace_na(list(sales = 0))

【讨论】:

  • 只花了一分钟。你能推荐其他我应该研究以避免使用 for 循环的包吗?谢谢。
  • @user3116753 绝对; dplyr 包,尤其是 group_by(适用于您想要拆分数据并重新组合的情况)。虽然您在这里不需要它,但请注意 dplyr 有一个函数 left_join 通常比合并更快;你应该试试看。我将在代码中进行编辑。
  • @user3116753 data.table 也适用于此,而且通常更快,尽管语法一开始不太直观。
  • @user3116753 另请注意,如果您的数据中所有月份都至少出现一次(在某些产品中),那么 tidyr 的 expand 就是为此目的而设计的:data2 %&gt;% expand(product_no, month) %&gt;% left_join(data2) %&gt;% replace_na(list(sales = 0))
  • 稳固!谢谢 Akhil 和 Robinson 先生。
【解决方案2】:

只是因为我很好奇,并且因为你说你有很多产品要迭代,所以我使用 for 循环运行了这个,使用 lapply,使用 David 的代码,然后并行运行它(在 4 个内核上)。这是我想出的:

> library(dplyr)
> library(tidyr)
> library(parallel)
> 
> data2 <- data.frame(productId = c("A", "A", "A", "B","B","B"), 
+                     sales = c(200, 130, 221, 310,109, 98), 
+                     month = c(1, 4, 5, 8,1, 12), 
+                     stringsAsFactors=FALSE)
> data2 <- do.call("rbind", lapply(1:1000, function(i) data2))
> data2$productId <- rep(1:2000, each = 3)
> 
> month_unique <- as.data.frame(seq(1,12, by=1))
> colnames(month_unique)[colnames(month_unique)=="seq(1, 12, by = 1)"] <- "month"
> 
> 
> #* For running the original code
> unique_product <- unique(data2$productId)
> data3 <- data.frame()
> 
> 
> system.time({
+   for (i in 1:length(unique_product)){
+     step1 <- subset(data2, productId==unique_product[i])
+     step2 <- merge(month_unique,step1, by="month", all.x = TRUE)
+     step2$productId <- unique_product[i]
+     step2[is.na(step2)] <- 0
+     data3 <- rbind(data3, step2)
+   }
+ })
   user  system elapsed 
   4.79    0.01    4.81 
> 
> 
> #* Function that is equivalent to the for loop
> dataFn <- function(up, data2, month_unique){
+   step1 <- subset(data2, productId==up)
+   step2 <- merge(month_unique,step1, by="month", all.x = TRUE)
+   step2$product_no <- up
+   step2[is.na(step2)] <- 0
+   step2
+ }
> 
> system.time({
+   data3 <- do.call("rbind", 
+                    lapply(unique_product, dataFn, data2, month_unique))
+ })
   user  system elapsed 
    2.1     0.0     2.1 
>   
> #David's code
> system.time({
+   combinations <- expand.grid(month = 1:12,
+                               productId = unique(data2$productId),
+                               stringsAsFactors = FALSE)
+   result <- left_join(combinations, data2, 
+                       by = c("month" = "month",
+                              "productId" = "productId"))
+   result <- replace_na(result, list(sales = 0))
+ })
   user  system elapsed 
      0       0       0 
> 
> # run in parallel
> system.time({
+   cl <- makeCluster(4)
+     clusterExport(cl, "dataFn")
+     clusterExport(cl, "data2")
+     clusterExport(cl, "month_unique")
+     data3_parallel <- 
+       do.call("rbind",
+             parLapply(cl, unique_product, dataFn, data2, month_unique))
+     stopCluster(cl)
+ })
   user  system elapsed 
   0.27    0.03    1.99 
>

所以使用 apply 函数似乎可以提高速度;并行化似乎没有什么好处(也许任务的大小太小了?),而且 David 的代码快如闪电。

【讨论】:

  • 漂亮的东西。谢谢本杰明。
猜你喜欢
  • 2019-12-25
  • 2016-05-30
  • 1970-01-01
  • 2020-11-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-13
  • 2014-01-02
相关资源
最近更新 更多