---
title: Pusto's package downloads
subtitle: September 2026 edition
date: '2026-09-22'
categories:
- programming
- Rstats
code-fold: true
code-tools: true
html-table-processing: none
toc: true
---
Back in 2019, I [posted an analysis](/posts/package-downloads/) of the R packages that I have developed, looking at how frequently they have been downloaded, comparing them to other topically related packages, and examining time trends in download frequency. I was coming up for tenure review at that point and so wanted to provide a little bit of data about the software tools that I'd worked on, even though (at my institution, at least) these weren't really understood as academic contributions like journal articles or books or whatnot.
These days, I'm (happily) tenured at a different university, still developing some R packages, but my work has become much more collaborative.
Of the four R packages that I looked at in 2019, two were solo projects (`clubSandwich` and `ARPobservation`) and two were my design, with some contributions from students (`SingleCaseES` and `scdhlm`).
I've since been involved with five additional R packages (`lmeInfo`, `metaselection`, `POMADE`, `simhelpers`, and `wildmeta`)---all developed in collaboration with students or colleagues---and my packages related to single-case effect sizes have seen major contributions from student collaborators.
In short, the packages are no longer _mine_, but rather represent tools to which I've contributed.
I'm no less proud of the products, though.
Since it has been over seven years since my previous post on package downloads, I felt like it was time for an updated look. I'll follow more or less the same methodology as before, using statistics from [METACRAN](https://www.r-pkg.org/). The site makes available data on daily downloads from the RStudio mirror of CRAN, which is just one of many mirrors around the world. Although the data do not represent complete tallies of all package downloads, as far as I know, it is still the best available source of this sort of data.
# Comparison packages
The nine packages that I've developed or contributed to do a variety of things, but the dominant theme is meta-analysis and effect size calculation. Here's how I'm thinking about packages to use as points of comparison:
* [POMADE](/software/POMADE/) does power calculations for meta-analysis with dependent effect sizes. Comparison package: [PowerUpR](https://cran.r-project.org/package=PowerUpR), a general power analysis package with included Shiny app.
* [wildmeta](/software/wildmeta/) implements bootstrap tests for meta-analytic models. Comparison packages: [bayesmeta](https://cran.r-project.org/package=bayesmeta) and [metaforest](https://cran.r-project.org/package=metaforest), which both do specialized meta-analysis stuff.
* [metaselection](/software/metaselection/) implements several estimators for meta-analytic selection models, with inference methods that handle dependent effect size estimates. This package is brand new so I will exclude it from further analysis.
* [scdhlm](/software/scdhlm/) and [SingleCaseES](/software/SingleCaseES/) do effect size calculations for use in meta-analysis of single-case designs. Comparison package: [compute.es](https://cran.r-project.org/package=compute.es) does effect size calculations for a variety of generic effect size measures.
* [ARPobservation](/software/ARPobservation/) provides tools for simulating behavioral observation data based on an alternating renewal process model. Comparison package: [Countr](https://cran.r-project.org/package=Countr) provides estimation routines for renewal process models.
Some of my other packages have a broader statistical scope:
* [clubSandwich](/software/clubSandwich/) provides cluster-robust variance estimators for a variety of different models (including meta-regression, hierarchical linear models, panel data models, GEE models, instrumental variables models, etc.). The obvious comparison would be [sandwich](https://cran.r-project.org/package=sandwich), which implements a broader class of sandwich estimators for a range of different models, but without some of the small-sample corrections provided by `clubSandwich`. Other relevant points of comparison are packages that implement some form of cluster-robust standard errors for a certain class of models, such as [plm](https://cran.r-project.org/package=plm) and [fixest](https://cran.r-project.org/package=fixest) for fixed effect models and [robumeta](https://cran.r-project.org/package=robumeta) for meta-regression models.
* [lmeInfo](/software/lmeInfo/) provides analytic derivatives for hierarchical linear models estimated using `nlme::lme()`. Comparison package: [merDeriv](https://cran.r-project.org/package=merDeriv) provides similar functionality for `lme4::lmer()` models; [nlme](https://cran.r-project.org/package=nlme), which my package enhances.
* [simhelpers](/software/simhelpers/) provides helper functions for running Monte Carlo simulations. Other packages that provide tools for Monte Carlo simulations include [SimDesign](https://cran.r-project.org/package=SimDesign), [SimEngine](https://cran.r-project.org/package=simEngine), [simFrame](https://cran.r-project.org/package=simFrame), [simstudy](https://cran.r-project.org/package=simstudy), [simulator](https://cran.r-project.org/package=simulator),
[simpr](https://cran.r-project.org/package=simpr),
[simTool](https://cran.r-project.org/package=simTool), and
[MonteCarlo](https://cran.r-project.org/package=MonteCarlo).
```{r setup, message = FALSE, warning = FALSE}
library(tidyverse)
library(lubridate)
library(cranlogs)
library(kableExtra)
library(ggrepel)
to_date <- as_date("2026-08-31")
file_name <- paste0("CRAN package downloads ", to_date, ".rds")
long_file_name <- paste0("CRAN package download history ", to_date, ".rds")
from_date <- as.character(as_date(to_date - duration(1, "year")))
```
```{r scrape-downloads, eval = FALSE}
pkg_downloads <-
available.packages() %>%
as_tibble() %>%
select(Package, Version) %>%
mutate(grp = 1 + trunc((row_number() - 1) / 100)) %>%
nest(data = c(Package, Version)) %>%
mutate(downloads = map(.$data, ~ cran_downloads(packages = .$Package, from = from_date))) %>%
select(-data) %>%
unnest(cols = downloads)
saveRDS(pkg_downloads, file = file_name)
```
```{r past-year}
pkg_downloads <- readRDS(file_name)
downloaded_last_yr <-
pkg_downloads %>%
filter(
date <= to_date - duration(6, "months"),
) %>%
group_by(package) %>%
summarise(
count = sum(count)
) %>%
filter(count > 0) %>%
select(package)
downloads_past_twelve <-
pkg_downloads %>%
filter(date > to_date - duration(12, "months")) %>%
semi_join(downloaded_last_yr, by = "package") %>%
group_by(package) %>%
summarise(
count = sum(count) / 12
) %>%
mutate(
pct_less = 100 * cume_dist(count),
package = fct_rev(fct_infreq(package, w = count))
)
```
# Downloads over the past year
I used daily download counts from the RStudio CRAN mirror from `r from_date` through `r to_date`.
I limited the sample to packages that had been downloaded at least once between `r to_date - duration(12, "months")` and `r as_date(to_date - duration(6, "months"))`.
This yielded `r nrow(downloaded_last_yr)` packages.
For each of these packages, I then calculated the average monthly download rate over the past twelve months, along with where that rate falls as a percentile of all packages in the sample.
Here are the average monthly download rates for each of my packages (highlighted in blue), along with the full set of comparison packages:
```{r}
comp_pkg_dat <- tribble(
~ Pusto_pkg, ~ Comp_pkg,
"ARPobservation", "Countr",
"scdhlm", "compute.es",
"SingleCaseES", NA_character_,
"lmeInfo", "merDeriv",
"lmeInfo", "nlme",
"clubSandwich", "sandwich",
"clubSandwich", "robumeta",
"clubSandwich", "plm",
"clubSandwich", "fixest",
"clubSandwich", "metafor",
"wildmeta", "bayesmeta",
"wildmeta", "metaforest",
"simhelpers", "SimDesign",
"simhelpers", "SimEngine",
"simhelpers", "simFrame",
"simhelpers", "simstudy",
"simhelpers", "simulator",
"simhelpers", "simTool",
"POMADE", "metapower"
)
pkg_groups <-
comp_pkg_dat %>%
mutate(
group = case_match(Pusto_pkg, "SingleCaseES" ~ "scdhlm/SingleCaseES", "scdhlm" ~ "scdhlm/SingleCaseES", .default = Pusto_pkg)
) %>%
pivot_longer(ends_with("_pkg"), names_to = "type", values_to = "package") %>%
mutate(
type = case_match(type, "Pusto_pkg" ~ "Pusto", "Comp_pkg" ~ "Comparison")
) %>%
filter(!is.na(package)) %>%
group_by(group, type, package) %>%
summarize(.groups = "drop") %>%
mutate(
package = factor(package, levels = levels(downloads_past_twelve$package))
)
Pusto_pkgs <- unique(comp_pkg_dat$Pusto_pkg)
comp_pkgs <- unique(comp_pkg_dat$Comp_pkg)
comp_pkgs <- comp_pkgs[!is.na(comp_pkgs)]
focal_package_downloads <-
pkg_groups %>%
left_join(downloads_past_twelve) %>%
arrange(desc(pct_less))
focal_package_downloads %>%
select(package, count, pct_less) %>%
rename(`Average monthly downloads` = count, `Percentage of packages with smaller download rate` = pct_less) %>%
kable(
table.attr = 'data-quarto-disable-processing="true"',
digits = c(0,0,1),
format = "html"
) %>%
kable_styling(full_width = FALSE, bootstrap_options = c("responsive","hover"), fixed_thead = TRUE) %>%
column_spec(2:3, width = "12em") %>%
row_spec(which(focal_package_downloads$type == "Pusto"), background = "lightblue")
```
```{r}
#| fig-width: 7
#| fig-height: 9
#| fig-column: body-outset
title_str <- paste("Average monthly downloads of R packages from", as_date(as_date(from_date)),"through",to_date)
downloads_graph <-
downloads_past_twelve %>%
arrange(package) %>%
left_join(pkg_groups, by = "package") %>%
mutate(
type = recode(type, .missing = "none"),
pkg_num = as.numeric(package)
)
focal_downloads_graph <-
downloads_graph %>%
filter(!is.na(group))
p <-
ggplot(focal_downloads_graph) +
aes(x = pkg_num, y = count) +
geom_col(aes(color = type, group = package), linewidth = 0.7) +
geom_line(data = downloads_graph, color = "black") +
scale_color_manual(values = c(Pusto = "green", Comparison = "grey")) +
scale_y_log10(
breaks = c(20, 50, 200, 500, 2000, 5000, 20000, 50000, 200000),
labels = scales::comma,
position = "right"
) +
coord_flip(ylim = c(10,NA)) +
theme_minimal() +
labs(x = "", y = "Downloads (per month)", title = title_str) +
theme(legend.position = "none", axis.line.y = element_blank(), axis.ticks.y = element_blank(), axis.text.y = element_blank())
p +
geom_label_repel(
data = filter(focal_downloads_graph, type == "Pusto"),
aes(y = count, label = package),
color = "darkgreen",
nudge_y = 0.2
) +
geom_label_repel(
data = filter(focal_downloads_graph, type == "Comparison"),
aes(y = 20, label = package),
color = "darkgrey",
max.overlaps = 20
)
```
# Time trends
```{r scrape-long-history, eval = FALSE}
long_from_date <- as.character(as_date(to_date - duration(11, units = "years")))
long_downloads <- cran_downloads(packages = c(Pusto_pkgs, comp_pkgs), from = long_from_date)
long_downloads <-
long_downloads %>%
mutate(
year = year(date),
qtr = ceiling(month(date) / 3),
mth = month(date),
yr = year + (mth - 1) / 12
) %>%
group_by(package, year, qtr, mth, yr) %>%
summarize(
count = sum(count)
)
saveRDS(long_downloads, file = long_file_name)
```
Here are the monthly download rates for each of these packages (note that the vertical scales of the graphs differ):
```{r}
#| fig-width: 10
#| fig-height: 16
#| fig-column: body-outset
long_downloads <-
readRDS(long_file_name) %>%
left_join(pkg_groups, by = "package")
long_downloads_select <-
long_downloads %>%
filter(! package %in% c("sandwich","nlme","plm"))
long_downloads_label <-
long_downloads_select %>%
filter(year == 2026, mth == 9) %>%
mutate(yr = yr + 0.2)
ggplot(long_downloads_select) +
aes(yr, count, color = type, group = package, label = package) +
geom_line() +
geom_text_repel(
data = long_downloads_label,
nudge_x = 0.8
) +
scale_color_manual(values = c(Pusto = "darkgreen", Comparison = "darkgrey")) +
scale_x_continuous(breaks = seq(2016, 2026, 2)) +
expand_limits(y = 0) +
expand_limits(x = 2025) +
facet_wrap(~ group, scales = "free", ncol = 1) +
theme_minimal() +
theme(
legend.position = "none",
strip.text = element_text(hjust = 0, face = "bold", size = 11)
) +
labs(
x = "Year",
y = "Monthly downloads"
)
```