library(survival)
head(lung[, c("time", "status", "sex")]) time status sex
1 306 2 1
2 455 2 1
3 1010 1 1
4 210 2 1
5 883 2 1
6 1022 1 1
survdiff(rho = ) (Peto-Peto)FH(p, q) with survMiscWhen survival curves cross or the treatment effect is delayed, the standard log-rank test loses power — weight the early or late events with survdiff(rho=) and survMisc::comp()
Run weighted log-rank tests in R when proportional hazards fail. Use survdiff(rho = 1) for the early-weighted Peto-Peto test and survMisc::comp() for the Fleming-Harrington FH(p, q) family, compare the p-values against the standard log-rank test on the NCCTG lung data, and learn which weight to prespecify for delayed, early, or crossing effects — without fishing.
June 25, 2026
July 7, 2026
survdiff(..., rho = 1) — the Peto-Peto test, which emphasizes early differences; rho = 0 is the standard test. The full family is Fleming-Harrington FH(p, q) via survMisc::comp() — FH(1, 0) early, FH(0, 1) late.The log-rank test is the default for comparing survival between groups, and for good reason — but it has one blind spot. It gives equal weight to every event time, which makes it the most powerful test only when the hazard ratio is roughly constant over follow-up (proportional hazards). Modern trials routinely break that assumption:
In all three cases the standard log-rank test loses power and can miss a real difference. A weighted log-rank test fixes this by emphasizing the part of follow-up where the difference actually lives. This lesson runs them in R — the quick Peto-Peto test with base survdiff(..., rho = ), then the full Fleming-Harrington FH(p, q) family with survMisc — on the NCCTG lung-cancer data, comparing every result against the standard test.
A weighted log-rank test is the answer to a specific problem: non-proportional hazards. Before reaching for it, confirm the problem exists — inspect the Kaplan-Meier curves and run the formal check in Testing the proportional hazards assumption (cox.zph()). If hazards are proportional, the standard log-rank test is the most powerful choice and a weighted test only costs you power.
The log-rank statistic accumulates, across every event time \(t_i\), the gap between the observed events in a group and the expected events under H₀. A weighted test multiplies each gap by a weight \(w(t_i)\) before summing:
The Fleming-Harrington family parametrises the weight with two numbers, p and q, applied to the pooled Kaplan-Meier survival estimate \(\hat S(t)\):
\[ w(t_i) = \hat S(t_i)^{\,p} \cdot \left[1 - \hat S(t_i)\right]^{\,q} \]
Early in follow-up \(\hat S(t)\) is near 1, so \(\hat S(t)^p\) is large and \([1-\hat S(t)]^q\) is small; late in follow-up the reverse holds. That gives a clean dial:
FH(0, 0) → \(w = 1\): the standard log-rank test.FH(1, 0) → \(w = \hat S(t)\): early emphasis (weight high while survival is high).FH(0, 1) → \(w = 1 - \hat S(t)\): late emphasis (weight grows as patients die — for delayed effects).FH(1, 1) → maximum weight in the middle of follow-up.The base survdiff() function takes a single parameter, rho, which is not the Fleming-Harrington p. survdiff(rho = 0) is the standard test; survdiff(rho = 1) is the Peto-Peto test, which weights by \(\hat S(t)\) — early emphasis (equivalent to Fleming-Harrington FH(1, 0)). For the full two-parameter family — including late emphasis — use survMisc::comp(), which takes p and q directly.
We use lung, the North Central Cancer Treatment Group dataset from the survival package: survival in 228 patients with advanced lung cancer. With survival attached, lung is available directly — no data() call needed. We compare survival by sex (1 = male, 2 = female):
time status sex
1 306 2 1
2 455 2 1
3 1010 1 1
4 210 2 1
5 883 2 1
6 1022 1 1
time — survival time in days.status — 1 = censored, 2 = dead (the event).sex — 1 = male, 2 = female (the grouping variable).Look at the curves before testing anything. The hero plot below marks the early stretch of follow-up — the period an early-weighted test emphasizes:
library(survival)
library(survminer)
fit <- survfit(Surv(time, status) ~ sex, data = lung)
ggsurvplot(
fit,
data = lung,
conf.int = TRUE,
palette = "jco", # colourblind-safe journal palette
legend.labs = c("Male", "Female"),
legend.title = "Sex",
xlab = "Time (days)",
ylab = "Survival probability",
ggtheme = theme_minimal()
)$plot +
ggplot2::annotate(
"rect", xmin = 0, xmax = 300, ymin = 0, ymax = 1,
alpha = 0.08, fill = "#3a86d4"
) +
ggplot2::annotate(
"text", x = 150, y = 0.05, label = "early emphasis",
size = 3, colour = "#3a86d4"
)
The curves separate early — the female curve pulls above the male curve within the first few hundred days — and the gap is widest while most patients are still at risk. That shape predicts what we are about to see: an early-weighted test will be the most powerful here, and a late-weighted one will be the weakest.
survdiff(rho = ) (Peto-Peto)Base survdiff() gives you a one-parameter weighted test for free. rho = 0 is the standard log-rank test; rho = 1 is the Peto-Peto test, which weights by \(\hat S(t)\) — early emphasis. Run both and compare:
Call:
survdiff(formula = Surv(time, status) ~ sex, data = lung, rho = 0)
N Observed Expected (O-E)^2/E (O-E)^2/V
sex=1 138 112 91.6 4.55 10.3
sex=2 90 53 73.4 5.68 10.3
Chisq= 10.3 on 1 degrees of freedom, p= 0.001
Call:
survdiff(formula = Surv(time, status) ~ sex, data = lung, rho = 1)
N Observed Expected (O-E)^2/E (O-E)^2/V
sex=1 138 70.4 55.6 3.95 12.7
sex=2 90 28.7 43.5 5.04 12.7
Chisq= 12.7 on 1 degrees of freedom, p= 4e-04
Read the two side by side:
rho = 0): χ² = 10.3 on 1 df, p = 0.0013. Survival differs significantly between the sexes — the result you would report under proportional hazards.rho = 1): χ² = 12.7 on 1 df, p = 0.0004. The early-weighted test is more significant (a larger χ², a smaller p). That is the signature of an early difference: putting more weight on the first event times — where the curves are furthest apart and the most patients are at risk — sharpens the signal.Notice the Observed and Expected counts in the rho = 1 table are fractional and smaller than in the standard table: they are weighted event counts, not raw deaths. That is expected — only the χ² and p-value are comparable across the two tests.
FH(p, q) with survMiscsurvdiff() only dials early emphasis. For the complete two-parameter family — including late emphasis for delayed effects — use survMisc::comp(). It takes a ten object (built from a survfit() fit) and vectors of p and q values, then runs every Fleming-Harrington weight at once:
Q Var Z pNorm
1 -20.4183 40.4280 -3.2113 6
n -3148.0000 795286.3234 -3.5300 4
sqrtN -248.2336 4952.6740 -3.5273 5
S1 -14.6872 16.9922 -3.5630 3
S2 -14.5848 16.7542 -3.5632 2
FH_p=1_q=0 -14.8065 17.2608 -3.5639 1
FH_p=0_q=1 -5.6118 9.1175 -1.8585 7
maxAbsZ Var Q pSupBr
1 2.2095e+01 4.0428e+01 3.4750 6
n 3.1750e+03 7.9529e+05 3.5603 5
sqrtN 2.5507e+02 4.9527e+03 3.6244 1
S1 1.4917e+01 1.6992e+01 3.6188 3
S2 1.4802e+01 1.6754e+01 3.6163 4
FH_p=1_q=0 1.5039e+01 1.7261e+01 3.6198 2
FH_p=0_q=1 7.0563e+00 9.1175e+00 2.3369 7
comp() prints a compact table of several weight families; the pNorm column on the printout is a rank (1 = most significant), not the p-value — the actual statistics live in the fitted object. Pull the three we care about into a clean summary:
Q Var Z pNorm
1 -20.4183 40.4280 -3.2113 6
n -3148.0000 795286.3234 -3.5300 4
sqrtN -248.2336 4952.6740 -3.5273 5
S1 -14.6872 16.9922 -3.5630 3
S2 -14.5848 16.7542 -3.5632 2
FH_p=1_q=0 -14.8065 17.2608 -3.5639 1
FH_p=0_q=1 -5.6118 9.1175 -1.8585 7
maxAbsZ Var Q pSupBr
1 2.2095e+01 4.0428e+01 3.4750 6
n 3.1750e+03 7.9529e+05 3.5603 5
sqrtN 2.5507e+02 4.9527e+03 3.6244 1
S1 1.4917e+01 1.6992e+01 3.6188 3
S2 1.4802e+01 1.6754e+01 3.6163 4
FH_p=1_q=0 1.5039e+01 1.7261e+01 3.6198 2
FH_p=0_q=1 7.0563e+00 9.1175e+00 2.3369 7
Test ChiSq p_value
1 FH(0,0) standard 10.31 0.001320
2 FH(1,0) early emphasis 12.70 0.000365
3 FH(0,1) late emphasis 3.45 0.063100
Read the family in plain language:
FH(0, 0) standard — χ² = 10.3, p = 0.0013. The equal-weight baseline (it matches survdiff(rho = 0)).FH(1, 0) early emphasis — χ² = 12.7, p = 0.0004. The most powerful test here, matching the Peto-Peto result — because the sex difference is an early one.FH(0, 1) late emphasis — χ² = 3.5, p = 0.063. The weakest test: it weights the late event times, where the curves have nearly converged and few patients remain — so it almost misses a difference that is genuinely there.That last row is the whole lesson in one number. A late-emphasis test applied to an early effect (or vice versa) can flip a clearly significant result into a non-significant one. The weight must match where the difference is — and you must decide which weight before you see the data.
FH(0, 0) / survdiff(rho = 0). A weighted test only costs power here.FH(0, 1) via comp(..., p = 0, q = 1).FH(1, 0) / survdiff(rho = 1) (Peto-Peto).Every weight you try is another test. Running FH(0,0), FH(1,0), FH(0,1), FH(1,1), … and reporting the smallest p-value inflates the type-I error rate far above 5% — you will “find” significant effects in pure noise. The fix is procedural, not statistical:
The one thing you must never do is scan every weight and report the best.
Survival was compared between male and female patients on the NCCTG lung-cancer data (n = 228, 165 deaths). Because the Kaplan-Meier curves separated early in follow-up, a prespecified early-weighted Fleming-Harrington test,
FH(1, 0)(Peto-Peto,survdiffrho = 1), was the primary comparison: survival differed significantly between the sexes (χ²(1) = 12.7, p = 0.0004). The standard log-rank test agreed (χ²(1) = 10.3, p = 0.0013); a late-emphasis testFH(0, 1)was weaker (χ²(1) = 3.5, p = 0.063), consistent with an early difference. Females survived longer (median 426 vs 270 days).
At each distinct event time \(t_i\), with \(d_i\) events among \(n_i\) patients at risk, let \(O_{1i}\) and \(E_{1i}\) be the observed and expected events in group 1, and \(V_i\) the variance — exactly the log-rank ingredients. The weighted statistic attaches a weight \(w_i\) to each term:
\[ Z_w = \frac{\sum_i w_i \,(O_{1i} - E_{1i})}{\sqrt{\sum_i w_i^2 \, V_i}} \]
and \(Z_w^2\) follows a chi-square distribution with 1 degree of freedom. Setting all \(w_i = 1\) recovers the standard log-rank test. The Fleming-Harrington weight is \(w_i = \hat S(t_i)^{\,p}\,[1 - \hat S(t_i)]^{\,q}\), where \(\hat S\) is the pooled Kaplan-Meier estimate just before \(t_i\): with \(p > 0\) the weight is large while survival is high (early), and with \(q > 0\) it grows as survival falls (late). Because the weight is fixed by \((p, q)\) before seeing group membership, a prespecified choice is a valid test; choosing \((p, q)\) to minimise the observed \(p\)-value is not.
Run the weighted tests yourself — change the grouping variable, or dial the FH(p, q) weights. The sandbox boots on first Run.
Ask Prova “my survival curves separate late — pick and run a weighted log-rank test in R that emphasizes late differences, compare it to the standard log-rank test, and tell me which to prespecify” — it answers with survival + survMisc code you can run on your own data. The runtime is the judge. Ask Prova →
You ran every weight and reported the smallest p-value. This inflates the type-I error rate well above 5% — the single most common misuse of weighted tests. Prespecify one weight from the biology before unblinding and treat the rest as sensitivity analyses; if the pattern is genuinely unknown, use a multiplicity-controlled combination test (MaxCombo), not weight-shopping.
Your curves cross and even the weighted test looks weak. A single weight emphasizes one direction of difference; when curves cross, early and late differences oppose each other and any single-weight test can still struggle. State the clinical question (is early or late survival what matters?) and consider restricted mean survival time as a weight-free complementary summary.
The Observed/Expected counts in survdiff(rho = 1) look wrong (fractional, too small). They are weighted event counts, not raw deaths — only the χ² statistic and the p-value are comparable to the standard test. Don’t try to reconcile the weighted counts with the actual number of deaths.
Use a weighted log-rank test when the proportional-hazards assumption fails — the survival curves cross, the treatment effect is delayed (immunotherapy), or it is early and then fades. In those cases the standard log-rank test gives equal weight to every time point and loses power. If hazards are proportional (curves stay roughly parallel and don’t cross), the standard log-rank test is the most powerful choice and a weighted test only costs you power — so check the curves and run cox.zph() first.
survdiff() exposes a single parameter, rho. rho = 0 is the standard log-rank test; rho = 1 is the Peto-Peto test, which weights by the survival estimate \(\hat S(t)\) — early emphasis (equivalent to Fleming-Harrington FH(1, 0)). The Fleming-Harrington family has two parameters, p and q, with weight \(\hat S(t)^p [1 - \hat S(t)]^q\) — p drives early emphasis, q drives late emphasis. For late emphasis (a delayed effect) you need survMisc::comp(..., p = 0, q = 1); survdiff() alone cannot do it.
A late-emphasis weight — Fleming-Harrington FH(0, 1) (comp(ten(fit), p = 0, q = 1)). Delayed effects show no early separation, then a growing benefit, so the difference lives in the later event times; weighting them up recovers the power the standard test loses. Prespecify this weight from the mechanism before unblinding, and report the standard log-rank test as a sensitivity analysis.
No. Each weight is a separate test, so scanning many and reporting the smallest p-value inflates the type-I error rate — you will find “significant” effects in noise. Prespecify one weight (from the biology or prior data) as primary and treat the others as sensitivity analyses. If you genuinely cannot predict the effect’s timing, use a MaxCombo combination test, which takes the maximum over a small prespecified set of weights with a proper multiplicity adjustment.
The standard log-rank test corresponds to a Cox model with a constant hazard ratio. When hazards are non-proportional, neither a single hazard ratio nor the standard log-rank test summarises the effect well. A weighted log-rank test gives a more powerful p-value for “do the curves differ?”, but still no effect size. To describe a time-varying effect, fit a Cox model with time-varying coefficients or report restricted mean survival time; see Testing the proportional hazards assumption for diagnosing the violation in the first place.
ph.ecog). Fill the blank to run an early-weighted Peto-Peto test, and compare its p-value to the standard log-rank test. Does the early weighting strengthen or weaken the evidence?For an early-weighted (Peto-Peto) test, set rho = 1 — rho = 0 is the standard test. Compare the Chisq and p lines from the two survdiff() calls. For question 2, a delayed effect lives in the late event times, so you need late emphasis.
survdiff(Surv(time, status) ~ ph.ecog, data = lung, rho = 1)
#> An early-weighted (Peto-Peto) test of survival across ECOG groups.
#> Compare its Chisq / p to the rho = 0 standard test: because worse
#> performance scores separate early, the early weighting strengthens
#> the evidence (a larger Chisq, a smaller p) here.For question 2: the effect is delayed — no early difference, then a late benefit — so the difference lives in the late event times. Prespecify a late-emphasis test, Fleming-Harrington FH(0, 1) (comp(ten(fit), p = 0, q = 1)), justified by the immunotherapy mechanism. A standard log-rank test would be diluted by the early non-difference, and an early-weighted test would point exactly the wrong way.
You expect a delayed treatment benefit (curves separate only after several months). You run survdiff(Surv(time, status) ~ arm, data = trial, rho = 1) as your primary test. Is that the right choice?
No. rho = 1 is the Peto-Peto / early-emphasis test — it weights the early event times, exactly where a delayed effect shows no difference, so it would lose power. For a delayed effect you need a late-emphasis test, Fleming-Harrington FH(0, 1) via survMisc::comp(ten(fit), p = 0, q = 1). The rho direction is the opposite of what the name might suggest — rho = 1 emphasizes early, not late.
When proportional hazards fail — delayed effects, early-then-fading effects, crossing curves — the standard log-rank test loses power because it weights every event time equally. A weighted log-rank test restores that power by emphasizing the part of follow-up where the difference lives: survdiff(..., rho = 1) for a quick early-weighted (Peto-Peto) test, and survMisc::comp() for the full Fleming-Harrington FH(p, q) family — FH(1, 0) early, FH(0, 1) late. On the lung data the sex difference was an early one, so the early-weighted test was strongest (χ² = 12.7, p = 0.0004) and the late-weighted test nearly missed it (p = 0.063) — a reminder that the weight must match the effect, and that you must prespecify it rather than fish across weights. Diagnose the non-proportionality first with the PH check; choose one weight from the biology; report the rest as sensitivity analyses.
Prove you can do it. Master the whole Survival Analysis in R series — track your path, build projects, and earn a certificate.
Go Pro — unlimited Prova on your own data and a verifiable certificate that proves the skill.
from $15/mo billed yearly
✓ You're Pro — keep going. The runtime is the judge.
Get new R & Python lessons by email
Practical, reproducible, no spam. Unsubscribe anytime.
Double opt-in. We never share your email.
Every result on this page was produced by the code shown, run at build time against a pinned R environment — edit any block and Run to reproduce it yourself.
@online{2026,
author = {},
title = {Weighted {Log-Rank} {Tests} in {R:} {Fleming-Harrington} for
{Non-Proportional} {Hazards}},
date = {2026-06-25},
url = {https://www.datanovia.com/learn/biostatistics/survival-analysis/weighted-log-rank},
langid = {en}
}