To count every combination of categorical columns in R, use table() for an array of counts or group a data.table by those columns and return .N. Use ftable() to print a multiway table in a compact, flat layout, and as.data.frame() when you need one row per combination for joins, plotting, or export.
Count combinations with base R
Base R’s table() cross-classifies factor-like inputs and records the number of rows at each combination of their levels. With three categorical columns, pass all three to table():
# Example data frame: dat
counts <- with(dat, table(group, treatment, outcome))
counts
The result is an array-based object with class table. Each dimension corresponds to one supplied variable, and each cell contains that combination’s frequency.
Use explicit column names
If your columns are stored in a data frame, the with() form keeps the expression readable. Equivalent code is:
#1 Best Overall
counts <- table(dat$group, dat$treatment, dat$outcome)
Factors contribute their defined levels, so a table can contain zero-count combinations as well as combinations observed in the data.
Display a multiway table with ftable()
A multidimensional array can be awkward to read in the console. ftable() produces a flat contingency-table display while retaining the cross-classified counts:
ftable(counts)
This is primarily a presentation choice. It does not change the underlying counts or turn the result into a statistical test.
Return one row per combination with data.table
When the rest of your workflow uses data.table, grouped aggregation gives a long-form result directly. The special symbol .N is the number of rows in the current group.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstalllibrary(data.table)
DT <- as.data.table(dat)
freq <- DT[, .(Freq = .N), by = .(group, treatment, outcome)]
freq
freq has one row for each distinct combination represented by the grouping operation, with the category columns followed by Freq. Add or remove columns in by = .(...) to change the dimensions being counted.
Count fewer or more dimensions
# Two-way frequency
DT[, .(Freq = .N), by = .(group, treatment)]
# Four-way frequency
DT[, .(Freq = .N), by = .(group, treatment, outcome, site)]
Use the base approach when an array is useful for multidimensional operations; use grouped .N when explicit rows fit your data-table pipeline.
Convert a base table to long form
as.data.frame() converts a table object into a data frame containing the classifying variables and a frequency column named Freq by default.
long_counts <- as.data.frame(counts)
long_counts
The resulting columns hold the category values and their corresponding counts, including zero-count combinations represented in the table’s levels. This shape is convenient for joins, visualisation, and file export.
Decide how missing values are counted
By default, table() excludes missing values from its counts. Set useNA when an NA category should be visible:
Rank #4
# Include an NA level only when at least one value is missing
with(dat, table(group, treatment, outcome, useNA = "ifany"))
# Always include an NA level in each classified input
with(dat, table(group, treatment, outcome, useNA = "always"))
The choice is analytical, not cosmetic: excluding missing records answers a different question from reporting them as a category. Make the policy explicit when publishing the result.
For grouped data.table counts, decide how missing categories are represented in your input and verify the resulting groups for your package version and data. The .N syntax supplies the row count; it does not by itself define a universal missing-value policy for every grouping scenario.
Choose the representation for the next task
| Need | Recommended form | Why |
|---|---|---|
| Compact multidimensional object | table(x, y, z) |
Stores counts in an array indexed by dimensions. |
| Readable console layout | ftable(counts) |
Prints multiway counts in a flat arrangement. |
| Rows for joins, plotting, or export | as.data.frame(counts) |
Provides category columns and a Freq column. |
Existing data.table workflow |
DT[, .(Freq = .N), by = .(…)] |
Returns grouped rows without a separate conversion step. |
Calculate margins and proportions from a base table
The array form works with base R contingency-table utilities. Use margin.table() for totals over selected dimensions, prop.table() for proportions, and addmargins() to append totals.
Best Value
# Totals over the third dimension
margin.table(counts, margin = c(1, 2))
# Proportions across all cells
prop.table(counts)
# Add row, column, or higher-dimensional totals
addmargins(counts)
Choose the margin deliberately: a proportion over all cells is not the same as a proportion within each group or treatment level.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Do not confuse a frequency table with a multiway chi-square test
A multiway count display describes the data, but it is not automatically an inferential model. R’s table documentation notes that chisq.test() currently handles only two-dimensional tables. If your question concerns association or independence across more than two dimensions, define the model and hypothesis first, then select an appropriate method rather than applying a two-way test mechanically to a multiway object.
Quick Recap
Practical checks before reporting counts
- Confirm that each classified column uses the intended categories and factor levels.
- Check whether zero-count level combinations should remain in the output.
- State whether missing values were excluded or included as a category.
- Verify that the sum of frequencies matches the rows intended for analysis (after accounting for excluded missing values).
- Use
ftable()for inspection and a long-form data frame or groupeddata.tablefor downstream operations.
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

