Standard Control Charts
Executive Summary

Executive Summary

Whether Fill Weight g is in statistical control

Points
80
Process Mean
101.575
Estimated Sigma
1.74
UCL
106.794
LCL
96.356
Special-Cause Signals
53
Points In Control (%)
33.8
Verdict
Out of control
Charting Fill Weight g as an Individuals control chart. The process mean is 101.57, with natural process limits of 96.36 to 106.79 (three sigma, sigma = 1.74). 53 out-of-control points were flagged by Rule 1, Rule 2, Rule 5, so the process is out of statistical control. 33.8% of points remain in control.
Interpretation

Your fill process is out of statistical control. The process mean is 101.575 g with natural limits of 96.356 to 106.794 g. Across the 80 measurements, 53 points triggered special-cause signals—9 points beyond the 3-sigma limits (Rule 1), 51 points in sustained same-side runs (Rule 2), and 18 points in near-limit clusters (Rule 5). Only 33.8% of points remain in control. This pattern indicates the presence of assignable causes that have shifted or disrupted the process at multiple points in the sequence.

Overview

Analysis Overview

An Individuals control chart of Fill Weight g over 80 ordered points.

N Points80
Process Mean101.575
Estimated Sigma1.74
In ControlFalse
Interpretation

A control chart distinguishes common-cause variation (ordinary process noise) from special-cause variation (signals of change). Your fill process has a mean of 101.575 g and natural process limits of 96.356 to 106.794 g (three sigma, where sigma = 1.74). These limits describe what your process actually does, not what you want it to do—specification limits are separate. A process in statistical control is stable and predictable, though not necessarily meeting target. Critically, being in control does not mean being good; it means the variation is understood and repeatable. Your fill process is currently not in control.

80 rows were loaded and ordered numerically by Sample Number (1 to 80). No measurements were missing, so no rows were dropped. The final series contains 80 ordered points of Fill Weight g, preserving the complete sequence for time-ordered analysis. Data quality is clean with no gaps or exclusions.

Data Preparation

Data Quality

How the raw rows became an ordered measurement series.

Initial Rows80
Final Rows80
N Dropped0
Sequence Ordernumeric
Interpretation

80 rows loaded. The points were ordered by the numeric value of Sample Number (1 to 80). No measurements were missing. Final series: 80 ordered points of Fill Weight g.

Visualization

Individuals Control Chart

Fill Weight g in order against the center line and 3-sigma limits.

Interpretation

The individuals chart shows Fill Weight g plotted in sequence against the center line (101.575 g), upper limit (106.794 g), and lower limit (96.356 g). Of the 80 points, 53 are flagged as out of control. The early sequence (samples 1–11) shows a sustained cluster below the mean, triggering Rule 2 (nine points on one side). Points 33, 61, 65, 70, 71, 72, 73, 77, and 80 breach the 3-sigma limits themselves (Rule 1). A near-limit cluster (Rule 5) appears around samples 30–33, 38, 40, and 61–73, indicating measurements bunching against the statistical boundaries. The pattern is not a smooth trend but rather a series of runs and excursions, suggesting intermittent process shifts rather than a single persistent drift.

Data Table

Special-Cause Signals

Each Western Electric / Nelson rule with the points it flagged.

RuleDescriptionPoints FlaggedCount
Rule 1 — beyond limitsA single point beyond the 3-sigma control limits33, 61, 65, 70, 71, 72, 73, 77, 809
Rule 2 — sustained shiftNine points in a row on the same side of the center line1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 26, and 39 more51
Rule 3 — trendSix points in a row steadily increasing or decreasingnone0
Rule 5 — near-limit clusterTwo of three points in a row beyond 2 sigma on the same side30, 32, 33, 38, 40, 61, 62, 65, 67, 68, 69, 70, and 6 more18
Interpretation

Three control rules fired across 53 flagged points. Rule 1 (beyond limits) caught 9 points: 33, 61, 65, 70, 71, 72, 73, 77, 80. Rule 2 (sustained shift—nine consecutive points on one side of center) fired 51 times, including the opening run (1–11) and additional runs at point 26 and point 39 onward. Rule 3 (six-point trend) did not fire. Rule 5 (near-limit cluster—two of three points beyond 2-sigma on the same side) flagged 18 points: 30, 32, 33, 38, 40, 61, 62, 65, 67, 68, 69, 70, and 6 others. The dominance of Rule 2 and Rule 5 signals indicates the process oscillates between sustained runs and near-boundary clustering rather than drifting monotonically.

Data Table

Process Summary

The headline process statistics and the in-control verdict.

