简体   繁体   中英

Summarising multiple variables without iteration

Consider this data that needs the summary measures mean and sd on multiple variables,

# Create grouping var; ####
mtcars <- mtcars %>% mutate(
        am = case_when(
                am == 0 ~ "Automatic",
                TRUE ~ "Manual"
        )
)

With the following custom function and purrr , I can create a baseline table ,

# Summarising function; ####
sum_foo <- function(data, var) {
        
        data %>% 
                group_by(am) %>% 
                summarise(
                        mean = mean( !!sym(var) , na.rm = TRUE),
                        sd   = sd( !!sym(var) , na.rm = TRUE)
                ) %>% 
                mutate(across(where(is.double), round, 2)) %>%  
                group_by(am) %>% 
                transmute(
                        value = paste(mean, "(±", sd, ")", sep = ""),
                        variable = var
                ) %>%
                pivot_wider(
                        names_from = "am"
                )
        
        
}


# Execute Function; ####
sum_variables <- c("mpg", "hp", "disp")


sum_variables %>% map(
        sum_foo,
        data = mtcars
) %>% reduce(
        bind_rows
)

Which gives the following output ,

# A tibble: 3 x 3
  variable Automatic       Manual        
  <chr>    <chr>           <chr>         
1 mpg      17.15(±3.83)    24.39(±6.17)  
2 hp       160.26(±53.91)  126.85(±84.06)
3 disp     290.38(±110.17) 143.53(±87.2) 

I want to get the output without using map and reduce , ie. without iterating through the variables with rowwise or map .

I'm looking for an alternative tidyverse -solution!

Maybe you could use this solution:

library(dplyr)
library(tidyr)
library(tibble)

sum_variables %>%
  enframe() %>%
  rowwise() %>%
  mutate(output = list(sum_foo(mtcars, value))) %>%
  select(output) %>%
  unnest(cols = output)

# A tibble: 3 x 3
  variable Automatic       Manual        
  <chr>    <chr>           <chr>         
1 mpg      17.15(±3.83)    24.39(±6.17)  
2 hp       160.26(±53.91)  126.85(±84.06)
3 disp     290.38(±110.17) 143.53(±87.2) 

Updated Or you could even modify your function in the following way:

sum_foo2 <- function(data, var) {
  data %>% 
    group_by(am) %>% 
    summarise(across(all_of(var), list(Mean = mean, sd = sd))) %>% 
    mutate(across(where(is.double), round, 2)) %>%  
    group_by(am) %>%
    summarise(across(ends_with("Mean"), ~ paste(.x, "(±", get(gsub("_Mean", "_sd", cur_column())), ")", sep = ""))) %>%
    pivot_longer(!am, names_to = "Mean", values_to = "Val") %>%
    pivot_wider(names_from = "am", values_from = "Val")
}

sum_foo2(mtcars, sum_variables)

# A tibble: 3 x 3
  Mean      Automatic       Manual        
  <chr>     <chr>           <chr>         
1 mpg_Mean  17.15(±3.83)    24.39(±6.17)  
2 hp_Mean   160.26(±53.91)  126.85(±84.06)
3 disp_Mean 290.38(±110.17) 143.53(±87.2) 
 

If I am to trim the function above into a more concise version:

sum_foo2 <- function(data, var) {
  data %>%
    group_by(am) %>%
    summarise(across(all_of(var), ~ paste0(round(mean(.x), 2), "(±", round(sd(.x), 2), ")"))) %>%
    pivot_longer(!am, names_to = "Mean", values_to = "Val") %>%
    pivot_wider(names_from = "am", values_from = "Val")
}

sum_foo2(mtcars, sum_variables)

Without using the function that you wrote, which require an iteration, ie rowwise/map, You could simply do:

sum_variables <- c("mpg", "hp", "disp")
mtcars %>%
  group_by(am) %>%
  summarise(across(all_of(sum_variables),
        ~sprintf('%.2f(\u00B1%.2f)', mean(.x), sd(.x))), .groups = 'drop') %>%
  data.table::transpose(keep.names = 'variable', make.names = TRUE)

 variable       Automatic         Manual
1      mpg    17.15(±3.83)   24.39(±6.17)
2       hp  160.26(±53.91) 126.85(±84.06)
3     disp 290.38(±110.17) 143.53(±87.20)

So: Please be gentile. My solution without using map and reduce :

library(dplyr)
library(tidyr)
library(stringr)

data %>% 
    group_by(am) %>% 
    summarise(across(c(mpg, hp, disp), list(mean = mean, sd = sd), .names = "{.col}_{.fn}")) %>% 
    pivot_longer (
        cols = 2:7,
        names_to = "variable",
        values_to = "values"
    ) %>% 
    pivot_wider(
        names_from = am,
        values_from = values
    ) %>% 
    mutate(variable = str_extract(variable, "[^_]*")) %>% 
    mutate(across(c(Automatic, Manual), lead, .names = "{.col}_{.fn}")) %>% 
    filter(row_number() %% 2 == 1) %>% 
    mutate(across(where(is.numeric), round, 2)) %>% 
    mutate(Automatic = paste0(Automatic,"(±",Automatic_1,")"), .keep="unused") %>% 
    mutate(Manual = paste0(Manual,"(±",Manual_1,")"), .keep="unused") 

Output:

  variable Automatic       Manual        
  <chr>    <chr>           <chr>         
1 mpg      17.15(±3.83)    24.39(±6.17)  
2 hp       160.26(±53.91)  126.85(±84.06)
3 disp     290.38(±110.17) 143.53(±87.2) 

With the formidable answers that I got, this is the final tidyverse -solution without iteration or map that were born,

mtcars %>% 
        group_by(am) %>% 
        summarise(
                across(
                        all_of(sum_variables),
                        ~ paste0(mean(.) %>% round(2), "(±", sd(.) %>% round(2), ")")
                        )
                ) %>% pivot_longer(
                        cols = -"am"
                ) %>% pivot_wider(
                        names_from = "am"
                )

Which gives the following output ,

# A tibble: 3 x 3
  name  Automatic       Manual        
  <chr> <chr>           <chr>         
1 mpg   17.15(±3.83)    24.39(±6.17)  
2 hp    160.26(±53.91)  126.85(±84.06)
3 disp  290.38(±110.17) 143.53(±87.2) 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM