# Saving and Sharing Your Tables

    library(tsg)
    library(dplyr)

Once you have a table, `tsg` can save it to a file in several formats. The steps are always the same: generate the table, optionally add a title and notes, then call the appropriate `write_*()` function.

Which format should I use?

Format | Best for
--- | ---
Excel (`.xlsx`) | Sharing with colleagues, further editing
HTML | Embedding in a website or report
PDF | Print-ready documents
Word (`.docx`) | Inserting into a Word report or document

Excel is the most fully-featured output and requires no additional packages beyond `tsg`. The other formats need a few extra packages — details in their sections below.

All examples use the `person_record` sample dataset included with the package.

## Saving to Excel

Use `write_xlsx()` to save any table to an `.xlsx` file.

### Basic save

    person_record |>
      generate_frequency(sex)
    #> # A tibble: 3 × 3
    #>   category   frequency percent
    #>   <int+lbl>      <int>   <dbl>
    #> 1 1 [Male]        1516    52.0
    #> 2 2 [Female]      1402    48.0
    #> 3 0 [Total]       2918   100

    person_record |>
      generate_frequency(sex) |>
      write_xlsx(path = "table-sex.xlsx")

### Add a title, subtitle, and notes

Chain `add_table_title()`, `add_table_subtitle()`, `add_source_note()`, and `add_footnote()` before saving. These appear as styled rows above and below the table in the Excel file.

    person_record |>
      generate_crosstab(marital_status, sex) |>
      add_table_title("Marital Status by Sex") |>
      add_table_subtitle("National Sample Survey, 2024") |>
      add_source_note("Source: person_record dataset.") |>
      add_footnote("Missing values are excluded from the denominator.")
    #> # A tibble: 6 × 6
    #>   category                 total frequency_1 frequency_2 percent_1 percent_2
    #>   <int+lbl>                <int>       <int>       <int>     <dbl>     <dbl>
    #> 1 1 [Single/never married]  1544         859         685      55.6      44.4
    #> 2 2 [Married]                769         387         382      50.3      49.7
    #> 3 3 [Common law/live-in]     424         211         213      49.8      50.2
    #> 4 4 [Widowed]                138          40          98      29.0      71.0
    #> 5 6 [Separated]               43          19          24      44.2      55.8
    #> 6 0 [Total]                 2918        1516        1402      52.0      48.0

    person_record |>
      generate_crosstab(marital_status, sex) |>
      add_table_title("Marital Status by Sex") |>
      add_table_subtitle("National Sample Survey, 2024") |>
      add_source_note("Source: person_record dataset.") |>
      add_footnote("Missing values are excluded from the denominator.") |>
      write_xlsx(path = "table-marital-sex.xlsx")

> Shortcut: You can also pass the title, subtitle, and notes directly as arguments to `write_xlsx()` instead of chaining the helper functions.

>     person_record |>
>       generate_crosstab(marital_status, sex) |>
>       write_xlsx(
>         path        = "table-marital-sex.xlsx",
>         title       = "Marital Status by Sex",
>         subtitle    = "National Sample Survey, 2024",
>         source_note = "Source: person_record dataset.",
>         footnotes   = "Missing values are excluded from the denominator."
>       )

### More table examples

#### Frequency table with running totals

    person_record |>
      generate_frequency(sex, add_cumulative = TRUE, add_cumulative_percent = TRUE)
    #> # A tibble: 3 × 5
    #>   category   frequency percent cumulative cumulative_percent
    #>   <int+lbl>      <int>   <dbl>      <int>              <dbl>
    #> 1 1 [Male]        1516    52.0       1516               52.0
    #> 2 2 [Female]      1402    48.0       2918              100
    #> 3 0 [Total]       2918   100           NA               NA

    person_record |>
      generate_frequency(sex, add_cumulative = TRUE, add_cumulative_percent = TRUE) |>
      write_xlsx(path = "table-sex-cumulative.xlsx")

#### Grouped frequency table