StatisticValueInterpretation
Process mean (center line)101.575The average of Fill Weight g over the ordered points
Estimated sigma1.74Within-process standard deviation, estimated as MRbar divided by 1.128
Upper control limit (UCL)106.794Center line plus three sigma — the upper natural process limit
Lower control limit (LCL)96.356Center line minus three sigma — the lower natural process limit
Mean moving range (MRbar)1.962Average absolute change between consecutive measurements
Points in control33.8%27 of 80 points show no special-cause signal
VerdictOut of statistical control53 special-cause signals across Rule 1, Rule 2, Rule 5
Interpretation

Your fill process has a mean of 101.575 g and estimated sigma of 1.74 g (calculated from a mean moving range of 1.962 g). The natural process limits are 96.356 g (lower) and 106.794 g (upper). Only 27 of 80 points (33.8%) show no special-cause signal; 53 are flagged. The verdict is out of statistical control. These statistics describe the voice of the process—what it currently does—not a specification target. An in-control process is predictable and repeatable, which supports root-cause investigation and improvement; your process is neither, signaling the need to identify and eliminate the assignable causes driving the 53 violations.

Methodology

Methodology

Statistical methodology and diagnostics for Control Chart — Is Your Process In Control?

Statistical Method

Control Chart — Is Your Process In Control?

Standard-library analysis: an Individuals (I-MR) control chart for a sequence of measurements — the SPC / Six Sigma staple for telling a stable process apart from one throwing special-cause signals. Computes the center line, the 3-sigma natural process limits from the moving range, and runs a practical subset of the Western Electric / Nelson rules to flag out-of-control points. Works on any dataset: map a sequence column (date, batch, or sample order) and the measured value.

Data
N = 80 observations
Assumptions
  • Measurements are individual values recorded in a meaningful order (one per point)
  • The measured column is numeric or cleanly convertible
  • Consecutive points are comparable — the moving range estimates the natural process spread
Limitations
  • Control limits describe the process as it is (voice of the process), not what you want it to be (voice of the customer) — no spec limits or Cp / Cpk are computed
  • The Individuals chart is less sensitive to small shifts than subgrouped charts and can be affected by strong autocorrelation
  • In statistical control means predictable, not good — a stable process can still miss its target
Software & Citation
MCP Analytics · mcpanalytics.ai
Code Appendix

Analysis Code

Complete R source code for this analysis

Control Chart — Is Your Process In Control?

Builds an Individuals (I-MR) control chart from an ordered sequence of measurements: orders the points, sets the center line at the process mean, estimates sigma from the average moving range, draws the 3-sigma natural process limits, and runs a practical subset of the Western Electric / Nelson rules to flag special-cause signals — then decides whether the process is in statistical control.

Why This Method?

The Individuals chart is the SPC / Six Sigma workhorse for a stream of one-value-per-point measurements. Estimating sigma from the moving range (rather than the overall standard deviation) keeps the limits honest when the process shifts, so a sustained change shows up as points beyond the limits instead of quietly inflating the spread. Base R only.

What This Analysis Covers

  • Individuals control chart with center line and 3-sigma limits
  • Western Electric / Nelson special-cause signals, flagged point by point
  • A process summary with the in-control verdict

Standard Library

Platform standard-library module (LAT-1441): runs on ANY dataset via the semantic mapping {sequence, measurement}. 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

Step 1: Order by the sequence column

Try dates first (forecasting-style parser); else a numeric sort; else fall back to the input order as given.

raw_seq <- df$sequence
  non_blank <- !(is.na(raw_seq) | !nzchar(trimws(as.character(raw_seq))))
  n_nonblank <- sum(non_blank)
  d <- parse_dates_robust(raw_seq)
  frac_dates <- if (n_nonblank > 0) sum(!is.na(d)) / n_nonblank else 0
  numseq <- suppressWarnings(as.numeric(as.character(raw_seq)))
  frac_num <- if (n_nonblank > 0) sum(!is.na(numseq)) / n_nonblank else 0

  if (frac_dates >= 0.8) {
    ord <- order(d, na.last = TRUE)
    seq_kind <- "date"
  } else if (frac_num >= 0.95 && n_nonblank > 0) {
    ord <- order(numseq, na.last = TRUE)
    seq_kind <- "numeric"
  } else {
    ord <- seq_len(nrow(df))
    seq_kind <- "order"
  }
  df <- df[ord, , drop = FALSE]

Step 2: Coerce the measurement (95% rule) and drop missing values

