Soluble IL-2R after ICI — Analysis

Who had a soluble IL-2 receptor drawn after an immune checkpoint inhibitor?

Author

Tianqi Ouyang

Published

September 30, 2026

NoteAbout this page

This page answers the four cohort questions for the soluble IL-2 receptor (sIL2R) project. Every number is an aggregate read from cache/summary.rds, which R/build.R writes locally from the RPDR extract and the ICI data pull. Counts from 1 to 10 are shown as <11. Patient-level detail (EMPI, dates) stays in the local workbook output/sIL2R_ICI_patient_level.xlsx and is not part of this site.

1 The requests, answered

Table 1: Primary definitions (Section 2). ‘After 2015’ = drawn on or after 2015-01-01; ‘before’ = ICI start date strictly earlier than the IL2R draw date; a measurement = one draw date with a numeric value in the RPDR Lab Result field.
Request N %
RPDR patients with a numeric soluble IL2R Result on/after 2015-01-01 (all-pre-2015 excluded) 2,202
… who had gotten an ICI (any dose) before an IL2R level was drawn 464 21.1% of 2,202
… of those, with at least 2 IL2R measurements after the ICI 117 25.2% of 464
… NOT found in the ICI cohort at all (chart-review candidates) 1,711 77.7% of 2,202

The remaining 27 patients are in the ICI cohort but started ICI on or after the date of every post-2015 IL2R draw, so 2,202 = 464 + 27 + 1,711.

Not counted above: 850 patients are in the RPDR patient set but have no IL2R row in the RPDR Lab file, so there is no draw date and they cannot be placed before or after ICI. They split as follows.

Table 2: Patients without a draw date. Dating them needs the RPDR procedures (Prc) file for the same patient set, or chart review; they are listed in the local workbook (sheet no_IL2R_result_row).
IL2R order, no result row Patients ICI in RPDR Med Malignancy code
In the ICI pull (request 1 status unknown) 168 suppressed 168
Not in the ICI pull (add to request 3 review) 682 0 370

2 Definitions as implemented

The code below is the executed definition, pulled verbatim from R/functions.R at render time — click Show code on any block.

2.1 Soluble IL2R draws (RPDR Lab)

An IL2R draw is any RPDR Lab row with Group_Id in the three sIL2R groups; the draw date is the date part of Seq_Date_Time. A row counts as an IL2R measurement only when its Result field starts with a number (e.g. 1308 or 1234.5 pg/mL H). Rows whose Result is blank or text only (e.g. SEE MANUAL REPORT, CREDITED: NO SPECIMEN RECEIVED) are not measurements. The free-text report field Result_Text is not used.

Show code
## RPDR Lab Group_Id values that are soluble IL-2 receptor (CD25) tests.
## (Test_Id: el:5200007493, wc954.1945, mcsq-il2ra, dcsqe-il2ra)
IL2R_GROUPS <- c("INTERL2-RS", "INTERL2", "IL2R-ALPHA")

## "After 2015" = drawn on or after this date. Patients whose IL2R values are
## ALL before this date are excluded.
CUTOFF_DATE <- as.Date("2015-01-01")

## Leading number, optionally with thousands separators ("12,345").
NUM_REGEX <- "^\\s*[<>]?\\s*[0-9]+(,[0-9]{3})*(\\.[0-9]+)?"

## An IL2R measurement = a row whose `Result` starts with a number. Rows with a
## blank or text-only `Result` (e.g. "SEE MANUAL REPORT", "CREDITED: ...") are
## not measurements. `Result_Text` (free-text report) is not used.
STATUS_LEVELS <- c("numeric Result", "no numeric Result")

## Classify every IL2R lab row from its `Result` field only.
classify_il2r_result <- function(result) {
  result <- fifelse(is.na(result), "", result)
  factor(fifelse(grepl(NUM_REGEX, result), STATUS_LEVELS[1], STATUS_LEVELS[2]),
         levels = STATUS_LEVELS)
}

2.2 ICI start date (ICI data pull)

On read, EMPI MRN (column G) is renamed to EMPI so it keys directly against the RPDR files, and SCHEDULEDSTARTDTS (raw YYYY-MM-DDThh:mm:ssZ) becomes a date (YYYY-MM-DD). Every row in the pull is a MAR New Bag administration, so every row is a dose given. The ICI start date is the earliest administration date, not counting blinded “X OR PLACEBO” trial products (the patient may have had placebo).

