Executive Summary
The 80/20 split across 30 categories
Seven of 30 categories drive 83.1% of total Cost. The single largest driver is D01 at 23.17%, and the top three (D01, D02, D03) together account for 54.6%. This extreme concentration—highlighted by a Gini coefficient of 0.729—means that targeting the A-class 7 categories will move the total most efficiently. The remaining 23 categories contribute only 16.9%, indicating that broad, unfocused effort across all defect types is wasteful. Prioritization on the vital few is where cost reduction effort yields the highest return.
Analysis Overview
Pareto analysis of 30 Defect Type categories across 506 rows.
The Pareto principle identifies where to focus effort to move the total. This analysis ranks 30 Defect Type categories by total Cost across 506 records, totalling 27,631. The vital few—just 7 categories (23.3% of the set)—carry 83.1% of the total Cost. These A-class categories are the leverage points: improving them yields proportionally larger gains than spreading effort across all 30. The remaining 23 categories contribute only 16.9%, making them lower-priority unless their defects are easily corrected. The concentration (Gini: 0.729) confirms that cost is highly skewed toward a small subset, validating the focus on the top 7.
Data Quality
Rows, categories, negatives handling, and the chart rollup.
Data quality is sound for Pareto analysis. Of 521 rows loaded, 506 were retained across 30 Defect Type categories, with a grand total Cost of 27,631. Fifteen records with negative Cost values (totalling -768) were dropped because Pareto assumes non-negative contributions; five zero-value records were retained. The chart displays the top 20 categories individually and rolls the remaining 10 into an Other bar (371.49 in value), but all 30 categories are included in the vital-few and ABC class calculations. This approach preserves analytical integrity while keeping the visual uncluttered.
Pareto Chart
Ranked category totals with the cumulative share.
The ranked bars fall steeply: D01 leads at 6,402.57 (23.17%), followed by D02 at 4,759.71 (17.23%) and D03 at 3,925.18 (14.21%). The cumulative share climbs sharply through the first seven categories, reaching 83.1% at D07 (1,137.56), then flattens dramatically. By D10 (625.34), the cumulative is already 91.41%, and the tail from D11 onward each contributes less than 1.5% individually. This steep-then-flat profile is the signature of Pareto dominance: a handful of categories account for most cost, while the rest scatter thinly. The visual confirms that effort on the top 7 yields disproportionate impact.
ABC Classes
Category counts and value share for the A, B, and C classes.
| Class | Categories | Value | Value Share PCT | Description |
|---|---|---|---|---|
| A | 7 | 22,962 | 83.1 | Vital few - the top categories that together reach about 80 percent of the total. |
| B | 7 | 3,467 | 12.5 | Useful many - the next categories carrying the 80 to 95 percent band. |
| C | 16 | 1,202 | 4.4 | Trivial many - the long tail below the last 5 percent. |
Class A (7 categories) holds 22,962 in Cost—83.1% of the total—and is the vital few demanding tight management and immediate intervention. Class B (7 categories) carries 3,467 (12.5%), spanning the 80–95% cumulative band; these merit batched or periodic review. Class C (16 categories) totals 1,202 (4.4%), the trivial many below 95%; these can be handled in bulk or deprioritized. The three-tier split clarifies governance: A categories require individual scrutiny and targeted cost-reduction initiatives; B categories warrant consolidated monitoring; C categories absorb minimal resources. This segmentation aligns effort intensity with value impact.
Top Contributors
The highest-value categories ranked, with cumulative share.
| Rank | Category | Value | Share PCT | Cumulative PCT |
|---|---|---|---|---|
| 1 | D01 | 6,403 | 23.17 | 23.17 |
| 2 | D02 | 4,760 | 17.23 | 40.4 |
| 3 | D03 | 3,925 | 14.21 | 54.6 |
| 4 | D04 | 2,886 | 10.45 | 65.05 |
| 5 | D05 | 2,253 | 8.15 | 73.2 |
| 6 | D06 | 1,598 | 5.78 | 78.99 |
| 7 | D07 | 1,138 | 4.12 | 83.1 |
| 8 | D09 | 863 | 3.13 | 86.23 |
| 9 | D08 | 805 | 2.91 | 89.14 |
| 10 | D10 | 625 | 2.26 | 91.41 |
| 11 | D11 | 391 | 1.42 | 92.82 |
| 12 | D12 | 297 | 1.07 | 93.9 |
| 13 | D14 | 274 | 0.99 | 94.89 |
| 14 | D15 | 210 | 0.76 | 95.65 |
| 15 | D13 | 197 | 0.71 | 96.36 |
D01 dominates at 6,403 (23.17% share), followed by D02 at 4,760 (17.23%) and D03 at 3,925 (14.21%). The cumulative share crosses the 80% vital-few threshold at rank 7 (D07: 1,138, 4.12% share, cumulative 83.1%). Below rank 7, individual category shares drop sharply—D09 is 3.13%, D08 is 2.91%, and by rank 10 (D10) the share falls to 2.26%. This ranked list is the concrete priority roadmap: the first seven categories are the cost-drivers to address; the remainder are secondary. Focusing resources on reducing D01, D02, and D03 alone would cut total cost by more than half (54.6%).
Methodology
Statistical methodology and diagnostics for Pareto Analysis — The Vital Few (80/20)
Statistical Method
Standard-library analysis: find the few categories that drive most of the total, straight from a raw category/value table. The analysis totals your value by category, ranks them biggest to smallest, builds the cumulative 80/20 curve, and reports the vital few that reach 80%, the ABC classes (A/B/C), and how concentrated the total is — the workhorse Lean and Six Sigma tool for defects, complaints, revenue concentration, or SKUs. Map one category column and one numeric value column.
- Values are contributions to a total (counts or amounts), not rates or ratios
- Negative values are refunds or corrections and are dropped; zeros are kept
- Categories that repeat across rows are summed before ranking
- Pareto ranks and concentrates a total — it does not test significance or explain why a category is large
- A very long tail of tiny categories can make the 80/20 split look sharper than day-to-day operations feel
- The chart caps at the top 20 categories (the rest pool into Other), though every category still counts in the vital-few and ABC totals
Analysis Code
Complete R source code for this analysis
Pareto Analysis — The Vital Few (80/20)
Finds the few categories that drive most of the total. Aggregates a raw category/value table, sorts the categories from biggest to smallest, builds the cumulative curve, and reports the "vital few" that reach 80% of the total, the ABC classes, and how concentrated the total is.
Why This Method?
The Pareto principle — roughly 80% of effects come from 20% of causes — is the workhorse of Lean and Six Sigma. It turns a long, flat list of defects, complaints, SKUs, or revenue lines into a ranked priority list: fix the vital few and you move most of the total. It needs no model, only a category and an amount.
What This Analysis Covers
- The vital few: the smallest set of categories reaching 80% of the total
- ABC classes (A up to 80%, B 80-95%, C the tail)
- The Pareto chart (ranked bars with the cumulative share)
- Concentration: the top category and top-three shares
Standard Library
Platform standard-library module (LAT-1441): runs on ANY dataset via the semantic mapping {category, value}. 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
Number formatter — plain rounded strings with thousands separators.
Table numerics are formatted through this so large totals never serialize in scientific notation (e.g. 2.76e+04) inside a rendered table.
fmt_num <- function(x, digits = 0) {
format(round(as.numeric(x), digits), big.mark = ",", scientific = FALSE,
trim = TRUE, nsmall = digits)
}Gini concentration (0 = every category equal, 1 = one holds it all).
Defensive: needs a positive total and >=2 categories, else returns NA.
gini_concentration <- function(v) {
v <- v[!is.na(v)]
n <- length(v)
s <- sum(v)
if (n < 2 || s <= 0) return(NA_real_)
vs <- sort(v)
(2 * sum(seq_len(n) * vs) / (n * s)) - (n + 1) / n
}
compute_shared <- function(df, params, col_map = list()) {
# === SHARED EXPORTS ===
# initial_rows/final_rows/rows_removed $ row accounting
# n_missing_cat / n_missing_value / n_negative / n_zero $ preprocessing counts
# negative_total $ numeric — summed value of dropped negatives
# cat_name / value_name $ humanized user column names
# agg_df $ data.frame(category, value, share_pct,
# cumulative, cumulative_pct, rank, abc) sorted desc
# n_categories / total_value $ integer / numeric
# vital_few_n / vital_few_cat_share / vital_few_value_share
# top_category / top_share / top3_share $ concentration headline
# gini $ numeric — Gini concentration
# abc_summary_df $ data.frame(class, categories, value,
# value_share_pct, description)
# n_a / n_b / n_c $ integer — ABC class sizes
# pareto_df $ data.frame(category, value, cumulative_pct) top 20 + Other
# top_contributors_df $ data.frame(rank, category, value, share_pct, cumulative_pct)
# metrics / json_output
# === /SHARED EXPORTS ===Step 1: Required semantic columns + humanized names
initial_rows <- nrow(df)
cat_name <- humanize_semantic("category", col_map)[1]
value_name <- humanize_semantic("value", col_map)[1]
for (need in c("category", "value")) {
if (!need %in% names(df)) {
stop(sprintf("Required column '%s' is not mapped.",
humanize_semantic(need, col_map)[1]))
}
}Step 2: Category — character, drop blank/missing
cat_v <- trimws(as.character(df$category))
keep_cat <- !is.na(cat_v) & cat_v != ""
n_missing_cat <- sum(!keep_cat)
cat_v <- cat_v[keep_cat]
val <- df$value[keep_cat]Step 3: Value — 95% numeric coercion rule; drop NA
if (!is.numeric(val)) {
conv <- suppressWarnings(as.numeric(as.character(val)))
n_orig <- sum(!is.na(val) & trimws(as.character(val)) != "")
if (n_orig == 0 || sum(!is.na(conv)) < 0.95 * n_orig) {
stop(sprintf(
"Column '%s' is not numeric enough to total up — fewer than 95%% of its values could be read as numbers.",
value_name))
}
val <- conv
}
keep_val <- !is.na(val)
n_missing_value <- sum(!keep_val)
cat_v <- cat_v[keep_val]
val <- val[keep_val]Step 4: Drop negatives (Pareto assumes non-negative contributions),
keep zeros. Count and total the dropped negatives for the narrative.
neg_mask <- val < 0
n_negative <- sum(neg_mask)
negative_total <- if (n_negative > 0) sum(val[neg_mask]) else 0
cat_v <- cat_v[!neg_mask]
val <- val[!neg_mask]
n_zero <- sum(val == 0)
final_rows <- length(val)
rows_removed <- initial_rows - final_rows
if (final_rows < 5) {
stop(sprintf(
"Only %d usable rows remain after cleaning '%s' and '%s' — need at least 5.",
final_rows, cat_name, value_name))
}Step 5: Aggregate (sum) by category, sort biggest to smallest
agg <- data.frame(category = cat_v, value = val, stringsAsFactors = FALSE) %>%
group_by(category) %>%
summarise(value = sum(value), .groups = "drop") %>%
as.data.frame(stringsAsFactors = FALSE)
agg <- agg[order(-agg$value), , drop = FALSE]
rownames(agg) <- NULL
n_categories <- nrow(agg)
total_value <- sum(agg$value)
if (total_value <= 0) {
stop(sprintf(
"The total of '%s' across all categories is zero, so there is no share to distribute.",
value_name))
}Step 6: Shares + cumulative curve
agg$share_pct <- round(100 * agg$value / total_value, 2)
agg$cumulative <- cumsum(agg$value)
agg$cumulative_pct <- round(100 * agg$cumulative / total_value, 2)
agg$rank <- seq_len(n_categories)Step 7: The vital few — smallest prefix reaching 80% cumulative.
cumulative_pct is monotonic and its last value is 100, so a match always exists; which.max returns the first TRUE (guarded fallback regardless).
reach80 <- agg$cumulative_pct >= 80
vital_few_n <- if (any(reach80)) which.max(reach80) else n_categories
vital_few_cat_share <- round(100 * vital_few_n / n_categories, 1)
vital_few_value_share <- round(agg$cumulative_pct[vital_few_n], 1)Step 8: ABC classes (A up to 80%, B 80-95%, C the tail)
reach95 <- agg$cumulative_pct >= 95
b_end <- if (any(reach95)) max(which.max(reach95), vital_few_n) else n_categories
abc <- rep("C", n_categories)
abc[seq_len(vital_few_n)] <- "A"
if (b_end > vital_few_n) abc[(vital_few_n + 1):b_end] <- "B"
agg$abc <- abc
n_a <- sum(abc == "A"); n_b <- sum(abc == "B"); n_c <- sum(abc == "C")
abc_desc <- c(
A = "Vital few - the top categories that together reach about 80 percent of the total.",
B = "Useful many - the next categories carrying the 80 to 95 percent band.",
C = "Trivial many - the long tail below the last 5 percent."
)
abc_summary_df <- do.call(rbind, lapply(c("A", "B", "C"), function(cl) {
idx <- abc == cl
data.frame(
class = cl,
categories = sum(idx),
value = fmt_num(sum(agg$value[idx]), 0),
value_share_pct = round(100 * sum(agg$value[idx]) / total_value, 1),
description = unname(abc_desc[cl]),
stringsAsFactors = FALSE
)
}))
rownames(abc_summary_df) <- NULLStep 9: Concentration headline
top_category <- agg$category[1]
top_share <- agg$share_pct[1]
top3_n <- min(3, n_categories)
top3_share <- round(sum(agg$share_pct[seq_len(top3_n)]), 1)
gini <- round(gini_concentration(agg$value), 3)Step 10: Pareto chart data — top 20 categories, rest rolled into
"Other" (chart only; the vital-few and ABC counts use the FULL data).
cap <- 20
if (n_categories > cap) {
head_df <- agg[seq_len(cap), c("category", "value", "cumulative_pct")]
other_value <- total_value - sum(agg$value[seq_len(cap)])
pareto_df <- rbind(
head_df,
data.frame(category = "Other", value = round(other_value, 2),
cumulative_pct = 100, stringsAsFactors = FALSE)
)
} else {
pareto_df <- agg[, c("category", "value", "cumulative_pct")]
}
pareto_df$value <- round(pareto_df$value, 2)
rownames(pareto_df) <- NULLStep 11: Top contributors table — top 15, values as plain strings
tc_n <- min(15, n_categories)
top_contributors_df <- data.frame(
rank = agg$rank[seq_len(tc_n)],
category = agg$category[seq_len(tc_n)],
value = fmt_num(agg$value[seq_len(tc_n)], 0),
share_pct = agg$share_pct[seq_len(tc_n)],
cumulative_pct = agg$cumulative_pct[seq_len(tc_n)],
stringsAsFactors = FALSE
)
rownames(top_contributors_df) <- NULL
metrics <- list(
`Categories` = n_categories,
`Vital Few(80%)` = vital_few_n,
`Vital Few Share %` = vital_few_cat_share,
`Top Category Share %` = top_share,
`Top 3 Share %` = top3_share,
`Concentration(Gini)` = gini
)
json_output <- list(
answer = paste0(
"Pareto analysis of ", format(n_categories, big.mark = ","),
" categories from ", format(final_rows, big.mark = ","), " rows: ",
"the vital few is ", vital_few_n, " ", cat_name,
if (vital_few_n == 1) " category" else " categories", " (",
vital_few_cat_share, "% of them) driving ", vital_few_value_share,
"% of total ", value_name, ". The single biggest is ", top_category,
" at ", top_share, "%; the top ", top3_n, " together hold ", top3_share,
"%. ABC split: A = ", n_a, ", B = ", n_b, ", C = ", n_c, " categories."
),
cards = lapply(
c("tldr", "overview", "preprocessing", "pareto_chart",
"abc_classes", "top_contributors"),
function(cid) list(id = cid, metrics = metrics)
)
)
list(
initial_rows = initial_rows, final_rows = final_rows,
rows_removed = rows_removed,
n_missing_cat = n_missing_cat, n_missing_value = n_missing_value,
n_negative = n_negative, negative_total = negative_total, n_zero = n_zero,
cat_name = cat_name, value_name = value_name,
agg_df = agg, n_categories = n_categories, total_value = total_value,
vital_few_n = vital_few_n, vital_few_cat_share = vital_few_cat_share,
vital_few_value_share = vital_few_value_share,
top_category = top_category, top_share = top_share, top3_share = top3_share,
top3_n = top3_n, gini = gini,
abc_summary_df = abc_summary_df, n_a = n_a, n_b = n_b, n_c = n_c,
pareto_df = pareto_df, top_contributors_df = top_contributors_df,
metrics = metrics, json_output = json_output
)
}