【问题标题】:Way to loop over multiple tables and keep only if condition met?循环遍历多个表并仅在满足条件时保留的方法?
【发布时间】:2020-11-22 05:25:34
【问题描述】:

所以我正在处理具有多个数据表的项目,这些数据表按月份分隔,我需要对其进行迭代。速度在这里至关重要,除非我通过数据表函数进行大量交叉连接,否则我似乎无法将时间缩短到合理的水平。所以这是我的表:

表 1

Product Date        Cost
A       8/1/2020    10
A       8/2/2020    20
A       8/3/2020    30
B       8/4/2020    15
B       8/5/2020    25
B       8/6/2020    35

和表 2:

Product Date    Price
A       9/1/2020    20
A       9/2/2020    30
A       9/3/2020    40
B       9/4/2020    27
B       9/5/2020    33
B       9/6/2020    42

所以我需要遍历表 2 价格 - 表 1 成本的每个组合,并按产品进行。所以输出将是:

新表

Product Date1         Date2          Profit
A       8/1/2020      9/1/2020       10
A       8/1/2020      9/2/2020       20
...

编辑:为了澄清,新表应该继续。产品 A 应该有 27 个不同的利润(A x 3 折扣率下的 3 个日期)假设它们都高于 0。如果任何利润低于 0,那么我不希望它们作为新的一部分表。

我还有一个折扣因子,我需要将其应用于价格的每个排列,因为我们提供了相当多的折扣

Discount = c(10%,12%,18%)

我尝试过使用循环和各种使用 apply 的方式,但循环需要很长时间才能完成(几个小时,有些永远不会)。这些组合导致数百万行,但我只想保留有利可图的行,其中价格 * 折扣 > 成本,数量可能只有 10,000。

我的解决方案是交叉连接数据表以创建一个我可以对其进行矢量化的大型表,这要快得多(大约 1 分钟),但对于一些较大的表,我很快就会遇到内存限制,但事实并非如此非常可扩展。

