assign is going down a bad road; unless you're working with environments, it encourages bad idioms. If you trade for loops for list mapping functions, the code gets easier. Stick the lists in list columns of a data frame, and it can be downright pretty. For example,
library(tidyverse)
library(janitor)
tab_freq <- function(data, v1, v2) {
data %>%
tabyl({{ v1 }}, {{ v2 }}) %>%
adorn_totals(c("row", "col")) %>%
adorn_percentages("all") %>%
adorn_pct_formatting() %>%
adorn_ns(position = "front") # no need to print here
}
Instead of writing a loop, you write a function to call on each element of the vector or list:
tab_mtcars <- function(v1, v2){
tab_freq(mtcars, !!ensym(v1), !!ensym(v2)) # use ensym to substitute the string arguments
}
tab_df <- tibble(col1 = names(mtcars)[-length(mtcars)], # make a data frame of cols
col2 = names(mtcars)[-1]) %>%
mutate(name = paste(col1, col2, sep = "_"),
tab = map2(col1, col2, tab_mtcars)) # map the function
tab_df
#> # A tibble: 10 x 4
#> col1 col2 name tab
#> <chr> <chr> <chr> <list>
#> 1 mpg cyl mpg_cyl <df[,5] [26 × 5]>
#> 2 cyl disp cyl_disp <df[,29] [4 × 29]>
#> 3 disp hp disp_hp <df[,24] [28 × 24]>
#> 4 hp drat hp_drat <df[,24] [23 × 24]>
#> 5 drat wt drat_wt <df[,31] [23 × 31]>
#> 6 wt qsec wt_qsec <df[,32] [30 × 32]>
#> 7 qsec vs qsec_vs <df[,4] [31 × 4]>
#> 8 vs am vs_am <df[,4] [3 × 4]>
#> 9 am gear am_gear <df[,5] [3 × 5]>
#> 10 gear carb gear_carb <df[,8] [4 × 8]>
Everything you want is already in that data frame, but you can put the names on the list column and pull it out if you like:
tab_list <- tab_df %>%
mutate(tab = set_names(tab, name)) %>%
pull(tab)
tab_list[8:9]
#> $vs_am
#> vs 0 1 Total
#> 0 12 (37.5%) 6 (18.8%) 18 (56.2%)
#> 1 7 (21.9%) 7 (21.9%) 14 (43.8%)
#> Total 19 (59.4%) 13 (40.6%) 32 (100.0%)
#>
#> $am_gear
#> am 3 4 5 Total
#> 0 15 (46.9%) 4 (12.5%) 0 (0.0%) 19 (59.4%)
#> 1 0 (0.0%) 8 (25.0%) 5 (15.6%) 13 (40.6%)
#> Total 15 (46.9%) 12 (37.5%) 5 (15.6%) 32 (100.0%)
Note that nested tidy eval can go weird sometimes—if you try to write tab_mtcars as an anonymous function within mutate (which like tabyl uses tidy eval), weird things may happen. (Curiously, it's because the code is using expressions instead of quosures, which carry their environment with them in order to solve this exact problem.) Defining the function beforehand keeps the scopes separated and avoids the issue.