Show code
## ICI data pull. Two changes are applied on read, so the rest of the pipeline
## (and every downstream file) matches the RPDR conventions:
##   1. `EMPI MRN` (column G) is renamed to `EMPI`
##   2. SCHEDULEDSTARTDTS "YYYY-MM-DDThh:mm:ssZ" becomes a Date (YYYY-MM-DD)
read_ici <- function(path) {
  stopifnot(file.exists(path))
  ici <- fread(path, colClasses = "character", showProgress = FALSE)
  stopifnot("EMPI MRN" %in% names(ici), "SCHEDULEDSTARTDTS" %in% names(ici))
  setnames(ici, "EMPI MRN", "EMPI")
  ici[, SCHEDULEDSTARTDTS_raw_hour := as.integer(substr(SCHEDULEDSTARTDTS, 12, 13))]
  ici[, SCHEDULEDSTARTDTS := parse_ici_date(SCHEDULEDSTARTDTS)]
  ici[]
}

## ICI SCHEDULEDSTARTDTS: ISO-8601 "YYYY-MM-DDThh:mm:ssZ" -> calendar Date.
## The date is taken exactly as written (no time-zone shift): the trailing "Z"
## is an export artefact, not a real UTC offset -- scheduled hours cluster at
## 08:00-17:00, i.e. local clinic hours.
parse_ici_date <- function(x) {
  ok <- is.na(x) | grepl("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z?$", x)
  if (!all(ok)) stop(sum(!ok), " SCHEDULEDSTARTDTS values are not ISO-8601")
  as.Date(substr(x, 1, 10))
}

## Blinded placebo-controlled trial product: the patient may have received
## placebo. "RELATLIMAB OR PLACEBO-NIVOLUMAB" is NOT blinded for ICI exposure
## (nivolumab is given in both arms), nor are "X OR <other ICI>" products.
is_blinded <- function(x) {
  grepl("or placebo", x, ignore.case = TRUE) &
    !grepl("placebo-nivolumab", x, ignore.case = TRUE)
}

2.3 Patient classification

Show code
## Patient-level classification of the post-cutoff IL2R cohort.
##   il2  : IL2R rows with EMPI, draw_date, status
##   ici  : ICI rows with EMPI, ici_date, blinded
## Returns one row per patient with >=1 valid IL2R draw on/after `cutoff`.
derive_cohort <- function(il2, ici,
                          cutoff            = CUTOFF_DATE,
                          valid_status      = VALID_STATUS,
                          include_blinded   = FALSE,
                          same_day_is_prior = FALSE) {
  post <- il2[status %in% valid_status & draw_date >= cutoff]

  pts <- post[, .(n_post_draws    = .N,
                  n_post_dates    = uniqueN(draw_date),
                  first_post_draw = min(draw_date),
                  last_post_draw  = max(draw_date)), by = EMPI]

  ici_use <- if (include_blinded) ici else ici[blinded == FALSE]
  ici_use <- ici_use[!is.na(ici_date)]
  start   <- ici_use[, .(ici_start = min(ici_date)), by = EMPI]
  ## regimen = every ICI given on the start date (e.g. "ipilimumab + nivolumab")
  reg <- ici_use[start, on = .(EMPI, ici_date = ici_start), nomatch = 0L][
    , .(ici_first_regimen = paste(sort(unique(ici_generic(MEDICATIONDSC))), collapse = " + ")),
    by = EMPI]
  start <- merge(start, reg, by = "EMPI", all.x = TRUE)

  pts[, in_ici_file := EMPI %chin% unique(ici$EMPI)]
  pts <- merge(pts, start, by = "EMPI", all.x = TRUE)

  after <- merge(post[, .(EMPI, draw_date)], start[, .(EMPI, ici_start)], by = "EMPI")
  after <- if (same_day_is_prior) after[draw_date >= ici_start] else after[draw_date > ici_start]
  a <- after[, .(n_after_draws  = .N,
                 n_after_dates  = uniqueN(draw_date),
                 first_after    = min(draw_date)), by = EMPI]
  pts <- merge(pts, a, by = "EMPI", all.x = TRUE)
  pts[is.na(n_after_draws), `:=`(n_after_draws = 0L, n_after_dates = 0L)]

  ## same-day: an IL2R draw on the ICI start date (reported separately)
  sd <- merge(post[, .(EMPI, draw_date)], start[, .(EMPI, ici_start)], by = "EMPI")[
    draw_date == ici_start, unique(EMPI)]
  pts[, same_day_draw := EMPI %chin% sd]

  pts[, group := factor(fcase(
    n_after_dates >= 1L,                GROUP_LEVELS[1],
    in_ici_file & !is.na(ici_start),    GROUP_LEVELS[2],
    in_ici_file &  is.na(ici_start),    GROUP_LEVELS[3],
    default =                           GROUP_LEVELS[4]), levels = GROUP_LEVELS)]
  pts[, days_ici_to_first_after := as.integer(first_after - ici_start)]
  pts[]
}

