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
install.packages(c("dplyr","collapse"))
library(dplyr) # Used for comparison (standard tidy data manipulation)
library(collapse) # Used for fast, C-optimized grouped computationsExample Data Frame
To illustrate the difference in approach and performance, we construct a large panel-style dataset:
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) 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.72597This 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
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 user system elapsed
3.18 0.14 3.30The 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
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 user system elapsed
0.07 0.00 0.07The 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.
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
)[1] TRUEWe can further confirm equivalence by comparing summary statistics:
summary(df_dplyr$mean_id) Min. 1st Qu. Median Mean 3rd Qu. Max.
79.49 96.80 100.01 99.99 103.20 122.94summary(df_collapse$mean_id) Min. 1st Qu. Median Mean 3rd Qu. Max.
79.49 96.80 100.01 99.99 103.20 122.94Both 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
| Feature | dplyr | data.table | collapse |
|---|---|---|---|
| Syntax style | Declarative | Compact | Explicit |
| Performance | Moderate | High | High |
| Memory efficiency | Moderate | High | Very high |
| Learning curve | Low | Steep | Moderate |
| Suitability for large data | Limited | Good | Excellent |
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.