【问题标题】:Aggregate data frame while keeping original order, in a simple manner以简单的方式在保持原始顺序的同时聚合数据框
【发布时间】:2012-08-05 23:21:14
【问题描述】:

我在聚合数据框时遇到了一些麻烦,同时保持组的原始顺序(基于数据框中第一次出现的顺序)。我已经设法做到了,但希望有一种更简单的方法来解决它。

这是一个可以处理的示例数据集:

set.seed(7)
sel.1 <- sample(1:5, 20, replace = TRUE)     # selection vector 1
sel.2 <- sample(1:5, 20, replace = TRUE)
add.1 <- sample(81:100)                      # additional vector 1
add.2 <- sample(81:100)
orig.df <- data.frame(sel.1, sel.2, add.1, add.2)

需要注意的几点:有两个选择列来确定数据如何组合在一起。他们将是相同的,并且他们的名字是已知的。我只在此数据中添加了两列,但可能还有更多。我给出了以 'sel' 和 'add' 开头的列名,以便于理解,但实际数据有不同的名称(所以虽然 grep 技巧很酷,但在这里它们不会有用)。

我要做的是根据“sel”列将数据框聚合成组,并将所有“add”列加在一起。这很简单,使用aggregate 如下:

# Get the names of all the additional columns
all.add <- names(orig.df)[!(names(orig.df)) %in% c("sel.1", "sel.2")]
aggr.df <- aggregate(orig.df[,all.add], 
                     by=list(sel.1 = orig.df$sel.1, sel.2 = orig.df$sel.2), sum)

问题是结果是按 'sel' 列排序的;我希望根据每个组在原始数据中的首次出现对其进行排序。

这是我为完成这项工作所做的最佳尝试:

## Attempt 1
# create indices for each row (x) and find the minimum index for each range
index.df <- aggregate(x = 1:nrow(orig.df),
                      by=list(sel.1 = orig.df$sel.1, sel.2 = orig.df$sel.2), min)
# Make sure the x vector (indices) are in the right range for aggr.df
index.order <- (1:nrow(index.df))[order(index.df$x)]
aggr.df[index.order,]

## Attempt 2
# get the unique groups. These are in the right order.
unique.sel <- unique(orig.df[,c("sel.1", "sel.2")])
# use sapply to effectively loop over data and sum additional columns.
sums <- t(sapply(1:nrow(unique.sel), function (x) {
    sapply(all.add, function (y) {
        sum(aggr.df[which(aggr.df$sel.1 == unique.sel$sel.1[x] & 
                          aggr.df$sel.2 == unique.sel$sel.2[x]), y])
        })
}))
data.frame(unique.sel, sums)

虽然这些给了我正确的结果,但我希望有人能指出一个更简单的解决方案。如果该解决方案适用于标准 R 安装附带的软件包,那将是更可取的。

我查看了aggregatematch 的文档,但找不到答案(我想我希望aggregate 有类似“keep.original.order”的参数)。

任何帮助将不胜感激!


更新:(以防有人偶然发现)

这是我尝试了几天后能找到的最干净的方法:

unique(data.frame(sapply(names(orig.df), function(x){
    if(x %in% c("sel.1", "sel.2")) orig.df[,x] else
    ave(orig.df[,x], orig.df$sel.1, orig.df$sel.2, FUN=sum)},
simplify=FALSE)))

【问题讨论】:

  • 感谢您的更新,这可能是没有使用 data.table 的最佳解决方案。如何让 R 开发团队为聚合实现“keep.original.order”参数?这似乎是一个明显的疏忽……

标签: r aggregate data.table


【解决方案1】:

data.table 很简短。默认情况下,它以首次出现的顺序返回组。

require(data.table)
DT = as.data.table(orig.df)
DT[, list(sum(add.1),sum(add.2)), by=list(sel.1,sel.2)]

    sel.1 sel.2  V1  V2
 1:     5     4  96  84
 2:     2     2 175 176
 3:     1     5 384 366
 4:     2     5  95  89
 5:     4     1 174 192
 6:     2     4  82  87
 7:     5     3  91  98
 8:     3     2 189 178
 9:     1     4 170 183
10:     1     1 100  91
11:     3     3  81  82
12:     5     5  83  88
13:     2     3  90  96

这对于大数据来说会很快,因此如果您发现速度问题,以后无需更改代码。以下替代语法是传递分组依据的最简单方法。

DT[, lapply(.SD,sum), by=c("sel.1","sel.2")]

    sel.1 sel.2 add.1 add.2
 1:     5     4    96    84
 2:     2     2   175   176
 3:     1     5   384   366
 4:     2     5    95    89
 5:     4     1   174   192
 6:     2     4    82    87
 7:     5     3    91    98
 8:     3     2   189   178
 9:     1     4   170   183
10:     1     1   100    91
11:     3     3    81    82
12:     5     5    83    88
13:     2     3    90    96