## The three requested counts from a derive_cohort() frame.
answer_counts <- function(pts) {
  c(post_cutoff_patients    = nrow(pts),
    q1_ici_before_draw      = sum(pts$n_after_dates >= 1L),
    q2_ge2_after_ici        = sum(pts$n_after_dates >= 2L),
    q2_ge2_after_ici_draws  = sum(pts$n_after_draws >= 2L),
    q3_not_in_ici_cohort    = sum(!pts$in_ici_file),
    in_ici_not_before       = sum(pts$in_ici_file & pts$n_after_dates == 0L))
}

3 Cohort flow

Table 3: From the RPDR patient set to the post-2015 sIL2R cohort.
Step N
RPDR patient set (IL2R lab OR Epic IL2R procedure code) 3,171
with >=1 IL2R row in the Lab file 2,321
with >=1 numeric IL2R Result on/after 2015-01-01 2,202
Table 4: Patients dropped at each step of the cohort flow above; the last two rows split the second row. A row whose Result is blank or text only (e.g. SEE MANUAL REPORT, CREDITED: …) is not a measurement.
Not in the post-2015 cohort because N
In RPDR set, but no IL2R row in the Lab file (no draw date) 850
IL2R rows, but no numeric Result on/after 2015-01-01 119
… no numeric Result at any date suppressed
… numeric Results, but all before 2015-01-01 <11

4 Request 1 — ICI before the IL2R draw

Table 5: Every patient in the post-2015 cohort (N = 2,202) falls in exactly one group. Group B2 (only a blinded or-placebo trial product) is empty in this cohort.
Group N %
A. ICI before >=1 post-2015 IL2R draw 464 21.1
B. In ICI cohort, ICI start on/after every post-2015 draw 27 1.2
C. Not in ICI cohort 1,711 77.7

4.1 Time from ICI start to the first post-ICI draw

Table 6: Group A (N = 464). Median 200 days (IQR 76-427).
ICI start to first IL2R draw N %
1-90 days 130 28.0
91-180 days 81 17.5
181-365 days 121 26.1
1-2 years 65 14.0
> 2 years 67 14.4

4.2 First ICI regimen

Table 7: Group A. Every ICI product given on the ICI start date, so combination starts (e.g. ipilimumab + nivolumab) show as one regimen; each product is labelled by the first ICI or fixed-dose co-formulation named in its description. Regimens with fewer than 11 patients are combined as ‘Other’.
ICI given on the start date N %
pembrolizumab 216 46.6
nivolumab 86 18.5
ipilimumab + nivolumab 72 15.5
durvalumab 35 7.5
cemiplimab 15 3.2
atezolizumab 14 3.0
Other 26 5.6

4.3 Calendar year

Table 8: IL2R testing with a numeric Result is concentrated from 2021, when the Epic-coded assay (el:5200007493) begins; Group A is therefore entirely in 2021 onward.
Year of first post-2015 IL2R draw Group A Group B Group C
2015-2020 0 <11 60
2021 21 <11 266
2022 54 <11 294
2023 95 <11 283
2024 172 <11 413
2025 122 <11 395

5 Request 2 — at least two measurements after ICI

117 of the 464 patients in Group A had IL2R measured on at least two different days after the ICI start date (fewer than 11 more if two draws on one day are counted separately). Group A contributes 643 post-ICI measurement days in total.

Table 9: Group A (N = 464). ‘At least 2’ = the sum of the last three rows.
Post-ICI IL2R measurement days N %
1 347 74.8
2 78 16.8
3 25 5.4
4 or more 14 3.0

