Я пытаюсь назначить векторный вывод (т.е. больше длины 1) функции нескольким столбцам в одной операции (или, по крайней мере, как можно более кратко).
Возьмем, к примеру, функцию range()
, которая возвращает числовой вектор длины 2, обозначающий минимум и максимум соответственно. Допустим, я хочу вычислить range()
для каждой группы и назначить результат двум столбцам min
и max
.
Мой текущий подход заключается в объединении summarize
с последующим добавлением ключа вручную, а затем изменением формы до широкого формата:
library(magrittr)
# create data
df <- dplyr::tibble(group = rep(letters[1:3], each = 3),
x = rpois(9, 10))
df
#> # A tibble: 9 x 2
#> group x
#> <chr> <int>
#> 1 a 8
#> 2 a 12
#> 3 a 8
#> 4 b 9
#> 5 b 14
#> 6 b 9
#> 7 c 11
#> 8 c 6
#> 9 c 12
# summarize gives two lines per group
range_df <- df %>%
dplyr::group_by(group) %>%
dplyr::summarize(range = range(x)) %>%
dplyr::ungroup()
range_df
#> # A tibble: 6 x 2
#> group range
#> <chr> <int>
#> 1 a 8
#> 2 a 12
#> 3 b 9
#> 4 b 14
#> 5 c 6
#> 6 c 12
# add key and reshape
range_df %>%
dplyr::mutate(key = rep(c("min", "max"), 3)) %>%
tidyr::pivot_wider(names_from = key, values_from = range)
#> # A tibble: 3 x 3
#> group min max
#> <chr> <int> <int>
#> 1 a 8 12
#> 2 b 9 14
#> 3 c 6 12
Есть ли более элегантная/краткая альтернатива этому?
Редактировать:
В идеале альтернативное решение могло бы обрабатывать произвольное количество выходных данных (например, если функция возвращает выходные данные длиной 3, необходимо создать 3 переменные).
set.seed(1)
df <- dplyr::tibble(group = rep(letters[1:3], each = 3),
x = rpois(9, 10))
функция
g <- function(x){
data.frame(min = min(x), max = max(x))
}
звоню г:
df %>%
group_by(group) %>%
summarise(across(x, g, .unpack = TRUE))
Вопрос в том, как получить вывод более коротким скриптом! - перекройка широкого формата
Главный вопрос: «Я пытаюсь назначить векторный вывод (т.е. больше длины 1) функции нескольким столбцам в одной операции (или, по крайней мере, как можно более кратко)». чтобы не сокращать этот пример.
отредактировано!, до сих пор непонятно, зачем создавать функцию, если она уже существует в tidyverse. Кстати, большое спасибо за ваше уведомление.
# Writw a small function that does the job:
library(tidyverse)
f <- function(x){
setNames(data.frame(t(range(x))), c('min', 'max'))
}
df %>%
summarise(across(x, f, .unpack = TRUE), .by=group)
#> # A tibble: 3 × 3
#> group x_min x_max
#> <chr> <int> <int>
#> 1 a 10 13
#> 2 b 7 10
#> 3 c 10 12
Если вы используете более старую версию dplyr
df %>%
group_by(group)%>%
summarise(across(x, f))%>%
unpack(x)
#> # A tibble: 3 × 3
#> group min max
#> <chr> <int> <int>
#> 1 a 6 9
#> 2 b 7 12
#> 3 c 6 10
Обратите внимание: если вы не хотите распаковывать, вы можете использовать функцию do
после group_by
. то есть df %>%group_by(group)%>%do(f(.$x))
Основываясь на ответе onyambu, я создаю для этого небольшую общую функцию. Вероятно, будут некоторые крайние случаи, когда это не сработает.
out2col <- function(x, fun, out_names = c(), add_args = list()) {
tmp <- do.call(what = fun, args = c(list(x), add_args))
out <- data.frame(t(tmp))
if (length(out_names) != 0) {
if (length(tmp) != length(out_names)) {
stop("provided names did not match the number of outputs")
}
out <- setNames(object = out, nm = out_names)
}
return(out)
}
Примеры без дополнительных параметров:
df %>%
summarise(across(x, out2col, .unpack = TRUE, fun = range),
.by=group)
Выход:
# A tibble: 3 × 3
group x_X1 x_X2
<chr> <int> <int>
1 a 7 10
2 b 11 14
3 c 9 14
Примеры с дополнительными параметрами:
df %>%
summarise(across(x, out2col, .unpack = TRUE, fun = quantile,
out_names = c("min", "max", "Q25"),
add_args = list(probs = c(0, 1, 0.25))
),
.by=group)
Выход:
# A tibble: 3 × 4
group x_min x_max x_Q25
<chr> <dbl> <dbl> <dbl>
1 a 7 10 7.5
2 b 11 14 11.5
3 c 9 14 10
ОП просил решение, в котором вывод функции является вектором. Разве вы не просто заменили функцию ввода (диапазон -> мин и макс)?