CTbl =setkey(CTbl[,c(k=1,.SD)],k)[Price[,c(k=1,.SD)],allow.cartesian=TRUE][,k:=NULL]
CTbl[,Profit:=(Discount*Price - Cost]
CTbl = setDT(CTbl)[, .SD[Price > Cost ]]
DT = CTbl[,list(MinProfit = min(Profit)),by = Product]

当然,这非常快,但是当我真正想要的是有利可图的行时,这会浪费大量内存,当然还有持续的内存问题。

有人可以帮忙吗?我问过一些工作中的 R 用户,但他们似乎也很困惑,他们制作的循环无法接近运行上述程序所需的不到 5 分钟。如果这意味着我可以扩大规模,我不介意多花一点时间。

谢谢!

【问题讨论】:

  • 为什么你只有两个日期? A 有三个成本值和三个价格,所以新表应该有三个利润。那正确吗?还有三个日期是正确的吗?
  • 只有数百万行的结果需要这么长时间,这有点奇怪——R 是为处理这种大小甚至更大的数据而构建的。您是否尝试过并行化其中一些 for 循环?将它们转换为 foreach 语法通常不会太。困难
  • 它应该遍历表1中的日期和表2中的日期并获得每个组合,然后查看产品组的利润。所以到最后,产品 A 应该有 27 个总利润记录(8/1/2020 和 9/1/2020、8/1/2020 和 9/2/2020...到...8/6/ 2020 年和 2020 年 9 月 6 日适用于 3 个折扣率中的每一个)。如果任何利润
  • @WalkerHarrison 我已经尝试过了,但它仍然比上面的数据表解决方案花费了更长的时间。也许我的数据表循环效率低下?我做了DT[[i]]类型的循环语法,有没有更DT的方法呢?
  • 也许尝试使用非 equi 连接来限制结果的数量。例如DT2[, paste0("Disc", 1L:length(Discount)) := lapply(Discount, function(k) Price * k)] ;rbindlist(lapply(paste0("Cost<Disc", 1L:length(Discount)), function(x) DT1[DT2, on=c("Product", x)]), use.names=TRUE, fill=TRUE)。但由于它的排列,我认为你不能将结果的数量减少很多

标签: r loops data.table combinations nested-loops


【解决方案1】:

这听起来像是dplyr 包的问题,​​它。 dplyr 包允许您在“管道”中将数据操作串在一起,以避免将内容存储在内存中。管道运算符%>%获取左侧函数的输出并将其用作右侧函数的第一个参数。 dplyr 包中的每个函数都适用于整个向量或数据 tibble,因此无需循环。

因此,您的操作可能如下所示:

# Initialize random data like your first table
df1 <- data.frame(product = sample(LETTERS[1:10], 10000, replace = TRUE),
              date1 = sample(seq(as.Date("2020/08/01"), as.Date("2020/08/31"), 
                                 by = "day"), 10000, replace = TRUE),
              cost = round(runif(10000, 5, 100)))
# Initialize random data like your second table 
df2 <- data.frame(product = sample(LETTERS[1:10], 10000, replace = TRUE),
              date2 = sample(seq(as.Date("2020/09/01"), as.Date("2020/09/30"), 
                                 by = "day"), 10000, replace = TRUE),
              price = round(runif(10000, 5, 100)))
# Initialize discounts 
discounts <- data.frame(product = rep(LETTERS[1:10],4), 
                    discount = rep(c(0, 0.1, 0.12, 0.18), 10))
library(dplyr)
out_table <- df1 %>%
  full_join(df2) %>%
  full_join(discounts) %>%
  mutate(profit = price * discount - cost) %>%
  filter(profit > 0)

对于我的随机数据,这在我的机器上大约需要 3 秒。此外,filter 动词只保留我们想要的那些行。

【讨论】:

  • 感谢@Ben Norris,这实际上非常快,但我仍然遇到内存问题,主要是因为 dplyr 在过滤步骤之前进行了完全等值连接。我使用的表相当大,但这是重组代码的好方法。
  • 所以看起来正是我想要的,我需要使用 sqldf 或一些我需要进一步调查的 data.table 公式,但你的答案是最快/最接近我的寻找。
【解决方案2】:

这不是您问题的完整答案,但也许您可以通过 products 迭代一个循环。以下函数查找指定产品的利润。该功能不包括折扣,但如果该功能按您的意愿工作,则可以添加。

profit = function(product, df1, df2) {

    cost = with(df1, df1[which(Product == product), 'Cost'])
    price = with(df2, df2[which(Product == product), 'Price'])
    date = merge(
            with(df1, df1[which(Product == product), 'Date']), 
            (with(df2, df2[which(Product == product), 'Date']))
            )
    product = t(matrix(rep(price, length(cost)), nrow = length(cost)) - t(matrix(rep(cost, length(price)), ncol = length(price))))
    product = data.frame(cbind(date[which(product > 0), ], product[which(product > 0)]))
    names(product) = c('costdate', 'pricedate', 'profit')
    return(product)

}

例子:

df1 = data.frame(Product = c('A', 'A', 'A', 'B', 'B', 'B'), 
                Date = c('8/1/2020', '8/2/2020', '8/3/2020', '8/4/2020', '8/5/2020', '8/6/2020'),
                Cost = c(10, 20, 30, 15, 25, 35))
df2 = data.frame(Product = c('A', 'A', 'A', 'B', 'B', 'B'), 
                Date = c('9/1/2020', '9/2/2020', '9/3/2020', '9/4/2020', '9/5/2020', '9/6/2020'),
                Price = c(20, 30, 40, 27, 33, 42))

> profit('A', df1, df2)
  costdate pricedate profit
1 8/1/2020  9/1/2020     10
4 8/1/2020  9/2/2020     20
5 8/2/2020  9/2/2020     10
7 8/1/2020  9/3/2020     30
8 8/2/2020  9/3/2020     20
9 8/3/2020  9/3/2020     10
> profit('B', df1, df2)

  costdate pricedate profit
1 8/4/2020  9/4/2020     12
2 8/5/2020  9/4/2020      2
4 8/4/2020  9/5/2020     18
5 8/5/2020  9/5/2020      8
7 8/4/2020  9/6/2020     27
8 8/5/2020  9/6/2020     17
9 8/6/2020  9/6/2020      7

由于数据有限,无法正确测试。

【讨论】:

  • 谢谢!我不得不删除矩阵部分的几个括号,但代码运行良好,并且给了我一个想法,将表格分成更小的块,处理它们,然后在事后绑定。让我再处理一下,看看它在大型数据表上的速度。
  • 所以速度和内存仍然是一个问题,因为我在较大的表上使用它,主要是因为该函数进行连接,然后过滤掉大于 0 的利润,而不是逐行增加连接并只保留那些高于 0。但是这个想法是合理的,并且可能是我最终要做的,将表分解成更易于管理的块并最终绑定。
  • 我很高兴听到这个消息。祝你好运。
猜你喜欢
  • 1970-01-01
  • 2019-02-03
  • 2015-03-30
  • 2021-10-30
  • 1970-01-01
  • 2017-03-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多