Mode Calculation in R: A Step-by-Step Guide

R Published Updated 3 mins read By Leon Wei
Mode Calculation in R: A Step-by-Step Guide cover image

Quick summary

Summarize this blog with AI

Introduction

The statistical mode is a most-frequent value. R’s base mode() reports an object’s storage mode; it does not calculate this statistic. A useful helper must specify how it treats ties, missing values and empty input without converting decimals or labels into integers.

Tested with R 4.5.1 and dplyr 1.1.4. Run the code blocks in order. Install the named packages before starting; inputs are supplied below.

Define the contract before choosing a function

This lesson returns every value tied for maximum frequency, in first-appearance order. If all values occur once, all are returned as tied modes; some reporting conventions call that “no mode,” so state yours. Missing values are removed by default; retaining them treats missingness as one category. The allowed inputs are plain numeric, character or logical vectors and factors, not matrices or lists. Exact equality is used; nearby floating-point values are not automatically binned.

print(mode(c(2, 2, 3)))
 # "numeric" describes storage, not the most frequent value.
 stopifnot(mode(c(2, 2, 3)) == "numeric")

Count positions and subset the original values

Using match() with unique values creates integer category positions. tabulate() counts those positions; subsetting the original unique values preserves decimals, labels and factor levels. No table names are converted back into numeric data.

stat_modes <- function(x, na.rm = TRUE) {
   valid_type <- is.factor(x) ||
     (!is.object(x) && (is.numeric(x) || is.character(x) || is.logical(x)))
   if (!valid_type || !is.null(dim(x))) stop("Need a supported one-dimensional vector")
   if (!is.logical(na.rm) || length(na.rm) != 1L || is.na(na.rm))
     stop("na.rm must be TRUE or FALSE")
   if (na.rm) x <- x[!is.na(x)]
   if (!length(x)) return(x)
   # Treat NA and NaN as one missing category when retained.
   x[is.na(x)] <- NA
   values <- unique(x)
   counts <- tabulate(match(x, values), nbins = length(values))
   values[counts == max(counts)]
 }
 print(stat_modes(c(1.5, 2.5, 1.5, 2.5, 9)))
 print(stat_modes(c("pear", "apple", "pear", "apple")))
 print(stat_modes(c(4L, 4L, 8L)))

The outputs are c(1.5, 2.5), c("pear", "apple"), and integer 4. A helper that converts table names with as.integer() would lose this contract.

Empty or all-missing input after removal returns a zero-length vector of the input type. With na.rm = FALSE, two missing entries can win the frequency count. A factor output retains its factor levels, including currently unused levels; levels are metadata, not observations.

stopifnot(identical(stat_modes(c(1.5, 2.5, 1.5, 2.5)), c(1.5, 2.5)),
           identical(stat_modes(c("b", "a", "b", "a")), c("b", "a")),
           identical(stat_modes(c(1L, 2L, 3L)), 1:3),
           identical(stat_modes(numeric()), numeric()),
           identical(stat_modes(c(NA_real_, NA_real_)), numeric()),
           identical(stat_modes(c(NA_real_, NaN, 2), FALSE), NA_real_),
           identical(stat_modes(c(TRUE, FALSE, TRUE)), TRUE))
 f <- factor(c("low", "high", "low"), levels = c("low", "high", "unused"))
 stopifnot(identical(stat_modes(f), f[1]), identical(levels(stat_modes(f)), levels(f)))
 fails <- function(expr) inherits(tryCatch(expr, error = identity), "error")
 stopifnot(fails(stat_modes(list(1, 2))), fails(stat_modes(matrix(1:4, 2))),
           fails(stat_modes(c(1, 2), NA)))

Keep multiple group modes in a list column

A grouped summary must not silently discard ties. In dplyr summarise(), a list column gives each group one row while storing any number of modes. For very large data, measure memory and runtime on representative inputs rather than assuming one implementation is always faster.

library(dplyr)
 observations <- data.frame(group = c("A", "A", "A", "B", "B", "B"),
                            value = c("red", "blue", "red", "red", "blue", NA))
 result <- observations |>
   group_by(group) |>
   summarise(modes = list(stat_modes(value)), .groups = "drop")
 print(result)
 stopifnot(identical(result$modes, list("red", c("red", "blue"))))

GroupA has mode red; groupB has the tied modes red and blue after removing its missing observation. Base R’s storage-mode documentation and the tests above make the distinction explicit; a package is optional, not necessary for this frequency calculation.

Primary references

Interview Prep

Begin Your SQL, Python, and R Journey

Master 230 interview-style coding questions and build the data skills needed for analyst, scientist, and engineering roles.

Related Articles

All Articles
Range Calculation in R: A Guide cover image
r May 7, 2024

Range Calculation in R: A Guide

Learn to calculate range in R with our comprehensive guide. Perfect for beginners eager to master R programming with detailed code samples.