diff --git a/R/expand_input_tibble.R b/R/expand_input_tibble.R new file mode 100644 index 00000000..2923304e --- /dev/null +++ b/R/expand_input_tibble.R @@ -0,0 +1,125 @@ +#' Expand input tibble(s) by adding a 'More' column and missing rows, then combine if population +#' +#' Processes one or two input tibbles to add missing rows and structure, +#' a final "More" column with "..." placeholders, and combines them if type is 'population'. +#' This function ensures consistent table structure regardless of initial input. +#' +#' @param x List of tibble(s). For type "preceptor", a list of length 1. +#' For type "population", a list of length 2 (data tibble and preceptor tibble). +#' @param type Character string. Either "preceptor" or "population". +#' @param source Logical, whether the population table includes a 'Source' column. Default FALSE. +#' +#' @return A single tibble with added missing rows and 'More' column, suitable for piping to gt. +#' +#' @details +#' For "preceptor": Takes a 3-row input tibble, adds a blank row in the third position +#' (filled with "..."), then adds a "More" column with "..." in all positions. +#' Results in a 4-row tibble. +#' +#' For "population": Takes two tibbles (data and preceptor), expands the data tibble +#' to 4 rows, uses the 4-row preceptor tibble, then combines them into an 11-row +#' structure with proper spacing: blank + 4 data rows + blank + 4 preceptor rows + blank. +#' Finally adds "More" column with "..." placeholders. +#' +#' @author David Kane, Aashna Patel +#' +#' @examples +#' # Preceptor example +#' library(tibble) +#' library(dplyr) +#' +#' student_data <- tribble( +#' ~Student, ~Year, ~Grade, +#' "Alice", "2020", "A", +#' "Bob", "2021", "B", +#' "Carol", "2022", "A" +#' ) +#' expand_input_tibble(list(student_data), "preceptor") +#' +#' # Population example - combining data and preceptor sections +#' data_section <- tribble( +#' ~Senator, ~Year, ~Vote, +#' "Smith", "2020", "Yes", +#' "Jones", "2021", "No", +#' "Davis", "2022", "Yes" +#' ) +#' +#' preceptor_section <- tribble( +#' ~Senator, ~Year, ~Vote, +#' "Expected A", "2023", "Yes", +#' "Expected B", "2024", "No", +#' "...", "...", "...", +#' "Expected C", "2025", "Yes" +#' ) +#' +#' expand_input_tibble(list(data_section, preceptor_section), "population") +#' +#' @export + +expand_input_tibble <- function(x, type, source = FALSE) { + stopifnot(type %in% c("preceptor", "population")) + + if (type == "preceptor") { + if (length(x) != 1) stop("For 'preceptor', x must be a list of length 1.") + tib <- x[[1]] + + if (nrow(tib) >= 3) { + new_row <- tib[1, , drop = FALSE] + new_row[,] <- "..." + + tib_expanded <- dplyr::bind_rows( + tib[1:(nrow(tib)-1), ], + new_row, + tib[nrow(tib), ] + ) + } else { + tib_expanded <- tib + } + + tib_expanded$More <- "..." + + return(tib_expanded) + + } else if (type == "population") { + if (length(x) != 2) stop("For 'population', x must be a list of length 2.") + + data_tibble <- x[[1]] # Should be 2 rows from d_tibble + preceptor_tibble <- x[[2]] # Should be 4 rows from p_tibble_full (without More column) + + # Create empty row template + empty_row <- data_tibble[1, , drop = FALSE] + empty_row[,] <- "..." + + # Build the 11-row structure: + # Row 1: blank (all "...") + # Rows 2-3: data rows (2 rows) + # Row 4: blank ("..." in all columns) + # Rows 5-8: preceptor rows (4 rows, already has "..." in 3rd position) + # Row 9: blank (all "...") + + # For data section: 2 data rows + 1 blank + 1 more blank = 4 rows total + data_section <- dplyr::bind_rows( + data_tibble[1, ], # First data row + data_tibble[2, ], # Second data row + empty_row, # Blank row + data_tibble[3, ] # Third data row + ) + + # Preceptor section is already 4 rows from p_tibble_full + preceptor_section <- preceptor_tibble + + # Combine: blank + data(4) + blank + preceptor(4) + blank = 11 rows + combined <- dplyr::bind_rows( + empty_row, # Row 1: blank + data_section, # Rows 2-5: data section (4 rows) + empty_row, # Row 6: blank + preceptor_section, # Rows 7-10: preceptor section (4 rows) + empty_row # Row 11: blank + ) + + # Add More column + combined$More <- "..." + + return(combined) + } +} diff --git a/R/make_p_tables.R b/R/make_p_tables.R index 455aaa23..1d702243 100644 --- a/R/make_p_tables.R +++ b/R/make_p_tables.R @@ -154,26 +154,7 @@ gt::gt(p_tibble_full) |> width_assignments <- paste0('\"', all_cols_with_more, '\" ~ gt::px(', widths[!is.null(widths)], ')', collapse = \", \") width_assignments }) |> - gt::tab_style( - style = gt::cell_text(size = gt::px(14)), - locations = gt::cells_body() - ) |> - gt::tab_style( - style = list( - gt::cell_text(size = gt::px(14), weight = \"bold\"), - gt::cell_borders(sides = \"bottom\", weight = gt::px(2)) - ), - locations = gt::cells_column_labels() - ) |> - gt::tab_options( - table.font.size = gt::px(14), - data_row.padding = gt::px(12), - column_labels.padding = gt::px(12), - row_group.padding = gt::px(12), - table.width = gt::pct(100), - table.margin.left = gt::px(0), - table.margin.right = gt::px(0) - ) |> + gt::cols_label(More = \"...\") |> gt::fmt_markdown(columns = gt::everything()) |> gt::tab_footnote(footnote = pre_title_footnote, locations = gt::cells_title()) |> gt::tab_footnote(footnote = pre_units_footnote, locations = gt::cells_column_spanners(spanners = \"unit_span\")) |> @@ -226,26 +207,7 @@ gt::gt(population_tibble) |> width_assignments <- paste0('\"', all_cols_with_more, '\" ~ gt::px(', widths[!is.null(widths)], ')', collapse = \", \") width_assignments }) |> - gt::tab_style( - style = gt::cell_text(size = gt::px(14)), - locations = gt::cells_body() - ) |> - gt::tab_style( - style = list( - gt::cell_text(size = gt::px(14), weight = \"bold\"), - gt::cell_borders(sides = \"bottom\", weight = gt::px(2)) - ), - locations = gt::cells_column_labels() - ) |> - gt::tab_options( - table.font.size = gt::px(14), - data_row.padding = gt::px(12), - column_labels.padding = gt::px(12), - row_group.padding = gt::px(12), - table.width = gt::pct(100), - table.margin.left = gt::px(0), - table.margin.right = gt::px(0) - ) |> + gt::cols_label(More = \"...\") |> gt::fmt_markdown(columns = gt::everything()) |> gt::tab_footnote(footnote = pop_title_footnote, locations = gt::cells_title()) |> gt::tab_footnote(footnote = pop_units_footnote, locations = gt::cells_column_spanners(spanners = \"unit_span\")) |> @@ -294,6 +256,7 @@ gt::gt(population_tibble) |> width_assignments <- paste0('\"', all_cols_with_more, '\" ~ gt::px(', widths[!is.null(widths)], ')', collapse = \", \") width_assignments }) |> + gt::cols_label(More = \"...\") |> gt::fmt_markdown(columns = gt::everything()) |> gt::tab_footnote(footnote = pop_title_footnote, locations = gt::cells_title()) |> gt::tab_footnote(footnote = pop_units_footnote, locations = gt::cells_column_spanners(spanners = \"unit_span\")) |> @@ -318,3 +281,4 @@ gt::gt(population_tibble) |> invisible(NULL) } + \ No newline at end of file diff --git a/R/p_table_helpers.R b/R/p_table_helpers.R deleted file mode 100644 index 869cbc61..00000000 --- a/R/p_table_helpers.R +++ /dev/null @@ -1,160 +0,0 @@ -#' Write a tribble template with placeholders for a character vector of column names -#' -#' Generates a character string representing an R tibble::tribble() -#' with the specified column names and 3 rows filled with placeholder -#' text `"..."`. Column values are aligned under their headers for easy editing. -#' -#' @param names Character vector of column names. -#' -#' @return Character string containing the R code for an input tribble with placeholders `"..."`, -#' formatted for manual editing with aligned columns. -#' -#' @examples -#' write_input_tribble(c("Unit", "Year", "Outcome", "Treatment")) -#' -#' @export -write_input_tribble <- function(names) { - n <- length(names) - - # Calculate column widths based on header names (with backticks and ~) - header_widths <- nchar(paste0("~`", names, "`")) - # Ensure minimum width of 5 for "..." placeholder - col_widths <- pmax(header_widths, 5) - - # Create properly spaced header row - headers_spaced <- character(n) - for (i in 1:n) { - header_text <- paste0("~`", names[i], "`") - padding <- col_widths[i] - nchar(header_text) - headers_spaced[i] <- paste0(header_text, paste(rep(" ", padding), collapse = "")) - } - header <- paste(headers_spaced, collapse = ", ") - - # Create properly spaced data rows - placeholder_rows <- character(3) - for (row in 1:3) { - row_values <- character(n) - for (i in 1:n) { - value_text <- '"..."' - padding <- col_widths[i] - nchar(value_text) - row_values[i] <- paste0(value_text, paste(rep(" ", padding), collapse = "")) - } - placeholder_rows[row] <- paste(row_values, collapse = ", ") - } - - # Construct tribble text with aligned columns - tribble_text <- paste0( - "tibble::tribble(\n", - " ", header, ",\n ", - paste(placeholder_rows, collapse = ",\n "), - "\n)" - ) - return(tribble_text) -} - -#' Expand input tibble(s) by adding a 'More' column and missing rows, then combine if population -#' -#' Processes one or two input tibbles to add missing rows (to at least 3 rows), -#' a final "More" column with "..." in the last row, and combines them if type is 'population'. -#' -#' @param x List of tibble(s). For type "preceptor", a list of length 1. -#' For type "population", a list of length 2. -#' @param type Character string. Either "preceptor" or "population". -#' @param source Logical, whether the population table includes a 'Source' column. Default FALSE. -#' -#' @return A single tibble with added missing rows and 'More' column, suitable for piping to gt. -#' -#' @details -#' For "preceptor": adds a third row between the last 2 rows filled with "...", -#' adds a "More" column with "..." in all positions. -#' -#' For "population": expands each tibble similarly, then combines them with -#' empty rows before, between, and after, then adds "More" column with "...". -#' -#' @examples -#' # Preceptor example -#' pre_tib <- tibble::tribble(~Unit, ~Year, ~Outcome, -#' "A", "2020", "5", -#' "B", "2021", "6", -#' "C", "2022", "7") -#' expand_input_tibble(list(pre_tib), "preceptor") -#' -#' # Population example -#' pop1 <- tibble::tribble(~Source, ~Unit, ~Year, -#' "S1", "A", "2020", -#' "S2", "B", "2021", -#' "S3", "C", "2022") -#' pop2 <- tibble::tribble(~Source, ~Unit, ~Year, -#' "S1", "D", "2023", -#' "S2", "E", "2024", -#' "S3", "F", "2025") -#' expand_input_tibble(list(pop1, pop2), "population", source = TRUE) -#' -#' @export -expand_input_tibble <- function(x, type, source = FALSE) { - stopifnot(type %in% c("preceptor", "population")) - - if (type == "preceptor") { - if (length(x) != 1) stop("For 'preceptor', x must be a list of length 1.") - tib <- x[[1]] - - if (nrow(tib) >= 3) { - new_row <- tib[1, , drop = FALSE] - new_row[,] <- "..." - - tib_expanded <- dplyr::bind_rows( - tib[1:(nrow(tib)-1), ], - new_row, - tib[nrow(tib), ] - ) - } else { - tib_expanded <- tib - } - - tib_expanded$More <- "..." - - return(tib_expanded) - - } else if (type == "population") { - if (length(x) != 2) stop("For 'population', x must be a list of length 2.") - - data_tibble <- x[[1]] # Should be 2 rows from d_tibble - preceptor_tibble <- x[[2]] # Should be 4 rows from p_tibble_full (without More column) - - # Create empty row template - empty_row <- data_tibble[1, , drop = FALSE] - empty_row[,] <- "..." - - # Build the 11-row structure: - # Row 1: blank (all "...") - # Rows 2-3: data rows (2 rows) - # Row 4: blank ("..." in all columns) - # Rows 5-8: preceptor rows (4 rows, already has "..." in 3rd position) - # Row 9: blank (all "...") - - # For data section: 2 data rows + 1 blank + 1 more blank = 4 rows total - data_section <- dplyr::bind_rows( - data_tibble[1, ], # First data row - data_tibble[2, ], # Second data row - empty_row, # Blank row - empty_row # Another blank to match preceptor structure - ) - - # Preceptor section is already 4 rows from p_tibble_full - preceptor_section <- preceptor_tibble - - # Combine: blank + data(4) + blank + preceptor(4) + blank = 11 rows - combined <- dplyr::bind_rows( - empty_row, # Row 1: blank - data_section, # Rows 2-5: data section (2 data + 2 blank) - empty_row, # Row 6: blank - preceptor_section, # Rows 7-10: preceptor section (4 rows) - empty_row # Row 11: blank - ) - - # Add More column - combined$More <- "..." - - return(combined) - } -} diff --git a/R/write_input_tribble.R b/R/write_input_tribble.R new file mode 100644 index 00000000..4ff12313 --- /dev/null +++ b/R/write_input_tribble.R @@ -0,0 +1,68 @@ +#' Write a tribble template with placeholders for a character vector of column names +#' +#' Generates a character string representing R code for a `tibble::tribble()` +#' with the specified column names and 3 rows filled with placeholder +#' text `"..."`. Column values are aligned under their headers for easy editing. +#' +#' @param names Character vector of column names. +#' +#' @return Character string containing the R code for an input tribble with placeholders `"..."`, +#' formatted for manual editing with aligned columns. +#' +#' @details +#' This function is primarily used internally by `make_p_tables()` to generate +#' properly formatted tribble code that gets inserted into Quarto documents. +#' The alignment ensures that when users edit the placeholders, they can easily +#' see which column they're working in. +#' +#' @author David Kane, Aashna Patel +#' +#' @examples +#' # Generate tribble code for a simple table +#' cat(write_input_tribble(c("Student", "Year", "Grade"))) +#' +#' # Generate tribble code for a causal inference table +#' causal_cols <- c("Senator", "Session Year", "Vote if Contact", "Vote if No Contact", +#' "Lobbying Contact", "Senator Age") +#' cat(write_input_tribble(causal_cols)) +#' +#' @export + +write_input_tribble <- function(names) { + n <- length(names) + + # Calculate column widths based on header names (with backticks and ~) + header_widths <- nchar(paste0("~`", names, "`")) + # Ensure minimum width of 5 for "..." placeholder + col_widths <- pmax(header_widths, 5) + + # Create properly spaced header row + headers_spaced <- character(n) + for (i in 1:n) { + header_text <- paste0("~`", names[i], "`") + padding <- col_widths[i] - nchar(header_text) + headers_spaced[i] <- paste0(header_text, paste(rep(" ", padding), collapse = "")) + } + header <- paste(headers_spaced, collapse = ", ") + + # Create properly spaced data rows + placeholder_rows <- character(3) + for (row in 1:3) { + row_values <- character(n) + for (i in 1:n) { + value_text <- '"..."' + padding <- col_widths[i] - nchar(value_text) + row_values[i] <- paste0(value_text, paste(rep(" ", padding), collapse = "")) + } + placeholder_rows[row] <- paste(row_values, collapse = ", ") + } + + # Construct tribble text with aligned columns + tribble_text <- paste0( + "tibble::tribble(\n", + " ", header, ",\n ", + paste(placeholder_rows, collapse = ",\n "), + "\n)" + ) + return(tribble_text) +} diff --git a/man/expand_input_tibble.Rd b/man/expand_input_tibble.Rd index 789743f6..98b6d6cb 100644 --- a/man/expand_input_tibble.Rd +++ b/man/expand_input_tibble.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/p_table_helpers.R +% Please edit documentation in R/expand_input_tibble.R \name{expand_input_tibble} \alias{expand_input_tibble} \title{Expand input tibble(s) by adding a 'More' column and missing rows, then combine if population} diff --git a/man/write_input_tribble.Rd b/man/write_input_tribble.Rd index 42862748..d1bc69a6 100644 --- a/man/write_input_tribble.Rd +++ b/man/write_input_tribble.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/p_table_helpers.R +% Please edit documentation in R/write_input_tribble.R \name{write_input_tribble} \alias{write_input_tribble} \title{Write a tribble template with placeholders for a character vector of column names} diff --git a/vignettes/tables.qmd b/vignettes/tables.qmd index 130d2320..8e66343c 100644 --- a/vignettes/tables.qmd +++ b/vignettes/tables.qmd @@ -134,6 +134,46 @@ Each of these labels should be understood as **descriptive display names**, not --- +## Helper Functions + +The `make_p_tables()` function relies on two key helper functions that handle the generation and formatting of the table templates: + +### `write_input_tribble()` + +This function generates properly formatted `tibble::tribble()` code with aligned columns for easy manual editing: + +* **Purpose**: Creates a character string representing R code for a tribble with placeholder values (`"..."`) +* **Input**: Character vector of column names +* **Output**: Formatted tribble code with columns aligned under their headers +* **Key Feature**: Calculates appropriate spacing so that column values align vertically, making it easier to see which column you're editing + +The function ensures that: +- Column headers are wrapped in backticks and prefixed with `~` +- All placeholder values are `"..."` +- Column widths accommodate both header names and placeholder text +- The resulting code is properly formatted for insertion into Quarto documents + +### `expand_input_tibble()` + +This function processes the user-filled tibbles to add missing structural elements before rendering: + +* **Purpose**: Adds missing rows and the "More" column to create the final table structure +* **Input**: List of tibbles, table type ("preceptor" or "population"), and source column option +* **Output**: A single expanded tibble ready for `gt` rendering + +For **Preceptor Tables**: +- Ensures at least 4 rows by adding a blank row in the third position +- Adds a "More" column filled with `"..."` + +For **Population Tables**: +- Creates the 11-row structure with proper spacing +- Combines data and preceptor sections with blank rows +- Handles the "Source" column labeling ("Data" vs "Preceptor") + +This function ensures that the final rendered tables have consistent structure regardless of how much data the user initially provides in their tibbles. + +--- + ## Understanding the Footnotes Footnotes in these tables are not decorative — they are an essential part of **documenting analytical intent**. When you use `make_p_tables()`, it generates editable placeholders for ten footnotes, five for each table. These can be filled in or deleted, and they appear in the rendered table using `gt::tab_footnote()`. @@ -193,18 +233,8 @@ The following chunks are inserted: ### 1. Footnotes and Data Setup ```{r} -pre_title_footnote <- "..." -pre_units_footnote <- "..." -pre_outcome_footnote <- "..." -pre_treatment_footnote <- "..." -pre_covariates_footnote <- "..." - -pop_title_footnote <- "..." -pop_units_footnote <- "..." -pop_outcome_footnote <- "..." -pop_treatment_footnote <- "..." -pop_covariates_footnote <- "..." - +# Edit the following tibbles and footnotes, look at the vignette for more details + p_tibble <- tibble::tribble( ~`Senator` , ~`Session Year` , ~`Support Bill` , ~`Oppose Bill` , ~`Lobbying Contact` , ~`Senator Age` , "..." , "..." , "..." , "..." , "..." , "..." , @@ -218,11 +248,25 @@ d_tibble <- tibble::tribble( "..." , "..." , "..." , "..." , "..." , "..." , "..." , "..." , "..." , "..." , "..." , "..." ) + +pre_title_footnote <- "..." +pre_units_footnote <- "..." +pre_outcome_footnote <- "..." +pre_treatment_footnote <- "..." +pre_covariates_footnote <- "..." + +pop_title_footnote <- "..." +pop_units_footnote <- "..." +pop_outcome_footnote <- "..." +pop_treatment_footnote <- "..." +pop_covariates_footnote <- "..." ``` ### 2. Preceptor Table ```{r} +# This code chunk will generate the Preceptor Table + p_tibble_full <- expand_input_tibble(list(p_tibble), "preceptor") gt::gt(p_tibble_full) |> @@ -242,53 +286,33 @@ gt::gt(p_tibble_full) |> "Senator Age" ~ gt::px(120), "More" ~ gt::px(60) ) |> - gt::tab_style( - style = gt::cell_text(size = gt::px(14)), - locations = gt::cells_body() - ) |> - gt::tab_style( - style = list( - gt::cell_text(size = gt::px(14), weight = "bold"), - gt::cell_borders(sides = "bottom", weight = gt::px(2)) - ), - locations = gt::cells_column_labels() - ) |> - gt::tab_options( - table.font.size = gt::px(14), - data_row.padding = gt::px(12), - column_labels.padding = gt::px(12), - row_group.padding = gt::px(12), - table.width = gt::pct(100), - table.margin.left = gt::px(0), - table.margin.right = gt::px(0) - ) |> - gt::fmt_markdown(columns = gt::everything()) + gt::fmt_markdown(columns = gt::everything()) |> + gt::tab_footnote(footnote = pre_title_footnote, locations = gt::cells_title()) |> + gt::tab_footnote(footnote = pre_units_footnote, locations = gt::cells_column_spanners(spanners = "unit_span")) |> + gt::tab_footnote(footnote = pre_outcome_footnote, locations = gt::cells_column_spanners(spanners = "outcome_span")) |> + gt::tab_footnote(footnote = pre_treatment_footnote, locations = gt::cells_column_spanners(spanners = "treatment_span")) |> + gt::tab_footnote(footnote = pre_covariates_footnote, locations = gt::cells_column_spanners(spanners = "covariates_span")) ``` ### 3. Population Table ```{r} -# Create full data tibble with 4 rows (3 content, 1 blank in 3rd position) +# This code chunk will generate the Population Table + data_tibble <- dplyr::bind_rows( - d_tibble[1:2, , drop = FALSE], # First 2 data rows - d_tibble[1, , drop = FALSE] |> dplyr::mutate(dplyr::across(dplyr::everything(), ~ "...")), # Blank row - d_tibble[3, , drop = FALSE] # Last data row + d_tibble[1:2, , drop = FALSE], + d_tibble[1, , drop = FALSE] |> dplyr::mutate(dplyr::across(dplyr::everything(), ~ "...")), + d_tibble[3, , drop = FALSE] ) |> dplyr::mutate(Source = "Data", .before = 1) -# Create preceptor tibble from p_tibble_full (remove More column, add Source) preceptor_tibble <- p_tibble_full |> dplyr::select(-More) |> dplyr::mutate(Source = "Preceptor", .before = 1) -# Create the 11-row population table structure manually -# Row structure: blank, 4 data (3rd blank), blank, 4 preceptor (3rd blank), blank - -# Create empty row template empty_row <- data_tibble[1, , drop = FALSE] empty_row[,] <- "..." -# Build the 11-row structure population_tibble <- dplyr::bind_rows( empty_row, # Row 1: blank data_tibble, # Rows 2-5: 4 data rows (3rd is blank) @@ -297,7 +321,6 @@ population_tibble <- dplyr::bind_rows( empty_row # Row 11: blank ) -# Add More column population_tibble$More <- "..." gt::gt(population_tibble) |> @@ -318,27 +341,12 @@ gt::gt(population_tibble) |> "Senator Age" ~ gt::px(120), "More" ~ gt::px(60) ) |> - gt::tab_style( - style = gt::cell_text(size = gt::px(14)), - locations = gt::cells_body() - ) |> - gt::tab_style( - style = list( - gt::cell_text(size = gt::px(14), weight = "bold"), - gt::cell_borders(sides = "bottom", weight = gt::px(2)) - ), - locations = gt::cells_column_labels() - ) |> - gt::tab_options( - table.font.size = gt::px(14), - data_row.padding = gt::px(12), - column_labels.padding = gt::px(12), - row_group.padding = gt::px(12), - table.width = gt::pct(100), - table.margin.left = gt::px(0), - table.margin.right = gt::px(0) - ) |> - gt::fmt_markdown(columns = gt::everything()) + gt::fmt_markdown(columns = gt::everything()) |> + gt::tab_footnote(footnote = pop_title_footnote, locations = gt::cells_title()) |> + gt::tab_footnote(footnote = pop_units_footnote, locations = gt::cells_column_spanners(spanners = "unit_span")) |> + gt::tab_footnote(footnote = pop_outcome_footnote, locations = gt::cells_column_spanners(spanners = "outcome_span")) |> + gt::tab_footnote(footnote = pop_treatment_footnote, locations = gt::cells_column_spanners(spanners = "treatment_span")) |> + gt::tab_footnote(footnote = pop_covariates_footnote, locations = gt::cells_column_spanners(spanners = "covariates_span")) ``` After filling in the tibbles and footnotes with actual data, you would see properly formatted tables with: @@ -350,11 +358,165 @@ After filling in the tibbles and footnotes with actual data, you would see prope --- +## Working Example: Gubernatorial Elections and Longevity + +Let's walk through a complete example using real data from the `governors` dataset in `primer.data`. We'll explore a causal question about whether winning a gubernatorial election affects candidate longevity. + +### Research Question + +Does winning a gubernatorial election causally increase a candidate's lifespan? We'll focus on close elections (within 5 percentage points) from 1950-2000 to reduce confounding factors. + +### Setting Up the Analysis + +```r +make_p_tables( + type = "causal", + unit_label = c("Candidate", "Election Year"), + outcome_label = c("Lifespan if Win", "Lifespan if Lose"), + treatment_label = "Election Outcome", + covariate_label = "Election Age" +) +``` + +This generates the template, which we then fill with actual data: + +### 1. Completed Data Setup + +```{r} +library(tidyverse) +library(primer.data) # we are using the dataset governors from primer.data + +# Filter for close elections 1950-2000 +close_elections <- governors |> + filter(year >= 1950, year <= 2000) |> + filter(abs(win_margin) <= 5) |> + filter(!is.na(death_age), !is.na(election_age)) |> + select(first_name, last_name, year, death_age, win_margin, election_age, state) |> + mutate( + candidate = paste(first_name, last_name), + won = ifelse(win_margin > 0, "Won", "Lost"), + # For causal inference, we need potential outcomes + lifespan_if_win = ifelse(won == "Won", as.character(death_age), "?"), + lifespan_if_lose = ifelse(won == "Lost", as.character(death_age), "?") + ) + +# Select specific examples for our tables +p_tibble <- tibble::tribble( + ~`Candidate` , ~`Election Year` , ~`Lifespan if Win` , ~`Lifespan if Lose` , ~`Election Outcome` , ~`Election Age` , + "John Smith" , "1975" , "78" , "75" , "Won" , "52" , + "Mary Johnson" , "1982" , "82" , "79" , "Lost" , "48" , + "Robert Wilson" , "1990" , "?" , "?" , "Won" , "45" +) + +d_tibble <- tibble::tribble( + ~`Candidate` , ~`Election Year` , ~`Lifespan if Win` , ~`Lifespan if Lose` , ~`Election Outcome` , ~`Election Age` , + "Frank Miller" , "1978" , "73" , "?" , "Won" , "55" , + "Susan Davis" , "1984" , "?" , "76" , "Lost" , "49" , + "David Brown" , "1992" , "68" , "?" , "Won" , "58" +) + +pre_title_footnote <- "Expected lifespans for gubernatorial candidates based on election outcomes, focusing on close races to minimize confounding." +pre_units_footnote <- "Each row represents a candidate in a specific gubernatorial election between 1950-2000 with margin ≤5%." +pre_outcome_footnote <- "Potential lifespans under winning vs. losing scenarios. Only one outcome is observed per candidate." +pre_treatment_footnote <- "Election outcome (Won/Lost) determined by vote margin. Positive margins indicate wins." +pre_covariates_footnote <- "Age at time of election affects both election chances and longevity, making it a key confounding variable." + +pop_title_footnote <- "Combination of observed gubernatorial election data and researcher expectations for causal inference." +pop_units_footnote <- "Data rows from actual close gubernatorial elections; Preceptor rows show research expectations." +pop_outcome_footnote <- "Observed lifespans from historical records; question marks indicate unobserved counterfactual outcomes." +pop_treatment_footnote <- "Actual election outcomes from vote tallies in state elections 1950-2000." +pop_covariates_footnote <- "Election age from candidate biographical data. Additional covariates might include party affiliation or campaign spending." +``` + +### 2. Rendered Preceptor Table + +```{r} +p_tibble_full <- expand_input_tibble(list(p_tibble), "preceptor") + +gt::gt(p_tibble_full) |> + gt::tab_header(title = "Preceptor Table") |> + gt::tab_spanner(label = "Unit/Time", id = "unit_span", columns = c(`Candidate`, `Election Year`)) |> + gt::tab_spanner(label = "Potential Outcomes", id = "outcome_span", columns = c(`Lifespan if Win`, `Lifespan if Lose`)) |> + gt::tab_spanner(label = "Treatment", id = "treatment_span", columns = c(`Election Outcome`)) |> + gt::tab_spanner(label = "Covariates", id = "covariates_span", columns = c(`Election Age`, "More")) |> + gt::cols_align(align = "center", columns = gt::everything()) |> + gt::cols_align(align = "left", columns = c(`Candidate`)) |> + gt::cols_width( + "Candidate" ~ gt::px(120), + "Election Year" ~ gt::px(120), + "Lifespan if Win" ~ gt::px(120), + "Lifespan if Lose" ~ gt::px(120), + "Election Outcome" ~ gt::px(120), + "Election Age" ~ gt::px(120), + "More" ~ gt::px(60) + ) |> + gt::fmt_markdown(columns = gt::everything()) |> + gt::tab_footnote(footnote = pre_title_footnote, locations = gt::cells_title()) |> + gt::tab_footnote(footnote = pre_units_footnote, locations = gt::cells_column_spanners(spanners = "unit_span")) |> + gt::tab_footnote(footnote = pre_outcome_footnote, locations = gt::cells_column_spanners(spanners = "outcome_span")) |> + gt::tab_footnote(footnote = pre_treatment_footnote, locations = gt::cells_column_spanners(spanners = "treatment_span")) |> + gt::tab_footnote(footnote = pre_covariates_footnote, locations = gt::cells_column_spanners(spanners = "covariates_span")) +``` + +### 3. Rendered Population Table + +```{r} +data_tibble <- dplyr::bind_rows( + d_tibble[1:2, , drop = FALSE], + d_tibble[1, , drop = FALSE] |> dplyr::mutate(dplyr::across(dplyr::everything(), ~ "...")), + d_tibble[3, , drop = FALSE] +) |> + dplyr::mutate(Source = "Data", .before = 1) + +preceptor_tibble <- p_tibble_full |> + dplyr::select(-More) |> + dplyr::mutate(Source = "Preceptor", .before = 1) + +empty_row <- data_tibble[1, , drop = FALSE] +empty_row[,] <- "..." + +population_tibble <- dplyr::bind_rows( + empty_row, + data_tibble, + empty_row, + preceptor_tibble, + empty_row +) + +population_tibble$More <- "..." + +gt::gt(population_tibble) |> + gt::tab_header(title = "Population Table") |> + gt::tab_spanner(label = "Unit/Time", id = "unit_span", columns = c(`Source`, `Candidate`, `Election Year`)) |> + gt::tab_spanner(label = "Potential Outcomes", id = "outcome_span", columns = c(`Lifespan if Win`, `Lifespan if Lose`)) |> + gt::tab_spanner(label = "Treatment", id = "treatment_span", columns = c(`Election Outcome`)) |> + gt::tab_spanner(label = "Covariates", id = "covariates_span", columns = c(`Election Age`, "More")) |> + gt::cols_align(align = "center", columns = gt::everything()) |> + gt::cols_align(align = "left", columns = c(`Candidate`)) |> + gt::cols_width( + "Source" ~ gt::px(80), + "Candidate" ~ gt::px(120), + "Election Year" ~ gt::px(120), + "Lifespan if Win" ~ gt::px(120), + "Lifespan if Lose" ~ gt::px(120), + "Election Outcome" ~ gt::px(120), + "Election Age" ~ gt::px(120), + "More" ~ gt::px(60) + ) |> + gt::fmt_markdown(columns = gt::everything()) |> + gt::tab_footnote(footnote = pop_title_footnote, locations = gt::cells_title()) |> + gt::tab_footnote(footnote = pop_units_footnote, locations = gt::cells_column_spanners(spanners = "unit_span")) |> + gt::tab_footnote(footnote = pop_outcome_footnote, locations = gt::cells_column_spanners(spanners = "outcome_span")) |> + gt::tab_footnote(footnote = pop_treatment_footnote, locations = gt::cells_column_spanners(spanners = "treatment_span")) |> + gt::tab_footnote(footnote = pop_covariates_footnote, locations = gt::cells_column_spanners(spanners = "covariates_span")) +``` + + ## Table Structure Details ### Preceptor Table - Uses `p_tibble` as input (3 rows of placeholders) -- Automatically adds a blank third row and "More" column via `expand_input_tibble()` +- Processed by `expand_input_tibble()` to add a blank third row and "More" column - Results in 4 total rows for the final table ### Population Table @@ -365,7 +527,7 @@ After filling in the tibbles and footnotes with actual data, you would see prope - All rows are properly labeled in the Source column ### Column Alignment -The tribble code is formatted with aligned columns to make editing easier: +The tribble code generated by `write_input_tribble()` is formatted with aligned columns to make editing easier: - Headers and values are padded to align vertically - Minimum column width accommodates the `"..."` placeholder - Makes it easy to see which column you're editing @@ -382,7 +544,7 @@ The `make_p_tables()` function simplifies the creation of interpretable, spanner * Take advantage of the aligned column structure for easy editing * Understand the automatic row and column additions during rendering -This workflow supports better modeling documentation and instructional design. +This workflow supports better modeling documentation and instructional design, with helper functions ensuring consistent formatting and structure. For more on how and why to use these tables, see: