R summarise_at динамически по условию: среднее значение для одних столбцов, сумма для других

Я бы хотел, но с условиями в summarise_at()

Правка №1: я добавил слово "динамически" в заголовок: когда я использую vars(c()) в summarise_at(), это для быстрых и ясных примеров, но на самом деле это для использования contains(), starts_with() и matches(,, perl=TRUE), потому что я имеют 50 столбцов, со многими sum() и некоторыми mean().

И цель состоит в том, чтобы сгенерировать динамический SQL с помощью tbl()..%>% group_by() ... %>% summarise_at()...%>% collect().

Изменить №2: я добавил пример с SQL, сгенерированным во втором примере.

library(tidyverse)
(mtcars 
  %>% group_by(carb)
  %>% summarise_at(vars(c("mpg","cyl","disp")), list (~mean(.),~sum(.)))
  # I don't want this line below, I would like a conditional in summarise_at() because I have 50 columns in my real case
  %>% select(carb,cyl_mean,disp_mean,mpg_sum)
)
#> # A tibble: 6 x 4
#>    carb cyl_mean disp_mean mpg_sum
#>   <dbl>    <dbl>     <dbl>   <dbl>
#> 1     1     4.57      134.   177. 
#> 2     2     5.6       208.   224  
#> 3     3     8         276.    48.9
#> 4     4     7.2       309.   158. 
#> 5     6     6         145     19.7
#> 6     8     8         301     15

Created on 2020-02-19 by the reprex package (v0.3.0)

Это работает, но мне нужна только сумма для миль на галлон и только для цилиндров и расхода:

library(RSQLite)
library(dbplyr)
library(tidyverse)
library(DBI)

db <- dbConnect(SQLite(),":memory:")

dbCreateTable(db, "mtcars_table", mtcars)

(tbl( db, build_sql( con=db,"select * from mtcars_table" ))
  %>% group_by(carb)
  %>% summarise_at(vars(c("mpg","cyl","disp")), list (~mean(.),~sum(.)))
  %>% select(carb,cyl_mean,disp_mean,mpg_sum)                   
  %>% show_query()
)
#> <SQL>
#> Warning: Missing values are always removed in SQL.[...]  to silence this warning
#> SELECT `carb`, `cyl_mean`, `disp_mean`, `mpg_sum`
#> FROM (SELECT `carb`, AVG(`mpg`) AS `mpg_mean`, AVG(`cyl`) AS `cyl_mean`, AVG(`disp`) AS `disp_mean`, SUM(`mpg`) AS `mpg_sum`, SUM(`cyl`) AS `cyl_sum`, SUM(`disp`) AS `disp_sum`
#> FROM (select * from mtcars_table)
#> GROUP BY `carb`)
#> # Source:   lazy query [?? x 4]
#> # Database: sqlite 3.30.1 [:memory:]
#> # … with 4 variables: carb <dbl>, cyl_mean <lgl>, disp_mean <lgl>,
#> #   mpg_sum <lgl>

Я перепробовал все подобные возможности, но это не работает или выдает ошибку.

(mtcars %>% group_by(carb)%>% summarise_at(vars(c("mpg","cyl","disp")),ifelse(vars(contains(names(.),"mpg")),list(sum(.)),list(mean(.)))) )

Плохо, слишком много столбцов

library(tidyverse)
(mtcars %>% group_by(carb)%>% summarise_at(vars(c("mpg","cyl","disp")),ifelse ((names(.)=="mpg"), list(~sum(.)) , list(~mean(.)))))
#> # A tibble: 6 x 34
#>    carb mpg_sum cyl_sum disp_sum mpg_mean..2 cyl_mean..2 disp_mean..2
#>   <dbl>   <dbl>   <dbl>    <dbl>       <dbl>       <dbl>        <dbl>
#> 1     1   177.       32     940.        25.3        4.57         134.
#> 2     2   224        56    2082.        22.4        5.6          208.
#> 3     3    48.9      24     827.        16.3        8            276.
#> 4     4   158.       72    3088.        15.8        7.2          309.
#> 5     6    19.7       6     145         19.7        6            145 
#> 6     8    15         8     301         15          8            301 
#> # … with 27 more variables: mpg_mean..3 <dbl>, cyl_mean..3 <dbl>,
#> #   disp_mean..3 <dbl>, mpg_mean..4 <dbl>, cyl_mean..4 <dbl>,
#> #   disp_mean..4 <dbl>, mpg_mean..5 <dbl>, cyl_mean..5 <dbl>,
#> #   disp_mean..5 <dbl>, mpg_mean..6 <dbl>, cyl_mean..6 <dbl>,
#> #   disp_mean..6 <dbl>, mpg_mean..7 <dbl>, cyl_mean..7 <dbl>,
#> #   disp_mean..7 <dbl>, mpg_mean..8 <dbl>, cyl_mean..8 <dbl>,
#> #   disp_mean..8 <dbl>, mpg_mean..9 <dbl>, cyl_mean..9 <dbl>,
#> #   disp_mean..9 <dbl>, mpg_mean..10 <dbl>, cyl_mean..10 <dbl>,
#> #   disp_mean..10 <dbl>, mpg_mean..11 <dbl>, cyl_mean..11 <dbl>,
#> #   disp_mean..11 <dbl>

