R help - функция для нескольких столбцов фрейма данных

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

> sample_data
# A tibble: 10 x 7
      REVENUEID AMOUNT  YEAR REPORT_CODE PAYMENT_METHOD INBOUND_CHANNEL  AMOUNT_CAT
          <chr>  <dbl> <chr>       <chr>          <chr>           <chr>      <fctr>
 1 rev-24985629     30  FY18           S          Check            Mail     [25,50)
 2 rev-22812413      1  FY16           Q          Other      Canvassing   [0.01,10)
 3 rev-23508794    100  FY17           Q    Credit card             Web   [100,250)
 4 rev-23506121    300  FY17           S    Credit card            Mail   [250,500)
 5 rev-23550444    100  FY17           S    Credit card             Web   [100,250)
 6 rev-21508672     25  FY14           J          Check            Mail     [25,50)
 7 rev-24981769    500  FY18           S    Credit card             Web [500,1e+03)
 8 rev-23503684     50  FY17           R          Check            Mail     [50,75)
 9 rev-24982087     25  FY18           R          Check            Mail     [25,50)
10 rev-24979834     50  FY18           R    Credit card             Web     [50,75)

Вот мой код:

AMOUNT_CAT<- sample_data %>% group_by(AMOUNT_CAT,YEAR) %>% summarize(num=n(),total=sum(AMOUNT)) %>% rename(REPORT_VALUE=AMOUNT_CAT) %>% mutate(REPORT_CATEGORY="AMOUNT_CAT")
INBOUND_CHANNEL<- sample_data %>% group_by(INBOUND_CHANNEL,YEAR) %>% summarize(num=n(),total=sum(AMOUNT)) %>% rename(REPORT_VALUE=INBOUND_CHANNEL) %>% mutate(REPORT_CATEGORY="INBOUND_CHANNEL")
PAYMENT_METHOD<- sample_data %>% group_by(PAYMENT_METHOD,YEAR) %>% summarize(num=n(),total=sum(AMOUNT)) %>% rename(REPORT_VALUE=PAYMENT_METHOD) %>% mutate(REPORT_CATEGORY="PAYMENT_METHOD")
REPORT_CODE<- sample_data %>% group_by(REPORT_CODE,YEAR) %>% summarize(num=n(),total=sum(AMOUNT)) %>% rename(REPORT_VALUE=REPORT_CODE) %>% mutate(REPORT_CATEGORY="REPORT_CODE")
final_product<-bind_rows(REPORT_CODE,PAYMENT_METHOD,INBOUND_CHANNEL,AMOUNT_CAT)

Вот конечный продукт этого кода:

       > final_product
        # A tibble: 27 x 5
        # Groups:   REPORT_VALUE [16]
           REPORT_CATEGORY REPORT_VALUE  YEAR   num total

                 <chr>        <chr> <chr> <int> <dbl>
     1     REPORT_CODE            J  FY14     1    25
     2     REPORT_CODE            Q  FY16     1     1
     3     REPORT_CODE            Q  FY17     1   100
     4     REPORT_CODE            R  FY17     1    50
     5     REPORT_CODE            R  FY18     2    75
     6     REPORT_CODE            S  FY17     2   400
     7     REPORT_CODE            S  FY18     2   530
     8  PAYMENT_METHOD        Check  FY14     1    25
     9  PAYMENT_METHOD        Check  FY17     1    50
    10  PAYMENT_METHOD        Check  FY18     2    55
    # ... with 17 more rows

Вот моя попытка сжать код, чтобы сделать его умнее и эффективнее (он не работает):

