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.
The ex vivo flow data consists of 2 tables :
publicData_ex_vivo_flow.tsv contains both pre- and
post-vaccination measurements collated from public studies;
2025LJI_ex_vivo_flow.tsv contains pre-vaccination data of
the “prediction/challenge” cohort. Use the other data types to build
your models and predict the unseen post-vaccination values. See the Task
description below for more details.
| 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. |
| subject | subject ID. |
Data preview :
library(tidyverse)
flow = read_tsv('../datasets/260512/train/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>
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/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
The challenge cohort provides pre-vaccination data only. To see the post-vaccination kinetics that the tasks ask you to predict, the plot below uses the public data (restricted to percentage units) for the two task populations across days 0, 1, and 7.
library(patchwork)
pub <- read.delim("../datasets/260512/train/publicData_ex_vivo_flow.tsv",
stringsAsFactors = FALSE)
# After de-duplication, the two task populations only carry paired
# (both-timepoint) data in different units: classical monocytes in cells/ul,
# ASC in percentage. So each population is drawn on its own y-axis, labelled
# with its own unit, rather than forcing them onto a shared axis.
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
Two tasks use the ex vivo flow data. Both ask contestants to predict a post-vaccination cell frequency for the 40 donors in the 2025LJI challenge dataset.