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 .

Experimental protocol

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.

Data processing and standardization

  • Cell population names were standardized according to the name_map .
  • Technical replicates were collapsed by taking the mean.
  • Baseline (“pre-vaccination”) values were established by averaging all values at pre-vaccination timepoints.
  • Publicly-available data were collated from multiple studies; note that these use a mix of reporting units (percentages, absolute counts, counts/µl).

The Data

Data tables

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 exploration

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

Prediction Task

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.

  • Task 1.2 Monocyte frequency (Day 1). Predict the day-1 frequency of classical monocytes (name = Classical_monocytes).
  • Task 1.3 Plasmablast frequency (Day 7). Predict the day-7 frequency of antibody-secreting cells / plasmablasts (name = Antibody-secreting_cells_(ASC)).