Blog

collapse: Speed Without Complexity

How the collapse package in R runs grouped transformations on a one-million-row dataset many times faster than dplyr, with identical results, and when it is worth reaching for.

Introduction

R has evolved into a powerful ecosystem for statistical computing, with packages like dplyr and data.table driving modern data manipulation workflows through expressive and user-friendly syntax.

However, as datasets grow into the millions of observations, performance and memory efficiency become critical. What works well for small data can introduce overhead at scale.

This is where the collapse package stands out — offering fast, memory-efficient data manipulation designed for large-scale workflows.

What is the collapse Package?

collapse is a high-performance R package designed for fast and memory-efficient operations on vectors and data frames. Its core functions are implemented in C, enabling significantly faster execution than many high-level abstractions.

By using a direct and explicit approach to grouped computations, collapse reduces overhead and improves efficiency — especially on large datasets.

Installing and loading the packages

R
install.packages(c("dplyr","collapse"))

library(dplyr)   # Used for comparison (standard tidy data manipulation)
library(collapse)    # Used for fast, C-optimized grouped computations

Example Data Frame

To illustrate the difference in approach and performance, we construct a large panel-style dataset:

R
set.seed(123)

n_subjects <- 100000
n_periods  <- 10

df <- data.frame(
  id     = rep(1:n_subjects, each = n_periods),
  period = rep(1:n_periods, times = n_subjects),
  value  = rnorm(n_subjects * n_periods, 100, 15)
)

df <- df[order(df$id, df$period), ]
head(df)
Output
  id period     value
1  1      1  91.59287
2  1      2  96.54734
3  1      3 123.38062
4  1      4 101.05763
5  1      5 101.93932
6  1      6 125.72597

This dataset represents:

  • 100,000 unique subjects
  • 10 repeated measurements per subject
  • A total of 1,000,000 rows

Such structures are common in longitudinal research, finance (time-series per entity), econometrics, machine learning pipelines, and healthcare analytics. At this scale, performance differences become measurable and meaningful.

Grouped Transformations

Suppose we want to compute, for each subject, the group mean, the group standard deviation, a centred value (the deviation from the group mean) and a standardised z-score. This is a common preprocessing step in many modelling workflows.

Using dplyr

R
time_dplyr <- system.time({
  
  df_dplyr <- df %>%
    group_by(id) %>%
    mutate(
      mean_id  = mean(value),
      sd_id    = sd(value),
      centered = value - mean_id,
      z_score  = centered / sd_id
    ) %>%
    ungroup()
  
})

time_dplyr
Output
   user  system elapsed 
   3.18    0.14    3.30

The dplyr syntax is readable and declarative, clearly expressing the analytical steps: grouping data, computing statistics, and transforming values within each group.

However, internally it constructs grouped objects, performs tidy evaluation, and manages additional abstraction layers. While this improves usability, it also introduces computational overhead that becomes noticeable on very large datasets.

Using collapse

R
time_collapse <- system.time({
  
  g <- GRP(df$id)
  
  df_collapse <- df
  
  df_collapse$mean_id  <- fmean(df$value, g, TRA = "replace")
  df_collapse$sd_id    <- fsd(df$value, g, TRA = "replace")
  df_collapse$centered <- df$value - df_collapse$mean_id
  df_collapse$z_score  <- df_collapse$centered / df_collapse$sd_id
  
})

time_collapse
Output
   user  system elapsed 
   0.07    0.00    0.07

The collapse implementation follows a more explicit and lower-level approach. GRP() creates a lightweight grouping index, while functions like fmean() and fsd() run in optimized C code. With TRA = “replace”, group results are efficiently replicated without constructing grouped data frames or additional abstraction layers.

This streamlined design reduces computational overhead and memory usage, producing numerically equivalent results with faster execution.

Timing Comparison

In the run shown above, dplyr took about 3.3 seconds, while collapse completed the same task in 0.07 seconds — roughly 47× faster on a one-million-row dataset. Exact timings vary by machine, but the gap is consistent.

Both methods produce identical results; the difference lies purely in execution efficiency.

Verifying Numerical Equivalence

To ensure fairness, we verify that both implementations produce identical results.

R
all.equal(
  as.data.frame(df_dplyr[, c("mean_id","sd_id","centered","z_score")]),
  as.data.frame(df_collapse[, c("mean_id","sd_id","centered","z_score")]),
  tolerance = 1e-8
)
Output
[1] TRUE

We can further confirm equivalence by comparing summary statistics:

R
summary(df_dplyr$mean_id)
Output
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  79.49   96.80  100.01   99.99  103.20  122.94
R
summary(df_collapse$mean_id)
Output
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  79.49   96.80  100.01   99.99  103.20  122.94

Both approaches yield identical distributions, confirming identical group means, standard deviations, transformations and standardised values.

The difference lies not in correctness, but in execution mechanics.

Why collapse Performs Better

1. Minimal Abstraction

Operations run closer to the underlying data structures, avoiding the overhead of complex evaluation frameworks.

2. C-Level Optimization

Core functions like fmean() and fsd() are implemented in optimized C code for faster execution.

3. Explicit Control of Group Behavior

The TRA argument clearly defines how group results are returned, avoiding hidden behavior.

4. Memory Efficiency

Reduced data copying and fewer intermediate objects improve performance and stability on large datasets.

Comparison with other Packages

Featuredplyrdata.tablecollapse
Syntax styleDeclarativeCompactExplicit
PerformanceModerateHighHigh
Memory efficiencyModerateHighVery high
Learning curveLowSteepModerate
Suitability for large dataLimitedGoodExcellent

Each package serves a different purpose, and the choice often depends on data size, performance requirements, and coding preferences.

When should we use collapse?

Use collapse when:

  • Working with very large datasets
  • Building production pipelines
  • Running repeated grouped operations
  • Optimizing performance and memory usage

Conclusion

The collapse package does not attempt to replace the broader tidyverse ecosystem. Instead, it offers a focused, performance-oriented alternative for scenarios where speed and efficiency matter most.

By combining numerical precision, explicit grouping control, C-level execution and memory-conscious design, collapse provides a powerful and scalable solution for modern data science workflows.

For practitioners working at scale, it represents a compelling addition to the R toolkit.

Verify a credential

Enter the reference from a DSI certificate to open its record in the issuing register.

Paste the credential link, or enter the reference on its own. The QR code on the certificate opens the same record.