групповое перекодирование переменных в тидиверс (функциональное / метапрограммирование) - PullRequest
0 голосов
/ 17 июня 2019

Я хочу перекодировать кучу переменных, используя как можно меньше вызовов функций. У меня есть один data.frame, где я хочу перекодировать несколько переменных. Я создаю именованный список всех имен переменных и аргументов записи, которые я хочу выполнить. Здесь у меня нет проблем с использованием map и dpylr. Однако, когда дело доходит до перекодирования, мне гораздо проще использовать recode из пакета car вместо собственной функции перекодирования dpylr. Дополнительный вопрос - есть ли хороший способ сделать то же самое с dplyr::recode.

В качестве следующего шага я разбиваю data.frame на вложенную таблицу. Здесь я хочу сделать конкретные записи в каждом подмножестве. Это где все усложняется, и я больше не могу делать это в dpylr трубе. Единственное, что я получаю, это очень уродливый for loop.

Ищите идеи, чтобы сделать это красиво и чисто.

Давайте начнем с простого примера:

library(carData)
library(dplyr)
library(purrr)
library(tidyr)

# global recode list
recode_ls = list(

  mar = "'not married' = 0;
          'married' = 1",

  wexp = "'no' = 0;
          'yes' = 1"
)

recode_vars <- names(Rossi)[names(Rossi) %in% names(recode_ls)]

Rossi2 <- Rossi # lets save results under a different name

Rossi2[,recode_vars] <- recode_vars %>% map(~ car::recode(Rossi[[.x]],
                                                          recode_ls[.x],
                                                          as.factor = FALSE,
                                                          as.numeric = TRUE))

Пока это кажется мне довольно чистым, за исключением того факта, что car :: recode гораздо проще в использовании, чем dplyr :: recode.

Вот моя настоящая проблема. Я пытаюсь перекодировать (в этом простом примере) переменные mar и wexp по-разному в каждом подмножестве таблицы. В моем реальном наборе данных переменных, которые я хочу перекодировать в каждом подмножестве, гораздо больше и они имеют разные имена. У кого-нибудь есть хорошая идея, как сделать это красиво и чисто, используя dpylr трубу и map?

    nested_rossi <- as_tibble(Rossi) %>% nest(-race)

    recode_wexp_ls = list(

      no = list(

      mar = "'not married' = 0;
             'married' = 1",

      wexp = "'no' = 0;
              'yes' = 1"
      ),

      yes = list(
        mar = "'not married' = 1;
               'married' = 2",

        wexp = "'no' = 1;
                'yes' = 2"
      )

Мы могли бы также прикрепить список к вложенному файлу data.frame, но я не уверен, что это повысит эффективность.

nested_rossi$recode = list(

          no = list(

          mar = "'not married' = 0;
                 'married' = 1",

          wexp = "'no' = 0;
                  'yes' = 1"
          ),

          yes = list(
            mar = "'not married' = 1;
                   'married' = 2",

            wexp = "'no' = 1;
                    'yes' = 2"
          )
        )

1 Ответ

1 голос
/ 18 июня 2019

Спасибо за крутой вопрос!Это отличный шанс использовать всю мощь метапрограммирования.

Сначала давайте рассмотрим функцию 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

Надеюсь, это обновление решит вашу проблему.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...