The ex vivo flow cytometry assay (which measures the frequency of immune cell populations in the blood) is a component of the CMI-Flu prediction challenge. An overview of all the data, including links to more detailed descriptions of the other data, may be found at CMI-x .
We performed spectral cytometry on PBMCs, staining for surface antibodies listed in Table 3. Gating strategy for all immune cell populations is available in Figure 3. For a given cell population, frequencies represent the proportion of cells within the indicated parent population. Samples with either less than 50% live PBMC at thawing or less than 30,000 live singlet cells recorded were excluded from the dataset.
2026-10-20 Post-vaccination timepoints added for Part 2 of the challenge
The ex vivo flow data consists of 2 tables :
publicData_ex_vivo_flow.tsv and
2025LJI_ex_vivo_flow.tsv contain both pre- and
post-vaccination measurements collated from public studies and for the
“prediction/challenge” cohort respectively.
| Ex vivo flow cytometry Assay | |
| Data dictionary | |
| Column | Description |
|---|---|
| participant_id | Donor ID, one for each subject in each study, in the format studyID.subjectID (links to participants.tsv) |
| timepoint | Days relative to influenza vaccination, or 'Pre-vacc' for the mean of the pre-vaccination timepoints. |
| name | Immune cell population measured (e.g. Classical_monocytes, Antibody-secreting_cells_(ASC), CD4_T_cells). |
| value | Frequency of the population. |
| unit | Unit of measurement. 'percentage' throughout the 2025LJI cohort; public data additionally include absolute counts (cells, cells/ul) and other units. |
| parent_population | Denominator population for value (e.g. CD45plus_cells, CD4_T_cells, B_cells). |
| population_definition | Gating strategy defining the population (e.g. CD3-CD19-CD56-HLADR+CD14+CD16- for classical monocytes). |
| material | Sample type: PBMCs (2025LJI); public data also include Whole blood, Serum, and Other. |
| comments | For 2025LJI, the batch/round in which the sample was acquired. |
| study_accession | Study identifier. Links to the investigations.tsv file. |
| subject | subject ID. |
Data preview :
library(tidyverse)
flow = read_tsv('../datasets/260512/train_part2//2025LJI_ex_vivo_flow.tsv')
head(flow)
## # A tibble: 6 × 11
## participant_id timepoint name value unit parent_population
## <chr> <chr> <chr> <dbl> <chr> <chr>
## 1 2025LJI.SUB7666 Pre-vacc Neutrophils 0.0006 percentage CD45plus_cells
## 2 2025LJI.SUB7666 -14 Neutrophils 0.0013 percentage CD45plus_cells
## 3 2025LJI.SUB7742 0 Neutrophils 0.0016 percentage CD45plus_cells
## 4 2025LJI.SUB5670 -14 Neutrophils 0.0019 percentage CD45plus_cells
## 5 2025LJI.SUB5670 Pre-vacc Neutrophils 0.0023 percentage CD45plus_cells
## 6 2025LJI.SUB7742 Pre-vacc Neutrophils 0.0023 percentage CD45plus_cells
## # ℹ 5 more variables: population_definition <chr>, material <chr>,
## # comments <chr>, study_accession <chr>, subject <chr>
table(flow$timepoint)
##
## -14 0 1 7 Pre-vacc
## 1478 1477 1479 1480 1478
The 2025LJI challenge cohort is measured uniformly as a percentage of parent. The plot below shows the landscape of all populations at baseline (Pre-vacc), ordered by median frequency.
lji <- read.delim("../datasets/260512/train_part2//2025LJI_ex_vivo_flow.tsv",
stringsAsFactors = FALSE)
base <- lji %>%
filter(timepoint == "Pre-vacc", value > 0)
ord <- base %>%
group_by(name) %>%
summarise(med = median(value), .groups = "drop") %>%
arrange(med) %>%
pull(name)
base <- base %>% mutate(name = factor(name, levels = ord))
p1 <- ggplot(base, aes(name, value)) +
geom_boxplot(outlier.shape = NA, alpha = 0.55, width = 0.6, fill = "#1b7837") +
geom_jitter(width = 0.15, size = 0.7, alpha = 0.4) +
scale_y_log10(labels = label_number()) +
coord_flip() +
labs(title = "Immune cell populations at baseline (2025LJI, Pre-vacc)",
x = NULL, y = "% of parent population (log10)") +
theme_bw(base_size = 11)
p1
Timpeoints:
library(patchwork)
pub <- read.delim("../datasets/260512/train_part2/publicData_ex_vivo_flow.tsv",
stringsAsFactors = FALSE)
table(pub$timepoint)
##
## -21 -5 -7 0 1 7 Pre-vacc
## 92 112 677 6901 1148 5130 6911
# Kinetics of 2 cell populations from the public data
paired_traj <- function(pop, tps, use_unit) {
d <- pub %>%
filter(name == pop, unit == use_unit, timepoint %in% tps, value > 0) %>%
mutate(timepoint = factor(timepoint, levels = c("Pre-vacc", "1", "7"),
labels = c("Pre-vacc", "Day 1", "Day 7"))) %>%
droplevels() %>%
# collapse technical replicates to one mean value per participant/timepoint
group_by(participant_id, timepoint) %>%
summarise(value = mean(value), .groups = "drop") %>%
# keep only participants measured at both timepoints, so every point is
# part of a connecting line (drops single-timepoint participants)
group_by(participant_id) %>%
filter(n_distinct(timepoint) == 2) %>%
ungroup()
# One line per participant (participant_id = studyID.subjectID, unique across
# public studies); red line is the median.
ggplot(d, aes(timepoint, value, group = participant_id)) +
geom_line(alpha = 0.3) +
geom_point(alpha = 0.4, size = 0.9) +
stat_summary(aes(group = 1), fun = median, geom = "line",
color = "red", linewidth = 1.1) +
scale_y_log10(labels = label_number()) +
labs(title = pop, x = NULL,
y = paste0(use_unit, " (log10)")) +
theme_bw(base_size = 12)
}
p_mono <- paired_traj("Classical_monocytes",
c("Pre-vacc", "1"), use_unit = "cells/ul")
p_asc <- paired_traj("Antibody-secreting_cells_(ASC)",
c("Pre-vacc", "7"), use_unit = "percentage")
p3 <- p_mono + p_asc +
plot_annotation(
title = "Post-vaccination kinetics (public data)"
)
p3