Выберите n-е значение агрегированного столбца после группирования по в R

#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 с самого начала с экономией времени, а не затрат. В любом случае, я просто выложил это там, чтобы другие могли решить, лучше ли это для них, даже если в вашем случае это не имеет смысла.