У меня есть фрейм данных, который инкапсулирует ряд статистических данных для экзамена, отслеживаемых за разные годы и группы. Я хотел бы создать функцию, которая добавляет новые столбцы, показывающие изменение этой статистики для каждой группы из динамически поставляемого списка базовых лет.
Вот пример вывода, который я хотел бы.
grades <- data.frame(
Group = c(rep("A", 4), rep("B", 4)),
Year = rep(seq(2015, 2018), 2),
Mean = c(seq(100, 130, 10), seq(200, 260, 20)),
PassR = c(seq(0.5, 0.53, 0.01), seq(0.6, 0.66, 0.02))
)
grades |> group_by(Group) |> calculateDifferences(c(2015, 2016))
# A tibble: 8 × 8
# Groups: Group [2]
Group Year Mean PassR Mean_Diff2015 Mean_Diff2016 PassR_Diff2015 PassR_Diff2016
<chr> <int> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 A 2015 100 0.5 0 -10 0 -0.0100
2 A 2016 110 0.51 10 0 0.0100 0
3 A 2017 120 0.52 20 10 0.0200 0.0100
4 A 2018 130 0.53 30 20 0.0300 0.0200
5 B 2015 200 0.6 0 -20 0 -0.0200
6 B 2016 220 0.62 20 0 0.0200 0
7 B 2017 240 0.64 40 20 0.0400 0.0200
8 B 2018 260 0.66 60 40 0.0600 0.0400
Моя лучшая попытка — это следующая функция, но она сталкивается с проблемами области видимости столбца «Год» в списке.
# Calculate differences from the given year for both mean and pass rate
calculateDifferences <- function(data, diffYears) {
mutate(data,
across(
any_of(c("Mean", "PassR")),
#list(Diff2015 = function(col) col - col[Year == 2015],
# Diff2016 = function(col) col - col[Year == 2016]),
map(as.list(diffYears), function(year) { function(col) col - col[Year == year] }) |>
set_names(str_c("Diff", diffYears)),
.names = "{.col}_{.fn}"
)
)
}
Запуск этого кода жалуется, что не может найти объект Year
. Я пытался ввести некоторый NSE, чтобы задержать оценку переменной, но ни !!substitute("Year")
, ни !!quo("Year")
не дают желаемого результата: он просто выдает ошибку dplyr::mutate_incompatible_size <named_list>
. Попытка заменить его на .data[["Year"]]
жалуется, что это не в контексте маскировки данных.
Если я жестко запрограммирую годы (как в закомментированном разделе функции), это работает правильно и дает желаемый результат, но не может адаптироваться к динамически предоставляемому списку лет.
Могу попробовать отдельно вытащить колонку Год с помощью data[["Year"]]
. Это хорошо работает, если данные не сгруппированы, но не работает, если данные сгруппированы.
Использование cur_data()
для доступа к данным текущей группы:
library(dplyr)
library(purrr)
library(stringr)
calculateDifferences <- function(data, diffYears) {
mutate(data,
across(
any_of(c("Mean", "PassR")),
map(as.list(diffYears), function(year) { function(col) col - col[cur_data()$Year == year] }) |>
set_names(str_c("Diff", diffYears)),
.names = "{.col}_{.fn}"
)
)
}
grades <- data.frame(
Group = c(rep("A", 4), rep("B", 4)),
Year = rep(seq(2015, 2018), 2),
Mean = c(seq(100, 130, 10), seq(200, 260, 20)),
PassR = c(seq(0.5, 0.53, 0.01), seq(0.6, 0.66, 0.02))
)
grades |> group_by(Group) |> calculateDifferences(c(2015, 2016))
# A tibble: 8 × 8
# Groups: Group [2]
Group Year Mean PassR Mean_Diff2015 Mean_Diff2016 PassR_Diff2015 PassR_Diff2016
<chr> <int> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 A 2015 100 0.5 0 -10 0 -0.0100
2 A 2016 110 0.51 10 0 0.0100 0
3 A 2017 120 0.52 20 10 0.0200 0.0100
4 A 2018 130 0.53 30 20 0.0300 0.0200
5 B 2015 200 0.6 0 -20 0 -0.0200
6 B 2016 220 0.62 20 0 0.0200 0
7 B 2017 240 0.64 40 20 0.0400 0.0200
8 B 2018 260 0.66 60 40 0.0600 0.0400
Мне непонятно, почему он может найти cur_data()$Year
, но .data[["Year"]]
или просто Year
.
Ой - да, либо сработает. cur_data()
недавно устарел в пользу pick()
, но я думаю, что есть много случаев (например, этот), где cur_data()
понятнее.
Вот альтернативный подход, основанный на возврате таблички внутри и последующей распаковке с использованием аргумента .unpack
. Я изменил функцию, чтобы переменные можно было передавать в качестве аргумента, а не жестко запрограммировать (что также позволяет при желании использовать функции tidyselect), а также группировку.
library(purrr)
library(dplyr)
calculateDifferences <- function(data, vars, diffYears, group = Group) {
data |>
mutate(
across({{ vars }}, ~
map(diffYears, \(year)
tibble("Diff{year}" := .x - .x[Year == year])
) |>
list_cbind(),
.unpack = TRUE),
.by = {{ group }}
)
}
grades |>
calculateDifferences(c(Mean, PassR), c(2015, 2016))
Group Year Mean PassR Mean_Diff2015 Mean_Diff2016 PassR_Diff2015 PassR_Diff2016
1 A 2015 100 0.50 0 -10 0.00 -0.01
2 A 2016 110 0.51 10 0 0.01 0.00
3 A 2017 120 0.52 20 10 0.02 0.01
4 A 2018 130 0.53 30 20 0.03 0.02
5 B 2015 200 0.60 0 -20 0.00 -0.02
6 B 2016 220 0.62 20 0 0.02 0.00
7 B 2017 240 0.64 40 20 0.04 0.02
8 B 2018 260 0.66 60 40 0.06 0.04
Я выбрал это решение, потому что оно кажется более надежным: здесь pick/cur_group не нужен, и голый год работает.
Отличный! В вашем коде есть pick(Year)$Year вместо cur_data()$Year, но это действительно работает. Я оставлю это открытым на несколько дней, чтобы посмотреть, есть ли другие подходы.