How to Create a Table in R: Methods, Packages, and What Affects Your Approach
Tables are one of the most fundamental ways to organize and display data in R. Whether you're summarizing survey results, comparing statistical outputs, or preparing data for a report, knowing how to create a table in R — and which method fits your situation — makes a real difference in both workflow and output quality.
What "Creating a Table" Actually Means in R
R uses the word "table" in at least two distinct ways, and it's worth separating them before diving in:
- Frequency tables — summaries that count how often values appear in a dataset
- Display tables — formatted outputs intended for reports, documents, or presentations
Both are common, both are useful, and the tools for each are different. Your goal — analysis vs. communication — determines which path makes sense.
Creating Frequency Tables with Base R
R's built-in table() function is the fastest way to generate a frequency count from a vector or factor.
# Single variable table(my_data$category) # Two-variable cross-tabulation table(my_data$gender, my_data$region) This produces a contingency table showing how many observations fall into each combination of values. No packages required. It's fast, readable in the console, and useful for quick exploratory analysis.
To add proportions instead of raw counts, wrap it in prop.table():
prop.table(table(my_data$category)) When Base R Tables Fall Short
The table() output is plain text — functional for analysis, but not formatted for sharing. It also doesn't handle data frames with multiple column types elegantly, and it won't produce publication-ready output without significant extra work.
That's where display-focused packages come in.
Creating Formatted Tables for Reports
If your goal is a table that appears in an R Markdown document, a Quarto report, a PDF, or an HTML page, several packages are purpose-built for this.
knitr::kable()
The kable() function from the knitr package is the most widely used starting point for formatted tables in R Markdown.
library(knitr) kable(my_dataframe, caption = "Summary of Results") It converts a data frame or matrix into a clean Markdown, HTML, or LaTeX table depending on your output format. Pair it with the kableExtra package for column styling, row grouping, footnotes, and conditional formatting.
gt Package
The gt package (short for "grammar of tables") treats table construction like ggplot2 treats charts — building tables in layers.
library(gt) my_dataframe |> gt() |> tab_header(title = "My Table") It offers fine-grained control over column formatting, spanners, row groups, and cell-level styling. It renders cleanly in HTML and supports PDF export. It's a strong choice when table appearance is a priority.
flextable
flextable is commonly used when the destination is a Word document or PowerPoint file. It integrates with the officer package for Office-format output, making it a practical option in corporate or academic environments where .docx delivery is standard.
DT (DataTables)
For interactive HTML tables — where users can sort columns, search rows, or paginate results — the DT package wraps the JavaScript DataTables library.
library(DT) datatable(my_dataframe) This is particularly useful in Shiny apps or interactive R Markdown documents, but it doesn't translate to static PDF or Word output.
📊 Quick Comparison: R Table Methods
| Method | Best For | Output Format | Interactivity |
|---|---|---|---|
table() | Frequency counts, EDA | Console text | None |
kable() + kableExtra | Reports, R Markdown | HTML, PDF, Word | None |
gt | Styled display tables | HTML, PDF | None |
flextable | Word/PowerPoint docs | Word, HTML | None |
DT | Interactive browsing | HTML only | Sorting, search, pagination |
Variables That Shape Which Approach Works for You
The "right" way to create a table in R isn't universal — it depends on several factors specific to your setup:
Output destination is the most decisive factor. A table destined for a PDF via LaTeX behaves differently from one heading into a Word document or a live Shiny dashboard. Some packages render beautifully in one format and break entirely in another.
Data structure matters too. A simple two-column summary needs different handling than a wide data frame with mixed numeric and character columns, grouped rows, or merged headers.
R Markdown vs. script context changes what's practical. If you're running an interactive script, table() output in the console is fine. If you're knitting a document for an audience, appearance and formatting controls become important.
Package familiarity and project dependencies are real constraints. Adding gt or flextable to a project introduces dependencies that need to be managed — a consideration in team environments or deployed applications.
Tidyverse alignment is another variable. If your workflow is already built around dplyr and tidyr, piping into gt or kable() feels natural. If you're working in base R, table() and xtabs() fit more smoothly without switching idioms.
🔍 A Note on xtabs() and ftable()
Two less-discussed base R functions are worth knowing:
xtabs()creates cross-tabulations using formula syntax, which integrates cleanly with model-style R workflowsftable()("flat table") formats multi-dimensional contingency tables into a more readable layout — useful whentable()output becomes hard to parse across three or more variables
Neither requires any packages, and both handle edge cases that the standard table() function makes awkward.
What Determines Your Best Path
Someone building a quick analysis for personal use has very different needs than someone delivering a formatted report to a non-technical stakeholder, or a developer embedding live data into a Shiny application. The mechanics of table(), kable(), gt, and DT are learnable in an afternoon — but which combination actually serves your workflow depends on where your data lives, where your output needs to go, and what level of visual control your situation demands. 🧩