Pipe a `group_by()` before `generate_frequency()` to stratify the output. The result is a single flat table with group labels in the category column.

    person_record |>
      group_by(sex) |>
      generate_frequency(employed)
    #> # A tibble: 8 × 4
    #>   sex        category         frequency percent
    #>   <int+lbl>  <int+lbl>            <int>   <dbl>
    #> 1 1 [Male]   1 [Yes]                599    39.5
    #> 2 1 [Male]   2 [No]                 508    33.5
    #> 3 1 [Male]   8 [Not reported]       409    27.0
    #> 4 1 [Male]   0 [Total]             1516   100
    #> 5 2 [Female] 1 [Yes]                323    23.0
    #> 6 2 [Female] 2 [No]                 678    48.4
    #> 7 2 [Female] 8 [Not reported]       401    28.6
    #> 8 2 [Female] 0 [Total]             1402   100

    person_record |>
      group_by(sex) |>
      generate_frequency(employed) |>
      write_xlsx(path = "table-employed-by-sex.xlsx")

#### Basic cross-tabulation

    person_record |>
      generate_crosstab(employed, sex)
    #> # A tibble: 4 × 6
    #>   category         total frequency_1 frequency_2 percent_1 percent_2
    #>   <int+lbl>        <int>       <int>       <int>     <dbl>     <dbl>
    #> 1 1 [Yes]            922         599         323      65.0      35.0
    #> 2 2 [No]            1186         508         678      42.8      57.2
    #> 3 8 [Not reported]   810         409         401      50.5      49.5
    #> 4 0 [Total]         2918        1516        1402      52.0      48.0

    person_record |>
      generate_crosstab(employed, sex) |>
      write_xlsx(path = "crosstab-employed-sex.xlsx")

#### Cross-tabulation with column percentages

    person_record |>
      generate_crosstab(employed, sex, percent_by_column = TRUE)
    #> # A tibble: 4 × 7
    #>   category       frequency_total frequency_1 frequency_2 percent_total percent_1
    #>   <int+lbl>                <int>       <int>       <int>         <dbl>     <dbl>
    #> 1 1 [Yes]                    922         599         323          31.6      39.5
    #> 2 2 [No]                    1186         508         678          40.6      33.5
    #> 3 8 [Not report…             810         409         401          27.8      27.0
    #> 4 0 [Total]                 2918        1516        1402         100       100
    #> # ℹ 1 more variable: percent_2 <dbl>

    person_record |>
      generate_crosstab(employed, sex, percent_by_column = TRUE) |>
      write_xlsx(path = "crosstab-column-pct.xlsx")

### Save multiple tables to one workbook

Pass a named list to `write_xlsx()`. Each element becomes a separate worksheet. The name of each list element becomes the sheet name.

    tables <- list(
      "Sex"        = person_record |> generate_frequency(sex),
      "Employment" = person_record |> generate_frequency(employed),
      "Crosstab"   = person_record |> generate_crosstab(employed, sex)
    )

    write_xlsx(tables, path = "multi-sheet.xlsx")

### Save each table to its own file

Set `separate_files = TRUE` and provide a folder path instead of a file name. The folder is created if it does not exist.

    write_xlsx(tables, path = "output-dir/", separate_files = TRUE)

### Add an index sheet to a multi-table workbook

Set `include_table_list = TRUE` to prepend an auto-generated index sheet. This is useful for statistical reports with many tables.

    write_xlsx(tables, path = "report.xlsx", include_table_list = TRUE)

## Managing metadata for large reports

When you have many tables, it is more practical to keep all titles, subtitles, and notes in one place — a reference spreadsheet — rather than scattering them across your analysis script. `tsg` supports this with the `table_list_reference` argument.

### Step 1 — Create a template

    generate_template("table-list-template.xlsx", template = "table-list")

The template has one row per table with these columns:

Column | What it contains
--- | ---
`table_id` | A unique identifier that must match the name of the list element in `write_xlsx()`
`table_number` | Display number shown in the index sheet
`table_name` | Short label shown in the index sheet
`title` | Full table title
`subtitle` | Optional subtitle
`footnotes` | Optional footnote text
`source_note` | Optional source line printed below the table

### Step 2 — Fill in the reference data