mv <- df$measurement
  if (!is.numeric(mv)) {
    conv <- suppressWarnings(as.numeric(as.character(mv)))
    n_orig <- sum(!is.na(mv) & nzchar(as.character(mv)))
    if (n_orig > 0 && sum(!is.na(conv)) >= 0.95 * n_orig) {
      mv <- conv
    } else {
      stop(sprintf("The &#x27;%s' column is not numeric — a control chart needs a numeric measurement.",
                   measurement_name))
    }
  }
  mv <- as.numeric(mv)
  d_sorted <- d[ord]
  num_sorted <- numseq[ord]
  keep <- !is.na(mv)
  n_dropped_na <- sum(!keep)
  x <- mv[keep]
  d_keep <- d_sorted[keep]
  num_keep <- num_sorted[keep]
  n_points <- length(x)
  if (n_points < 10) {
    stop(sprintf("Only %d valid %s of &#x27;%s' — a control chart needs at least 10 ordered measurements.",
                 n_points, pts(n_points), measurement_name))
  }
  final_rows <- n_points
  rows_removed <- initial_rows - final_rows

Step 3: Sequence range description (for the prose)

seq_range_text <- if (seq_kind == "date" && any(!is.na(d_keep))) {
    sprintf("%s to %s", format(min(d_keep, na.rm = TRUE)),
            format(max(d_keep, na.rm = TRUE)))
  } else if (seq_kind == "numeric" && any(!is.na(num_keep))) {
    sprintf("%s to %s", fmt_num(min(num_keep, na.rm = TRUE)),
            fmt_num(max(num_keep, na.rm = TRUE)))
  } else {
    "the input order"
  }

Step 4: Individuals chart geometry (center + moving-range sigma)

cl <- mean(x)
  mr <- abs(diff(x))
  mrbar <- mean(mr)
  sigma <- mrbar / 1.128            # d2 for a moving range of 2
  if (!is.finite(sigma) || sigma <= 0) {
    sigma <- 0
    ucl <- cl; lcl <- cl; two_up <- cl; two_dn <- cl
  } else {
    ucl <- cl + 3 * sigma; lcl <- cl - 3 * sigma
    two_up <- cl + 2 * sigma; two_dn <- cl - 2 * sigma
  }
  mr_ucl <- 3.267 * mrbar           # MR-chart upper limit (MR lower limit = 0)

Step 5: Western Electric / Nelson rules (a practical subset)

if (sigma > 0) {
    flag1 <- (x > ucl) | (x < lcl)                          # beyond 3 sigma
    flag2 <- flag_same_side_run(x, cl, 9)                   # nine same side
    flag3 <- flag_trend_run(x, 6)                           # six trending
    flag5 <- flag_two_of_three(x, two_up, two_dn)           # 2 of 3 beyond 2 sigma
  } else {
    flag1 <- flag2 <- flag3 <- flag5 <- rep(FALSE, n_points)
  }
  flag_any <- flag1 | flag2 | flag3 | flag5

  rule_defs <- list(
    list(rule = "Rule 1 — beyond limits",
         description = "A single point beyond the 3-sigma control limits", flag = flag1),
    list(rule = "Rule 2 — sustained shift",
         description = "Nine points in a row on the same side of the center line", flag = flag2),
    list(rule = "Rule 3 — trend",
         description = "Six points in a row steadily increasing or decreasing", flag = flag3),
    list(rule = "Rule 5 — near-limit cluster",
         description = "Two of three points in a row beyond 2 sigma on the same side", flag = flag5)
  )
  rule_counts <- setNames(vapply(rule_defs, function(r) sum(r$flag), integer(1)),
                          c("rule1", "rule2", "rule3", "rule5"))
  n_flagged <- sum(flag_any)
  n_in_control <- n_points - n_flagged
  pct_in_control <- 100 * n_in_control / n_points
  in_control <- (n_flagged == 0)
  verdict <- if (in_control) "In statistical control" else "Out of statistical control"

  rules_fired <- vapply(rule_defs, function(r) sum(r$flag) > 0, logical(1))
  rules_fired_text <- if (!any(rules_fired)) {
    "no rules"
  } else {
    paste(sub(" —.*$", "", vapply(rule_defs[rules_fired], function(r) r$rule, character(1))),
          collapse = ", ")
  }

Step 6: Control-points dataset (<=2000, always keep every flagged point)

if (n_points <= 2000) {
    cp_idx <- seq_len(n_points)
  } else {
    flagged_idx <- which(flag_any)
    norm_idx <- setdiff(seq_len(n_points), flagged_idx)
    budget <- max(0, 2000 - length(flagged_idx))
    set.seed(42)
    if (length(norm_idx) > budget) norm_idx <- sample(norm_idx, budget)
    cp_idx <- sort(c(flagged_idx, norm_idx))
  }
  control_points_df <- data.frame(
    sequence_index = cp_idx,
    measurement_value = round(x[cp_idx], 4),
    status = ifelse(flag_any[cp_idx], "out of control", "in control"),
    stringsAsFactors = FALSE
  )
  rownames(control_points_df) <- NULL

Step 7: Rule-violations table

