Executive Summary
Net Promoter Score from Likelihood to Recommend
The short answer
Your overall NPS is +15 (95% CI +9 to +21) from 600 responses — a positive but modest score, with more promoters than detractors. The composition is 40% promoters, 35% passives, and 25% detractors. Pro plan customers drive the difference: they score +30, while Free plan customers score +0, a statistically significant gap of +30 points.
The detail
Overall NPS = +15 points, 95% CI +9 to +21. Promoters (9–10) comprise 40% of responses, passives (7–8) 35%, and detractors (0–6) 25%. By segment: Pro plan NPS +30 (95% CI +21 to +39, n = 300); Free plan NPS +0 (95% CI −9 to +9, n = 300). The gap is statistically significant (z = 4.7, p < 0.0001) with non-overlapping confidence intervals.
What this can't tell you
This snapshot does not reveal why the segments differ or which features or experiences drive the Pro-plan advantage. Qualitative follow-up or feature-level analysis would be needed to understand the levers.
Analysis Overview
Net Promoter Score from 600 responses to Likelihood to Recommend.
The short answer
This analysis measures Net Promoter Score (NPS) on the standard 0-10 likelihood-to-recommend scale, where promoters score 9–10, passives 7–8, and detractors 0–6. NPS is the percentage of promoters minus the percentage of detractors, expressed as a point score from −100 to +100. Because NPS is estimated from a sample, it carries a 95% confidence interval: here +15 points with a range of ±6 points either side. Segment-level NPS scores and intervals are computed the same way, and the largest gap between segments is tested for statistical significance using a z-test.
The detail
The classical multinomial variance formula governs the confidence interval: Var(NPS) = (p_promoter + p_detractor − (p_promoter − p_detractor)²) / n. On this dataset of 600 valid responses, the standard error is 3.2 points, yielding a 95% interval of +9 to +21. When a Customer Segment column is mapped, each segment receives its own NPS and interval. The z-test for segment differences runs only when both segments have at least 30 responses; non-overlapping intervals and p < 0.05 both indicate a genuine difference.
What this can't tell you
This is a single cross-sectional snapshot; no trend over time is visible here. NPS is a population estimate and should not be treated as a precise individual-level predictor.
Data Quality
Row cleaning and scale validation applied before scoring.
The short answer
Of 616 rows loaded, 600 valid 0–10 responses were retained for scoring. Sixteen rows were removed: 12 had blank or non-numeric Likelihood to Recommend values, and 4 had scores outside the 0–10 range. No non-integer scores required rounding. The final dataset is clean and ready for NPS calculation.
The detail
The preprocessing pipeline drops any row with a blank or non-numeric score, then excludes any score falling outside 0–10. This export contained 12 blank-or-non-numeric rows and 4 out-of-range rows (16 total), leaving 600 valid observations. No non-integer values were rounded. Rare segment levels, if present, would be lumped into "Other" to preserve statistical power in segment comparisons.
What this can't tell you
The export does not show which specific out-of-range values were removed or why they occurred (e.g., data-entry error vs. system miscoding). If out-of-range scores cluster in a particular segment or time window, that pattern would require row-level diagnostics to detect.
Score Distribution
How the 600 responses spread across the 0-10 scale.
The short answer
Responses cluster at the extremes: the most common single score is 9, and the distribution is polarized between a delighted block at 9–10 and a disappointed block at the low end. Exactly 40% land in the promoter zone (9–10), 35% are passive (7–8), and 25% are detractor (0–6), with 10.3% scoring 3 or below. The average NPS masks two distinct customer experiences.
The detail
Score 9 is the modal response (129 out of 600). The 9–10 band holds 240 responses; the 7–8 band holds 210; the 0–6 band holds 150. The bottom tier (0–3) accounts for 62 responses. The bimodal shape — high mass at 9–10 and again at 0–6 — indicates polarization: satisfied customers cluster at the top, dissatisfied at the bottom, with relatively few in the middle.
What this can't tell you
This cross-section does not show whether the polarization is stable over time or whether the two blocks represent distinct user cohorts. Segmenting the score distribution by other attributes (e.g., plan type, tenure, feature use) would clarify whether the bimodality is uniform across all groups.
Promoters, Passives & Detractors
The NPS composition of each Customer Segment segment.
The short answer
Pro plan customers have a healthier NPS composition: 50% promoters, 30% passives, 20% detractors (NPS +30). Free plan customers are evenly split: 30% promoters, 40% passives, 30% detractors (NPS +0). The key difference is the promoter base — Pro customers are 20 percentage points higher — while Free plan detractors match the promoter share, pulling the score to neutral.
The detail
Pro plan: 50% promoters (9–10), 30% passives (7–8), 20% detractors (0–6). Free plan: 30% promoters, 40% passives, 30% detractors. Pro plan's NPS advantage stems from both a larger promoter block and a smaller detractor block. Free plan's NPS +0 reflects balance: the 30% promoter and 30% detractor shares cancel out, while passives (40%) form the largest single group. Overall, 40% promoters, 35% passives, 25% detractors.
What this can't tell you
The composition does not reveal which Free plan users are passives (potentially at risk of downgrading) versus which are stable, or whether Pro plan's higher promoter share reflects selection (only satisfied users upgrade) or causation (Pro features drive satisfaction). Churn or upgrade data would clarify this.
NPS by Segment
NPS, sample size, and 95% CI per Customer Segment segment.
| Segment | N | Nps | CI | Flag |
|---|---|---|---|---|
| Pro plan | 300 | 30 | +21 to +39 | |
| Free plan | 300 | 0 | -9 to +9 |
The short answer
Pro plan has an NPS of +30 (95% CI +21 to +39, n = 300), while Free plan has an NPS of +0 (95% CI −9 to +9, n = 300). The +30-point gap is statistically significant (z = 4.7, p < 0.0001) with non-overlapping confidence intervals, meaning the Pro-plan advantage is real, not noise.
The detail
Pro plan leads decisively: NPS +30, 95% CI +21 to +39. Free plan trails: NPS +0, 95% CI −9 to +9. Both segments have 300 responses, meeting the 30-response threshold for the z-test. The two-proportion z-test yields z = 4.7, p < 0.0001. The confidence intervals do not overlap (Pro's lower bound +21 exceeds Free's upper bound +9), confirming a genuine difference.
What this can't tell you
The gap's size and direction are clear, but the mechanism is not. Whether Pro customers are inherently more satisfied, benefit from Pro features, or represent a self-selected cohort of higher-engagement users cannot be determined from NPS alone. Analyzing feature adoption, tenure, or support interactions by segment would help isolate drivers.
Method & Fine Print
Formulas, validation rules, and this dataset's actual margin of error.
| Item | Detail |
|---|---|
| Score categories | Promoters score 9-10, passives 7-8, detractors 0-6 on the 0-10 likelihood-to-recommend scale. |
| NPS formula | NPS = % promoters minus % detractors, stated as a point score from -100 to +100. |
| Confidence interval | Classical multinomial variance formula: Var(NPS) = (p_promoter + p_detractor - (p_promoter - p_detractor)^2) / n on the proportion scale; SE in points = 100 x sqrt(Var); 95% CI = NPS plus or minus 1.96 x SE. |
| This dataset | n = 600 valid responses; SE = 3.2 points; the 95% interval spans 13 points (+9 to +21). |
| Segment comparison | Two-proportion-style z-test on the NPS difference between the best and worst segment, run only when both have at least 30 responses; otherwise the comparison is reported as underpowered. Gaps with overlapping 95% confidence intervals are never called differences. |
| Scale validation | Values outside 0-10 are dropped and reported. A column whose values sit entirely within 1-5 stops the analysis — a 1-5 rating scale cannot be honestly rescaled to NPS. |
| Small-sample policy | The confidence interval width in points is always stated alongside the score, so a small-sample NPS reads as the rough estimate it is. |
The short answer
NPS is calculated as % promoters minus % detractors on the 0–10 scale. Your dataset of 600 responses yields a standard error of 3.2 points, giving a confidence interval of ±9 to ±21. Segment comparisons use a two-proportion z-test only when both segments have at least 30 responses; overlapping confidence intervals are never called differences.
The detail
NPS = % promoters (9–10) minus % detractors (0–6), expressed as a point score from −100 to +100. The 95% confidence interval uses the classical multinomial variance formula Var(NPS) = (p_promoter + p_detractor − (p_promoter − p_detractor)²) / n. On this dataset (n = 600), the standard error is 3.2 points and the interval spans 13 points. Segment tests require both groups to have at least 30 responses; gaps with overlapping intervals are not reported as differences.
What this can't tell you
To compare NPS by segment, the segment-level data must be supplied separately. This card documents the methodology and precision; the segment breakdown requires a second card with segment-stratified counts.
NPS Analysis — Net Promoter Score with Honest Uncertainty
Computes the Net Promoter Score from a 0-10 likelihood-to-recommend column: promoter / passive / detractor shares, the NPS point score with a 95% confidence interval from the classical multinomial variance formula, the full 0-10 score distribution, and per-segment NPS with a statistically honest best-vs-worst comparison.
Why This Method?
NPS is usually reported as a bare number, which hides how noisy it is on small samples. Stating the confidence interval in points, and refusing to call overlapping-interval segment gaps "differences", turns the same arithmetic into a trustworthy answer.
What This Analysis Covers
- NPS = % promoters (9-10) minus % detractors (0-6), in points
- 95% CI via Var(NPS) = (p9 + p0_6 - (p9 - p0_6)^2) / n
- The raw 0-10 distribution and the promoter/passive/detractor mix
- Per-segment NPS with CIs and a gated best-vs-worst z-test
Standard Library
Platform standard-library module (LAT-1441): runs on ANY dataset via the semantic mapping {score, segment}. All narrative is derived from the user's own column names and computed values.
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))Core Analysis Pipeline
compute_shared <- function(df, params, col_map = list()) {
# === SHARED EXPORTS ===
# initial_rows/final_rows/rows_removed $ row accounting
# score_h / segment_h $ humanized user names for the mapped columns
# has_segment $ TRUE when a usable segment column is mapped
# n_na_score / n_out_of_range / n_non_integer $ cleaning counts
# lumped_levels / segment_constant $ segment cleaning notes
# overall $ list(n, prom_pct, pass_pct, det_pct, nps, se_pts,
# ci_low, ci_high, ci_half) — whole-sample NPS
# seg_stats $ list of the same per segment (NULL if no segment)
# segment_nps_df $ segment, n, nps, ci, flag (falls back to Overall)
# score_distribution_df $ score ("0".."10" in numeric order), count
# nps_mix_df $ segment_label, share_pct, nps_category
# methods_df $ item, detail — formulas + this dataset's numbers
# gap / gap_verdict / gap_text $ best-vs-worst segment comparison
# wide_ci / wide_note $ small-sample honesty (CI width in points)
# fmt_pts / fmt_ci / fmt_p $ formatters (signed points, no e-notation)
# metrics / json_output
# === /SHARED EXPORTS ===Step 0: Formatters — signed point scores, no bare asterisks, no e-notation
fmt_pts <- function(x) sprintf("%+d", as.integer(round(x)))
fmt_p <- function(p) {
if (is.na(p)) "p not computed"
else if (p < 0.0001) "p < 0.0001"
else sprintf("p = %.4f", p)
}Step 1: Resolve mapped columns (humanized for all prose)
initial_rows <- nrow(df)
score_h <- humanize_semantic("score", col_map)
has_segment <- "segment" %in% names(df)
segment_h <- if (has_segment) humanize_semantic("segment", col_map) else "segment"
if (!("score" %in% names(df))) {
stop(sprintf("NPS analysis needs the score column '%s' mapped — the 0-10 likelihood-to-recommend response.", score_h))
}Step 2: Coerce the score to numeric (95% rule)
v <- df$score
if (!is.numeric(v)) {
ch <- as.character(v)
non_blank <- !is.na(ch) & trimws(ch) != ""
conv <- suppressWarnings(as.numeric(ch))
if (sum(non_blank) == 0 ||
sum(!is.na(conv[non_blank])) < 0.95 * sum(non_blank)) {
stop(sprintf("The score column '%s' does not look numeric — fewer than 95%% of its values parse as numbers. NPS needs the raw 0-10 responses.", score_h))
}
v <- conv
}
seg_raw <- if (has_segment) as.character(df$segment) else NULL
keep <- !is.na(v)
n_na_score <- sum(!keep)
v <- v[keep]
if (has_segment) seg_raw <- seg_raw[keep]
if (length(v) == 0) {
stop(sprintf("No rows with a usable numeric value in '%s' remained after cleaning.", score_h))
}Step 3: Scale checks: HARD. 1-5 scales stop; outside 0-10 drops.
A column whose values sit entirely in 1..5 is a 1-5 rating scale, not the 0-10 NPS question. Rescaling would fabricate promoter/detractor categories that were never asked, so the analysis stops instead.
if (min(v) >= 1 && max(v) <= 5) {
stop(sprintf(
paste0("The score column '%s' only contains values from %s to %s — it looks like a 1-5 rating scale. ",
"NPS is defined on the 0-10 likelihood-to-recommend scale(detractors 0-6, passives 7-8, promoters 9-10), ",
"and a 1-5 scale cannot be honestly rescaled onto it — the categories would be invented, not measured. ",
"This analysis stops rather than silently rescale your data. ",
"For 1-5 ratings, use a distribution or group-comparison analysis instead, or field the question on a 0-10 scale."),
score_h,
format(round(min(v), 2), scientific = FALSE),
format(round(max(v), 2), scientific = FALSE)
))
}
in_range <- v >= 0 & v <= 10
n_out_of_range <- sum(!in_range)
if (n_out_of_range > 0.5 * length(v)) {
stop(sprintf("More than half of the values in '%s' fall outside 0-10 — this does not look like the 0-10 NPS question. Map the raw 0-10 likelihood-to-recommend column.", score_h))
}
v <- v[in_range]
if (has_segment) seg_raw <- seg_raw[in_range]
n_non_integer <- sum(v != round(v))
v <- round(v)
final_rows <- length(v)
rows_removed <- initial_rows - final_rows
if (final_rows < 10) {
stop(sprintf("Only %d usable 0-10 responses remained in '%s' — at least 10 are needed to compute an NPS.", final_rows, score_h))
}Step 4: Segment cleaning — blanks to Missing, lump beyond 8 levels
lumped_levels <- character(0)
segment_constant <- FALSE
if (has_segment) {
seg_raw[is.na(seg_raw) | trimws(seg_raw) == ""] <- "Missing"
tab <- sort(table(seg_raw), decreasing = TRUE)
if (length(tab) > 8) {
keep_lv <- names(tab)[1:8]
lumped_levels <- setdiff(names(tab), keep_lv)
seg_raw[seg_raw %in% lumped_levels] <- "Other"
}
if (length(unique(seg_raw)) < 2) {
segment_constant <- TRUE
has_segment <- FALSE
}
}Step 5: NPS with the classical multinomial variance formula
Var(NPS proportion) = (p9 + p0_6 - (p9 - p0_6)^2) / n, where p9 is the promoter share and p0_6 the detractor share. SE in points = 100 x sqrt(Var).
nps_stats <- function(x) {
n <- length(x)
p_prom <- mean(x >= 9)
p_det <- mean(x <= 6)
p_pass <- 1 - p_prom - p_det
nps <- 100 * (p_prom - p_det)
var_prop <- max(0, (p_prom + p_det - (p_prom - p_det)^2) / n)
se_pts <- 100 * sqrt(var_prop)
list(n = n,
prom_pct = 100 * p_prom, pass_pct = 100 * p_pass, det_pct = 100 * p_det,
nps = nps, se_pts = se_pts,
ci_low = nps - 1.96 * se_pts, ci_high = nps + 1.96 * se_pts,
ci_half = 1.96 * se_pts)
}
fmt_ci <- function(s) paste0(fmt_pts(s$ci_low), " to ", fmt_pts(s$ci_high))
overall <- nps_stats(v)Step 6: Per-segment NPS + the gated best-vs-worst comparison
seg_stats <- NULL
gap <- NULL
gap_verdict <- "none" # none | underpowered | overlap | different | not_significant
gap_text <- ""
if (has_segment) {
levs <- names(sort(table(seg_raw), decreasing = TRUE))
seg_stats <- lapply(levs, function(l) c(list(segment = l), nps_stats(v[seg_raw == l])))NA-safe best/worst pick (LAT-1445 class: never which.max over possible NAs)
nps_vals <- sapply(seg_stats, function(s) s$nps)
ok <- which(!is.na(nps_vals))
best <- seg_stats[[ok[which.max(nps_vals[ok])]]]
worst <- seg_stats[[ok[which.min(nps_vals[ok])]]]
gap <- list(best = best, worst = worst, diff = best$nps - worst$nps,
z = NA_real_, p = NA_real_,
cis_overlap = best$ci_low <= worst$ci_high)
if (best$segment != worst$segment) {
if (best$n >= 30 && worst$n >= 30) {Two-proportion-style z-test on the NPS difference (both n >= 30)
se_diff <- sqrt(best$se_pts^2 + worst$se_pts^2)
if (se_diff > 0) {
gap$z <- gap$diff / se_diff
gap$p <- 2 * stats::pnorm(-abs(gap$z))
}
if (gap$cis_overlap) {
gap_verdict <- "overlap"
gap_text <- paste0(
"The widest gap is ", fmt_pts(gap$diff), " points, between ", best$segment,
" (NPS ", fmt_pts(best$nps), ", 95% CI ", fmt_ci(best), ") and ", worst$segment,
" (NPS ", fmt_pts(worst$nps), ", 95% CI ", fmt_ci(worst),
"), but their 95% confidence intervals overlap — this gap must not be called a real difference",
if (!is.na(gap$p) && gap$p < 0.05) " despite the nominal test result" else "", ". ",
"Treat the segments as statistically indistinguishable on this sample."
)
} else if (!is.na(gap$p) && gap$p < 0.05) {
gap_verdict <- "different"
gap_text <- paste0(
best$segment, " (NPS ", fmt_pts(best$nps), ", 95% CI ", fmt_ci(best),
") is genuinely ahead of ", worst$segment, " (NPS ", fmt_pts(worst$nps),
", 95% CI ", fmt_ci(worst), "): a gap of ", fmt_pts(gap$diff),
" points that is statistically significant(z = ",
format(round(gap$z, 1), scientific = FALSE), ", ", fmt_p(gap$p),
"), with non-overlapping confidence intervals."
)
} else {
gap_verdict <- "not_significant"
gap_text <- paste0(
"The gap of ", fmt_pts(gap$diff), " points between ", best$segment,
" and ", worst$segment, " is not statistically reliable(",
fmt_p(gap$p), ") — treat it as noise on this sample."
)
}
} else {
gap_verdict <- "underpowered"
small <- if (best$n < 30 && worst$n < 30) paste0(best$segment, " (n = ", best$n, ") and ", worst$segment, " (n = ", worst$n, ") both have")
else if (best$n < 30) paste0(best$segment, " (n = ", best$n, ") has")
else paste0(worst$segment, " (n = ", worst$n, ") has")
gap_text <- paste0(
"The observed gap between ", best$segment, " (NPS ", fmt_pts(best$nps),
") and ", worst$segment, " (NPS ", fmt_pts(worst$nps), ") is ",
fmt_pts(gap$diff), " points, but the comparison is underpowered: ",
small, " fewer than 30 responses, so no test was run and this gap should not be read as a real difference."
)
}
}
}Step 7: Chart + table datasets (semantic column names)
cnt <- as.integer(table(factor(v, levels = 0:10)))
score_distribution_df <- data.frame(
score = as.character(0:10),
count = cnt,
stringsAsFactors = FALSE
)
cat_labels <- c("Promoters(9-10)", "Passives(7-8)", "Detractors(0-6)")
mix_source <- if (has_segment) seg_stats else list(c(list(segment = "Overall"), overall))
nps_mix_df <- do.call(rbind, lapply(mix_source, function(s) data.frame(
segment_label = s$segment,
nps_category = cat_labels,
share_pct = round(c(s$prom_pct, s$pass_pct, s$det_pct), 1),
stringsAsFactors = FALSE
)))
rownames(nps_mix_df) <- NULL
table_source <- if (has_segment) seg_stats[order(-sapply(seg_stats, function(s) s$nps))]
else list(c(list(segment = "Overall"), overall))
segment_nps_df <- do.call(rbind, lapply(table_source, function(s) data.frame(
segment = s$segment,
n = s$n,
nps = round(s$nps, 1),
ci = paste0(fmt_pts(s$ci_low), " to ", fmt_pts(s$ci_high)),
flag = if (s$n < 30) "low sample(fewer than 30)" else "",
stringsAsFactors = FALSE
)))
rownames(segment_nps_df) <- NULLStep 8: Honesty notes computed from the data
wide_ci <- overall$ci_half >= 15
wide_note <- if (wide_ci) paste0(
"Caution: with only ", format(overall$n, big.mark = ","), " responses the NPS point score is noisy — ",
"the 95% confidence interval spans ", as.integer(round(overall$ci_high - overall$ci_low)),
" points(", fmt_ci(overall), "), so ", fmt_pts(overall$nps),
" is a wide, rough estimate rather than a precise score."
) else ""
methods_df <- data.frame(
item = c(
"Score categories",
"NPS formula",
"Confidence interval",
"This dataset",
"Segment comparison",
"Scale validation",
"Small-sample policy"
),
detail = c(
"Promoters score 9-10, passives 7-8, detractors 0-6 on the 0-10 likelihood-to-recommend scale.",
"NPS = % promoters minus % detractors, stated as a point score from -100 to +100.",
"Classical multinomial variance formula: Var(NPS) = (p_promoter + p_detractor - (p_promoter - p_detractor)^2) / n on the proportion scale; SE in points = 100 x sqrt(Var); 95% CI = NPS plus or minus 1.96 x SE.",
paste0("n = ", format(overall$n, big.mark = ","), " valid responses; SE = ",
format(round(overall$se_pts, 1), scientific = FALSE), " points; the 95% interval spans ",
as.integer(round(overall$ci_high - overall$ci_low)), " points(", fmt_ci(overall), ")."),
"Two-proportion-style z-test on the NPS difference between the best and worst segment, run only when both have at least 30 responses; otherwise the comparison is reported as underpowered. Gaps with overlapping 95% confidence intervals are never called differences.",
"Values outside 0-10 are dropped and reported. A column whose values sit entirely within 1-5 stops the analysis — a 1-5 rating scale cannot be honestly rescaled to NPS.",
"The confidence interval width in points is always stated alongside the score, so a small-sample NPS reads as the rough estimate it is."
),
stringsAsFactors = FALSE
)
metrics <- list(
`NPS` = fmt_pts(overall$nps),
`95% CI` = fmt_ci(overall),
`Promoters %` = round(overall$prom_pct, 1),
`Passives %` = round(overall$pass_pct, 1),
`Detractors %` = round(overall$det_pct, 1),
`Responses` = overall$n
)
json_output <- list(
answer = paste0(
"NPS from ", format(overall$n, big.mark = ","), " responses to ", score_h, ": ",
fmt_pts(overall$nps), " (95% CI ", fmt_ci(overall), "), from ",
round(overall$prom_pct, 1), "% promoters, ", round(overall$pass_pct, 1),
"% passives, and ", round(overall$det_pct, 1), "% detractors. ",
if (nzchar(gap_text)) gap_text
else if (segment_constant) paste0("The mapped ", segment_h, " column has a single value, so no segment comparison was possible.")
else "No segment column was mapped, so the score is reported overall only.",
if (nzchar(wide_note)) paste0(" ", wide_note) else ""
),
cards = lapply(
c("tldr", "overview", "preprocessing", "score_distribution",
"nps_mix", "segment_nps", "methods"),
function(cid) list(id = cid, metrics = metrics)
)
)
list(
initial_rows = initial_rows, final_rows = final_rows,
rows_removed = rows_removed,
score_h = score_h, segment_h = segment_h, has_segment = has_segment,
n_na_score = n_na_score, n_out_of_range = n_out_of_range,
n_non_integer = n_non_integer,
lumped_levels = lumped_levels, segment_constant = segment_constant,
overall = overall, seg_stats = seg_stats,
segment_nps_df = segment_nps_df,
score_distribution_df = score_distribution_df,
nps_mix_df = nps_mix_df, methods_df = methods_df,
gap = gap, gap_verdict = gap_verdict, gap_text = gap_text,
wide_ci = wide_ci, wide_note = wide_note,
fmt_pts = fmt_pts, fmt_ci = fmt_ci, fmt_p = fmt_p,
metrics = metrics, json_output = json_output
)
}Your turn
Bring your own data and the question you actually need answered.
CympleData Scientist Send me your data and question, I’ll send you the analytics. ds@mcpanalytics.ai