#r #dataframe #group-by #aggregate
#r #dataframe #группирование по #агрегировать
Вопрос:
Задается df
следующим образом:
# group value
# 1 A 8
# 2 A 1
# 3 A 7
# 4 B 3
# 5 B 2
# 6 B 6
# 7 C 4
# 8 C 5
df <- structure(list(group = structure(c(1L, 1L, 1L, 2L, 2L, 2L, 3L,
3L), .Label = c("A", "B", "C"), class = "factor"), value = c(8L,
1L, 7L, 3L, 2L, 6L, 4L, 5L)), .Names = c("group", "value"), class = "data.frame", row.names = c(NA,
-8L))
И вектор индексов (возможно, с NA
):
inds <- c(2,1,NA)
Как мы можем получить n-й элемент столбца value
для каждой группы, предпочтительно в базе R?
Например, на основе inds
, мы хотим, чтобы второй элемент value
в группе A
, первый элемент в группе B
, NA
в группе C
. Таким образом, результат будет:
#[1] 1 3 NA
Комментарии:
1. Упорядочен ли ваш «data.frame» по столбцу «group»? Вы могли бы просто подмножить «значение», добавив соответствующее смещение к
inds
:df$value[cumsum(c(0, head(tabulate(df$group), -1))) inds]
2. @alexis_laz Хорошая мысль, но она не обязательно упорядочена по столбцу ‘group’.
Ответ №1:
Вот решение с mapply
и split
:
mapply("[", with(df, split(value, group)), inds)
который возвращает именованный вектор
A B C
1 3 NA
with(df, split(value, group))
разбивает фрейм данных по группам и возвращает список фреймов данных. mapply
принимает этот список и «inds» и применяет функцию подмножества «[» к каждой паре аргументов.
Комментарии:
1. очень элегантно!, Мне нужно преодолеть свое отвращение к оператору «[«!
2. Поначалу кажется неудобным использовать «[«, но это часто позволяет избежать построения универсальной функции, что, вероятно, повышает простоту интерпретации.
Ответ №2:
Используя levels
и sapply
вы могли бы сделать:
DF <- structure(list(group = structure(c(1L, 1L, 1L, 2L, 2L, 2L, 3L,
3L), .Label = c("A", "B", "C"), class = "factor"), value = c(8L,
1L, 7L, 3L, 2L, 6L, 4L, 5L)), .Names = c("group", "value"), class = "data.frame", row.names = c(NA,
-8L))
inds <- c(2,1,NA)
lvls = levels(DF$group)
groupInds = sapply(1:length(lvls),function(x) DF$value[DF$group==lvls[x]][inds[x]] )
groupInds
#[1] 1 3 NA
Ответ №3:
Снова используя mapply (но не так элегантно, как ответ IMO):
mapply(function(x, y) subset(df, group == x, value)[y,] ,levels(df$group), inds)
Ответ №4:
Я знаю, что вы сказали, что предпочтительно в базе R, но просто для записи, вот data.table
способ
setDT(df)[, .SD[inds[.GRP], value], by=group][,V1]
#[1] 1 3 NA
Ответ №5:
Я только что придумал другое решение:
diag(aggregate(value~group, df, function(x) x[inds])[,-1])
#[1] 1 3 NA
Сравнительный анализ
library(microbenchmark)
library(data.table)
df <- structure(list(group = structure(c(1L, 1L, 1L, 2L, 2L, 2L, 3L,
3L), .Label = c("A", "B", "C"), class = "factor"), value = c(8L,
1L, 7L, 3L, 2L, 6L, 4L, 5L)), .Names = c("group", "value"), class = "data.frame", row.names = c(NA,
-8L))
inds <- c(2,1,NA)
f_Imo <- function(df) as.vector(mapply("[", with(df, split(value, group)), inds))
f_Osssan <- function(df) {lvls = levels(df$group);sapply(1:length(lvls),function(x) df$value[df$group==lvls[x]][inds[x]])}
f_User2321 <- function(df) unlist(mapply(function(x, y) subset(df, group == x, value)[y,] ,levels(df$group), inds))
f_dww <- function(df) setDT(df)[, .SD[inds[.GRP], value], by=group][,V1]
f_m0h3n <- function(df) diag(aggregate(value~group, df, function(x) x[inds])[,-1])
all.equal(f_Imo(df), f_Osssan(df), f_User2321(df), f_dww(df), f_m0h3n(df))
# [1] TRUE
microbenchmark(f_Imo(df), f_Osssan(df), f_m0h3n(df), f_User2321(df), f_dww(df))
# Unit: microseconds
# expr min lq mean median uq max neval
# f_Imo(df) 71.004 85.1180 91.52996 91.748 96.8810 121.048 100
# f_Osssan(df) 252.788 276.5265 318.70529 287.648 301.5495 2651.492 100
# f_m0h3n(df) 1422.627 1555.4365 1643.47184 1618.740 1670.7095 4729.827 100
# f_User2321(df) 2889.738 3000.3055 3148.44916 3037.945 3118.7860 6013.442 100
# f_dww(df) 2960.740 3086.2790 3206.02147 3143.381 3250.9545 5976.229 100
Комментарии:
1. Один момент о сравнительном анализе — почти все время для f_dww занимает преобразование в data.table. Если вы работали с data.table в первую очередь (поэтому исключите
setDT
), я получаю среднее значение микропозиции 4,45 наносекунд , медиану0
и предупреждение:Could not measure a positive execution time for 77 evaluations.
2. @dww Да, но я думаю, что это часть решения. То есть, вторая часть не работает без
setDT(df)
. Это одна из причин, по которой я всегда поддерживаюbase
R-решения.3. Конечно, в вашем случае это может быть правдой, т. Е. Если вы работаете с фреймами данных для всего остального, тогда накладные расходы на преобразование могут оказаться бесполезными только для одного вычисления. Но во многих других случаях это единовременные затраты, которые окупаются экономией времени при многократных манипуляциях с данными. Кроме того, иногда можно использовать
fread
для загрузки данных намного быстрее, чемread.table
, предоставляя вам data.table с самого начала с экономией времени, а не затрат. В любом случае, я просто выложил это там, чтобы другие могли решить, лучше ли это для них, даже если в вашем случае это не имеет смысла.