Некоторые другие попытки и замечания: я хотел бы условное sum(.) или mean(.) в зависимости от имени столбца в summarise().

Было бы хорошо, если бы он принимал не только примитивные функции.

В конце концов tbl()..%>% group_by() ... %>% summarise_at()...%>% collect() сгенерирует условный SQL с AVG() и SUM().

Функция T-SQL, такая как ~(convert(varchar()), работает для mutate_at() и аналогичная ~AVG()работает для summarise_at(), но я прихожу к той же точке: условное summarise_at() не работает в зависимости от имени столбца.

:)


person phili_b    schedule 19.02.2020    source источник


Ответы (2)


Вариант состоит в том, чтобы group_by «углевод», а затем создать sum «миль на галлон» в качестве другой группирующей переменной, а затем использовать summarise_at с остальными необходимыми переменными.

library(dplyr)
mtcars %>%
    group_by(carb) %>%
    group_by(mpg_sum = sum(mpg), .add = TRUE) %>%
    summarise_at(vars(cyl, disp), list(mean = mean))
# A tibble: 6 x 4
# Groups:   carb [6]
#   carb mpg_sum cyl_mean disp_mean
#  <dbl>   <dbl>    <dbl>     <dbl>
#1     1   177.      4.57      134.
#2     2   224       5.6       208.
#3     3    48.9     8         276.
#4     4   158.      7.2       309.
#5     6    19.7     6         145 
#6     8    15       8         301 

Или, используя devel версию dplyr, это можно сделать в одном summarise, обернув блоки столбцов в across и один столбец сами по себе и применив к нему разные функции

mtcars %>%
  group_by(carb) %>% 
  summarise(across(one_of(c("cyl", "disp")), list(mean = mean)), 
            mpg_sum = sum(mpg))
# A tibble: 6 x 4
#   carb cyl_mean disp_mean mpg_sum
#  <dbl>    <dbl>     <dbl>   <dbl>
#1     1     4.57      134.   177. 
#2     2     5.6       208.   224  
#3     3     8         276.    48.9
#4     4     7.2       309.   158. 
#5     6     6         145     19.7
#6     8     8         301     15  

ПРИМЕЧАНИЕ. summarise_at/summarise_if/mutate_at/mutate_if/... и т. д. будут заменены глаголом across с функциями по умолчанию (summarise/mutate/filter/...) в следующих выпусках.

person akrun    schedule 19.02.2020
comment
Завтра я попробую оба на моем реальном случае, включая генерацию Mssql с этим :) - person phili_b; 19.02.2020
comment
Я еще не пробовал, но в вашем решении 1 мне не хватает :): первая функция группировки sum(mpg) не является динамической. Когда я использую vars(c()), это для быстрых и четких примеров, но на самом деле это для использования contains(), Starts_With() и matches(,, perl=TRUE), потому что у меня 50 столбцов, со многими sum() и некоторыми mean(). Если бы я не мог использовать эти динамические функции, я бы написал SQL с paste0(). :) - person phili_b; 20.02.2020
comment
Я бы предпочел решение с функцией в summarise_at(), например, мое (mtcars %>% group_by(carb)%>% summarise_at(vars(c("mpg","cyl","disp")),ifelse ((names(.)=="mpg"), list(~sum(.)) , list(~mean(.))))) или ваше решение 2, если бы я мог обновить. Решение 1 короткое, но у меня есть сомнения, что первое group_by() может быть динамическим, не по группировке dim(group_by_at() может, а по динамическим индикаторам. - person phili_b; 20.02.2020
comment
Ваше решение 1, с двумя group_by() не работает, потому что %>% group_by(carb, add = TRUE) и после %>% group_by(mpg_sum = sum(mpg)) создает SUM(mpg) OVER (PARTITION BY carb) в show_query(), что не по теме :) (и не говоря о динамическом выборе индикатора). - person phili_b; 20.02.2020
comment
Я добавил пример с SQL, сгенерированным во втором примере. - person phili_b; 20.02.2020
comment
@phili_b Вы можете сделать group_by динамичным. Будет ли это функция, тогда это будет group_by({{yourvar}}), где yourvar будет именем столбца без кавычек, переданным в функцию - person akrun; 20.02.2020
comment
Да, может быть, но два group_by() составляют SQL group by и sum over partition, которые мне не нужны. :) Я проголосовал за ваш ответ из-за вашей работы и информации о новой функции across(), спасибо, но, например, это не принятый ответ на мой вопрос с актуальной версией dplyr. :) Пожалуйста, посмотрите на мой обходной ответ: я ожидаю именно этого SQL. Я попробовал это на моем реальном случае: это работает. но регулярное выражение на самом деле не в философии tidyverse. :) Я думал, что сгенерированный SQL возможен с лямбдой или функцией в summarise_at() без across(), но это не так :) - person phili_b; 20.02.2020
comment
@phili_b с summarise_at существует ограничение на то, как вы хотите получить результат. Я думаю, как только версия для разработчиков будет интегрирована с sql, все будет работать. - person akrun; 20.02.2020
comment
Я предпочитаю функцию с техническим долгом, чем отсутствие функции вообще :-P (и я предпочитаю это, чем pasteed SQL, как я делал раньше). - person phili_b; 20.02.2020
comment
@phili_b тогда лучше запросить на dplyr github, потому что они решают, что - person akrun; 20.02.2020

