Executive Summary
Top driver and the priority opportunity for Score across 156 responses.
Social support is the strongest association with national happiness scores, accounting for 26.8% of combined driver importance, followed by GDP per capita at 24.6%. Across 156 responses, no driver falls into the priority (high importance, low performance) quadrant—the most important drivers are already rated well. Focus is on protecting these strengths rather than fixing gaps. Importance reflects association with Score, not proof of causal effect.
Analysis Overview
What Key Driver Analysis measures for Score across 6 drivers.
Key Driver Analysis identifies which attributes correlate most tightly with national happiness scores and ranks them by their combined standardized effect. The method crosses importance (how closely each driver tracks the outcome) with performance (how well each is rated today) to create an action quadrant. This analysis of 156 responses examined 6 drivers and found they collectively explain 77.9% of variation in Score. The analysis is strictly correlational—it shows which attributes move together with happiness, not which ones would move it if changed.
Data Quality
Outcome, drivers used and dropped, imputation, and model fit.
All 156 responses contained usable Score values; no rows were removed. The 6 drivers were all numeric and usable; missing values were imputed with column medians. Driver ratings follow a fixed scale, so performance was rescaled to 0–100 against that range. The model R-squared of 0.779 indicates the driver set accounts for 77.9% of Score variation, establishing the baseline for interpreting importance shares. This fit is strong enough to support the relative ranking of drivers and the quadrant placements that follow.
What Drives the Outcome
Each driver's share of the combined importance, ranked.
Social support leads at 26.8% relative importance, closely followed by GDP per capita at 24.6% and Healthy life expectancy at 20.8%. Together, these three drivers account for over 71% of combined importance. Freedom to make life choices contributes 16.6%, while Perceptions of corruption (7.3%) and Generosity (3.7%) trail significantly. The concentration in the top three reflects a clear hierarchy: social and economic factors dominate the association with happiness scores, while trust and charitable behavior show weaker correlations. High importance means tight tracking with Score, not proof that changing the driver will move it.
Driver Detail
Importance, correlation, performance, and quadrant for each driver of Score.
| Driver | Relative Importance | Correlation | Std Beta | Mean Performance | Quadrant |
|---|---|---|---|---|---|
| Social support | 26.8 | 0.777 | 0.302 | 24.2 | Maintain |
| GDP per capita | 24.6 | 0.794 | 0.278 | 18.1 | Maintain |
| Healthy life expectancy | 20.8 | 0.78 | 0.235 | 14.5 | Maintain |
| Freedom to make life choices | 16.6 | 0.567 | 0.187 | 7.9 | Monitor |
| Perceptions of corruption | 7.3 | 0.386 | 0.083 | 2.2 | Monitor |
| Generosity | 3.7 | 0.076 | 0.042 | 3.7 | Monitor |
The short answer
GDP per capita shows the highest plain correlation with happiness scores (0.794), while Social support has the largest standardized effect (0.302). All six drivers move in the same direction as happiness, but their current performance levels are low relative to their importance—meaning these are strengths to protect, not gaps to close.
The detail
GDP per capita: correlation 0.794, standardized effect 0.278, mean performance 18.1 (Maintain quadrant). Social support: correlation 0.777, standardized effect 0.302, mean performance 24.2 (Maintain quadrant). Healthy life expectancy: correlation 0.78, standardized effect 0.235, mean performance 14.5 (Maintain quadrant). Freedom to make life choices: correlation 0.567, standardized effect 0.187, mean performance 7.9 (Monitor quadrant). Perceptions of corruption: correlation 0.386, standardized effect 0.083, mean performance 2.2 (Monitor quadrant). Generosity: correlation 0.076, standardized effect 0.042, mean performance 3.7 (Monitor quadrant). Zero drivers sit in the Improve/Priority quadrant.
What this can't tell you
Correlation does not imply causation. A nation's GDP per capita and social support networks are entangled with governance, history, and culture—isolating the effect of any single driver would require experimental or quasi-experimental evidence. Current performance is measured on the survey's scale; absolute comparability across nations or over time depends on scale stability.
Importance vs Performance
Action quadrant: relative importance against current performance for each driver of Score.
All six drivers plot across Maintain and Monitor quadrants; none occupy the high-importance, low-performance Improve/Priority region. Social support, GDP per capita, and Healthy life expectancy cluster in the upper-left (Maintain): important and well-rated. Freedom to make life choices, Perceptions of corruption, and Generosity occupy the lower-left (Monitor): lower importance and lower performance. This pattern reflects a favorable situation—the strongest associations with happiness are already performing well. The layout is consistent with association, not causation; placement shows which attributes move together with Score, not which would move it if changed.
Action Summary
Recommended next step for each importance-performance quadrant.
| Quadrant | Drivers | Action |
|---|---|---|
| Maintain | Social support, GDP per capita, Healthy life expectancy | Protect these strengths. They are strongly associated with the outcome and already rated well. |
| Improve / Priority | None | Act here first. These are strongly associated with the outcome but rated low today, so gains here are most likely to move the outcome. |
| Monitor | Freedom to make life choices, Perceptions of corruption, Generosity | Low priority. Weakly associated with the outcome and rated low; keep an eye on them but do not over-invest. |
| Reduce effort / Possible over-invest | None | Possible over-investment. Rated highly but only weakly associated with the outcome, so extra effort here may not pay off. |
Three drivers occupy Maintain: Social support, GDP per capita, and Healthy life expectancy are strongly associated with Score and already rated well, so the action is to protect these strengths. Three drivers sit in Monitor: Freedom to make life choices, Perceptions of corruption, and Generosity are weakly associated with the outcome and rated low, warranting only light attention. No drivers require priority action (Improve/Priority quadrant is empty), and none suggest over-investment (Reduce effort quadrant is empty). Every recommendation rests on association with Score; treat the quadrants as places to test, not proven levers for change.
Key Driver Analysis — What Moves Satisfaction
The marquee CX / survey analysis: of the attributes you measure, which ones most move an outcome you care about (satisfaction, NPS, spend, retention)? Key Driver Analysis combines two lenses — statistical IMPORTANCE (how tightly each attribute tracks the outcome) and current PERFORMANCE (how well each attribute is rated today) — into an action quadrant that tells you where to invest first.
Why This Method?
Ranking drivers by raw correlation alone tells you what matters but not where you are weak. Ranking by performance alone tells you where you are weak but not whether it matters. Plotting importance against performance resolves both at once: the drivers that are important AND under-performing are the priorities; important-and-strong drivers are strengths to protect.
What This Analysis Covers
- Relative importance of each driver (standardized effect + correlation)
- Current performance of each driver on a 0-100 scale
- The importance-vs-performance action quadrant
- A per-quadrant action summary
Standard Library
Platform standard-library module (LAT-1441): runs on ANY dataset via the semantic mapping {outcome, driver_1..driver_N}. All narrative is derived from the user's own column names and computed values. This is a CORRELATIONAL analysis — it reports association, never proven causation.
suppressPackageStartupMessages(library(DT))
suppressPackageStartupMessages(library(htmlwidgets))
suppressPackageStartupMessages(library(arrow))
suppressPackageStartupMessages(library(knitr))
suppressPackageStartupMessages(library(rmarkdown))
suppressPackageStartupMessages(library(dplyr))
suppressPackageStartupMessages(library(tidyr))
suppressPackageStartupMessages(library(ggplot2))
suppressPackageStartupMessages(library(stringr))
suppressPackageStartupMessages(library(lubridate))
suppressPackageStartupMessages(library(broom))
suppressPackageStartupMessages(library(Matrix))
suppressPackageStartupMessages(library(cluster))
suppressPackageStartupMessages(library(data.table))Step 1: Row accounting + semantic column discovery
initial_rows <- nrow(df)
if (!"outcome" %in% names(df)) {
stop("column_mapping must map an 'outcome' column (the numeric outcome to explain, e.g. satisfaction, NPS, or spend)")
}
driver_cols <- grep("^driver_[0-9]+$", names(df), value = TRUE)
driver_cols <- driver_cols[order(as.integer(sub("^driver_", "", driver_cols)))]
if (length(driver_cols) == 0) {
stop("column_mapping must map at least one driver column(driver_1)")
}
outcome_name <- humanize_semantic("outcome", col_map)
driver_names <- setNames(humanize_semantic(driver_cols, col_map), driver_cols)Step 2: Coerce outcome to numeric; drop rows with a missing outcome
df$outcome <- suppressWarnings(as.numeric(df$outcome))
df <- df[!is.na(df$outcome), , drop = FALSE]
if (nrow(df) < 10) {
stop(sprintf(
"Only %d rows have a usable numeric value in the outcome column '%s'. At least 10 are required for Key Driver Analysis.",
nrow(df), outcome_name))
}Step 3: Coerce each driver to numeric (95%% rule); impute NA with median
dropped_drivers <- character(0)
for (dc in driver_cols) {
v <- df[[dc]]
if (!is.numeric(v)) {
conv <- suppressWarnings(as.numeric(as.character(v)))
n_orig <- sum(!is.na(v) & as.character(v) != "")
if (n_orig > 0 && sum(!is.na(conv)) >= 0.95 * n_orig) {
df[[dc]] <- conv
} else {
dropped_drivers <- c(dropped_drivers, dc); next
}
}
v <- df[[dc]]
med <- median(v, na.rm = TRUE)
if (is.na(med)) { dropped_drivers <- c(dropped_drivers, dc); next }
v[is.na(v)] <- med
df[[dc]] <- v
}Step 4: Drop zero-variance (constant) drivers — report them
for (dc in setdiff(driver_cols, dropped_drivers)) {
v <- df[[dc]]
if (isTRUE(var(v, na.rm = TRUE) == 0) || is.na(var(v, na.rm = TRUE))) {
dropped_drivers <- c(dropped_drivers, dc)
}
}
model_drivers <- setdiff(driver_cols, dropped_drivers)
if (length(model_drivers) == 0) {
stop("No usable driver columns remained after cleaning(all were constant, empty, or non-numeric).")
}
df_clean <- df[, c("outcome", model_drivers), drop = FALSE]
final_rows <- nrow(df_clean)
rows_removed <- initial_rows - final_rowsStep 5: Guard — need clearly more rows than drivers (p < n)
while (length(model_drivers) >= final_rows - 2 && length(model_drivers) > 1) {
drop_dc <- model_drivers[length(model_drivers)]
dropped_drivers <- c(dropped_drivers, drop_dc)
model_drivers <- model_drivers[-length(model_drivers)]
df_clean <- df_clean[, c("outcome", model_drivers), drop = FALSE]
}Step 6: Importance — standardized regression coefficients + correlations
Standardize outcome + drivers, fit OLS on the z-scores. |standardized beta| is each driver's independent effect on a common scale. Relative importance = each driver's share of the total |beta|, times 100 (a practical relative-weights proxy). Perfectly collinear drivers are aliased by lm (NA beta) — they carry no independent share, so their |beta| is treated as 0 and a collinearity note is raised.
zdf <- as.data.frame(scale(df_clean[, c("outcome", model_drivers), drop = FALSE]))
z_model <- lm(outcome ~ ., data = zdf)
z_coef <- coef(z_model)
std_betas_raw <- z_coef[model_drivers] # named by semantic; NA if aliased
aliased_any <- any(is.na(std_betas_raw))
abs_beta <- abs(std_betas_raw)
abs_beta[is.na(abs_beta)] <- 0
total_beta <- sum(abs_beta)
rel_importance <- if (total_beta > 0) 100 * abs_beta / total_beta else rep(0, length(abs_beta))
rel_importance <- round(as.numeric(rel_importance), 1)
r_squared <- summary(z_model)$r.squared
if (is.na(r_squared)) r_squared <- 0
correlations <- sapply(model_drivers, function(dc) {
suppressWarnings(cor(df_clean[[dc]], df_clean$outcome, use = "complete.obs"))
})
correlations[is.na(correlations)] <- 0Collinearity check — exact aliasing or any driver pair above 0.9 |r|.
max_pair_cor <- 0
if (length(model_drivers) >= 2) {
dm <- suppressWarnings(cor(df_clean[, model_drivers, drop = FALSE],
use = "pairwise.complete.obs"))
dm[!is.finite(dm)] <- 0
diag(dm) <- 0
max_pair_cor <- max(abs(dm))
}
collinear <- aliased_any || (max_pair_cor > 0.9)Step 7: Performance — mean rating normalized to 0-100
mean_ratings <- sapply(model_drivers, function(dc) mean(df_clean[[dc]], na.rm = TRUE))
all_vals <- unlist(df_clean[, model_drivers], use.names = FALSE)
vmax <- max(all_vals, na.rm = TRUE)
vmin <- min(all_vals, na.rm = TRUE)
scale_detected <- is.finite(vmax) && vmax <= 10 && vmin >= 0
if (scale_detected) {
scale_min <- if (vmin < 1) 0 else 1
scale_max <- if (vmax <= 5) 5 else if (vmax <= 7) 7 else 10
performance <- 100 * (mean_ratings - scale_min) / (scale_max - scale_min)
} else {
performance <- sapply(model_drivers, function(dc) {
lo <- min(df_clean[[dc]], na.rm = TRUE)
hi <- max(df_clean[[dc]], na.rm = TRUE)
if (hi > lo) 100 * (mean(df_clean[[dc]], na.rm = TRUE) - lo) / (hi - lo) else 50
})
}
performance <- round(pmin(100, pmax(0, as.numeric(performance))), 1)Step 8: Quadrants — median split of importance x performance
imp_median <- median(rel_importance)
perf_median <- median(performance)
high_imp <- rel_importance >= imp_median
high_perf <- performance >= perf_median
quadrant <- ifelse(high_imp & high_perf, "Maintain",
ifelse(high_imp & !high_perf, "Improve / Priority",
ifelse(!high_imp & !high_perf, "Monitor",
"Reduce effort / Possible over-invest")))
drivers_df <- data.frame(
semantic = model_drivers,
driver = unname(driver_names[model_drivers]),
relative_importance = rel_importance,
correlation = round(as.numeric(correlations), 3),
std_beta = round(as.numeric(ifelse(is.na(std_betas_raw), 0, std_betas_raw)), 3),
mean_rating = round(as.numeric(mean_ratings), 2),
mean_performance = performance,
quadrant = quadrant,
stringsAsFactors = FALSE
)
drivers_df <- drivers_df[order(-drivers_df$relative_importance,
-abs(drivers_df$correlation)), , drop = FALSE]
rownames(drivers_df) <- NULLStep 10: Headline drivers
top_driver_name <- drivers_df$driver[1]
top_rel_importance <- drivers_df$relative_importance[1]
priority_rows <- drivers_df[drivers_df$quadrant == "Improve / Priority", , drop = FALSE]
priority_driver_name <- if (nrow(priority_rows) > 0) priority_rows$driver[1] else NA_character_
priority_driver_perf <- if (nrow(priority_rows) > 0) priority_rows$mean_performance[1] else NA_real_
n_priority <- nrow(priority_rows)Step 11: KPI metrics
metrics <- list(
`Responses` = final_rows,
`Drivers Analysed` = length(model_drivers),
`Top Driver` = top_driver_name,
`Top Relative Importance` = round(top_rel_importance, 1),
`Model R Squared` = round(r_squared, 3),
`Priority Drivers` = n_priority
)Step 12: json_output machine channel
priority_clause <- if (!is.na(priority_driver_name)) {
paste0(priority_driver_name, " is the clearest priority — important yet rated ",
round(priority_driver_perf, 0), " out of 100.")
} else {
"No driver falls in the Improve / Priority quadrant."
}
json_output <- list(
answer = paste0(
"Key Driver Analysis of ", outcome_name, " across ",
format(final_rows, big.mark = ","), " responses on ",
n_things(length(model_drivers), "driver"), ": ", top_driver_name,
" is the most influential, holding about ", round(top_rel_importance, 0),
"% of the combined driver importance. ", priority_clause,
" Model R-squared is ", round(r_squared, 3),
". These are associations, not proof that a driver changes the outcome."
),
cards = lapply(
c("tldr", "overview", "preprocessing", "importance_chart",
"driver_table", "priority_matrix", "action_summary"),
function(cid) list(id = cid, metrics = metrics)
)
)
list(
initial_rows = initial_rows, final_rows = final_rows, rows_removed = rows_removed,
outcome_name = outcome_name, driver_names = driver_names,
model_drivers = model_drivers, dropped_drivers = dropped_drivers,
df_clean = df_clean, r_squared = r_squared,
scale_detected = scale_detected, collinear = collinear,
drivers_df = drivers_df, importance_df = importance_df,
driver_details_df = driver_details_df,
importance_performance_df = importance_performance_df,
quadrant_summary_df = quadrant_summary_df,
top_driver_name = top_driver_name, top_rel_importance = top_rel_importance,
priority_driver_name = priority_driver_name,
priority_driver_perf = priority_driver_perf, n_priority = n_priority,
metrics = metrics, json_output = json_output
)
}