← Methods repository
concept

Logistic Regression for Binary Outcomes

A generalized linear model that links the log-odds of a binary outcome to a linear predictor via the logit, estimated by maximum likelihood, yielding (conditional) odds ratios as its native effect measure for fixed-window dichotomous endpoints in real-world data.

Inferential_Statisticslogistic-regressionbinary-outcomesodds-ratiologitnon-collapsibilitypooled-logisticstandardizationclaims
Methods reference only. Use primary source citations and local policy before applying this in a study protocol, regulatory submission, payer dossier, or clinical decision.

In plain language

Logistic regression is a statistical method for asking: among two groups of patients, which group was more likely to experience a yes-or-no event — a hospitalization, a treatment response, a side effect — while accounting for differences between the groups? It works by estimating, for each patient, the probability of the event based on their characteristics, and the key number it produces is an odds ratio, which compares how much more often the event occurred in one group than the other. One honest limitation: when the event is common (say, more than 10% of patients), the odds ratio can make a difference look bigger than it really is, so analysts often convert it to a risk difference or risk ratio before reporting to clinicians or payers.

Logistic regression models a binary outcome Y in {0,1} through logit(P(Y=1|X)) = beta0 + beta'X, so that P(Y=1|X) = 1 / (1 + exp(-(beta0 + beta'X))). Each coefficient beta_j is a change in log-odds per unit of X_j, and exp(beta_j) is the conditional (covariate-adjusted) odds ratio. Parameters are fit by maximum likelihood (iteratively reweighted least squares / Newton-Raphson). In real-world evidence (RWE) it is the default for a dichotomous endpoint observed over a fixed window — response yes/no at 12 weeks, any major bleed within 90 days, 30-day readmission, treatment initiation, persistence-to-1-year — i.e., questions with no informative variation in follow-up time, or where time has been removed by a landmark or a fixed risk window.

Core conceptual distinction

