---
title: "ARL by Monte Carlo simulation"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{ARL by Monte Carlo simulation}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>",
                      fig.width = 7, fig.height = 4.2,
                      eval = FALSE)
```

```{r setup, message = FALSE}
library(shewhartr)
library(ggplot2)
library(dplyr)
```

The Average Run Length (ARL) of a control chart is the expected number
of samples until the chart signals. It is the single most important
operating characteristic — it tells you, on average, how *quickly*
the chart catches a real shift, and how *often* it cries wolf.

Two flavours:

- **In-control ARL** ($\mathrm{ARL}_0$): the expected number of
  in-control samples until a *false* alarm. Larger is better. A high
  $\mathrm{ARL}_0$ means low false-alarm rate.
- **Out-of-control ARL** ($\mathrm{ARL}_1$): the expected number of
  samples after a real shift before the chart signals. Smaller is
  better. A low $\mathrm{ARL}_1$ means fast detection.

These two are in tension. Adding more rules to a chart sharpens
detection (lower $\mathrm{ARL}_1$) but increases false alarms
(lower $\mathrm{ARL}_0$). `shewhart_arl()` quantifies the trade-off.

## Closed-form benchmarks

For the simplest setup — Nelson 1 only, normal data, 3-sigma limits —
the ARL is known in closed form. The probability of falling outside
3-sigma is $2 \cdot \Phi(-3) \approx 0.0027$, so:

$$
\mathrm{ARL}_0 = \frac{1}{0.0027} \approx 370.4.
$$

Let's verify by simulation:

```{r}
set.seed(2025)
shewhart_arl(
  shift = 0,
  rules = "nelson_1_beyond_3s",
  n_sim = 5000,
  max_run = 2000
)
```

The estimate should be close to 370 (Monte Carlo error around the
closed form).

## Adding rules sharpens detection

What happens if we add Nelson 2 (9 points same side) on top?

```{r}
set.seed(2025)
shewhart_arl(
  shift = 0,
  rules = c("nelson_1_beyond_3s", "nelson_2_nine_same"),
  n_sim = 5000
)
```

The ARL_0 drops from ~370 to ~250, meaning false alarms become more
common. Whether that's worth it depends on what we get in detection
power. Let's plot ARL across a grid of shifts:

```{r}
shifts <- seq(0, 3, by = 0.25)

set.seed(2025)
arl_n1 <- shewhart_arl(shifts, "nelson_1_beyond_3s", n_sim = 2000)
arl_n12 <- shewhart_arl(shifts,
                        c("nelson_1_beyond_3s", "nelson_2_nine_same"),
                        n_sim = 2000)

bind_rows(
  arl_n1  |> mutate(rules = "Nelson 1 only"),
  arl_n12 |> mutate(rules = "Nelson 1 + 2")
) |>
  ggplot(aes(shift, arl, colour = rules)) +
  geom_line(linewidth = 0.7) +
  geom_ribbon(aes(ymin = arl_lower, ymax = arl_upper, fill = rules),
              alpha = 0.12, colour = NA) +
  scale_y_log10() +
  scale_colour_manual(
    values = c(`Nelson 1 only` = unname(shewhart_palette("signal")["in_control"]),
               `Nelson 1 + 2`  = unname(shewhart_palette("family")["memory_based"]))) +
  scale_fill_manual(
    values = c(`Nelson 1 only` = unname(shewhart_palette("signal")["in_control"]),
               `Nelson 1 + 2`  = unname(shewhart_palette("family")["memory_based"]))) +
  labs(x = "Shift (sigma)", y = "ARL (log scale)",
       title = "Operating characteristics") +
  shewhart_theme()
```

The gain is largest for *small* shifts (around 0.5-1 sigma), where
Nelson 1 alone takes a long time to fire. For shifts of 2 sigma or
more, both rule sets detect within a couple of samples.

## A quantitative comparison: WE-7 vs Nelson 2

The original `Shewhart` package (now `shewhartr`) (v0.1.x) used a Western Electric
"7 in a row" rule for phase detection. The default in v1.0 is Nelson
2 (9 in a row). The trade-off:

```{r}
set.seed(2025)
arl_we  <- shewhart_arl(0, "we_seven_same",       n_sim = 5000, max_run = 2000)
arl_n2  <- shewhart_arl(0, "nelson_2_nine_same",  n_sim = 5000, max_run = 2000)
arl_we
arl_n2
```

The WE rule's ARL_0 is around 64 (a false phase change every 64
in-control samples on average); Nelson 2's is around 256 (one every
~256 samples). For most monitoring contexts that is a meaningful
difference. The WE rule is easier to teach, but the false-alarm cost
is high.

`shewhart_regression()` accepts either via the `phase_rule` argument:

```{r}
shewhart_regression(temperature_drift, value = temp_c, index = minute,
                    phase_rule = "we_seven_same")        # legacy default
shewhart_regression(temperature_drift, value = temp_c, index = minute,
                    phase_rule = "nelson_2_nine_same")    # new default
```

## Beyond normal residuals

`shewhart_arl()` simulates from a normal distribution by default.
Real residuals are often non-normal — heavy-tailed, skewed, or
autocorrelated. The closed-form ARL_0 of 370 for Nelson 1 then
overstates how rarely false alarms occur. A bootstrap variant
(simulating from your actual residuals) is on the roadmap; until
then, a quick check is to call `shewhart_diagnostics(fit)` and
inspect the Q-Q and ACF panels.

## References

- Champ, C. W., & Woodall, W. H. (1987). Exact Results for Shewhart
  Control Charts with Supplementary Runs Rules. *Technometrics*,
  29(4), 393-399.
- Wald, A. (1947). *Sequential Analysis*. Wiley.
- Page, E. S. (1954). Continuous Inspection Schemes. *Biometrika*,
  41(1-2), 100-115.
- Lucas, J. M., & Saccucci, M. S. (1990). Exponentially Weighted
  Moving Average Control Schemes: Properties and Enhancements.
  *Technometrics*, 32(1), 1-12.