或者,by 也可以是一个逗号分隔的列名字符串:

DT[, lapply(.SD,sum), by="sel.1,sel.2"]

【讨论】:

    【解决方案2】:

    有点难读,但它给了你想要的东西,我添加了一些 cmets 来澄清。

    # Define the columns you want to combine into the grouping variable
    sel.col <- grepl("^sel", names(orig.df))
    # Create the grouping variable
    lev <- apply(orig.df[sel.col], 1, paste, collapse=" ")
    # Split and sum up
    data.frame(unique(orig.df[sel.col]),
               t(sapply(split(orig.df[!sel.col], factor(lev, levels=unique(lev))),
                        apply, 2, sum)))
    

    输出如下所示

       sel.1 sel.2 add.1 add.2
    1      5     4    96    84
    2      2     2   175   176
    3      1     5   384   366
    5      2     5    95    89
    6      4     1   174   192
    7      2     4    82    87
    8      5     3    91    98
    10     3     2   189   178
    11     1     4   170   183
    14     1     1   100    91
    17     3     3    81    82
    19     5     5    83    88
    20     2     3    90    96
    

    【讨论】:

      【解决方案3】:

      寻找相同问题的解决方案,我发现了一个使用聚合()的新解决方案,但首先将选择变量转换为您想要的顺序的因素。

      all.add <- names(orig.df)[!(names(orig.df)) %in% c("sel.1", "sel.2")]
      
      # Selection variables as factor with leves in the order you want
      orig.df$sel.1 <- factor(orig.df$sel.1, levels = unique(orig.df$sel.1))
      orig.df$sel.2 <- factor(orig.df$sel.2, levels = unique(orig.df$sel.2))
      
      # This is ordered first by sel.1, then by sel.2
      aggr.df.ordered <- aggregate(orig.df[,all.add], 
                                   by=list(sel.1 = orig.df$sel.1, sel.2 = orig.df$sel.2), sum)
      

      输出是:

         newvar add.1 add.2
      1     1 1   100    91
      2     1 4   170   183
      3     1 5   384   366
      4     2 2   175   176
      5     2 3    90    96
      6     2 4    82    87
      7     2 5    95    89
      8     3 2   189   178
      9     3 3    81    82
      10    4 1   174   192
      11    5 3    91    98
      12    5 4    96    84
      13    5 5    83    88
      

      要让它在两个变量的每个组合的第一次出现时排序,您需要一个新变量:

      # ordered by first appearance of the two variables (needs a new variable)
      orig.df$newvar <- paste(orig.df$sel.1, orig.df$sel.2)
      orig.df$newvar <- factor(orig.df$newvar, levels = unique(orig.df$newvar))
      
      aggr.df.ordered2 <- aggregate(orig.df[,all.add], 
                                    by=list(newvar = orig.df$newvar,
                                            sel.1 = orig.df$sel.1, 
                                            sel.2 = orig.df$sel.2), sum)
      

      给出输出:

         newvar sel.2 sel.1 add.1 add.2
      1     5 4     4     5    96    84
      2     5 5     5     5    83    88
      3     5 3     3     5    91    98
      4     2 4     4     2    82    87
      5     2 2     2     2   175   176
      6     2 5     5     2    95    89
      7     2 3     3     2    90    96
      8     1 4     4     1   170   183
      9     1 5     5     1   384   366
      10    1 1     1     1   100    91
      11    4 1     1     4   174   192
      12    3 2     2     3   189   178
      13    3 3     3     3    81    82
      

      使用此解决方案,您无需安装任何新软件包。

      【讨论】:

        【解决方案4】:

        不确定该解决方案对于大型数据集的速度和存储容量等如何,但我认为这是解决此问题的一种非常简单的方法。

        # Create dataframe
        x <- c("C", "C", "A", "A", "A","B", "B")
        y <- c(5, 6, 3, 2, 7, 8, 9)
        df <- data.frame(x, y)
        df
        

        原始数据框:

          x y
        1 C 5
        2 C 6
        3 A 3
        4 A 2
        5 A 7
        6 B 8
        7 B 9
        

        解决方案:

        # Add column with the original order
        order <- seq(1:length(df$x))
        df$order <- order
        
        # Aggregate
        # use sum for column Y (the variable you want to aggregate according to X)
        df1 <- aggregate(y~x,data=df,FUN=sum)
        # use mean for column 'order'
        df2 <- aggregate(order~x, data=df,FUN=mean)
        
        # Add the mean of order values to the dataframe
        df <- df1
        df$order <- df2$order
        
        # Order the dataframe according the the mean of order values
        df <- df[order(df$order),]
        df
        

        相同顺序的聚合数据框:

          x  y order
        3 C 11   1.5
        1 A 12   4.0
        2 B 17   6.5
        

        【讨论】:

          猜你喜欢
          • 2013-07-26
          • 2020-05-25
          • 2021-12-28
          • 2021-03-12
          • 2016-11-19
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多