Data updates: See the change log.

2026-10-20: timpeoint post-vaccination timepoints added to 2025LJI.

The plasma cytokine assay 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

Plasma samples were analyzed in duplicate using two 12-plex Legendplex cytokine bead panels on a Cytek Aurora analyzer. Following automated bead gating and manual quality control – which led to the exclusion of one outlier replicate plate – mean fluorescence intensities were converted to concentrations using a 5-PL standard curve, with values below a cytokine- and experiment-specific limit of detection (LODHigh) thresholded to that limit.

Data Standardization

Cytokine data generated for CMI-Flu was combined with other publicly-available data:

  • Analyte names were standardized according to a cytokine name map.
  • Technical replicates were collapsed to the mean.
  • Baseline (“pre-vaccination”) values were established by averaging all values at pre-vaccination timepoints.

The Data

Change log

2026-10-20 Post-vaccination timepoints added for Part 2 of the challenge

Data tables

The cytokine data consists of 2 tables : publicData_cytokine.tsv and 2025LJI_cytokine.tsv contain both pre- and post-vaccination measurements collated from public studies and for the “prediction/challenge” cohort respectively. While these data are not directly linked to a specific prediction task, you can leverage them to build more robust models and improve predictions for other data types within the 2025 LJI prediction challenge cohort.

Cytokine 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. 'Pre-vacc' is the mean of all pre-vaccination timepoints.
analyte Cytokine or chemokine measured.
value Measured concentration.
unit Unit of measurement (pg/ml).
measurementTechnique Assay platform: Luminex xMAP or Legendplex (challenge).
material Sample type: Plasma, Serum, or Protein.
study_accession Study identifier. Links to the investigations.tsv file.
subject subject ID.

Data exploration

train <- read.delim("../datasets/260512/train_part2/publicData_cytokine.tsv", sep = "\t", stringsAsFactors = FALSE)
challenge <- read.delim("../datasets/260512/train_part2/2025LJI_cytokine.tsv", sep = "\t", stringsAsFactors = FALSE)

train$set <- "train"
challenge$set <- "challenge"

train = train %>% filter(!timepoint %in% c(-14,0))
challenge = challenge %>% filter(!timepoint %in% c(-14,0))

#table(train$timepoint)
#table(challenge$timepoint)

tp_levels_train    <- c("Pre-vacc", "1", "7")
tp_levels_challenge<- c("Pre-vacc", "1", "7")

train$timepoint    <- factor(train$timepoint,    levels = tp_levels_train)
challenge$timepoint<- factor(challenge$timepoint, levels = tp_levels_challenge)

both <- bind_rows(train, challenge)

Data preview :

head(both)
##     participant_id timepoint analyte  value  unit measurementTechnique material
## 1 SDY180.SUB119292         1     EGF   2.50 pg/ml         Luminex xMAP  Protein
## 2 SDY180.SUB119292         1 EOTAXIN  23.67 pg/ml         Luminex xMAP  Protein
## 3 SDY180.SUB119292         1    FGFB   7.50 pg/ml         Luminex xMAP  Protein
## 4 SDY180.SUB119292         1   GMCSF  12.85 pg/ml         Luminex xMAP  Protein
## 5 SDY180.SUB119292         1     GRO 625.19 pg/ml         Luminex xMAP  Protein
## 6 SDY180.SUB119292         1     IL8   3.55 pg/ml         Luminex xMAP  Protein
##   study_accession   subject   set
## 1          SDY180 SUB119292 train
## 2          SDY180 SUB119292 train
## 3          SDY180 SUB119292 train
## 4          SDY180 SUB119292 train
## 5          SDY180 SUB119292 train
## 6          SDY180 SUB119292 train

The training data consists of 4 studies which measured cytokines in response to influenza vaccination. These studies have a mean of 32 participants per study.

participants_per_study <- both %>%
  distinct(set, study_accession, participant_id) %>%
  count(set, study_accession, name = "n_participants")

p1 <- ggplot(participants_per_study,
             aes(x = reorder(study_accession, n_participants), y = n_participants,
                 fill = set)) +
  geom_col(position = position_dodge(width = 0.7), width = 0.6) +
  geom_text(aes(label = n_participants),
            position = position_dodge(width = 0.7), hjust = -0.2, size = 3.5) +
  coord_flip() +
  scale_fill_manual(values = c(train = "#2C7FB8", challenge = "#E34A33")) +
  labs(title = "Participants per study",
       x = "Study accession", y = "# distinct participants", fill = NULL) +
  expand_limits(y = max(participants_per_study$n_participants) * 1.15)

p1

Below is the distribution of cytokine measures found in the training data at Days 0 (pre-vaccination), 1, and 7.

shared_analytes <- intersect(unique(train$analyte), unique(challenge$analyte))

# Restrict to the three timepoints of interest: Pre-vacc, Day 1, and Day 7
train_shared <- train %>%
  filter(analyte %in% shared_analytes, timepoint %in% c("Pre-vacc", "1", "7")) %>%
  mutate(timepoint = factor(timepoint, levels = c("Pre-vacc", "1", "7"),
                             labels = c("Pre-vacc", "Day 1", "Day 7")))

# Order analytes by their overall median (across all three timepoints combined)
train_order <- train_shared %>%
  group_by(analyte) %>%
  summarise(med = median(value), .groups = "drop") %>%
  arrange(desc(med)) %>%
  pull(analyte)

train_plot <- train_shared %>% mutate(analyte = factor(analyte, levels = train_order))

p4 <- ggplot(train_plot, aes(x = analyte, y = value, fill = timepoint)) +
  geom_boxplot(outlier.size = 0.5, outlier.alpha = 0.3, alpha = 0.7,
               position = position_dodge(width = 0.75)) +
  scale_y_log10(labels = label_number()) +
  scale_fill_manual(values = c("Pre-vacc" = "#31A354", "Day 1" = "#756BB1", "Day 7" = "#E6550D")) +
  coord_flip() +
  labs(title = "Distribution of each cytokine (train set)",
       subtitle = paste0("Cytokine measures shared across challenge and train data"),
       x = NULL, y = "pg/ml (log10)", fill = NULL) +
  theme(axis.text.y = element_text(size = 9), legend.position = "top")
p4

The distribution of IP10 in the different studies is shown below.

ip10 <- both %>% filter(analyte == "IP10")

p5b <- ggplot(ip10, aes(x = timepoint, y = value, fill = set)) +
  geom_boxplot(alpha = 0.7) +
  scale_y_log10(labels = label_number()) +
  facet_wrap(~set, scales = "free_x") +
  scale_fill_manual(values = c(train = "#2C7FB8", challenge = "#E34A33")) +
  labs(title = "IP10 by timepoint", x = "Timepoint", y = "IP10 (pg/ml, log10 scale)",
       fill = NULL) +
  theme(legend.position = "none")

p5b