Dplyr суммировать, когда функция возвращает векторнозначную?

Функция dplyr::summarize() может применять произвольные функции по данным, но кажется, что функция должна возвращать скалярное значение. Мне любопытно, есть ли разумный способ обработки функций, которые возвращают векторное значение, не делая несколько вызовов функции.

Вот несколько глупый минимальный пример. Рассмотрим функцию, которая дает несколько значений, например:

f <- function(x,y){
  coef(lm(x ~ y, data.frame(x=x,y=y)))
}

и данные, которые выглядят следующим образом:

df <- data.frame(group=c('A','A','A','A','B','B','B','B','C','C','C','C'), x=rnorm(12,1,1), y=rnorm(12,1,1))

Я хотел бы сделать что-то вроде:

df %>% 
group_by(group) %>%
summarise(f(x,y))

и вернуть таблицу, в которой для каждого возвращаемого значения добавлено 2 столбца вместо обычного столбца 1. Вместо этого эти ошибки: Expecting single value

Конечно, мы можем получить несколько значений из dlpyr::summarise(), передав аргумент функции несколько раз:

f1 <- function(x,y) coef(lm(x ~ y, data.frame(x=x,y=y)))[[1]]
f2 <- function(x,y) coef(lm(x ~ y, data.frame(x=x,y=y)))[[2]]

df %>% 
group_by(group) %>%
summarise(a = f1(x,y), b = f2(x,y))

Это дает желаемый результат:

  group         a            b
1     A 1.7957245 -0.339992915
2     B 0.5283379 -0.004325209
3     C 1.0797647 -0.074393457

но кодирование таким образом является смехотворно грубым и уродливым.

data.table обрабатывает этот случай более кратко:

dt <- as.data.table(df)
dt[, f(x,y), by="group"]

но создает вывод, который расширяет таблицу, используя дополнительные строки, а не дополнительные столбцы, в результате чего результат, который сбивает с толку и сложнее работать с:

 group           V1
1:     A  1.795724536
2:     A -0.339992915
3:     B  0.528337890
4:     B -0.004325209
5:     C  1.079764710
6:     C -0.074393457

Конечно, есть более классические стратегии apply, которые мы могли бы использовать здесь,

sapply(levels(df$group), function(x) coef(lm(x~y, df[df$group == x, ])))


                     A            B           C
(Intercept)  1.7957245  0.528337890  1.07976471
y           -0.3399929 -0.004325209 -0.07439346

но это жертвует как элегантностью, так и я подозреваю скорость группировки. В частности, обратите внимание, что в этом случае мы не можем использовать нашу предварительно определенную функцию f, но должны жестко кодировать группировку в определение функции.

Есть ли функция dplyr для обработки этого случая? Если нет, существует ли более элегантный способ обработки этого процесса оценки векторнозначных функций над data.frame по группе?

Ответы

Ответ 1

Вы можете попробовать do

library(dplyr)
 df %>%
    group_by(group) %>%
    do(setNames(data.frame(t(f(.$x, .$y))), letters[1:2]))
 # group         a           b
 #1     A 0.8983217 -0.04108092
 #2     B 0.8945354  0.44905220
 #3     C 1.2244023 -1.00715248

Выход на основе f1 и f2 равен

df %>% 
  group_by(group) %>%
  summarise(a = f1(x,y), b = f2(x,y))
#  group         a           b
#1     A 0.8983217 -0.04108092
#2     B 0.8945354  0.44905220
#3     C 1.2244023 -1.00715248

Update

Если вы используете data.table, опция получения аналогичного результата

 library(data.table)
 setnames(setDT(df)[, as.list(f(x,y)) , group], 2:3, c('a', 'b'))[]

Ответ 2

Вот почему я все еще люблю plyr::ddply():

library(plyr)
f <- function(z) setNames(coef(lm(x ~ y, z)), c("a", "b"))
ddply(df, ~ group, f)
#   group           a          b
# 1     A   0.5213133 0.04624656
# 2     B   0.3020656 0.01450137
# 3     C   0.2189537 0.22998823