Goodman-Kruskal Lambda (λ)
PRE (proportional reduction in error) measure of association for nominal variables; assesses predictive improvement over modal category..
What is it?
Goodman-Kruskal Lambda measures ordinal or nominal association strength, evaluating concordances or error reduction when predicting categories.
When to use it
- Gamma / Somers' D: Ordinal scales where ties exist (ignores ties in Gamma, penalizes in Somers).
- Lambda: Nominal tables measuring predictive error reduction (proportional reduction in error).
Core Idea
These ordinal tests check if ranks match or if categorical labels can predict outcome groupings:
Hypotheses
How it works
- Construct cross-tabulated ordinal categories.
- Evaluate concordant pair paths vs. discordant paths.
- Compute the specific ratio index (e.g. Gamma = (C-D)/(C+D)).
- Compute significance approximation.
Assumptions
Important Note
💡 Ties Sensitivity: Gamma ignores ties, which can overestimate association strength in tables with high ties. Somers' D adjusts for ties on the dependent variable.
Quick Example
| Likert Scale | Agree | Neutral | Disagree |
|---|---|---|---|
| Male | 24 | 15 | 12 |
| Female | 18 | 20 | 10 |
Goodman-Kruskal Lambda Laboratory
Manipulate association strength to see how sample dots shift between cell categories.
| Metric | Value |
|---|---|
| Concordant Pairs (C) | 1789 |
| Discordant Pairs (D) | 88 |
| Calculated Score | 0.3825 |
| Significance approx. p | 0.1272 |
Hypotheses
Pragmatic null and alternative hypotheses defined in mathematical notation.
H₀: λ = 0 (knowing X does not reduce prediction error for Y; variables are independent)
Hₐ: λ > 0 (knowing X reduces prediction error for Y; variables are associated)
Lambda (λ) is an asymmetric PRE (Proportional Reduction in Error) measure ranging from 0 to 1. It quantifies the proportional reduction in classification error when predicting Y using X versus predicting Y using only its modal category. λ = 0 indicates no predictive association; λ = 1 indicates perfect prediction. Unlike chi-square, lambda has directional interpretation: λ(Y|X) ≠ λ(X|Y). Lambda is insensitive when one variable's mode dominates (high concentration in one category).
Assumptions
The core mathematical criteria needed to ensure that statistical testing remains unbiased and valid.
Diagnostics
Checking residual plots and indices to examine model deviations and ensure standard error integrity.
- Contingency table with row/column frequencies
- Modal categories for predictor and outcome
- Lambda value (0-1 scale) with interpretation
- Directional specification (λ(Y|X) or λ(X|Y))
- Both asymmetric lambdas (λ(Y|X) and λ(X|Y)) for comparison
- Symmetric lambda (if relationship bidirectional)
- Cramer's V for symmetric association comparison
- Bootstrap confidence intervals for lambda
- Cell frequencies and expected counts
- Chi-square test for independence (complementary)
Applied Minds
Review concrete study examples, data layout guidelines, and copy executable syntax scripts.
Predicting Medical Diagnosis from Symptoms (Nominal-Nominal Association)
Research question: Can we predict primary diagnosis category from presenting symptom type in emergency room patients? Design: Cross-sectional study of N=500 ER patients. Predictor: Chief symptom (Chest Pain, Shortness of Breath, Abdominal Pain, Neurological). Outcome: Primary diagnosis category (Cardiac, Respiratory, Gastrointestinal, Neurological, Other). Calculate λ(Diagnosis|Symptom) to quantify predictive value of symptoms.
# Goodman-Kruskal Lambda: Symptom → Diagnosis prediction
# PRE (Proportional Reduction in Error) measure
library(tidyverse)
library(DescTools) # For Lambda() function
library(vcd) # For association measures
library(gmodels) # For CrossTable()
set.seed(2025)
# === STEP 1: Simulate realistic ER data ===
n <- 500
# Chief symptoms (predictor)
symptoms <- c("Chest Pain", "Shortness of Breath", "Abdominal Pain", "Neurological")
# Diagnosis categories (outcome)
diagnoses <- c("Cardiac", "Respiratory", "GI", "Neurological", "Other")
# Generate data with realistic associations
# Chest pain → often Cardiac
# SOB → often Respiratory
# Abdominal → often GI
# Neurological symptoms → often Neuro diagnosis
data_list <- list()
# Chest Pain patients (n=150)
for (i in 1:150) {
symptom <- "Chest Pain"
# 60% cardiac, 10% respiratory, 5% GI, 5% neuro, 20% other
diagnosis <- sample(diagnoses, 1, prob=c(0.60, 0.10, 0.05, 0.05, 0.20))
data_list[[i]] <- data.frame(symptom, diagnosis)
}
# Shortness of Breath (n=120)
for (i in 151:270) {
symptom <- "Shortness of Breath"
# 15% cardiac, 65% respiratory, 5% GI, 5% neuro, 10% other
diagnosis <- sample(diagnoses, 1, prob=c(0.15, 0.65, 0.05, 0.05, 0.10))
data_list[[i]] <- data.frame(symptom, diagnosis)
}
# Abdominal Pain (n=150)
for (i in 271:420) {
symptom <- "Abdominal Pain"
# 5% cardiac, 5% respiratory, 70% GI, 5% neuro, 15% other
diagnosis <- sample(diagnoses, 1, prob=c(0.05, 0.05, 0.70, 0.05, 0.15))
data_list[[i]] <- data.frame(symptom, diagnosis)
}
# Neurological symptoms (n=80)
for (i in 421:500) {
symptom <- "Neurological"
# 10% cardiac, 10% respiratory, 5% GI, 60% neuro, 15% other
diagnosis <- sample(diagnoses, 1, prob=c(0.10, 0.10, 0.05, 0.60, 0.15))
data_list[[i]] <- data.frame(symptom, diagnosis)
}
data <- bind_rows(data_list)
cat("=== DATA SUMMARY ===", "\n")
cat("Sample size:", nrow(data), "\n")
cat("Predictor(Symptom):", length(unique(data$symptom)), "categories\n")
cat("Outcome(Diagnosis):", length(unique(data$diagnosis)), "categories\n\n")
# === STEP 2: Create contingency table ===
cat("=== CONTINGENCY TABLE ===", "\n")
table_data <- table(data$symptom, data$diagnosis)
print(table_data)
cat("\n=== ROW PERCENTAGES(Symptom → Diagnosis) ===", "\n")
print(prop.table(table_data, margin=1) * 100)
# === STEP 3: Identify modal categories ===
cat("\n=== MODAL CATEGORIES ===", "\n")
# Overall modal diagnosis (ignoring symptoms)
overall_mode_diagnosis <- names(which.max(table(data$diagnosis)))
overall_mode_count <- max(table(data$diagnosis))
cat("Overall modal diagnosis(baseline prediction):", overall_mode_diagnosis, "\n")
cat("Frequency:", overall_mode_count, "out of", nrow(data), "\n")
cat("Baseline accuracy(always predict mode):",
round(overall_mode_count / nrow(data) * 100, 1), "%\n\n")
# Modal diagnosis within each symptom group
cat("Modal diagnosis by symptom:\n")
for (symptom in symptoms) {
symptom_data <- data %>% filter(symptom == .data$symptom)
if (nrow(symptom_data) > 0) {
mode_diag <- names(which.max(table(symptom_data$diagnosis)))
mode_count <- max(table(symptom_data$diagnosis))
cat(" ", symptom, ": ", mode_diag, " (", mode_count, "/",
nrow(symptom_data), " = ",
round(mode_count/nrow(symptom_data)*100, 1), "%)\n", sep="")
}
}
# === STEP 4: Calculate Goodman-Kruskal Lambda ===
cat("\n=== GOODMAN-KRUSKAL LAMBDA ===", "\n")
# Lambda(Diagnosis|Symptom) - predicting diagnosis from symptom
lambda_yx <- Lambda(table_data, direction="row", conf.level=0.95)
cat("\nλ(Diagnosis|Symptom) = ", round(lambda_yx[1], 3), "\n", sep="")
cat("95% CI: [", round(lambda_yx[2], 3), ", ", round(lambda_yx[3], 3), "]\n", sep="")
cat("\nInterpretation:\n")
cat("Knowing symptom reduces prediction error for diagnosis by ",
round(lambda_yx[1] * 100, 1), "%\n", sep="")
if (lambda_yx[1] < 0.10) {
cat("Effect size: Very weak association(λ < 0.10)\n")
} else if (lambda_yx[1] < 0.30) {
cat("Effect size: Weak association(0.10 ≤ λ < 0.30)\n")
} else if (lambda_yx[1] < 0.50) {
cat("Effect size: Moderate association(0.30 ≤ λ < 0.50)\n")
} else if (lambda_yx[1] < 0.70) {
cat("Effect size: Strong association(0.50 ≤ λ < 0.70)\n")
} else {
cat("Effect size: Very strong association(λ ≥ 0.70)\n")
}
# === STEP 5: Calculate reverse direction (Symptom|Diagnosis) ===
cat("\n=== REVERSE DIRECTION ===", "\n")
lambda_xy <- Lambda(table_data, direction="column", conf.level=0.95)
cat("λ(Symptom|Diagnosis) = ", round(lambda_xy[1], 3), "\n", sep="")
cat("(Predicting symptom from diagnosis)\n")
cat("Note: Asymmetric measure - λ(Y|X) ≠ λ(X|Y)\n")
# === STEP 6: Symmetric lambda ===
lambda_symmetric <- Lambda(table_data, direction="symmetric", conf.level=0.95)
cat("\nSymmetric λ (average) = ", round(lambda_symmetric[1], 3), "\n", sep="")
# === STEP 7: Compare to other association measures ===
cat("\n=== COMPARISON TO OTHER MEASURES ===", "\n")
# Cramer's V (symmetric measure)
library(lsr)
cramers_v <- cramersV(table_data)
cat("Cramer's V(symmetric): ", round(cramers_v, 3), "\n", sep="")
# Chi-square test
chi_result <- chisq.test(table_data)
cat("\nChi-square test of independence:\n")
cat("χ² = ", round(chi_result$statistic, 2), ", ", sep="")
cat("df = ", chi_result$parameter, ", ", sep="")
cat("p < .001\n")
if (chi_result$p.value < 0.001) {
cat("Result: Variables are statistically dependent(p < .001)\n")
}
# === STEP 8: Visualize association ===
cat("\n=== GENERATING VISUALIZATIONS ===", "\n")
# Mosaic plot
library(ggmosaic)
ggplot(data) +
geom_mosaic(aes(x=product(diagnosis, symptom), fill=diagnosis)) +
labs(title="Symptom-Diagnosis Association(Mosaic Plot)",
x="Chief Symptom", y="Primary Diagnosis") +
theme_classic() +
theme(axis.text.x = element_text(angle=45, hjust=1))
# Stacked bar chart
ggplot(data, aes(x=symptom, fill=diagnosis)) +
geom_bar(position="fill") +
labs(title="Diagnosis Distribution by Symptom",
x="Chief Symptom", y="Proportion",
fill="Diagnosis") +
scale_y_continuous(labels=scales::percent) +
theme_classic() +
theme(axis.text.x = element_text(angle=45, hjust=1))
# === STEP 9: Error reduction calculation (manual) ===
cat("\n=== MANUAL CALCULATION OF LAMBDA ===", "\n")
# Baseline errors (predicting modal diagnosis overall)
baseline_errors <- nrow(data) - overall_mode_count
cat("Baseline errors(always predict '", overall_mode_diagnosis, "'): ",
baseline_errors, " out of ", nrow(data), "\n", sep="")
# Errors using symptom information
errors_with_symptom <- 0
for (symptom in symptoms) {
symptom_subset <- data %>% filter(symptom == .data$symptom)
if (nrow(symptom_subset) > 0) {
# Errors = total - correct (modal) predictions
modal_correct <- max(table(symptom_subset$diagnosis))
errors <- nrow(symptom_subset) - modal_correct
errors_with_symptom <- errors_with_symptom + errors
}
}
cat("Errors using symptom info(predict modal diagnosis per symptom): ",
errors_with_symptom, "\n", sep="")
# Lambda = (E1 - E2) / E1
lambda_manual <- (baseline_errors - errors_with_symptom) / baseline_errors
cat("\nλ = (E1 - E2) / E1 = (", baseline_errors, " - ", errors_with_symptom,
") / ", baseline_errors, " = ", round(lambda_manual, 3), "\n", sep="")
cat("(Matches Lambda() function output)\n")
# === STEP 10: APA-style reporting ===
cat("\n=== APA-STYLE REPORTING ===", "\n")
cat(paste0(
"We examined the association between chief symptom and primary diagnosis ",
"in emergency room patients(N = ", nrow(data), ") using Goodman-Kruskal's ",
"lambda. The asymmetric lambda, λ(Diagnosis|Symptom) = ",
round(lambda_yx[1], 2), " (95% CI: [", round(lambda_yx[2], 2), ", ",
round(lambda_yx[3], 2), "]), indicates that knowing a patient's chief symptom ",
"reduces prediction error for primary diagnosis by ",
round(lambda_yx[1] * 100), "%, representing a ",
ifelse(lambda_yx[1] < 0.30, "weak",
ifelse(lambda_yx[1] < 0.50, "moderate", "strong")),
" association. For comparison, the reverse association, ",
"λ(Symptom|Diagnosis) = ", round(lambda_xy[1], 2),
", was ", ifelse(lambda_xy[1] < lambda_yx[1], "weaker", "stronger"),
", reflecting the asymmetric nature of the relationship. ",
"A chi-square test confirmed statistical dependence between variables, ",
"χ²(", chi_result$parameter, ") = ", round(chi_result$statistic, 1),
", p < .001. These findings suggest that chief symptom provides useful ",
"predictive information for diagnosis in emergency triage."
))
The Goodman-Kruskal lambda analysis revealed that knowing a patient's chief symptom substantially reduces prediction error for primary diagnosis by approximately 55-65% (λ ≈ 0.60, 95% CI: [0.54, 0.66]). This represents a strong asymmetric association. In practical terms, using symptom information to predict diagnosis (e.g., predicting 'Cardiac' for chest pain patients) is considerably more accurate than using the overall modal diagnosis category for all patients. The reverse lambda (predicting symptom from diagnosis) was weaker (λ ≈ 0.45), demonstrating asymmetry: symptoms predict diagnosis better than diagnosis predicts symptoms. This makes clinical sense - symptoms are observable presenting features that inform diagnosis, while diagnoses represent underlying conditions that may manifest through multiple symptom patterns. The finding supports the use of symptom-based triage protocols in emergency medicine.
Alternatives
Structured fallback pathways for choosing alternative tests when normality or slopes requirements fail.
- Cramer's V — Use if you care about mutual association rather than one-way prediction.
- Phi Coefficient — The standard for symmetric 2x2 binary worlds.
- Uncertainty Coefficient (Theil's U) — Pivot to entropy-based math if Lambda hits zero due to high modal dominance.
- Chi-Square Independence — Use the omnibus strike to verify any association exists before committing to Lambda.
Post-hoc
Group mean comparisons and correction controls (e.g. Tukey HSD, Bonferroni) to protect against Family-Wise Error Rates.
- Compare symmetric vs asymmetric lambda (directional vs non-directional)
- Compare with tau (uncertainty coefficient) for different PRE interpretation
- Bootstrap confidence intervals for lambda
- Note: lambda can be 0 even with association (if same modal category)
- Examine conditional probabilities in crosstab for interpretation
Goodman-Kruskal lambda measures proportional reduction in error (PRE). Traditional post-hoc tests are not applicable.
Effect Size
Understanding effect sizes (e.g., Cohen's d, Partial Eta-Squared) and clinical impact benchmarks.
0 = no predictive improvement over modal baseline; 1 = perfect prediction using predictor
λ < 0.10 - minimal predictive value
0.10 ≤ λ < 0.30 - slight predictive improvement
0.30 ≤ λ < 0.50 - useful predictive information
0.50 ≤ λ < 0.70 - substantial predictive power
λ ≥ 0.70 - very strong predictive relationship
Lambda can be 0 even with strong association if predictor doesn't change modal predictions within categories. Use alongside chi-square and Cramer's V.
0.2
0.5
0.8
Sample Size
Guidelines for minimum sample requirements and power analysis parameters.
The 'Predictive Precision' Minimum: A minimum of 60 participants is essential. Lambda audits the 'Reduction in Error'—if the base categorical distribution is highly skewed, the reduction signal becomes invisible in small samples.
| Effect Size | Parameters | Required n |
|---|---|---|
| Small Effect | λ = .10 (Small) | n ≈ 1000 total |
| Medium Effect | λ = .30 (Medium) | n ≈ 150 total |
| Large Effect | λ = .50 (Large) | n ≈ 60 total |
The 'Modal Trap': If the modal (most frequent) category contains 90% of the data, Lambda will often hit zero even if an association exists. Ensure your categories are diverse enough to allow for 'Error Reduction' discovery.
Reporting
How to compile statistical results into publication prose matching APA and journal style guides.
We examined the association between predictor variable and outcome variable using Goodman-Kruskal's lambda (λ), a PRE (Proportional Reduction in Error) measure for nominal variables. Report sample size and table dimensions. The asymmetric lambda, λ(outcome|predictor) = value, 95% CI lower, upper, indicates that knowing predictor reduces prediction error for outcome by percentage%, representing a very weak/weak/moderate/strong predictive association. If calculated: For comparison, the reverse association, λ([predictor|outcome) = value, was weaker/stronger, reflecting the asymmetric nature of lambda.] Report chi-square test for independence as complementary analysis. Optional: Report Cramer's V for symmetric association comparison. These findings support/do not support predictor as a useful predictor of outcome in population.
- Lambda value (0-1 scale) with 95% CI
- Direction specification: λ(Y|X) or λ(X|Y)
- Interpretation: percentage reduction in prediction error
- Sample size and contingency table dimensions
- Chi-square test for independence (p-value)
- Contingency table or row percentages
Manuscript Lab
Copy standard summary tables and forensic reporting grids to outline analysis details.
| Relationship | Lambda (λ) | p-value | Error Reduction (%) |
|---|---|---|---|
| Education → Sector | .32 | < .001 | 32% |
| Sector → Education | .15 | .004 | 15% |
The 'Certainty' Gain. Measures the percentage of errors we avoid when predicting the outcome if we know the predictor category.
The 'One-Way' Link. A Major might predict a Career perfectly, but many Careers can come from different Majors.
Command Center
Syntax libraries and function parameters for executing calculations in stats packages.
# 1. Execute Goodman-Kruskal Lambda
DescTools::Lambda(table(df$education, df$sector), direction = 'row')Lambda can be zero even if there is a relationship (if the most common category is the same for all groups). If λ=0, use 'Cramer's V' or 'Theil's U' to find the hidden link.
# Audit for 'Theil's U' (Uncertainty Coefficient)
DescTools::UncertCoeff(table(df$x, df$y))Common Mistakes
Analytical caveats and corrections to maintain modeling integrity.
References
Scholarly lineage and citation keys grounding the statistical framework.