Aggregate data frame, сохраняя исходный порядок, простым способом

у меня возникли проблемы с агрегированием фрейма данных, сохраняя группы в исходном порядке (порядок, основанный на первом появлении в фрейме данных). Мне удалось сделать это правильно, но я надеялся, что есть более простой способ сделать это.

вот пример набора данных для работы на:

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.

Я просмотрел документацию для aggregate и match, но я не мог найти ответ (я думаю, я надеялся на что-то вроде "держать.оригинальный.параметр "заказать" для aggregate).

любая помощь была бы очень признательна!


Update: (в случае, если кто-нибудь наткнется на это)

вот самый чистый способ, который я мог найти, попробовав еще несколько дней:

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)))

4 ответов


немного трудно читать, но это дает вам то, чего вы хотите, и я добавил некоторые комментарии, чтобы уточнить.

# 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

это короткий и простой в данные.таблица. Он возвращает группы в первом порядке появления по умолчанию.

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"]

ища решения той же проблемы, я нашел новый, используя aggregate (), но сначала Преобразуя выбранные переменные в качестве факторов в нужном порядке.

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

С помощью этого решения вам не нужно устанавливать новый пакет.


не уверен, как это решение для скорости и емкости хранения и т. д. для больших наборов данных, но я думал, что это был довольно простой способ решения этой проблемы.

# 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