Self-Controlled Case Series (SCCS)
A within-person method that uses only cases and estimates the age- and time-adjusted relative incidence of an acute outcome in exposure-defined risk windows versus a person's own baseline periods, via conditional Poisson regression conditioned on each case's total observed event count.
In plain language
The Self-Controlled Case Series (SCCS) is a study design that asks: does a person's risk of a short-lived medical event go up right after a specific exposure, like a vaccine or a short course of medication? Instead of comparing two different groups of people, it compares each person to themselves — looking at whether that person had more events during a brief window after the exposure than during their own quiet, unexposed time earlier or later in the same follow-up period. Because every person is their own comparison, characteristics that never change — like genetics, sex, or long-standing health conditions — are automatically ruled out as explanations. The catch is that this design only works when both the exposure and the outcome are short-lived and datable to a specific day.
The self-controlled case series (SCCS) estimates the relative incidence of an acute, recurrent-or-rare outcome during a transient exposure-defined risk window compared with the same individual's baseline (control) periods, using only people who experience the event ("cases"). Each case acts as their own control, so all fixed (time-invariant) confounders — genetics, sex, baseline frailty, chronic comorbidity, socioeconomic status, even unmeasured ones — are cancelled by the within-person conditioning. The likelihood conditions on the total number of events each case had over their observation period, leaving a multinomial/conditional-Poisson model for when within the observation period events fell relative to exposure. Age and other shared time-varying confounders are handled by splitting each person's follow-up into calendar/age intervals and including them as covariates. The canonical use is vaccine and drug safety, where the exposure is a discrete event (a vaccine dose, a short antibiotic course) and the outcome is acute (febrile seizure, intussusception, MI, GI bleed).
Core estimand distinction
— SCCS targets the incidence rate ratio (IRR) within persons — relative incidence during risk versus baseline time — not an absolute rate, an attributable risk, or a between-person comparative effect. It is a self-matched cohort over person-time, not a case-control study (it has no separate controls and no odds ratio) and not a standard cohort (it uses no unexposed people). Because conditioning removes the person-level baseline rate entirely, SCCS answers "does risk of the event transiently rise after exposure?" It cannot answer "how many events did the exposure cause in the population?" without external incidence data, and it cannot rank two drugs head-to-head the way an active-comparator cohort does. The conditioning that buys immunity to fixed confounding is exactly why absolute effects are unrecoverable from SCCS alone.
Interpreting the output
From the worked example: one febrile seizure falls in the 14-day vaccine risk window; across all cases in the study the conditional Poisson model yields IRR = 1.9 (95% CI 1.4–2.6) for the 0–7 day window.
Formal interpretation: Febrile seizures occurred at 1.9 times the within-person rate during the 0–7 day post-vaccination risk window compared with each child's own baseline (unexposed) person-time. The conditioning on each child's total event count removes the child's baseline seizure propensity entirely — the comparison is within-person, not between children. Because all time-invariant confounders (genetics, underlying seizure threshold, chronic comorbidity, socioeconomic status) are constant within a person, they cannot explain this temporal excess. Residual time-varying confounders shared with the post-vaccination window (concurrent illness, seasonal effects) are addressed by the age and calendar adjustments, but not eliminated. The IRR is not an absolute risk, not a risk ratio between vaccinated and unvaccinated children, and not a between-person comparative estimate.
Practical interpretation: The risk of febrile seizures is approximately 90% higher in the week after vaccination than during the same child's own quiet baseline periods. This within-person signal is robust to confounding by indication and stable background factors, making it stronger evidence of a temporal relationship than a between-person cohort comparison. Two mandatory caveats: (1) SCCS controls only time-fixed confounders — a time-varying factor that simultaneously predicts vaccination scheduling and seizure risk would residually confound the IRR; (2) if vaccine hesitancy leads clinicians to defer vaccination in children who recently seized (event-dependent exposure), the pre-exposure window check (a depressed pre-dose rate) is required to rule out bias.
Pros, cons, and trade-offs
(vs the alternatives named below). - vs cohort / active-comparator new-user designs: SCCS eliminates all time-invariant between-person confounding by construction (no PS, no matching, no measured covariates needed for fixed factors), and is dramatically more efficient per case because it needs only cases. Cost: it requires a transient exposure and an acute outcome, assumes the event does not alter future exposure or end observation, and gives only a within-person IRR — not the comparative or absolute estimands a cohort delivers. Prefer SCCS when confounding by indication and unmeasured frailty are severe and the exposure/outcome are transient and acute; prefer a cohort for chronic exposures, cumulative effects, or absolute risk. - vs self-controlled risk interval (SCRI): SCRI is the special-case SCCS that uses only a short pre-specified control window adjacent to the risk window (rather than all of each person's observation time), trading statistical efficiency and full age adjustment for robustness to long-term time trends and lighter age modeling. Prefer SCRI when secular or seasonal trends are strong and the relevant comparison is local in time (e.g., seasonal vaccines); prefer full SCCS when you need efficiency and have modeled age/season well. - vs case-crossover: Both are within-person, but case-crossover samples discrete referent windows and is built for rare, abrupt triggers of a single event with stable exposure prevalence; SCCS models the full observation period as continuous person-time and naturally handles recurrent events and time-varying exposure intensity. SCCS is more flexible for vaccines/drugs with extended risk windows; case-crossover is simpler for instantaneous triggers (e.g., physical exertion and MI).
When to use
— acute outcome with a biologically plausible transient risk window after a discrete exposure; strong or unmeasurable between-person confounding (frailty, indication) that would cripple a cohort; sparse data where retaining only cases is an efficiency advantage; signal evaluation in vaccine/drug safety (the FDA Sentinel and vaccine-safety literature lean on SCCS/SCRI heavily). It shines when randomization is impossible but the exposure is "switch-like" and the outcome is datable to a day.
When NOT to use — and when it is actively misleading or dangerous
(decision rules below). - Event-dependent exposure. If having the event changes the probability of subsequent exposure (e.g., a stroke makes a clinician stop the drug, or a death precludes future vaccination), the standard SCCS likelihood is biased. This is the single most dangerous violation: a protective-looking IRR can be an artifact of clinicians withholding exposure after the event. Use event-dependent-exposure SCCS extensions (Farrington) or a different design. - Event-dependent observation / censoring by death. If the event can end observation (death, or events that trigger disenrollment), the assumption that observation length is independent of event timing fails; use the event-dependent- observation SCCS extension. Applying naive SCCS to a high-fatality outcome (e.g., sudden cardiac death) is misleading. - Chronic or cumulative exposures, or non-acute outcomes. SCCS cannot separate a transient risk window from a stable baseline if exposure is continuous (a maintenance statin) or if the outcome accrues slowly (cancer). There is no within-person contrast to exploit. - No within-person variation in exposure timing relative to follow-up. If everyone is exposed for essentially their whole observation window, baseline person-time is near zero and the IRR is unidentified. - Outcome rate genuinely depends on calendar/seasonal time confounded with exposure. Failure to model age/season when a seasonal vaccine is given in a high-incidence season produces confounding within person; SCCS removes fixed but not unmodeled time-varying confounders.
Data-source operational depth
across claims, EHR, registry, and linked data. - Claims (FFS): Exposure dates come from pharmacy fills (`fill_date` + `days_supply` to construct the risk window) or procedure/administration codes (vaccine CPT/CVX). Outcomes are inpatient/ED diagnosis dates — prefer the inpatient admission date over the claim adjudication date. Define the observation period as continuous-enrollment spans; person-time outside continuous A/B (and D, for drug exposure) must be excluded or the "baseline" is unobserved. Failure modes: Medicare Advantage person-time lacks FFS claims, so both exposure and outcome are silently missing — restrict to FFS-enrolled spans and treat MA periods as gaps, not baseline. A claim's service date can lag the true event by days, smearing events across the risk-window boundary; pre-specify which date defines event onset. - EHR: Exposure may be an order or in-clinic administration (good for vaccines given on-site) but pharmacy linkage is needed to confirm a dispensed drug was taken. Outcome dating is sharper (problem-list onset, lab dates) but visit-driven capture means events occurring outside the system are missed; if a vaccine and its adverse event are both captured only on system visits, ascertainment is correlated with exposure — a within-person confounder. Define observation as enrollment/active-patient spans, not "first to last visit," to avoid event-dependent observation. - Registry: Excellent, adjudicated outcome dates (e.g., a febrile-seizure or intussusception registry) and often the natural data source for SCCS; weak for complete exposure history. Link to claims/immunization information systems for dose dates. National immunization registries are the gold-standard exposure source for vaccine SCCS. - Linked claims–EHR–vital records: Best substrate — claims completeness + EHR onset dating + a death index to handle event-dependent observation (mortality is the most common censoring mechanism that breaks naive SCCS). Reconcile order/fill/service date discrepancies before assigning risk windows; linkage selection (only the linkable subset) can differentially drop the sickest.
Worked claims example
Question: does the IRR of febrile seizure rise in the 0–7 and 8–14 days after a measles- containing vaccine dose in children, using a Medicare-style FFS pediatric claims database? (1) Cases: all children with ≥1 inpatient/ED febrile-seizure diagnosis (use the admission date as event onset) during observed time. SCCS uses only these children. (2) Observation period: continuous medical enrollment spans between ages 12 and 24 months; drop any MA-only or non-enrolled person-time (otherwise baseline is unobserved). (3) Exposure: vaccine administration date from the CPT/CVX claim; define risk windows `[dose, dose+7]` and `[dose+8, dose+14]`, with everything else in the observation period as baseline. (4) Age adjustment: split each child's follow-up into 1-month age bands (febrile-seizure incidence is strongly age-dependent) and include age band as a factor. (5) Model: conditional Poisson regression of the event indicator on risk-window and age-band dummies, with an offset for the log length of each interval, conditioned on each child's total seizure count — equivalently a stratified (by child) Poisson/Cox fit. (6) Read-out: exp(beta) for each risk window is the age-adjusted within-child IRR vs that child's own baseline; e.g., IRR 1.9 (95% CI 1.4–2.6) in days 0–7. (7) Sensitivity: add a pre-exposure window `[dose-14, dose-1]` to detect event-dependent exposure (a clinician deferring vaccination in a recently-seizing child shows up as a depressed pre-window), test alternative risk-window lengths, and exclude the day of vaccination to probe contraindication bias.
Worked example
Scenario
A 14-month-old child receives a measles-containing vaccine on 2024-04-09. Researchers want to know whether febrile seizures happen more often in the 14 days after the dose than during this child's own quiet time before and after that window. The child's continuous enrollment spans 2024-01-01 through 2024-09-30 (273 days total). One febrile seizure is recorded on 2024-04-14, which falls inside the risk window. The SCCS compares the daily event rate within the 14-day risk period to the daily event rate across the remaining 259 days of baseline time.
Dataset
Raw rows an analyst would see across three linked tables for this one child
| person_id | date | record_type | detail |
|---|---|---|---|
| C001 | 2024-01-01 | enrollment_start | continuous FFS enrollment begins |
| C001 | 2024-04-09 | exposure | measles-containing vaccine — CPT 90707 |
| C001 | 2024-04-14 | outcome | febrile seizure — inpatient admission date |
| C001 | 2024-09-30 | enrollment_end | continuous FFS enrollment ends |
Steps
Total observation for this child: 2024-01-01 through 2024-09-30 = 273 days.
Define the risk window: from the vaccine date (2024-04-09) through 14 days later = 2024-04-09 to 2024-04-22 = 14 days.
Define the baseline period: all observation days outside the risk window = 273 - 14 = 259 days (2024-01-01 to 2024-04-08 plus 2024-04-23 to 2024-09-30).
Count events in the risk window: 1 febrile seizure on 2024-04-14.
Count events in the baseline period: 0 febrile seizures.
Rate in risk window: 1 event / 14 days = 0.0714 events per day.
Rate in baseline period: 0 events / 259 days = 0.0 events per day.
Incidence rate ratio = 0.0714 / (0.0 + small background rate) — in the conditional Poisson model fitted across all cases in the study, the per-child comparison is pooled. For this single case, the event fell in the risk window rather than baseline, contributing evidence that the risk window rate is elevated versus baseline.
Result
For this child, the only event landed in the 14-day risk window (14 days) rather than the 259 days of baseline time. Across all children in the study, the conditional Poisson model would estimate an incidence rate ratio (IRR) for the risk window versus baseline. A study using these methods in the vaccine safety literature found IRR values around 1.9 (95% CI 1.4-2.6) for the 0-7 day window, meaning febrile seizures occurred at roughly twice the rate during the risk period compared with each child's own quiet baseline time.
Timeline Spec
- Title
SCCS follow-up for one child — vaccine-day risk window vs own baseline
- Window
- Start
2024-01-01
- End
2024-09-30
- Label
273-day continuous enrollment (observation period)
- Events
- Label
Vaccine dose (2024-04-09)
- Start
2024-04-09
- Length Days
1
- Quantity
1-day exposure milestone
- Label
Febrile seizure (2024-04-14)
- Start
2024-04-14
- Length Days
1
- Quantity
outcome event — day 5 of risk window
- Spans
- Kind
unexposed
- Start
2024-01-01
- End
2024-04-08
- Label
Baseline: 99 days (pre-exposure)
- Kind
exposed
- Start
2024-04-09
- End
2024-04-22
- Label
Risk window: 14 days after vaccine
- Kind
unexposed
- Start
2024-04-23
- End
2024-09-30
- Label
Baseline: 160 days (post-risk-window)
- Result
- Label
1 event in 14 risk-window days vs 0 events in 259 baseline days — within-person IRR estimated by conditional Poisson across all cases
- Value
1.9
- Caption
This child's 273-day enrollment is split into 259 days of baseline time (blue, before and after the risk window) and a 14-day risk window after the vaccine dose (orange). The single febrile seizure fell on day 5 of the risk window. The SCCS compares the event rate per day in the orange band to the event rate per day in the blue bands — using only this child's own data, so fixed traits like genetics are not a factor.
- Alt Text
Timeline showing a 273-day enrollment period for one child split into two blue baseline spans (99 days before the vaccine and 160 days after the risk window) and one orange 14-day risk window starting at the vaccine dose date, with a marker for the febrile seizure event on day 5 of the risk window.
Runnable example
python implementation
SCCS data construction + conditional Poisson estimation from claims-style inputs. Required inputs (cleaned): cases : one row per case -> person_id, obs_start (datetime), obs_end (datetime) # continuous FFS-enrolled span exposures: vaccine/drug dates ->...
import numpy as np
import pandas as pd
import statsmodels.formula.api as smf
AGE_BAND_DAYS = 30 # split follow-up into 30-day age bands to absorb time-varying age confounding
def build_intervals(cases: pd.DataFrame, exposures: pd.DataFrame) -> pd.DataFrame:
rows = []
exp_by_person = exposures.groupby("person_id")
for _, c in cases.iterrows():
pid, start, end = c["person_id"], c["obs_start"], c["obs_end"]
# Cut points: observation bounds, age-band edges, and each exposure's risk-window edges.
cuts = pd.to_datetime([start, end])
edges = pd.date_range(start, end, freq=f"{AGE_BAND_DAYS}D")
cuts = cuts.append(edges)
if pid in exp_by_person.groups:
for _, e in exp_by_person.get_group(pid).iterrows():
r0, r1 = e["exposure_date"], e["exposure_date"] + pd.Timedelta(days=int(e["risk_len"]))
cuts = cuts.append(pd.to_datetime([r0, r1]))
cuts = pd.Series(cuts).clip(lower=start, upper=end).drop_duplicates().sort_values().reset_index(drop=True)
for a, b in zip(cuts[:-1], cuts[1:]):
if b <= a:
continue
mid = a + (b - a) / 2
in_risk = False
if pid in exp_by_person.groups:
for _, e in exp_by_person.get_group(pid).iterrows():
if e["exposure_date"] <= mid < e["exposure_date"] + pd.Timedelta(days=int(e["risk_len"])):
in_risk = True
break
age_band = int((mid - start).days // AGE_BAND_DAYS)
rows.append(dict(person_id=pid, start=a, end=b,
length=(b - a).days, risk=int(in_risk), age_band=age_band))
return pd.DataFrame(rows)
def fit_sccs(intervals: pd.DataFrame, events: pd.DataFrame) -> "smf.glm":
# Count events per interval (events fall on event_date within [start, end)).
iv = intervals.copy()
iv["n_events"] = 0
ev = events.merge(iv, on="person_id")
ev = ev[(ev["event_date"] >= ev["start"]) & (ev["event_date"] < ev["end"])]
counts = ev.groupby(["person_id", "start"]).size().rename("k")
iv = iv.merge(counts, on=["person_id", "start"], how="left")
iv["n_events"] = iv["k"].fillna(0).astype(int)
iv = iv[iv["length"] > 0].copy()
iv["log_len"] = np.log(iv["length"])
# Conditional Poisson via person fixed effects + offset = stratified within-person likelihood.
model = smf.glm(
"n_events ~ C(risk) + C(age_band) + C(person_id)",
data=iv,
offset=iv["log_len"],
family=__import__("statsmodels.api", fromlist=["families"]).families.Poisson(),
).fit()
return model # exp(params['C(risk)[T.1]']) = within-person, age-adjusted IRRr implementation
SCCS using the SCCS package (Farrington/Whitaker reference implementation). Inputs: cases : indiv, astart, aend # observation start/end in days-of-age (or days since origin) exposures : indiv, edate # exposure date in the same time scale events : indiv,...
library(SCCS)
# `cd` is one row per (person, exposure) with observation bounds and event days already merged.
# indiv, astart, aend : observation window (e.g., days of age 365..730)
# edate : exposure date; risk window = [edate, edate + 14]
# eventday : day of the acute outcome (multiple rows per person allowed)
fit <- standardsccs(
event ~ vacc,
indiv = indiv,
astart = astart,
aend = aend,
aevent = eventday,
adrug = edate,
aedrug = edate + 14, # 14-day risk window after exposure
agegrp = seq(min(cd$astart), max(cd$aend), by = 30), # 30-day age bands
data = cd
)
summary(fit) # exp(coef) = within-person, age-adjusted IRR with 95% CI
# ---- Manual conditional-Poisson fallback (custom windows / diagnostics) ----
library(gnm)
# `iv` = pre-split intervals: indiv, n_events, risk (0/1), age_band (factor), log_len (offset)
cp <- gnm(n_events ~ risk + age_band, eliminate = factor(indiv),
family = poisson, offset = log_len, data = iv)
exp(coef(cp)["risk"]) # IRR; eliminate=indiv gives the within-person conditional fit