Part 0. Objective take many argument function, where arguments are orthogonal, and make a pipable interface

pivot_count() create count table based on direct user inputs

library(tidyverse)
pivot_count <- function (data, rows = NULL, cols = NULL) {
    
  cols_quo <- rlang::enquo(cols)
  
  grouped <- data %>% dplyr::group_by(
    dplyr::across(c({{cols}}, {{rows}})))
  
  summarized <- grouped |> 
    dplyr::count() |> 
    dplyr::ungroup()

    if (rlang::quo_is_null(cols_quo)) {
        summarized
    }
    else {
        summarized %>% 
        tidyr::pivot_wider(names_from = {{cols}}, 
                           values_from = n)
    }
}

status quo usage: enter arguments at once

Titanic |> 
  data.frame() |>
  uncount(Freq) ->
tidy_titanic

pivot_count(data = tidy_titanic,
            rows = Survived,
            cols = c(Sex, Age))
## # A tibble: 2 × 5
##   Survived Male_Child Male_Adult Female_Child Female_Adult
##   <fct>         <int>      <int>        <int>        <int>
## 1 No               35       1329           17          109
## 2 Yes              29        338           28          316

Desired usage: Piped pivot ppivot

ppivot(tidy_titanic) |> # a count of observations
  set_rows(Survived) |> # a count by Survived
  set_cols(c(Sex, Age)) # count by Sex, Age and Survived 

Part. 1. Implementation

Step 1. New 3S object Create new_tidypivot() which defines components in ‘tp’ a 3S object.

library(tidyverse)

new_tidypivot <- function(data = data.frame(),
                          rows = NULL,
                          columns = NULL,
                          value = NULL,
                          weight = NULL) {

  # table specification components !
  tp <- list(
    data = data,
    rows = rows,
    columns = columns,
    value = value,
    weight = weight
    # more 'slots' to be added
  )

  # declare class 'tidypivot'
  class(tp) <- "tidypivot"

  # Return the created object
  invisible(tp)

}



# # table specification components !
# tp <- list(
#     data,
#     rows,
#     columns,
#     value,
#     weight
#     # more 'slots' to be added
#   )
# 
# # declare class 'tidypivot'
# class(tp) <- "tp"

Step 2. Print: Once specification is defined, actually do manipulation to produce the table - we use a routine from the {tidypivot} package.

return_specified_table() create table based on table specification (tp)

return_specified_table = function(tp){

      out <- 'pivot_count(thedata, rows, cols)'

      str_replace_or_null <- function(x, pattern, replacement){
        
        if(is.null(replacement)){x |> str_replace(pattern, 'NULL')}
        else{x |> str_replace(pattern, replacement)}
        
      }
      
      out <- str_replace_or_null(out, "rows",  tp$rows)
      out <- str_replace_or_null(out, "cols",  tp$cols)

      eval(parse(text = out))         
      
}

print.tidypivot declare print method for tp, the 3S objects

print.tidypivot <- function(tp){
  
  print(return_specified_table(tp))
  invisible(tp)
  
}

Step 3. user-facing functions!

ppivot() initiate table

ppivot <- function(data){
  
  thedata <<- data # don't love this

  tp <- new_tidypivot(deparse(substitute(thedata)))
  
  last_tp <<- tp
  
  tp

}

ppivot(cars)
## # A tibble: 1 × 1
##       n
##   <int>
## 1    50

set_rows(), (the other ‘set’ functions are hidden - set up the same way…)

set_rows <- function(tp, rows = NULL){
  
  tp$rows <- deparse(substitute(rows))
  
  last_tp <<- tp
  
  tp

  
}

last_table()

last_table <- function(){
  
  last_tp
  
}

Part 2. Test functionality!

Slow build with last_table()

Titanic |> 
  data.frame() |>
  uncount(Freq) ->
tidy_titanic

tidy_titanic |> tibble()
## # A tibble: 2,201 × 4
##    Class Sex   Age   Survived
##    <fct> <fct> <fct> <fct>   
##  1 3rd   Male  Child No      
##  2 3rd   Male  Child No      
##  3 3rd   Male  Child No      
##  4 3rd   Male  Child No      
##  5 3rd   Male  Child No      
##  6 3rd   Male  Child No      
##  7 3rd   Male  Child No      
##  8 3rd   Male  Child No      
##  9 3rd   Male  Child No      
## 10 3rd   Male  Child No      
## # ℹ 2,191 more rows
tidy_titanic |>
  ppivot() 
## # A tibble: 1 × 1
##       n
##   <int>
## 1  2201
last_table() |> 
  set_rows(Survived)
## # A tibble: 2 × 2
##   Survived     n
##   <fct>    <int>
## 1 No        1490
## 2 Yes        711
last_table() |> 
  set_cols(c(Sex))
## # A tibble: 2 × 3
##   Survived  Male Female
##   <fct>    <int>  <int>
## 1 No        1364    126
## 2 Yes        367    344
last_table() |>
  set_rows(Class)
## # A tibble: 4 × 3
##   Class  Male Female
##   <fct> <int>  <int>
## 1 1st     180    145
## 2 2nd     179    106
## 3 3rd     510    196
## 4 Crew    862     23

Piped |>

ppivot(mtcars) |> 
  set_rows(cyl) |> 
  set_cols(am) |>
  set_cols(c(gear, am)) 
## # A tibble: 3 × 5
##     cyl `3_0` `4_0` `4_1` `5_1`
##   <dbl> <int> <int> <int> <int>
## 1     4     1     2     6     2
## 2     6     2     2     2     1
## 3     8    12    NA    NA     2
last_table() |> str()
## List of 6
##  $ data   : chr "thedata"
##  $ rows   : chr "cyl"
##  $ columns: NULL
##  $ value  : NULL
##  $ weight : NULL
##  $ cols   : chr "c(gear, am)"
##  - attr(*, "class")= chr "tidypivot"

Part 3. Dispatch Define behavior when tps are piped to other environment

Future work