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 .
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.
Cytokine data generated for CMI-Flu was combined with other publicly-available data:
cytokine name map.The cytokine data consists of 2 tables :
publicData_cytokine.tsv contains both pre- and
post-vaccination measurements. Train your model on this data, then apply
it to 2025LJI_cytokine.tsv (which contains pre-vaccination
data only) to predict the unseen post-vaccination values. See the Task
description below for details.
| 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. |
train <- read.delim("../datasets/260512/train/publicData_cytokine.tsv", sep = "\t", stringsAsFactors = FALSE)
challenge <- read.delim("../datasets/260512/train/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")
tp_levels_challenge<- c("Pre-vacc", "1")
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) and 1.
shared_analytes <- intersect(unique(train$analyte), unique(challenge$analyte))
# Restrict to the two timepoints of interest: Pre-vacc and Day 1
train_shared <- train %>%
filter(analyte %in% shared_analytes, timepoint %in% c("Pre-vacc", "1")) %>%
mutate(timepoint = factor(timepoint, levels = c("Pre-vacc", "1"),
labels = c("Pre-vacc", "Day 1")))
# Order analytes by their overall median (across both 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")) +
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
Task 1.1 asks contestants to predict the fold change of IP10 (CXCL10) from Pre-vacc to Day 1, for the 40 donors found in the 2025LJI challenge dataset.
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