fmt_points <- function(idx) {
    if (length(idx) == 0) return("none")
    shown <- head(idx, 12)
    txt <- paste(shown, collapse = ", ")
    if (length(idx) > 12) txt <- paste0(txt, ", and ", length(idx) - 12, " more")
    txt
  }
  if (n_flagged == 0) {
    violations_df <- data.frame(
      rule = "No special-cause signals",
      description = "Every point sits within the control limits with no shift, trend, or near-limit cluster — the process appears stable",
      points_flagged = "none",
      count = 0L,
      stringsAsFactors = FALSE
    )
  } else {
    violations_df <- do.call(rbind, lapply(rule_defs, function(r) {
      idx <- which(r$flag)
      data.frame(rule = r$rule, description = r$description,
                 points_flagged = fmt_points(idx), count = length(idx),
                 stringsAsFactors = FALSE)
    }))
  }
  rownames(violations_df) <- NULL

Step 8: Process-summary table

process_stats_df <- data.frame(
    statistic = c("Process mean(center line)", "Estimated sigma",
                  "Upper control limit(UCL)", "Lower control limit(LCL)",
                  "Mean moving range(MRbar)", "Points in control",
                  "Verdict"),
    value = c(fmt_num(cl, 3), fmt_num(sigma, 3), fmt_num(ucl, 3), fmt_num(lcl, 3),
              fmt_num(mrbar, 3), paste0(fmt_num(pct_in_control, 1), "%"),
              verdict),
    interpretation = c(
      sprintf("The average of %s over the ordered points", measurement_name),
      "Within-process standard deviation, estimated as MRbar divided by 1.128",
      "Center line plus three sigma — the upper natural process limit",
      "Center line minus three sigma — the lower natural process limit",
      "Average absolute change between consecutive measurements",
      sprintf("%d of %d %s show no special-cause signal", n_in_control, n_points, pts(n_points)),
      if (in_control) "No rule fired — the process is stable and predictable"
      else sprintf("%d special-cause %s across %s", n_flagged, sig_word(n_flagged), rules_fired_text)
    ),
    stringsAsFactors = FALSE
  )
  rownames(process_stats_df) <- NULL

  metrics <- list(
    `Points`                = n_points,
    `Process Mean`          = round(cl, 3),
    `Estimated Sigma`       = round(sigma, 3),
    `UCL`                   = round(ucl, 3),
    `LCL`                   = round(lcl, 3),
    `Special-Cause Signals` = as.integer(n_flagged),
    `Points In Control(%)` = round(pct_in_control, 1),
    `Verdict`               = if (in_control) "In control" else "Out of control"
  )

  json_output <- list(
    answer = paste0(
      "Individuals control chart on ", n_points, " ordered ",
      measurement_name, " ", pts(n_points), " (mean ", fmt_num(cl, 2),
      ", sigma ", fmt_num(sigma, 3), ", natural limits ", fmt_num(lcl, 2),
      " to ", fmt_num(ucl, 2), "): ",
      if (in_control) {
        paste0("no special-cause signals — the process is in statistical control.")
      } else {
        paste0(n_flagged, " out-of-control ", pts(n_flagged), " flagged by ",
               rules_fired_text, " — the process is out of statistical control.")
      }
    ),
    cards = lapply(
      c("tldr", "overview", "preprocessing", "control_chart",
        "rule_violations", "process_summary"),
      function(cid) list(id = cid, metrics = metrics)
    )
  )

  list(
    initial_rows = initial_rows, final_rows = final_rows, rows_removed = rows_removed,
    sequence_name = sequence_name, measurement_name = measurement_name,
    seq_kind = seq_kind, seq_range_text = seq_range_text,
    n_points = n_points, n_dropped_na = n_dropped_na,
    cl = cl, sigma = sigma, mrbar = mrbar, ucl = ucl, lcl = lcl,
    two_up = two_up, two_dn = two_dn, mr_ucl = mr_ucl,
    rule_counts = rule_counts, n_flagged = n_flagged, n_in_control = n_in_control,
    pct_in_control = pct_in_control, in_control = in_control, verdict = verdict,
    rules_fired_text = rules_fired_text,
    control_points_df = control_points_df, violations_df = violations_df,
    process_stats_df = process_stats_df,
    metrics = metrics, json_output = json_output
  )
}
Your data has more stories to tell. Run any analysis on your own data — 60+ validated R modules, interactive reports, AI insights, and PDF export. 500 free credits on signup.
Try Free — No Signup Sign Up Free

Report an Issue

Tell us what's wrong. You'll get a free re-run of this analysis so you can try again with different parameters. If the re-run still doesn't meet your expectations, we'll refund your credits.

Want to run this analysis on your own data? Upload CSV — Free Analysis See Pricing