Edit the template in Excel, or build it in R:

    table_ref <- tibble::tibble(
      table_id     = c("table_sex", "table_emp", "table_ct"),
      table_number = 1:3,
      table_name   = c("Sex", "Employment", "Employment × Sex"),
      title        = c(
        "Distribution by Sex",
        "Employment Status",
        "Employment Status by Sex"
      ),
      subtitle     = c(NA, NA, "Cross-tabulation"),
      footnotes    = NA,
      source_note  = "Source: person_record dataset."
    )

### Step 3 — Export with the reference

The `table_id` values in your reference must match the names of your list. `write_xlsx()` looks up each table, applies its metadata, and builds the index sheet automatically.

    tables <- list(
      table_sex = person_record |> generate_frequency(sex),
      table_emp = person_record |> generate_frequency(employed),
      table_ct  = person_record |> generate_crosstab(employed, sex)
    )

    write_xlsx(
      tables,
      path                 = "report.xlsx",
      include_table_list   = TRUE,
      table_list_reference = table_ref
    )

## Saving to HTML

> Required package: `gt` — install with `install.packages("gt")`

    person_record |>
      generate_frequency(sex) |>
      add_table_title("Distribution by Sex") |>
      write_html(path = "table-sex.html")

Cross-tabulations with grouped column headers are fully supported:

    person_record |>
      generate_crosstab(marital_status, sex) |>
      add_table_title("Marital Status by Sex") |>
      write_html(path = "crosstab.html")

### Multiple tables in one HTML file

When `data` is a named list, all tables are written to a single self-contained HTML file by default. Each table is wrapped in its own section. Set `include_table_list = TRUE` to add a clickable table of contents.

    tables <- list(
      "Sex"           = person_record |> generate_frequency(sex),
      "Marital Status"= person_record |> generate_frequency(marital_status),
      "Sex × Marital" = person_record |> generate_crosstab(sex, marital_status)
    )

    write_html(tables, path = "report.html", include_table_list = TRUE)

Set `separate_files = TRUE` to write each table to its own `.html` file in a folder.

    write_html(tables, path = "html-output/", separate_files = TRUE)

## Saving to PDF

> Required packages: `gt` and `webshot2` — install with `install.packages(c("gt", "webshot2"))`.
> `webshot2` also requires a Chromium browser; run `webshot2::install_phantomjs()` or ensure Chrome/Chromium is available.

    person_record |>
      generate_frequency(sex) |>
      add_table_title("Distribution by Sex") |>
      write_pdf(path = "table-sex.pdf")

When `data` is a list, the default is one PDF file per table inside the specified folder:

    write_pdf(tables, path = "pdf-output/")

To combine all tables into a single PDF file, set `separate_files = FALSE` (requires the `qpdf` package):

    write_pdf(tables, path = "report.pdf", separate_files = FALSE)

## Saving to Word

> Required packages: `officer` and `flextable` — install with `install.packages(c("officer", "flextable"))`

    person_record |>
      generate_frequency(sex) |>
      add_table_title("Distribution by Sex") |>
      add_source_note("Source: person_record dataset") |>
      write_docx(path = "table-sex.docx")

Cross-tabulations are fully supported:

    person_record |>
      generate_crosstab(marital_status, sex) |>
      add_table_title("Marital Status by Sex") |>
      add_footnote("Missing values excluded from the denominator.") |>
      write_docx(path = "crosstab.docx")

When `data` is a named list, the default is a single `.docx` file with one table per page:

    tables <- list(
      "Sex"           = person_record |>
        generate_frequency(sex) |>
        add_table_title("Distribution by Sex"),
      "Marital Status"= person_record |>
        generate_frequency(marital_status) |>
        add_table_title("Distribution by Marital Status"),
      "Sex × Marital" = person_record |>
        generate_crosstab(sex, marital_status) |>
        add_table_title("Sex by Marital Status")
    )

    write_docx(tables, path = "report.docx")

Set `separate_files = TRUE` to write each table to its own `.docx` file:

    write_docx(tables, path = "docx-output/", separate_files = TRUE)

## Summary

Format | Function | Key packages
--- | --- | ---
Excel | `write_xlsx()` | (none beyond tsg)
HTML | `write_html()` | `gt`
PDF | `write_pdf()` | `gt`, `webshot2` (+ `qpdf` for combined)
Word | `write_docx()` | `officer`, `flextable`