cat.list <- c("REPORT_CODE","PAYMENT_METHOD","INBOUND_CHANNEL","AMOUNT_CAT")
repeat_procs <- lapply(cat.list, function(x) x <- sample_data %>% group_by(x,YEAR) %>% summarize(num=n(),total=sum(AMOUNT)) %>% rename(REPORT_VALUE=x) %>% mutate(REPORT_CATEGORY="x")

Может кто-нибудь, пожалуйста, посоветуйте мне, как написать "умный" код, который повторяется не так часто?

Спасибо!

3 ответа

Решение

Вам нужно разобрать строки по символам (rlang::sym) и удалите их в кавычки group_by а также rename как следующее. Стоит отметить, что cat.list уже является строковым вектором, поэтому нет необходимости добавлять двойные кавычки x в mutate:

library(dplyr)
library(rlang)

cat.list <- c("REPORT_CODE","PAYMENT_METHOD","INBOUND_CHANNEL","AMOUNT_CAT")
repeat_procs <- lapply(cat.list, function(x){
  final_data <- sample_data %>% 
    group_by(!!sym(x), YEAR) %>% 
    summarize(num=n(),total=sum(AMOUNT)) %>% 
    rename(REPORT_VALUE=!!sym(x)) %>% 
    mutate(REPORT_CATEGORY=x)
}) %>%
  bind_rows()

Результат:

> repeat_procs
# A tibble: 27 x 5
# Groups:   REPORT_VALUE [16]
   REPORT_VALUE   YEAR   num total REPORT_CATEGORY
          <chr> <fctr> <int> <int>           <chr>
 1            J   FY14     1    25     REPORT_CODE
 2            Q   FY16     1     1     REPORT_CODE
 3            Q   FY17     1   100     REPORT_CODE
 4            R   FY17     1    50     REPORT_CODE
 5            R   FY18     2    75     REPORT_CODE
 6            S   FY17     2   400     REPORT_CODE
 7            S   FY18     2   530     REPORT_CODE
 8        Check   FY14     1    25  PAYMENT_METHOD
 9        Check   FY17     1    50  PAYMENT_METHOD
10        Check   FY18     2    55  PAYMENT_METHOD
# ... with 17 more rows

Чтобы получить более "умный" код, необходимо преобразовать данные в форму "аккуратные данные" перед группировкой и суммированием.

data_tidy <-
  tidyr::gather(sample_data, key = "REPORT_CATEGORY", value = "REPORT_VALUE", !! cat.list)

data_tidy
#>       REVENUEID AMOUNT YEAR REPORT_CATEGORY REPORT_VALUE
#> 1  rev-24985629     30 FY18     REPORT_CODE            S
#> 2  rev-22812413      1 FY16     REPORT_CODE            Q
#> 3  rev-23508794    100 FY17     REPORT_CODE            Q
#> 4  rev-23506121    300 FY17     REPORT_CODE            S
#> 5  rev-23550444    100 FY17     REPORT_CODE            S
#> 6  rev-21508672     25 FY14     REPORT_CODE            J
#> 7  rev-24981769    500 FY18     REPORT_CODE            S
#> 8  rev-23503684     50 FY17     REPORT_CODE            R
#> 9  rev-24982087     25 FY18     REPORT_CODE            R
#> 10 rev-24979834     50 FY18     REPORT_CODE            R
#> 11 rev-24985629     30 FY18  PAYMENT_METHOD        Check
#> 12 rev-22812413      1 FY16  PAYMENT_METHOD        Other
#> 13 rev-23508794    100 FY17  PAYMENT_METHOD  Credit card
#> 14 rev-23506121    300 FY17  PAYMENT_METHOD  Credit card
#> 15 rev-23550444    100 FY17  PAYMENT_METHOD  Credit card
#> 16 rev-21508672     25 FY14  PAYMENT_METHOD        Check
#> 17 rev-24981769    500 FY18  PAYMENT_METHOD  Credit card
#> 18 rev-23503684     50 FY17  PAYMENT_METHOD        Check
#> 19 rev-24982087     25 FY18  PAYMENT_METHOD        Check
#> 20 rev-24979834     50 FY18  PAYMENT_METHOD  Credit card
#> 21 rev-24985629     30 FY18 INBOUND_CHANNEL         Mail
#> 22 rev-22812413      1 FY16 INBOUND_CHANNEL   Canvassing
#> 23 rev-23508794    100 FY17 INBOUND_CHANNEL          Web
#> 24 rev-23506121    300 FY17 INBOUND_CHANNEL         Mail
#> 25 rev-23550444    100 FY17 INBOUND_CHANNEL          Web
#> 26 rev-21508672     25 FY14 INBOUND_CHANNEL         Mail
#> 27 rev-24981769    500 FY18 INBOUND_CHANNEL          Web
#> 28 rev-23503684     50 FY17 INBOUND_CHANNEL         Mail
#> 29 rev-24982087     25 FY18 INBOUND_CHANNEL         Mail
#> 30 rev-24979834     50 FY18 INBOUND_CHANNEL          Web
#> 31 rev-24985629     30 FY18      AMOUNT_CAT      [25,50)
#> 32 rev-22812413      1 FY16      AMOUNT_CAT    [0.01,10)
#> 33 rev-23508794    100 FY17      AMOUNT_CAT    [100,250)
#> 34 rev-23506121    300 FY17      AMOUNT_CAT    [250,500)
#> 35 rev-23550444    100 FY17      AMOUNT_CAT    [100,250)
#> 36 rev-21508672     25 FY14      AMOUNT_CAT      [25,50)
#> 37 rev-24981769    500 FY18      AMOUNT_CAT  [500,1e+03)
#> 38 rev-23503684     50 FY17      AMOUNT_CAT      [50,75)
#> 39 rev-24982087     25 FY18      AMOUNT_CAT      [25,50)
#> 40 rev-24979834     50 FY18      AMOUNT_CAT      [50,75)

data_tidy %>%
  group_by(REPORT_CATEGORY, REPORT_VALUE, YEAR) %>%
  summarise(num = n(), total = sum(AMOUNT)) %>%
  ungroup()
#> # A tibble: 27 x 5
#>    REPORT_CATEGORY REPORT_VALUE  YEAR   num total
#>              <chr>        <chr> <chr> <int> <int>
#>  1      AMOUNT_CAT    [0.01,10)  FY16     1     1
#>  2      AMOUNT_CAT    [100,250)  FY17     2   200
#>  3      AMOUNT_CAT      [25,50)  FY14     1    25
#>  4      AMOUNT_CAT      [25,50)  FY18     2    55
#>  5      AMOUNT_CAT    [250,500)  FY17     1   300
#>  6      AMOUNT_CAT      [50,75)  FY17     1    50
#>  7      AMOUNT_CAT      [50,75)  FY18     1    50
#>  8      AMOUNT_CAT  [500,1e+03)  FY18     1   500
#>  9 INBOUND_CHANNEL   Canvassing  FY16     1     1
#> 10 INBOUND_CHANNEL         Mail  FY14     1    25
#> # ... with 17 more rows

purrr добавлен подход, чтобы сделать ваш код немного лаконичнее и smarter,

library(tidyverse)
library(rlang) 
cat.list <- c("REPORT_CODE","PAYMENT_METHOD","INBOUND_CHANNEL","AMOUNT_CAT")

map_df(cat.list,
       function(report_cat) {
           sample_data %>%
               group_by(!!sym(report_cat), YEAR) %>%
               summarize(num=n(),total=sum(AMOUNT)) %>% 
               rename(REPORT_VALUE = !!sym(report_cat)) %>% 
               mutate(REPORT_CATEGORY = report_cat)
       }
      )

Как Хэдли описывает здесь (примерно на полпути):

map_df(x, f) фактически так же, как do.call("rbind", lapply(x, f)) но под капотом гораздо эффективнее.

Полное раскрытие, спасибо @useR за показ мне, как использовать sym(!!() подход. Я загнал себя в угол, используя Programming in Dplyr виньетка, чтобы построить то, что я считал самым современным подходом к функционализации dplyr, Я получил основной dplyr функция для запуска достаточно гладко, используя var <- enquo(var) затем !!var но я не смог найти способ справиться с запуском указанных имен в cat.list через map_df или же lapply, Спасибо, useR за то, что научили меня лучше кодировать в Tidyverse

Редактировать: Спасибо, Дж. Гротендик, за то, что разблокировал, как получить список строк, которые будут плавно приняты функцией dplyr: здесь

Это позволяет мне завершить альтернативный quosured подход, который я разработал ранее:

report <- function(report_cat){
    report_cat <- enquo(report_cat)
    sample_data %>%
    group_by(!!report_cat, YEAR) %>%
    summarize(num=n(),total=sum(AMOUNT)) %>% 
    rename(REPORT_VALUE = !!report_cat) %>% 
    mutate(REPORT_CATEGORY := as.character(quote(!!report_cat))[2])
}
report_named <- function(x) {do.call("report", list(as.name(x)))}
map_df(cat.list, report_named)
> map_df(cat.list, report_named)
    # A tibble: 27 x 5
    # Groups:   REPORT_VALUE [16]
       REPORT_VALUE  YEAR   num total REPORT_CATEGORY
              <chr> <chr> <int> <int>           <chr>
     1            J  FY14     1    25     REPORT_CODE
     2            Q  FY16     1     1     REPORT_CODE
     3            Q  FY17     1   100     REPORT_CODE
     4            R  FY17     1    50     REPORT_CODE
     5            R  FY18     2    75     REPORT_CODE
     6            S  FY17     2   400     REPORT_CODE
     7            S  FY18     2   530     REPORT_CODE
     8        Check  FY14     1    25  PAYMENT_METHOD
     9        Check  FY17     1    50  PAYMENT_METHOD
    10        Check  FY18     2    55  PAYMENT_METHOD
    # ... with 17 more rows

NB: опрятное решение yutannihilation действительно является оптимальным решением ИМХО - я просто использовал это как возможность расширить мое понимание того, как мы можем продвигаться по принципу разделения, применения, объединения, чтобы включить dplyr функции.

Другие вопросы по тегам