Signal Detection: From First Principles to a Validated Signal
One course, three depths. Climb from first principles to disproportionality statistics and Bayesian methods, learn exactly how to build and optimise the detection pipeline, and get the cross-testing angles that prove every number is correct — with a live calculator and an animated end-to-end flow.
Foundations
Start at zero. Every term defined, then built up to disproportionality and Bayesian methods — with a worked example you can follow.
Build track
How the data flows, the schema, the engine pseudocode, the workflow state machine, performance and the pitfalls to avoid.
QC & review
How to test the system from every angle — positive/negative controls, hand-calc cross-checks, data integrity, edge cases — so no detail is wrong.
What signal detection is
Finding a new safety concern about a medicine — earlier and more reliably than reading cases one by one ever could.
The definition
A signal is information suggesting a new potentially causal association — or a new aspect of a known one — between a medicine and an event, serious enough to warrant verification. The key word is suggests: a signal is a hypothesis, never a proven fact.
Why it exists
A trial has a few thousand patients. After launch, millions use the drug in combinations, doses and conditions no trial covered. Rare and delayed harms surface only there. Signal detection watches that real-world population continuously.
Think of it like antivirus for a medicine. Antivirus doesn't wait for one expert to inspect every file — it scans everything and raises alerts on suspicious patterns. Signal detection scans every drug–event combination and raises alerts (SDRs) on statistically suspicious ones. A human then confirms or dismisses each alert.
Signal detection generates hypotheses. It never, by itself, proves a drug caused harm — proof comes later, in validation and assessment.
PV vocabulary & the anatomy of a case
Before any maths, you must speak the language. These are the words every PV person uses daily — and every developer must model correctly in the database.
| Term | Plain meaning | Why it matters for detection |
|---|---|---|
| AE (Adverse Event) | Any bad medical happening during treatment — cause not yet known. | Raw input; not yet linked to the drug. |
| ADR (Adverse Drug Reaction) | An AE where a causal link to the drug is at least suspected. | The thing detection is ultimately about. |
| ICSR | Individual Case Safety Report — one patient's full report. | The unit that gets counted. |
| PT / MedDRA | The standard coded term for the event (Preferred Term). | Disproportionality is computed on Drug × PT. |
| Seriousness | Death, life-threatening, hospitalisation, disability, congenital anomaly, or an IME. | Serious cases get priority & expedited reporting. |
| Expectedness | Is the event already in the drug's label? | Unexpected + serious = highest signal interest. |
| Causality | How likely the drug caused it (WHO-UMC / Naranjo scales). | Central to validating a signal. |
The MedDRA hierarchy foundational
Events are coded into a 5-level tree. Detection usually runs at PT level, but you can roll up to spot broader patterns.
Store the LLT (what the reporter actually wrote) and the mapped PT — never overwrite the verbatim. Detection queries group by PT; audits and re-coding need the original LLT. Model the hierarchy as a versioned lookup (MedDRA updates ~twice a year), so historical cases keep the coding valid at their time.
Causality scales intermediate
WHO-UMC
A categorical ladder a reviewer assigns: Certain → Probable → Possible → Unlikely → Conditional → Unassessable. Uses time relationship, dechallenge, rechallenge, plausibility, and alternative causes.
Naranjo
A 10-question scored questionnaire → total maps to Definite (≥9) / Probable (5–8) / Possible (1–4) / Doubtful (≤0). More reproducible because it's numeric.
Where it sits: the lifecycle
This settles the most common confusion. Quantitative detection runs after case processing — because it needs coded, de-duplicated, locked data to count.
1 · Coding needed
PRR/ROR use Drug × PT pairs. No MedDRA coding → no pair → no maths.
2 · De-duplication
A case counted twice inflates n → false signal. Dedup is a processing job.
3 · Locked data
Run on half-finished cases and the result can't be defended.
A single case can be a signal (e.g. agranulocytosis with positive rechallenge). But even it must be processed and coded first — so processing always precedes detection. Here detection is qualitative, not counting.
Data — where to take it, and why
Two families of data feed detection. They answer different questions — so both are needed, not alternatives.
Your own processed cases
Answers: “how many / how often?” — the numbers.
- Where: your safety database — locked, coded ICSRs.
- Why: your own denominator lets you compute disproportionality reliably.
- Benefit: statistical proof a regulator can re-check line by line.
The world outside your DB
Answers: “is it real? why? did anyone see it first?”
- Where: FAERS, WHO VigiBase, PubMed, regulatory sites, social media.
- Why: early warning + causal evidence your counts can't give.
- Benefit: confirmation & context — defensible from two directions.
External is not all qualitative. FAERS & VigiBase are external quantitative confirmation (they run disproportionality too). Literature, mechanism and social listening are the qualitative layer.
Quantitative methods — with a live calculator
Every disproportionality method starts from one small table. Understand it once, and the rest is just different ways of dividing it.
The 2×2 contingency table foundational
a = reports of this drug with this event — the cell we care about. b, c, d from the rest of the database.
The four core measures
PRR = [a/(a+b)] / [c/(c+d)]
ROR = ad / bc
χ² = N(ad−bc)² / [(a+b)(c+d)(a+c)(b+d)]
IC = log₂( a·N / [(a+b)(a+c)] )
Worked example — follow every step intermediate
Scenario: Drug X, event “hepatotoxicity”. Database has 100,000 reports. Drug X appears in 500 reports; 25 of them mention hepatotoxicity. Across all other drugs, 300 reports mention hepatotoxicity.
16.6 looks dramatic, but it's still only a statistical alert. Maybe those 25 cases are duplicates, or all from one hospital, or the drug is genuinely hepatotoxic. Validation decides — not the ratio.
Live calculator — try your own numbers
Change a, b, c, d and watch every measure and every signal flag update. Newcomers build intuition; reviewers use it to hand-verify the platform's output.
| Method | Family | Typical signal threshold | Best when… |
|---|---|---|---|
| PRR | Frequentist | PRR ≥ 2, χ² ≥ 4, a ≥ 3 | first-line screening |
| ROR | Frequentist | lower 95% CI > 1 | adjusting for confounders |
| IC / BCPNN | Bayesian | IC025 > 0 | rare events (shrinkage) |
| EBGM (MGPS) | Bayesian | EB05 > 2 | large DBs, many pairs |
Advanced caveats every reviewer checks advanced
Masking / competition bias
A very frequent drug–event pair can hide (mask) a real signal for another pair by inflating the background. Re-run with the dominant pair removed.
Small-count instability
At a=3–5, PRR swings wildly. Bayesian shrinkage (IC/EBGM) pulls weak estimates toward the null — fewer false alarms.
Confounding & stratification
Age, sex, indication can drive a spike. Stratify (compute within subgroups) before believing the headline ratio.
Qualitative & single-case detection
Not every signal is a number. Some of the most important are caught by a trained eye on a single well-documented case.
Designated Medical Events (DME/IME)
A short watch-list so serious and so drug-specific that one credible case opens a signal — no waiting for counts.
- Agranulocytosis
- Stevens–Johnson syndrome / TEN
- Anaphylaxis
- Rhabdomyolysis
- PML
- Acute liver failure, torsades de pointes…
What makes one case a signal
Clinical judgement, not arithmetic. A reviewer weighs:
- Positive rechallenge — event returned on restart.
- Dechallenge — resolved when stopped.
- Biological plausibility — a known mechanism fits.
- No confounders — nothing else explains it.
- Temporal link — timing fits exposure.
This is why detection is quantitative AND qualitative. The platform surfaces the maths; a PV reviewer catches rare, high-value single-case signals the maths misses because n is too small.
External sources & the routing rule
The same source — PubMed or social media — plays two roles. Fetch once, route twice. This is the design decision that keeps the platform clean.
Same source, two purposes
In intake, PubMed answers “is there a reportable case here?” In the signal app, it answers “what evidence supports or detects this drug–event signal?” Same source, different purpose — not a duplicate.
Social media listening
Most posts aren't valid ICSRs. But a sudden spike of one symptom for one drug is an emerging-issue hint — an aggregate signal, not a case. It feeds the emerging-issue tracker, not the case queue.
Intake creates cases. The Signal app only reads, links and assesses. If the Signal app finds a new reportable case, one button sends it to intake — it never creates the case itself.
Build a single ingestion worker that fetches & de-duplicates once, writing to a raw documents store. A separate classifier/router reads each doc and emits two independent events: case.candidate → intake and evidence.linked → signal. Never let the signal service write to the case table — enforce it with DB permissions, not just code review.
SDR → validated signal
The single most important idea in this course: a statistical spike is not a confirmed signal. It is an SDR — an alert to investigate.
Is it real?
Check underlying cases: duplicates removed? coding right? already labelled? confounder? Many SDRs die here — correctly.
What does it mean?
Full causality review — Bradford Hill, mechanism, external data, benefit-risk. This is where literature earns its place.
What do we do?
Label update, PSUR/PBRER entry, RMP change, or documented “no action”. Every step auditable.
“PRR spiked, so the drug causes X.” Wrong. Correct: “A statistical SDR was raised; on validation and assessment it was [confirmed / refuted / already known].” The word SDR keeps you honest and defensible.
The complete flow — animated
Everything above in one moving picture. Two feeder lanes merge into detection; a spike becomes an amber SDR; validation turns it green.
How to build it — the engineering
The same flow as a technical pipeline. This section is written for the developer who must design, build and optimise it.
Data layer — analysis-ready store
Locked ICSRs materialised into an aggregate table keyed by drug × PT × period. Multi-tenant, partitioned by tenant, so each client's counts stay isolated.
Disproportionality engine
On each data-lock it rebuilds the 2×2 for every drug–event pair and computes PRR, ROR, IC/BCPNN, EBGM with CIs and the a ≥ 3 guard.
Qualitative & AI layer
DME/IME watch-list auto-flags single serious cases. AI assists triage — clustering narratives, summarising, drafting the assessment — but a qualified reviewer always decides.
External connectors
Shared ingestion pulls FAERS/VigiBase, PubMed and social once; the router splits case-worthy content to intake, evidence/trend to the Signal Evidence Explorer.
Workflow & audit
SDR → validation → assessment → decision is a state machine. Every transition is e-signed and time-stamped for 21 CFR Part 11 / EU Annex 11.
The aggregate schema intermediate
-- one row per drug × PT × period × tenant; rebuilt at each data-lock CREATE TABLE signal_counts ( tenant_id uuid NOT NULL, -- partition key, tenant isolation data_lock date NOT NULL, -- the cut-off this snapshot belongs to drug_id bigint NOT NULL, pt_code int NOT NULL, -- MedDRA Preferred Term a int, b int, c int, d int, -- the 2x2 cells prr numeric, ror numeric, chi2 numeric, ic025 numeric, eb05 numeric, -- lower bounds drive the flag is_sdr boolean, -- a>=3 AND prr>=2 AND chi2>=4 PRIMARY KEY (tenant_id, data_lock, drug_id, pt_code) ) PARTITION BY LIST (tenant_id);
The engine, in pseudocode intermediate
def run_disproportionality(tenant, data_lock):
df = load_locked_cases(tenant, data_lock) # coded, de-duplicated only
N = len(df)
for (drug, pt) in distinct_pairs(df):
a = count(df, drug=drug, pt=pt)
b = count(df, drug=drug) - a
c = count(df, pt=pt) - a
d = N - a - b - c
if a < 3: # stability guard — skip / flag low
store(drug, pt, a,b,c,d, is_sdr=False); continue
prr = (a/(a+b)) / (c/(c+d))
ror = (a*d) / (b*c)
chi2 = N*(a*d-b*c)**2 / ((a+b)*(c+d)*(a+c)*(b+d))
ic = log2( a*N / ((a+b)*(a+c)) ) # Bayesian variants add shrinkage
is_sdr = (prr >= 2) and (chi2 >= 4) and (a >= 3)
store(drug, pt, a,b,c,d, prr, ror, chi2, ic, is_sdr)- Precompute at data-lock, don't compute on read. Dashboards read a snapshot table; never recalculate 2×2s on every page load.
- Guard divide-by-zero. Zero cells (a=0 or b=0) break ROR. Use continuity corrections or skip with a clear flag.
- Numeric precision. Use decimal/float64 carefully; huge d values × tiny ratios lose precision if you multiply in the wrong order.
- Store the 2×2, not just the ratio. An auditor must reproduce your number from a, b, c, d — keep them.
- Never mutate a locked snapshot. A new data-lock = a new row set. History stays immutable for audit.
The workflow state machine advanced
# every transition writes an immutable, e-signed audit row
DETECTED ▶ SDR_RAISED ▶ UNDER_VALIDATION ─┬▶ REFUTED (closed, reason logged)
├▶ ALREADY_KNOWN (closed, ref to label)
└▶ VALIDATED ▶ UNDER_ASSESSMENT
├▶ NO_ACTION (documented)
└▶ ACTION ▶ LABEL / PSUR / RMPHow to test it — cross-check from every angle
This section is for the reviewer who must prove the system is correct before anyone trusts a signal from it. Test from six independent angles — if all pass, no single detail is wrong.
You are not testing whether the drug is dangerous — you are testing whether the tool computes and records correctly. That's computer system validation (OQ/PQ): feed known inputs, compare to a known-correct expected output, and prove reproducibility. If the maths is right on controlled data, you can trust it on real data.
The six cross-testing angles
| Angle | Question it answers | How to test |
|---|---|---|
| 1 · Math correctness | Does PRR/ROR/IC match a hand calculation? | Feed the worked example (a=25…); expect PRR≈16.6, ROR≈17.4. |
| 2 · Positive control | Does a known true signal fire? | Inject a well-established drug–ADR pair; it must raise an SDR. |
| 3 · Negative control | Does noise stay quiet? | Inject a random, implausible pair; it must NOT raise an SDR. |
| 4 · Edge cases | a<3, zero cells, single case? | Confirm a<3 is not flagged; zero cell doesn't crash; DME single case still flags qualitatively. |
| 5 · Data integrity | Are duplicates & coding clean? | Seed known duplicates; confirm counts don't inflate. Check PT roll-up. |
| 6 · Reproducibility & audit | Same input → same output, fully traced? | Re-run the same data-lock; numbers identical; audit trail shows who/when/what. |
A sample OQ test script
| # | Test step | Input | Expected result | Pass? |
|---|---|---|---|---|
| 1 | Compute PRR on known 2×2 | 25,475,300,99200 | PRR 16.5–16.7 | ☐ |
| 2 | Low-count guard | a=2 | Not flagged as SDR | ☐ |
| 3 | Zero-cell handling | b=0 | Graceful (no crash / correction) | ☐ |
| 4 | Positive control fires | known ADR pair | is_sdr = true | ☐ |
| 5 | Negative control silent | random pair | is_sdr = false | ☐ |
| 6 | Re-run reproducibility | same data-lock ×2 | identical outputs | ☐ |
Every number the system produces must be Attributable, Legible, Contemporaneous, Original, Accurate — plus Complete, Consistent, Enduring, Available. Concretely: who ran the analysis and when, the exact input snapshot, the original verbatim preserved, the calculation reproducible, nothing silently overwritten. If any of these fails, the signal isn't defensible no matter how correct the maths.
Denominator uses total reports vs total drug exposures (be consistent) · duplicates not removed before counting · threshold applied without the a≥3 guard · masking not checked on high-volume pairs · a confirmed signal stated as causal without assessment · audit trail missing the “no action” decisions.
Golden rules — recap
If a new joiner remembers only these, they can already read a signal the correct way.
- Processing first, detection after — quantitative maths needs coded, de-duplicated, locked cases.
- A spike is an SDR, not a signal — an alert to investigate, never a conclusion.
- Detection is quantitative AND qualitative — one strong DME case can be a signal at n = 1.
- Own cases measure; external confirms — FAERS/VigiBase confirm quantitatively, literature/social give evidence.
- Statistics needs enough n — for rare events, lean on qualitative + external early warning.
- Fetch once, route twice — the Signal app reads and links; only intake creates cases.
- Prove it before you trust it — positive/negative controls, hand-calc cross-check, reproducibility, ALCOA+.
- Everything is audit-trailed — if it isn't recorded and e-signed, to a regulator it didn't happen.
Frequently asked questions
Common questions
A signal is information suggesting a new potentially causal association between a medicine and an event, serious enough to warrant verification — it is a hypothesis, never a proven fact. An SDR (Signal of Disproportionate Reporting) is the statistical alert a disproportionality method raises (e.g. PRR ≥ 2, chi-square ≥ 4, a ≥ 3). An SDR only becomes a validated signal after it passes validation (is it real?) and assessment (what does it mean, causally?).
Quantitative disproportionality methods count Drug × MedDRA Preferred Term pairs, which requires coded, de-duplicated, locked data. Without MedDRA coding there is no pair to count; without de-duplication a case counted twice inflates the count into a false signal; and running on half-finished cases produces a result that can't be defended. The one exception is a single, well-documented Designated Medical Event case, which can be a qualitative signal at n = 1 — but it still has to be processed and coded first.
All four measures are computed from the same 2×2 table (a = this drug with this event, b/c/d fill out the rest of the database). PRR and ROR are frequentist ratios, typically screened with a ≥ 3, PRR ≥ 2 and chi-square ≥ 4. IC (Information Component / BCPNN) and EBGM (MGPS) are Bayesian methods that shrink weak, low-count estimates toward the null, which makes them more stable for rare events where a is small.
The same fetched content plays two roles depending on the question being asked: in intake it answers is there a reportable case here?; in the signal system it answers what evidence supports or detects this drug–event signal? The design rule that prevents duplication is fetch once, route twice — a single ingestion worker fetches and de-duplicates once, and a router emits two independent events (case.candidate to intake, evidence.linked to signal). The signal system never creates cases itself.
Test it the way computer-system validation (OQ/PQ) tests any tool: you are not testing whether the drug is dangerous, you are testing whether the tool computes and records correctly. Six angles cover it — math correctness against a hand calculation, a positive control (a known true pair must fire), a negative control (a random pair must stay quiet), edge cases (a<3, zero cells, single-case DME), data integrity (duplicates, PT roll-up), and reproducibility (the same data-lock re-run must give identical, audit-trailed output).
Screening tens of thousands of pairs at a nominal significance threshold guarantees a large number of false positives by chance alone, so raw p-values or an uncorrected chi-square cutoff are not enough at scale. Two established mitigations: run a Bayesian shrinkage method (IC/BCPNN or EBGM/MGPS) instead of a pure frequentist ratio, since shrinkage pulls noisy, low-count estimates toward the null before they can fire; and, if you keep a frequentist screen, apply a multiplicity adjustment (e.g. a false-discovery-rate control) rather than a flat PRR ≥ 2 cutoff across the whole pair space. Either way, treat the review queue's false-positive rate as a metric you monitor and tune, not a one-time design decision.
The standard 2×2 (drug × event) doesn't capture interaction effects — a case with two co-administered drugs and an event can look identical to two separate single-drug reports in a naive count. Interaction signal detection instead compares the observed rate of the drug pair + event combination against the rate expected if the two drugs' individual effects were simply additive (an Omega shrinkage measure, or an interaction term added to a logistic/Bayesian model). This is a materially different computation from single-drug disproportionality, not a variant of it — plan for a separate pair-level aggregate table (drug A × drug B × PT) if interaction detection is in scope, rather than trying to bolt it onto the drug × PT schema.
The mechanics are similar but the interpretation differs enough that vaccine signal detection is treated as its own discipline. Vaccines are typically given once or a few times to large, otherwise-healthy cohorts (often children), so background incidence of the event in the unexposed population matters more than it does for a chronic-use drug — observed-versus-expected (using an independent background incidence rate) is used alongside or instead of PRR/ROR precisely because the comparator arm inside spontaneous-report data ("other vaccines") is a weaker denominator than an epidemiological background rate. If a platform supports vaccine PV, the disproportionality engine needs a background-rate input path, not just the drug × PT database comparator.
Validation confirms the SDR is real (not duplicates, coding error, or already labelled); assessment is the separate step that weighs causality once validity is established, and Bradford Hill's criteria are the structured checklist a reviewer works through there — strength of association (how large is the effect versus background), consistency (seen across multiple, independent case series or databases), temporality (exposure clearly precedes the event), biological plausibility (a mechanism fits), dose-response, and coherence with related information (e.g. class-effect precedent). No single criterion is decisive and Bradford Hill is a framework for documented judgement, not a scoring formula — which is exactly why the assessment step's reasoning has to be recorded in the audit trail alongside the SDR's statistics, not just the final action taken.
At high volume, a fixed PRR ≥ 2 / a ≥ 3 screen becomes noisy in the opposite direction from the small-n problem: with tens of thousands of reports, statistically significant but clinically trivial ratios (PRR = 2.1 driven by a huge denominator) start firing constantly, and masking from the product's own dominant, well-known reactions gets worse as those reactions accumulate more reports. The usual response is to move from a single flat threshold to a stratified and trend-aware review — track a pair's ratio over successive data-locks rather than reacting to one snapshot, stratify by market/region/time period to unmask pairs hidden behind a dominant reaction, and set review priority by clinical seriousness of the event, not purely by the size of the ratio.