обходной путь ожидания across() с регулярным выражением

library(RSQLite)
library(dbplyr)
library(tidyverse)
library(DBI)

db <- dbConnect(SQLite())

mtcars_table <- mtcars %>% rename(mpg_sum=mpg,cyl_mean=cyl,disp_mean=disp )

RSQLite::dbWriteTable(db, "mtcars_table", mtcars_table)

req<-as.character((tbl( db, build_sql( con=db,"select * from mtcars_table" ))
                   %>% group_by(carb)
                   %>% summarise_at(vars(c(ends_with("mean"), ends_with("sum")) ), ~sum(.))

) %>% sql_render())
#> Warning: Missing values are always removed in SQL.
#> Use `SUM(x, na.rm = TRUE)` to silence this warning
#> This warning is displayed only once per session.

req<-gsub("(SUM)(\\(.{1,30}mean.{1,10}\\))", "AVG\\2", req, perl=TRUE)
print(req)
#> [1] "SELECT `carb`, AVG(`cyl_mean`) AS `cyl_mean`, AVG(`disp_mean`) AS `disp_mean`, 
# SUM(`mpg_sum`) AS `mpg_sum`\nFROM (select * from mtcars_table)\n
# GROUP BY `carb`"

dbGetQuery(db, req)
#>   carb cyl_mean disp_mean mpg_sum
#> 1    1 4.571429  134.2714   177.4
#> 2    2 5.600000  208.1600   224.0
#> 3    3 8.000000  275.8000    48.9
#> 4    4 7.200000  308.8200   157.9
#> 5    6 6.000000  145.0000    19.7
#> 6    8 8.000000  301.0000    15.0

информация о сеансе()

R version 3.6.1 (2019-07-05)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 16.04.6 LTS

Matrix products: default
BLAS:   /usr/lib/libblas/libblas.so.3.6.0
LAPACK: /usr/lib/lapack/liblapack.so.3.6.0

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C               LC_TIME=en_US.UTF-8       
 [4] LC_COLLATE=en_US.UTF-8     LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
 [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                  LC_ADDRESS=C              
[10] LC_TELEPHONE=C             LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] DBI_1.1.0       forcats_0.4.0   stringr_1.4.0   dplyr_0.8.4     purrr_0.3.3    
 [6] readr_1.3.1     tidyr_1.0.2     tibble_2.1.3    ggplot2_3.2.1   tidyverse_1.3.0
[11] dbplyr_1.4.2    RSQLite_2.2.0  

loaded via a namespace (and not attached):
 [1] xfun_0.10        tidyselect_1.0.0 haven_2.2.0      lattice_0.20-38  colorspace_1.4-1
 [6] vctrs_0.2.2      generics_0.0.2   htmltools_0.4.0  blob_1.2.1       rlang_0.4.4     
[11] pillar_1.4.3     glue_1.3.1       withr_2.1.2      bit64_0.9-7      modelr_0.1.5    
[16] readxl_1.3.1     lifecycle_0.1.0  munsell_0.5.0    gtable_0.3.0     cellranger_1.1.0
[21] rvest_0.3.5      memoise_1.1.0    evaluate_0.14    knitr_1.25       callr_3.3.2     
[26] ps_1.3.0         fansi_0.4.1      broom_0.5.2      Rcpp_1.0.3       clipr_0.7.0     
[31] scales_1.1.0     backports_1.1.5  jsonlite_1.6.1   fs_1.3.1         bit_1.1-15.1    
[36] hms_0.5.3        digest_0.6.23    stringi_1.4.5    processx_3.4.1   grid_3.6.1      
[41] cli_2.0.1        tools_3.6.1      magrittr_1.5     lazyeval_0.2.2   whisker_0.4     
[46] crayon_1.3.4     pkgconfig_2.0.3  xml2_1.2.2       reprex_0.3.0     lubridate_1.7.4 
[51] assertthat_0.2.1 rmarkdown_1.16   httr_1.4.1       rstudioapi_0.10  R6_2.4.1        
[56] nlme_3.1-141     compiler_3.6.1  
person phili_b    schedule 20.02.2020