Спасибо за крутой вопрос!Это отличный шанс использовать всю мощь метапрограммирования.
Сначала давайте рассмотрим функцию recode()
.Он получает вектор и произвольное количество (именованных) аргументов и возвращает тот же вектор со значениями, замененными аргументами функции:
x <- c("a", "b", "c")
recode(x, a = "Z", c = "X")
#> [1] "Z" "b" "X"
recode
справка говорит, что мы можем использовать сплайсинг без кавычек (!!!
) чтобы передать в него именованный список.
x_codes <- list(a = "Z", c = "X")
recode(x, !!!x_codes)
#> [1] "Z" "b" "X"
Эта возможность может быть использована при мутировании фрейма данных.Предположим, у нас есть подмножество набора данных Росси:
library(carData)
library(tidyverse)
rossi <- Rossi %>%
as_tibble() %>%
select(mar, wexp)
Чтобы изменить две переменные в одном вызове функции, мы можем использовать этот фрагмент (обратите внимание, что подходы с именованными аргументами и сплайсингом без кавычек работают хорошо):
mar_codes <- list(`not married` = 0, married = 1)
wexp_codes <- list(no = 0, yes = 1)
rossi %>%
mutate(
mar_code = recode(mar, "not married" = 0, "married" = 1),
wexp_code = recode(wexp, !!!wexp_codes)
)
#> # A tibble: 432 x 4
#> mar wexp mar_code wexp_code
#> <fct> <fct> <dbl> <dbl>
#> 1 not married no 0 0
#> 2 not married no 0 0
#> 3 not married yes 0 1
#> 4 married yes 1 1
#> 5 not married yes 0 1
Таким образом, объединение без кавычек является хорошим методом передачи нескольких аргументов в функцию в нестандартной среде оценки.
Теперь предположим, что у нас есть список списков кодов:
mapping <- list(mar = mar_codes, wexp = wexp_codes)
mapping
#> $mar
#> $mar$`not married`
#> [1] 0
#> $mar$married
#> [1] 1
#> $wexp
#> $wexp$no
#> [1] 0
#> $wexp$yes
#> [1] 1
Нам нужно преобразовать этот список в список выражений для размещения внутри mutate()
:
expressions <- mapping %>%
imap(
~ quo(
recode(!!sym(.y), !!!.x)
)
)
expressions
#> $mar
#> <quosure>
#> expr: ^recode(mar, not married = 0, married = 1)
#> env: 0x7fbf374513c0
#> $wexp
#> <quosure>
#> expr: ^recode(wexp, no = 0, yes = 1)
#> env: 0x7fbf37453468
Последний шаг.Передайте этот список выражений в mutate и посмотрите, что он будет делать:
mutate(rossi, !!!expressions)
#> # A tibble: 432 x 2
#> mar wexp
#> <dbl> <dbl>
#> 1 0 0
#> 2 0 0
#> 3 0 1
#> 4 1 1
#> 5 0 1
Теперь вы можете расширять списки переменных для перекодирования, обрабатывать несколько списков одновременно и так далее.
С такой мощной техникой (метапрограммирование) вы можете делать удивительные вещи.Я настоятельно рекомендую вам углубиться в эту тему.И нет лучшего ресурса для начала, чем Книга продвинутого R Хедли Уикхема .
Надеюсь, это то, что вы искали.
Обновление
Дайвинг глубже.Вопрос был: как применить эту технику к столбцу tibble?
Давайте создадим вложенный тиббл group
и df
(наши данные для перекодирования)
rossi <-
head(Rossi, 5) %>%
as_tibble() %>%
select(mar, wexp)
nested <- tibble(group = c("yes", "no"), df = list(rossi))
nested
выглядит так:
# A tibble: 2 x 2
group df
<chr> <list>
1 yes <tibble [5 × 2]>
2 no <tibble [5 × 2]>
Мы уже знаем, как построить список выражений из списка кодов.Давайте создадим функцию, которая будет обрабатывать ее для нас.
build_recode_expressions <- function(list_of_codes) {
imap(list_of_codes, ~ quo(recode(!!sym(.y), !!!.x)))
}
Там аргумент list_of_codes
представляет собой именованный список для каждой переменной, необходимой для перекодирования.
Предполагая, что у нас есть список из нескольких повторений codes
, мы можем преобразовать его в список из нескольких списков выражений.Число переменных в каждом списке может быть произвольным.
codes <- list(
yes = list(mar = list(`not married` = 0, married = 1)),
no = list(
mar = list(`not married` = 10, married = 20),
wexp = list(no = "NOOOO", yes = "YEEEES")
)
)
exprs <- map(codes, build_recode_expressions)
Теперь мы можем легко добавить exprs
во вложенный фрейм данных в качестве нового столбца списка.
Есть еще одна функция, которая можетбыть полезным для дальнейшей работы.Эта функция берет фрейм данных и список выражений в кавычках и возвращает новый фрейм данных с перекодированными столбцами.
recode_df <- function(df, exprs) mutate(df, !!!exprs)
Пора объединить все вместе.У нас есть tibble-column df
, list-column exprs
и функция recode_df
, которая связывает их вместе, но один за другим.
Ключом является map2
функция.Это позволяет нам выполнять итерации по двум спискам одновременно:
nested %>%
mutate(exprs = exprs) %>%
mutate(df_recoded = map2(df, exprs, recode_df)) %>%
unnest(df, df_recoded)
И это вывод:
# A tibble: 10 x 5
group mar wexp mar1 wexp1
<chr> <fct> <fct> <dbl> <chr>
1 yes not married no 0 no
2 yes not married no 0 no
3 yes not married yes 0 yes
4 yes married yes 1 yes
5 yes not married yes 0 yes
6 no not married no 10 NOOOO
7 no not married no 10 NOOOO
8 no not married yes 10 YEEEES
9 no married yes 20 YEEEES
10 no not married yes 10 YEEEES
Надеюсь, это обновление решит вашу проблему.