6 Request 3 — not found in the ICI cohort

1,711 of the 2,202 post-2015 patients (77.7%) have no record at all in the ICI data pull. Before deciding on chart review, two independent RPDR sources were checked for the same patients.

Table 10: Cross-check against the RPDR Med file: any ICI generic, sponsor code or brand name. ‘suppressed’ = the complement within the group is below 11.
Group Patients ICI in RPDR Med (any time) ICI in RPDR Med before last IL2R draw
A. ICI before >=1 post-2015 IL2R draw 464 385 372
B. In ICI cohort, ICI start on/after every post-2015 draw 27 suppressed <11
C. Not in ICI cohort 1,711 <11 <11

The RPDR medication file finds ICI in most of Group A, which shows it captures ICI exposure well — yet it finds ICI in almost none of Group C. As a second triage signal, every Group C patient was checked in the RPDR Dia file for a malignancy code (ICD-10 C00–C96 including C4A/C7A/C7B; ICD-9 140–209.3) and, failing that, a cancer-history or chemotherapy/immunotherapy code (Z85, Z51.1x, Z92.21; V10, V58.1x). All 464 Group A patients have a malignancy code.

Table 11: Group C (N = 1,711), hierarchical: each patient is placed in the first tier that applies.
Chart-review tier (Group C) N %
1. ICI in RPDR Med file <11 –
2. Malignancy code on/before last IL2R draw 830 48.5
3. Malignancy code only after last IL2R draw 51 3.0
4. Cancer-history / chemotherapy code only suppressed –
5. No malignancy, history or chemotherapy code 784 45.8
TipSuggested chart-review scope

The RPDR medication file records an ICI for 83.0% of Group A, so it is a reasonable second net — and it finds ICI for only <11 Group C patients. Review those first. The 830 tier-2 patients (a malignancy code on or before the last IL2R draw) are where ICI given outside MGB, or before Epic, is still plausible. Tier-5 patients (784) have no malignancy, cancer-history or chemotherapy code in RPDR, so ICI exposure is unlikely — though RPDR can miss cancer care given entirely outside MGB — and a small random sample of them would confirm that before skipping the rest.

Also worth a look: <11 Group B patients have an ICI in the RPDR medication file before their last IL2R draw, even though their first ICI in the pull comes later. They may belong in Group A.

Group C tiers are in the local workbook (sheet not_in_ICI_cohort, column review_tier); the flagged Group B patients are in sheet ICI_cohort_RPDR_ICI_earlier.

7 Sensitivity analyses

Table 12: Primary row = counts; other rows = change from primary. Changes of 1-10 are shown as +/-<11, and a cohort change is ‘suppressed’ when it would reveal one by subtraction. R1 and R2 move by fewer than 11 patients under every alternative definition; R3 moves most under ‘Any IL2R row, numeric Result or not’.
Analysis Post-2015 cohort R1: ICI before draw R2: >= 2 days after ICI R3: not in ICI cohort
Primary 2,202 464 117 1,711
Include blinded or-placebo trial drug same +<11 same same
Same-day ICI counts as prior same same +<11 same
Cutoff 2016-01-01 (after calendar 2015) -<11 same same -<11
Any IL2R row, numeric Result or not suppressed +<11 +<11 +73

8 Caveats

  • ICI coverage starts May 2015. The ICI pull holds Epic MAR administrations from May 2015 to July 2026. ICI given before Epic, or outside MGB, is not in it — which is what the RPDR medication check in Section 6 is for.
  • IL2R results in the Lab file are uneven over time. The Epic-coded assay (el:5200007493) starts in 2021-04; few results exist for 2018–2020, and the latest result in the extract is from 2025-10. IL2R tests from those gaps may belong to the patients with no IL2R row in the Lab file (Table 2).
  • Date resolution. ICI start is a calendar date, so an IL2R drawn on the ICI start day is counted as not after ICI (<11 patients affected; see the ‘Same-day ICI counts as prior’ row of Table 12).
  • Units differ by assay (pg/mL for the Epic-coded assay, U/mL for the older mcsq/dcsqe assays). This does not affect any count here, but values must be harmonised before they are compared.

Aggregate results only; cells below 11 suppressed. Built from R/build.R on 2026-09-30.