Two distinctions decide whether logistic regression is the right tool and how to read its output. (1) Odds ratio vs risk ratio vs risk difference. The OR is the only effect measure logistic regression produces directly, but it is rarely the clinically or policy-relevant quantity. The OR approximates the risk ratio only when the outcome is rare in both arms; for common outcomes it is numerically further from 1 than the RR and is routinely misread as an RR, exaggerating the apparent effect. The RD (and number-needed-to-treat) is what HTA and clinicians usually want. Recover the marginal RR/RD by standardization / g-computation (predict each subject's risk under treated and untreated, average, contrast) rather than reporting the OR as if it were a risk ratio. (2) Conditional vs marginal effect. The covariate-adjusted OR from a logistic model is a conditional (subject-specific) effect that is not collapsible: adding a strong, even non-confounding, covariate moves the OR away from the null even when the marginal effect is unchanged. This non-collapsibility means an adjusted OR and an IPTW-weighted marginal OR answer different questions and need not agree — a point that confuses comparisons across models and must be pre-specified in the estimand.

Pros, cons, and trade-offs

- vs Cox proportional-hazards regression (cox-ph-regression): Logistic is correct when the endpoint is a fixed-window yes/no with no meaningful censoring or time-to-event structure, and it sidesteps the proportional-hazards assumption entirely. Cox is correct when follow-up varies, censoring is informative about person-time, or the question is "how fast." Forcing logistic onto a time-to-event question silently assumes everyone is observed for the full window and discards differential follow-up. Prefer logistic only when the risk window is administratively fixed and complete for both arms; otherwise use Cox or its discrete-time logistic expansion (below). - vs modified Poisson / log-binomial relative-risk regression (poisson-negative-binomial-count-models): When the outcome is common and the RR is the target, log-binomial gives the RR directly but frequently fails to converge; Zou's modified Poisson (Poisson with a robust/sandwich variance) is the robust workhorse for adjusted RRs. Logistic remains numerically the most stable and is preferred when you will standardize to a marginal RR/RD anyway, or when the OR is genuinely the estimand (case-control sampling). - vs the linear probability model (OLS on 0/1): OLS gives RDs directly and collapsible coefficients, but predicts probabilities outside [0,1], is heteroscedastic by construction, and behaves badly near the bounds. Prefer logistic for inference and prediction; consider the LPM only as a quick RD sanity check. - vs flexible ML classifiers (predictive-and-causal-ml-models-rwe): Tree ensembles / penalized learners can out-discriminate logistic in very high-dimensional claims, but they forfeit transparent coefficients and require targeted-learning / double-ML wrappers for valid causal inference. Logistic is the interpretable outcome model inside g-computation, AIPW, and TMLE.

When to use

A truly binary endpoint over a fixed, administratively complete risk window (response at a landmark, an event within a pre-specified attribution window, an initiation/persistence flag); a case-control design where the OR is the only estimable measure; the outcome-regression component of g-computation, AIPW, or TMLE for a binary outcome; or a discrete-time survival problem reframed as pooled logistic regression (expand person-time into intervals, model the hazard of the event in each interval with a flexible function of time), which approximates Cox when intervals are short and per-interval events are rare and is the standard machinery inside many target-trial emulations.

Interpreting the output

Consider a logistic model for 90-day hospitalization comparing Drug A vs Drug B, yielding an adjusted coefficient of 1.792, so exp(1.792) = 6.0 (95% CI 3.1–11.6) from the worked example above (Drug A: 40/100 events; Drug B: 10/100 events).

Formal interpretation: Patients in Drug A had 6.0 times the adjusted odds of 90-day hospitalization compared to Drug B patients, holding baseline covariates fixed. This is a conditional (covariate-specific) odds ratio — the effect for a patient with a given covariate profile, not the average across the population. Because the outcome is common (40% in Drug A), the OR of 6.0 materially overstates the relative frequency: the risk ratio for this data is 40%/10% = 4.0, not 6.0. OR ≠ RR whenever outcome prevalence is not rare in both arms; reporting OR = 6.0 as "six times the risk" is an interpretation error. The OR is also noncollapsible — adding a strong but non-confounding prognostic covariate will push the conditional OR further from the null even when the marginal effect is unchanged.

Practical interpretation: For clinical or HTA communication, report the marginal risk difference alongside the OR. Here, the risk difference is 40% − 10% = 30 percentage points (approximately 30 additional hospitalizations per 100 Drug A patients). That absolute figure, not the OR, is what drives a cost-effectiveness calculation or a number-needed-to-harm statement. Use g-computation to standardize the logistic model to the marginal RD and RR rather than presenting the conditional OR as the effect size.

When NOT to use — and when it is actively misleading or dangerous

- Differential or incomplete follow-up. If one arm is observed longer (disenrollment, death, end of data) a fixed-window logistic treats unobserved person-time as event-free, biasing toward the better-retained arm. Use time-to-event methods, not logistic, when the window is not complete for everyone. - Reporting the OR as a risk ratio for a common outcome. With baseline risk of, say, 30-40%, an OR of 0.5 is not a halving of risk; quoting it as such overstates benefit. Standardize to RR/RD. - Adjusting for post-baseline variables. Conditioning on mediators or colliders measured after time zero (on-treatment labs, downstream procedures) induces collider/mediator bias — a logistic model invites this because "more covariates" looks like better adjustment. Restrict covariates to the baseline window. - Separation and sparse data. With rare events or rare exposure-covariate cells, MLEs diverge (perfect or quasi-complete separation): coefficients explode and Wald CIs become meaningless. Use Firth penalized likelihood (firth-penalized-regression-rwe) or exact logistic, and never trust an OR with an astronomically wide CI. - Within-patient or clustered data analyzed as independent. Multiple eligible episodes per patient, or facility clustering, violate the independence assumption; naive SEs are too small. Use GEE (gee-population-average-models-rwe) or cluster-robust (sandwich) variances.

Data-source operational depth

- Claims (FFS vs MA vs commercial): The binary outcome is built from validated diagnosis/procedure code lists in a fixed window after time zero (e.g., first inpatient MI code within 90 days). Two failure modes dominate. (1) Unobserved person-time. Medicare Advantage encounter data are incomplete relative to fee-for-service claims, so a "no event" can be missingness rather than a true non-event — restrict to FFS Parts A/B (and D for drug exposure) and exclude MA-only person-time, or you will misclassify outcomes differentially. (2) Detection / surveillance bias. The exposure arm may be monitored more intensively (more visits, more testing), so the same true risk produces more coded events — a differential misclassification that inflates or deflates the OR depending on direction. Mitigate with high-PPV validated algorithms and quantify with PPV/sensitivity from a validation substudy. Always confirm continuous medical enrollment across the entire risk window so absence of a code is observed, not unobserved. - EHR: Outcomes come from labs against thresholds (HbA1c <7% as "controlled"), problem lists, orders, or NLP on notes — richer for severity than claims but plagued by visit-driven, missing-not-at-random capture: sicker, in-system patients accrue more recorded events (informed-presence / coding-intensity bias). Patients who leave the system are differentially lost; treat that as informative and run missing-data sensitivity analyses or multiple imputation rather than complete-case logistic. - Registry: Cleanest binary endpoints (adjudicated response, graded AEs, confirmed readmission) and the natural benchmark for validating a claims/EHR logistic outcome algorithm, but typically incomplete for full exposure and longitudinal capture — link to claims for those. - Linked claims-EHR-registry: Best substrate (EHR severity + claims completeness + adjudicated outcomes) but the linkable subset is selected, threatening transportability; reconcile date discrepancies before fixing the risk window.

Worked claims example

Question: any major bleed within a fixed 180-day risk window among new initiators of DOAC A vs DOAC B for nonvalvular atrial fibrillation, in a commercial + Medicare FFS database. (1) Eligibility/time zero: first qualifying DOAC fill (`fill_date`) with 365 days of continuous medical+pharmacy FFS enrollment beforehand and no DOAC fill in that washout (incident users); assign `arm` from the dispensed NDC. (2) Outcome: bleed = 1 if a validated inpatient major-bleeding `dx` code (high-PPV algorithm) appears in (index_date, index_date + 180]; else 0 — but only count a patient as "0" if they were continuously enrolled and FFS-observable through day 180, otherwise censoring is incomplete and logistic is the wrong tool (fall back to Cox). (3) Covariates measured strictly in [index_date - 365, index_date]: age, sex, HAS-BLED components, prior bleed, renal impairment, antiplatelet/NSAID use, baseline utilization. (4) Fit `logit(P(bleed)) = arm + covariates`, but report the standardized marginal RD and RR (g-computation across the cohort), not the adjusted OR alone. (5) Because DOAC dosing/monitoring differs, run a detection-bias sensitivity analysis (adjust for baseline visit count; negative-control outcome) and, if major bleed is rare in subgroups, switch to Firth penalized likelihood to avoid separation.

Worked example

Scenario

A pharmacoepidemiology team is studying whether patients who started Drug A had a higher rate of 90-day hospitalization compared to patients who started Drug B. Each patient's 90-day window was complete and fully observable. The team wants to compare the two arms using logistic regression. Here is the raw 2x2 count table they start with before running any model.

Dataset

90-day hospitalization counts by treatment arm (one row per arm, 100 patients each).

armhospitalized_yeshospitalized_no
Drug A (exposed)4060
Drug B (unexposed)1090

Steps

  • Label the cells: a = 40 (Drug A, hospitalized), b = 60 (Drug A, not hospitalized), c = 10 (Drug B, hospitalized), d = 90 (Drug B, not hospitalized).

  • Compute the odds of hospitalization in the Drug A arm: a / b = 40 / 60 = 0.667.

  • Compute the odds of hospitalization in the Drug B arm: c / d = 10 / 90 = 0.111.

  • Compute the odds ratio using the cross-product formula: OR = (a × d) / (b × c) = (40 × 90) / (60 × 10) = 3600 / 600 = 6.0.

  • Logistic regression estimates a coefficient for the treatment arm variable; that coefficient equals the natural log of the OR: log(6.0) = 1.792.

  • Exponentiating the coefficient recovers the odds ratio: exp(1.792) = 6.0, confirming that the model output and the 2x2 table arithmetic agree exactly.

Result

OR = 6.0: patients in the Drug A arm had six times the odds of being hospitalized within 90 days compared to patients in the Drug B arm. The logistic regression coefficient for treatment arm = log(6.0) = 1.792, and exp(1.792) = 6.0. In plain terms, hospitalization was much more common among Drug A users — but because 40% of Drug A patients were hospitalized (a common outcome), the OR of 6.0 overstates how large the risk difference actually is; analysts would follow up by computing the risk difference (40% − 10% = 30 percentage points) to communicate the finding to clinicians.

Runnable example

python implementation

Adjusted logistic regression for a fixed-window binary outcome, plus standardized marginal risk difference/ratio (g-computation). Required input (one row per eligible new initiator, already cleaned): cohort : person_id, arm (0/1 or 'STUDY'/'COMPARATOR'),...

import numpy as np
import pandas as pd
import statsmodels.formula.api as smf

def fit_binary_outcome(cohort: pd.DataFrame, covariates: list[str],
                       treat: str = "arm", outcome: str = "outcome",
                       n_boot: int = 1000, seed: int = 1) -> dict:
    d = cohort.copy()
    d[treat] = (d[treat].isin([1, "STUDY", "treated"])).astype(int)  # normalize arm to 0/1

    rhs = " + ".join([treat] + covariates)
    fit = smf.logit(f"{outcome} ~ {rhs}", data=d).fit(disp=0)

    # Conditional (adjusted) odds ratio for treatment.
    beta = fit.params[treat]
    ci = fit.conf_int().loc[treat]
    odds_ratio = (np.exp(beta), float(np.exp(ci[0])), float(np.exp(ci[1])))

    # Marginal effects via g-computation: predict risk setting everyone treated vs untreated.
    def marginal(df):
        d1, d0 = df.copy(), df.copy()
        d1[treat], d0[treat] = 1, 0
        r1, r0 = fit.predict(d1).mean(), fit.predict(d0).mean()
        return r1 - r0, r1 / r0
    rd, rr = marginal(d)

    # Nonparametric bootstrap CIs for the marginal contrasts (refit each draw).
    rng = np.random.default_rng(seed)
    rds, rrs = [], []
    for _ in range(n_boot):
        b = d.iloc[rng.integers(0, len(d), len(d))]
        bf = smf.logit(f"{outcome} ~ {rhs}", data=b).fit(disp=0)
        d1, d0 = b.copy(), b.copy()
        d1[treat], d0[treat] = 1, 0
        r1, r0 = bf.predict(d1).mean(), bf.predict(d0).mean()
        rds.append(r1 - r0); rrs.append(r1 / r0)
    rd_ci = (float(np.percentile(rds, 2.5)), float(np.percentile(rds, 97.5)))
    rr_ci = (float(np.percentile(rrs, 2.5)), float(np.percentile(rrs, 97.5)))

    return {"odds_ratio": odds_ratio, "risk_difference": (rd, *rd_ci),
            "risk_ratio": (rr, *rr_ci), "n": len(d), "events": int(d[outcome].sum())}
r implementation

Adjusted logistic regression with profile-likelihood OR and standardized marginal RD/RR (g-computation), plus cluster-robust SEs for multi-episode/clustered data. Input mirrors the Python version: cohort : person_id, arm (0/1), index_date, outcome (0/1 over...

library(marginaleffects)
library(sandwich)

fit_binary_outcome <- function(cohort, covariates,
                               treat = "arm", outcome = "outcome",
                               cluster = "person_id") {
  cohort[[treat]] <- as.integer(cohort[[treat]] %in% c(1, "STUDY", "treated"))
  f <- reformulate(c(treat, covariates), response = outcome)

  fit <- glm(f, family = binomial(), data = cohort)

  # Conditional (adjusted) OR with profile-likelihood CI.
  or  <- exp(coef(fit)[[treat]])
  cic <- exp(suppressMessages(confint(fit))[treat, ])

  # Cluster-robust covariance (multiple eligible episodes per person, facility clustering).
  vcl <- sandwich::vcovCL(fit, cluster = cohort[[cluster]])

  # Marginal RD and RR by standardization (avgcomparisons = g-computation contrast).
  rd <- avg_comparisons(fit, variables = setNames(list(0:1), treat),
                        comparison = "difference", vcov = vcl)
  rr <- avg_comparisons(fit, variables = setNames(list(0:1), treat),
                        comparison = "ratio",      vcov = vcl)

  list(odds_ratio = c(or, cic),
       risk_difference = rd[, c("estimate", "conf.low", "conf.high")],
       risk_ratio      = rr[, c("estimate", "conf.low", "conf.high")],
       n = nrow(cohort), events = sum(cohort[[outcome]]))
}
# Rare-event / separation fallback:
# library(logistf); fit <- logistf(f, data = cohort)  # Firth-penalized ORs + profile-likelihood CIs