2016-10-18 4 views
4

Учитывая df следующим образом:Выберите п-е значения агрегированного столбца после группы в R

# 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 
+0

Ваш «data.frame» заказал в колонке «группы»? Вы можете, просто, подмножество «значение», добавив соответствующее смещение в 'inds':' df $ value [cumsum (c (0, head (tabulate (df $ group), -1))) + inds] ' –

+0

@ alexis_laz Хорошая точка, но это необязательно упорядочено столбцом «группа». – 989

ответ

5

Вот решение с mapply и split:

mapply("[", with(df, split(value, group)), inds) 

который возвращает именованный вектор

A B C 
1 3 NA 

with(df, split(value, group)) разделяет кадр данных по группам и возвращает список фреймов данных. mapply принимает этот список и «inds» и применяет функцию подмножества «[» к каждой пар аргументов.

+0

очень элегантный !, Мне нужно преодолеть мое отвращение к «[» оператору! – OdeToMyFiddle

+2

Первоначально кажется неудобным использовать «[», но он часто может избежать построения общей функции, что, вероятно, увеличивает простоту интерпретации. – lmo

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 
1

Используя снова mapply (но не так элегантно, как ответ ИМО):

mapply(function(x, y) subset(df, group == x, value)[y,] ,levels(df$group), inds) 
0

я просто придумал другое решение:

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 
+0

Один момент о бенчмаркинге - почти все время для f_dww принимается преобразованием в data.table. Если вы сначала работали с data.table (так что исключите 'setDT'), я получаю среднее значение микрообъекта 4.45 * наносекунды *, медианное значение' 0' и предупреждение: 'Не удалось измерить положительное время выполнения для 77 оценок. ' – dww

+0

@dww Да, но я думаю, что это часть решения. То есть вторая часть не работает без 'setDT (df)'.Это одна из причин, почему я всегда выступаю за «базовые» решения R. – 989

+0

Уверен, что в вашем случае это может быть правдой - т. Е. Если вы работаете с кадром данных для всего остального, тогда накладные расходы на конвертирование могут оказаться нецелесообразными только для одного вычисления. Но во многих других случаях это одноразовая стоимость, которая позволяет сэкономить время на нескольких манипуляциях с данными. Кроме того, иногда можно использовать 'fread' для загрузки данных намного быстрее, чем' read.table', что дает вам таблицу данных с самого начала с сохранением во времени, а не стоимостью. Во всяком случае, я просто поместил это, чтобы другие могли решить, подходит ли это для них, даже если в вашем случае это не имеет смысла. – dww

1

Я знаю, что вы сказали предпочтительно в базе R, но только для записи, вот data.table путь

setDT(df)[, .SD[inds[.GRP], value], by=group][,V1] 
#[1] 1 3 NA