遍历多个表并仅在满足条件时保留的方法?

Way to loop over multiple tables and keep only if condition met?

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

TABLE 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

和TABLE 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

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

新 TABLE

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

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

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

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

我尝试过使用循环和各种使用 apply 的方法,但循环花费的时间太长而无法完成(数小时,有些永远不会完成)。这些组合导致数百万行,但我只想保留 profitable 行,其中 Price*Discount > Cost,可能只有 10,000 行。

我的解决方案是交叉连接数据 tables 以创建一个巨大的 table,我可以对其进行矢量化,这要快得多(大约 1 分钟),但有一些较大的 tables 我很快 运行 进入内存限制并且它不是很可扩展。

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]

当然这是相当快的,但是当我真正想要的是 profitable 行时,这是一个巨大的内存浪费,当然还有持续的内存问题。

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

谢谢!

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

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

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

这听起来像是 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 动词只保留我们想要的那些行。