【问题标题】:R - Multiple criteria sum across different length data framesR - 跨不同长度数据帧的多个标准总和
【发布时间】:2018-10-10 10:07:34
【问题描述】:

第一次发帖,老用户。

我正在尝试根据 2 个标准为另一个不同长度的数据框中的每个 ID 有效地对列进行求和。下面是一个例子:

   ID
1  A 
2  B
3  C

ID   Color   Type  Price
A  Green   1     5
A  Blue    2     6
B  Green   3     7
B  Blue    2     2
C  Green   2     4
C  Blue    4     5

对于每个 ID,如果颜色为蓝色且类型为 2,我想对价格求和。希望结果如下:

   ID  Price
1  A   6
2  B   2
3  C   0

这似乎是一项简单的任务,但由于某种原因我无法弄清楚。此外,我需要对 2 个大型数据集(每个 >1,000,000 行)执行此操作。我已经创建了一个函数并在循环中使用它来解决像这样的先前问题,但是由于信息量很大,该解决方案不起作用。我觉得apply 的功能可能是最好的,但我无法让它们工作。

【问题讨论】:

  • 提示:创建一个定义为 df$Price*(df$Type==2 & df$Color=="Blue") 的新列(当满足条件时为 Price,否则为 0),然后在 ID 列和 @987654328 旁边的 aggregate 该列@(或使用任何 dplyrdata.table 等效项)。
  • dplyrdt %>% group_by(ID) %>% summarize(totalPrice = sum(Price[Type==2 & Color==1])

标签: r sum apply sapply sumifs


【解决方案1】:

我对您的数据示例进行了一些更改,因此它考虑到并非所有 ID 都在第一个数据帧中,并且有两个值要单独求和:

df1 <- data.frame(ID = c("A","B","C"))

df2 <- read.table(text = "
                  ID   Color   Type  Price
                  A  Green   1     5
                  A  Blue    2     6
                  A  Blue    2     4
                  B  Green   3     7
                  B  Blue    2     2
                  C  Green   2     4
                  C  Blue    4     5
                  D  Green   2     2
                  D  Blue    4     8
                  ",header = T)

在大 data.frame 上快速执行此操作的两个主要包是 dplyrdata.table。它们是相当的(几乎,参见data.table vs dplyr: can one do something well the other can't or does poorly?)。以下是两种解决方案:

library(data.table)

setDT(df2)[ID %in% unique(df1$ID), .(sum = sum(Price[ Type == 2 & Color == "Blue"])),by = ID]

   ID sum
1:  A  10
2:  B   2
3:  C   0

你可以这样做

setDT(df2)[ID %in% unique(df1$ID) & Type == 2 & Color == "Blue", .(sum = sum(Price)),by = ID]

但您将丢弃 C,因为不满足行选择的整个条件:

   ID sum
1:  A  10
2:  B   2

并使用 dplyr:

library(dplyr)

df2 %>%
  filter(ID %in% unique(df1$ID)) %>%
  group_by(ID) %>%
  summarize(sum = sum(Price[Type==2 & Color=="Blue"]))

# A tibble: 3 x 2
  ID      sum
  <fct> <int>
1 A        10
2 B         2
3 C         0

【讨论】:

    【解决方案2】:

    sapply 版本。它可能存在更优雅的编写方式,但如果您有如您所说的大表,您可以轻松地将其并行化。

    使用@denis 提出的数据:

    df1 <- data.frame(ID = c("A","B","C"))
    
    df2 <- read.table(text = "
                      ID   Color   Type  Price
                      A  Green   1     5
                      A  Blue    2     6
                      A  Blue    2     4
                      B  Green   3     7
                      B  Blue    2     2
                      C  Green   2     4
                      C  Blue    4     5
                      D  Green   2     2
                      D  Blue    4     8
                      ",header = T)
    

    这里有一个简单的函数,可以用sapply 做你想做的事:

     getPrices <- function(tableid=df1,tablevalues=df2,color="Blue",type=2){
         filteredtablevalues <- droplevels(tablevalues[ tablevalues$Color == "Blue" & tablevalues$Type == 2 & tablevalues$ID %in% df1$ID,])
         #droplevels could be skipped by using unique(as.character(filteredtablevalues$ID)) in the sapply, not sure what would be the quickest 
         sapply(levels(filteredtablevalues$ID),function(id,tabval)
                {
                sum(tabval$Price[tabval$ID == id])
            },tabval=filteredtablevalues)
     }
    

    如您所见,我添加了两个参数,允许您选择配对颜色/类型。你可以添加这个:

     tmp=getPrices(df1,df2)
     finaltable=cbind.data.frame(ID=names(tmp),Price=tmp)
    

    如果您绝对需要具有列 ID 和列价格的数据框。

    如果我有时间,我会尝试一些基准测试,但是这样编写您应该能够轻松地将其与 library(parallel)library(Rmpi) 并行化,如果您有非常非常大的数据集,这可以挽救您的生命。

    编辑:

    基准测试:

    我无法重现@denis 提出的 dplyr 示例,但我可以比较 data.table 版本:

    #Create a bigger dataset
    nt=10000 #nt as big as you want
    df2=rbind.data.frame(df2,
                         list(ID= sample(c("A","B","C"),nt,replace=T),
                              Color=sample(c("Blue","Green"),nt,replace=T),
                              Type=sample.int(5,nt,replace=T),
                              Price=sample.int(5,nt,replace=T)
                              )
                         )
    

    您可以使用library(microbenchmark) 进行基准测试:

    library(microbenchmark)
    microbenchmark(sply=getPrices(df1,df2),dtbl=setDT(df2)[ID %in% unique(df1$ID), .(sum = sum(Price[ Type == 2 & Color == "Blue"])),by = ID],dplyr=df2 %>%  filter(ID %in% unique(df1$ID)) %>%  group_by(ID) %>%  summarize(sum = sum(Price[Type==2 & Color=="Blue"])))
    

    在我的电脑上它给出:

    Unit: milliseconds
      expr      min       lq      mean    median        uq      max neval
      sply 78.37484 83.89856  97.75373  89.17033 118.96890 131.3226   100
      dtbl 75.67642 83.44380  93.16893  85.65810  91.98584 137.2851   100
     dplyr 90.67084 97.58653 114.24094 102.60008 136.34742 150.6235   100
    

    编辑2:

    sapply 似乎比data.table 方法稍微快一些,但并不明显。但是使用sapply 可能对您拥有巨大的ID 表非常有帮助。然后您使用library(parallel) 并获得更多时间。

    现在data.table 方法似乎是最快的。但是,sapply 的优势在于您可以轻松地将其并行化。虽然在那种情况下,考虑到我是如何编写函数getPrices 的,但只有当你的ID 表很大时它才会有效。

    【讨论】:

    • 在做更多基准测试后,我认为 sapply(没有并行化)对于nt&gt;10000000 总是更快,而 dplyr 总是更慢。
    最近更新 更多