| Title: | Cumulative Standardized Binomial EWMA for Multiple Stream Processes |
| Version: | 1.1.0 |
| Author: | Faruk Muritala [aut, cre], Austin Brown [aut], Dhrubajyoti Ghosh [aut], Sherry Ni [aut] |
| Maintainer: | Faruk Muritala <fmurital@students.kennesaw.edu> |
| Description: | Implements the Cumulative Standardized Binomial Exponentially Weighted Moving Average (CSB-EWMA) control chart for monitoring multiple independent streams with binomial outcomes. Provides exact variance calculations, adaptive control limits, post-hoc identification with multiple testing corrections (Bonferroni, Holm, Benjamini-Hochberg), and visualization tools. The method is described in Muritala et al. (2026) <doi:10.48550/arXiv.2601.09968>. |
| License: | MIT + file LICENSE |
| URL: | https://github.com/fmurital/csbewma_R_package |
| BugReports: | https://github.com/fmurital/csbewma_R_package/issues |
| Encoding: | UTF-8 |
| Depends: | R (≥ 3.5) |
| Imports: | ggplot2, stats, patchwork, utils |
| Suggests: | testthat (≥ 3.0.0) |
| Config/roxygen2/version: | 8.1.0 |
| Config/testthat/edition: | 3 |
| NeedsCompilation: | no |
| Packaged: | 2026-09-10 00:30:30 UTC; fmurital |
| Repository: | CRAN |
| Date/Publication: | 2026-09-10 03:30:02 UTC |
Apply multiple testing corrections
Description
Applies Bonferroni, Holm, or Benjamini-Hochberg corrections.
Usage
apply_multiple_testing(pvals, method = "BH", alpha = 0.05)
Arguments
pvals |
Numeric vector of raw p-values |
method |
Correction method: "BH", "bonferroni", "holm", or "raw" |
alpha |
Significance level (default = 0.05) |
Value
List with adjusted_pvals and flags
Examples
pvals <- c(0.001, 0.01, 0.03, 0.10, 0.50)
result <- apply_multiple_testing(pvals, method = "BH", alpha = 0.05)
Calculate p-values for all streams
Description
Computes exact binomial p-values for each stream based on successes.
Usage
calculate_pvalues(bin_matrix, p0 = 0.5)
Arguments
bin_matrix |
Matrix of binary indicators (streams as rows, time as columns) |
p0 |
In-control proportion (default = 0.5) |
Value
Numeric vector of p-values
Examples
bin_mat <- matrix(rbinom(500, 1, 0.5), nrow = 10, ncol = 50)
pvals <- calculate_pvalues(bin_mat)
Check Pairwise Correlation Among Streams for the Independence Assumption
Description
The CSB-EWMA control chart assumes that all monitored streams are
independent (dissertation Section 6.2.7 and Limitations discussion). This
function reports the pairwise Pearson correlation among a set of candidate
streams and flags any pair whose absolute correlation exceeds a threshold,
so the user can decide which streams to keep before calling
run_csb_ewma() or csb_ewma(). It does not remove or adjust
any stream automatically. No validated method for correcting correlated
streams currently exists for this chart; see the companion paper's
Discussion section, which lists correlated-stream handling as future work.
Usage
check_stream_correlation(data, threshold = 0.3, use = "pairwise.complete.obs")
Arguments
data |
A numeric matrix or data frame with candidate streams as columns. |
threshold |
Absolute Pearson correlation above which a pair is flagged. Default is 0.3, the threshold used in dissertation Section 6.2.7 to select independent streams for the Brisbane River application and restated generally in the Limitations discussion. This is a practical default carried over from that application, not a universally derived optimal value; adjust it if your own application calls for a different threshold. |
use |
Passed to |
Value
A list with three elements: correlation_matrix (the full
pairwise Pearson correlation matrix), flagged_pairs (a data frame
of stream pairs with abs(r) > threshold, sorted by descending
absolute correlation, zero rows if none), and independence_ok
(TRUE if no pair exceeds the threshold).
Examples
set.seed(1)
x <- matrix(rnorm(600), ncol = 6)
colnames(x) <- paste0("stream_", 1:6)
check_stream_correlation(x)
CSB-EWMA Control Chart
Description
Runs the Cumulative Standardized Binomial EWMA control chart on multiple stream data.
Usage
csb_ewma(
data,
lambda,
L,
p0 = 0.5,
max_time = NULL,
stop_at_signal = TRUE,
distribution = NULL,
posthoc_method = "BH",
alpha = 0.05,
verbose = FALSE
)
Arguments
data |
A matrix of binary indicators (0/1) with streams as rows and time as columns |
lambda |
Smoothing parameter for EWMA (0 < lambda <= 1) |
L |
Control limit multiplier |
p0 |
In-control proportion (default = 0.5) |
max_time |
Maximum time points to monitor (default = NULL uses all) |
stop_at_signal |
Logical. If TRUE (default), monitoring stops at the
first signal and post-hoc identification is performed once, stored in
|
distribution |
If data is continuous, specify distribution |
posthoc_method |
Method for post-hoc identification (default = "BH") |
alpha |
Significance level for post-hoc (default = 0.05) |
verbose |
Logical. If TRUE, prints informational messages about whether the input data required dichotomization. Default FALSE (silent). |
Details
By default (stop_at_signal = TRUE), monitoring stops at the first
signal and post-hoc identification is performed once, matching the
behavior of every released version of this function through 1.1.0.
Setting stop_at_signal = FALSE instead continues monitoring
through max_time and performs post-hoc identification separately
at every signal recorded in signal_times, using only the data
observed up to and including that signal. See run_csb_ewma
for the full explanation of continuation mode; it applies identically
here since this function calls that one internally.
Value
A list of class "csb_ewma" containing chart results and flagged
streams. When stop_at_signal = FALSE, also contains
signal_times (every time point at which a signal was detected)
and flagged_by_signal (post-hoc identification results at each
of those times).
See Also
run_csb_ewma() for the lower-level engine this function calls, useful directly when a variance cache should be precomputed once and reused across many chart runs (for example, in simulation studies).
Examples
set.seed(123)
bin_data <- matrix(rbinom(10*100, 1, 0.5), nrow = 10, ncol = 100)
for(i in 1:3) bin_data[i, ] <- rbinom(100, 1, 0.8)
result <- csb_ewma(bin_data, lambda = 0.175, L = 1.375)
print(result)
plot(result)
# Continue monitoring past the first signal and see every signal time
result2 <- csb_ewma(bin_data, lambda = 0.175, L = 1.375,
stop_at_signal = FALSE)
print(result2$signal_times)
Dichotomize Continuous Data to Binary Indicators
Description
Converts continuous observations to binary indicators based on whether each observation exceeds the in-control median or specified quantile.
Usage
dichotomize_data(data, distribution, p0 = 0.5)
Arguments
data |
Numeric vector of continuous observations |
distribution |
Character string specifying the distribution type |
p0 |
In-control proportion (default = 0.5) |
Value
Integer vector of binary indicators (0 or 1)
Examples
x <- rnorm(100)
binary <- dichotomize_data(x, distribution = "normal", p0 = 0.5)
Convert Continuous Measurements to Binary Using an Acceptable Range
Description
Converts continuous measurements to binary indicators based on whether each value falls inside or outside an acceptable operating range. An observation is coded 1 if it falls below the lower bound or above the upper bound of the range, and 0 if it falls inside the range. This is the practical, real-data preprocessing rule used in dissertation Section 6.4.1 (Brisbane River water quality application), generalized here so the range bounds can come from either source a practitioner is likely to have.
Usage
dichotomize_range(
data,
lower_limit = NULL,
upper_limit = NULL,
reference = NULL,
lower_percentile = 0.01,
upper_percentile = 0.99
)
Arguments
data |
Numeric vector of continuous measurements to convert. |
lower_limit |
Known lower bound of the acceptable range. Provide
together with |
upper_limit |
Known upper bound of the acceptable range. Provide
together with |
reference |
Numeric vector of historical or Phase I reference data,
used to estimate the range bounds when they are not already known. Do
not combine with |
lower_percentile |
Empirical lower percentile used to estimate the
range from |
upper_percentile |
Empirical upper percentile used to estimate the
range from |
Details
Known specification or standard limits: supply lower_limit and
upper_limit directly. Use this when the acceptable range for a
variable is already established, for example a regulatory or engineering
specification, or values a domain expert already knows for their process.
Estimated from historical (Phase I) data: supply reference instead,
along with lower_percentile and upper_percentile (default
0.01 and 0.99, the 1st and 99th percentile). The bounds are then computed
as the empirical percentiles of reference. This is the approach
used in dissertation Section 6.4.1, where the acceptable range for each
water quality variable was not independently known and was instead
estimated from a Phase I reference period.
This is a separate function from dichotomize_data(), which
generates and thresholds data from an assumed theoretical distribution for
the simulation studies. The two are not interchangeable. This function
never assumes a distribution, and dichotomize_data() is unchanged
and still governs the simulation-study workflow.
Value
Integer vector of binary indicators (0 or 1), the same length as
data. A value of 1 means the observation fell outside the
acceptable range.
Examples
# Known specification limits (e.g. a regulatory or engineering standard)
measurements <- c(6.1, 7.0, 7.8, 9.0, 5.5)
dichotomize_range(measurements, lower_limit = 6.5, upper_limit = 8.5)
# Bounds estimated from historical data (dissertation Section 6.4.1 approach)
historical <- rnorm(1000, mean = 7, sd = 0.5)
new_readings <- c(6.8, 7.1, 9.2)
dichotomize_range(new_readings, reference = historical)
Convert Multiple Variables to Binary Using Per-Variable Acceptable Ranges
Description
Applies dichotomize_range() separately to each column (variable) of
a dataset, using a distinct range specification for each one. This is for
the common practical case where different variables monitored together do
not share one acceptable range. For example, an acceptable heart rate
range differs by patient age group, and an acceptable height range
differs by population subgroup; applying a single percentile or a single
pair of limits uniformly across all variables would misrepresent
variables that legitimately have different tolerances. This function does
not choose or estimate a shared default range for variables that lack
one; every variable's range must be specified explicitly through
specs.
Usage
dichotomize_range_multi(data, specs)
Arguments
data |
A numeric matrix or data frame with variables (streams) as
columns. Column names, if present, are used to match against the names
of |
specs |
A named list, one element per column of |
Details
Each element of specs is itself a list of arguments passed
directly to dichotomize_range() for that column, so it follows the
same rules: supply either lower_limit and upper_limit
(known specification or standard limits for that variable), or
reference (optionally with lower_percentile and
upper_percentile) to estimate the range for that variable from its
own historical data. Different variables may use different approaches;
nothing requires them to match.
This function builds on dichotomize_range() and does not modify
it. It is a dataset-level convenience wrapper only. dichotomize_data()
(the simulation-study distributional method) remains entirely separate
and is unaffected by this function.
Value
An integer matrix with the same dimensions and column names as
data (coerced to a data frame internally if a matrix was
supplied), where each column is the binary result of applying that
column's own range specification via dichotomize_range().
Examples
# Two variables with genuinely different, manually specified ranges,
# e.g. distinct acceptable bands for two water quality measurements
water_data <- data.frame(
pH = c(6.9, 7.2, 8.6, 5.9, 7.0),
turbidity = c(2.1, 15.4, 3.0, 4.2, 50.0)
)
specs <- list(
pH = list(lower_limit = 6.5, upper_limit = 8.5),
turbidity = list(lower_limit = 0, upper_limit = 10)
)
dichotomize_range_multi(water_data, specs)
Summarize flagged streams
Description
Creates a summary of which streams were flagged.
Usage
flagged_streams_summary(flagged_results)
Arguments
flagged_results |
Output from identify_ooc() |
Value
List with flagged streams and summary table
Examples
bin_mat <- matrix(rbinom(10*100, 1, 0.5), nrow = 10, ncol = 100)
for(i in 1:3) bin_mat[i, ] <- rbinom(100, 1, 0.8)
result <- identify_ooc(bin_mat)
summary <- flagged_streams_summary(result)
print(summary$flagged_streams)
Generate Continuous Data from Specified Distribution
Description
Generates continuous observations from normal, Laplace, uniform, or exponential distributions with optional shift parameter for out-of-control simulation.
Usage
generate_continuous_data(distribution, n, shift = 0, p0 = 0.5)
Arguments
distribution |
Character string: "normal", "laplace", "uniform", or "exponential" |
n |
Number of observations to generate |
shift |
Amount to shift the proportion (default = 0) |
p0 |
In-control proportion (default = 0.5) |
Value
A numeric vector of length n containing the generated data
Examples
data <- generate_continuous_data("normal", n = 100, shift = 0.2)
Identify out-of-control streams
Description
Main function for post-hoc identification using multiple testing corrections.
Usage
identify_ooc(bin_matrix, alpha = 0.05, method = "BH", p0 = 0.5)
Arguments
bin_matrix |
Matrix of binary indicators (streams as rows, time as columns) |
alpha |
Significance level (default = 0.05) |
method |
Correction method (default = "BH") |
p0 |
In-control proportion (default = 0.5) |
Value
Data frame with p-values and flags for each stream
Examples
set.seed(123)
bin_mat <- matrix(rbinom(10*100, 1, 0.5), nrow = 10, ncol = 100)
for(i in 1:3) bin_mat[i, ] <- rbinom(100, 1, 0.8)
result <- identify_ooc(bin_mat)
print(result[result$flagged, ])
Plot CSB-EWMA Control Chart
Description
Creates a professional control chart showing EWMA statistic and limits.
Usage
## S3 method for class 'csb_ewma'
plot(x, title = "CSB-EWMA Control Chart", show_signal = TRUE, ...)
Arguments
x |
csb_ewma object from csb_ewma() function |
title |
Plot title (default = "CSB-EWMA Control Chart") |
show_signal |
Whether to highlight signal point (default = TRUE) |
... |
Additional arguments passed to ggplot |
Value
A ggplot object (invisibly) and displays the plot
Examples
# See csb_ewma() for examples
Combined Diagnostic Dashboard
Description
Creates a combined plot showing both the CSB-EWMA control chart and the flagged streams bar plot side by side or stacked.
Usage
plot_chart_with_flagged(chart_result, flagged_results, layout = "side")
Arguments
chart_result |
csb_ewma object from csb_ewma() function |
flagged_results |
Output from identify_ooc() function |
layout |
Either "side" for side-by-side or "stacked" for vertical |
Value
A combined ggplot object (invisibly) and displays the plot
Direct Plot for CSB-EWMA Results
Description
Creates a professional control chart showing EWMA statistic and limits. This function can be called directly without S3 dispatch.
Usage
plot_csb_ewma_direct(
result,
title = "CSB-EWMA Control Chart",
show_signal = TRUE
)
Arguments
result |
csb_ewma object from csb_ewma() or run_csb_ewma() |
title |
Plot title (default = "CSB-EWMA Control Chart") |
show_signal |
Whether to highlight signal point (default = TRUE) |
Value
A ggplot object (invisibly) and displays the plot
Plot Flagged Streams Bar Chart
Description
Creates a bar plot showing -log10(p-values) for each stream. Flagged streams appear in red, others in gray.
Usage
plot_flagged_streams(flagged_results, alpha = 0.05, title = "Stream P-values")
Arguments
flagged_results |
Output data frame from identify_ooc() function |
alpha |
Significance level for reference line (default = 0.05) |
title |
Plot title (default = "Stream P-values") |
Value
A ggplot object (invisibly) and displays the plot
Precompute variance vector for all time points
Description
This function precomputes the exact variance for all time points from 1 to max_t. For efficiency, it computes exact variances up to a convergence threshold (converge_t) and then sets remaining values to 1 (asymptotic). Based on the derivation, variance reaches 99% of asymptotic value by t=227, so converge_t = 500 is safe and efficient.
Usage
precompute_variance(lambda, max_t, converge_t = 500)
Arguments
lambda |
Smoothing parameter for EWMA |
max_t |
Maximum time point to precompute variance for |
converge_t |
Time point after which variance is set to 1 (asymptotic) |
Value
A numeric vector of length max_t containing variance at each time point
Examples
# Precompute variance for lambda = 0.175, up to t = 1000
var_cache <- precompute_variance(lambda = 0.175, max_t = 1000, converge_t = 500)
Prints a formatted summary of CSB-EWMA chart results.
Description
Prints a formatted summary of CSB-EWMA chart results.
Usage
## S3 method for class 'csb_ewma'
print(x, ...)
Arguments
x |
csb_ewma object from csb_ewma() or run_csb_ewma() function |
... |
Additional arguments (not used) |
Value
Invisibly returns the object
Generate Laplace (Double Exponential) Random Variables
Description
Generates random numbers from the Laplace distribution using inverse transform sampling.
Usage
rlaplace(n, location = 0, scale = 1)
Arguments
n |
Number of observations to generate |
location |
Location parameter (median) of the distribution (default = 0) |
scale |
Scale parameter (spread) of the distribution (default = 1) |
Value
A numeric vector of length n containing Laplace random variables
Examples
## Not run:
x <- rlaplace(100, location = 0, scale = 1)
## End(Not run)
Run CSB-EWMA Chart on Binary Data
Description
This function implements the core CSB-EWMA monitoring algorithm exactly as implemented in the original simulation code.
Usage
run_csb_ewma(
bin_matrix,
lambda,
L,
var_cache,
max_time = NULL,
p0 = 0.5,
stop_at_signal = TRUE
)
Arguments
bin_matrix |
A matrix of binary indicators (streams as rows, time as columns) |
lambda |
Smoothing parameter for EWMA (0 < lambda <= 1) |
L |
Control limit multiplier |
var_cache |
Precomputed variance vector from precompute_variance() |
max_time |
Maximum time points to monitor (default = NULL uses all) |
p0 |
In-control proportion (default = 0.5) |
stop_at_signal |
Logical. If TRUE (default), monitoring stops at the first signal, reproducing the behavior of every version through 1.1.0. If FALSE, monitoring continues through max_time and every signal time is recorded in signal_times. |
Details
The algorithm works as follows:
Initialize cumulative sum (cum_sum = 0) and EWMA (r_prev = 0)
For each time point t = 1, 2, ..., max_time:
Get binary vector for current time point
Calculate C_t = sum of binary indicators
Update cumulative sum: cum_sum = cum_sum + C_t
Compute standardized statistic: W_t = (cum_sum - mu0t) / sqrt(tsigma2_0)
Update EWMA: r_t = lambda * W_t + (1 - lambda) * r_prev
Get exact variance from precomputed cache: v_t = var_cachet
Compute control limits: UCL_t = L * sqrt(v_t), LCL_t = -L * sqrt(v_t)
If r_t > UCL_t or r_t < LCL_t, a signal is recorded at time t
Update r_prev = r_t and continue, unless stop_at_signal ends monitoring here
By default (stop_at_signal = TRUE), monitoring stops at the first
signal, matching the behavior of every released version of this function
through 1.1.0. Setting stop_at_signal = FALSE instead continues
monitoring through max_time, recording every time point at which a
signal condition is met in signal_times, without resetting the
cumulative sum or the EWMA statistic after a signal; the formula above is
unchanged, only the decision to stop early changes. This continuation mode
is a practical convenience for real-time or dashboard-style monitoring
where the process keeps running after an alarm; it is not itself a
procedure described in the dissertation, which presents only the
stop-at-first-signal convention.
Value
A list of class "csb_ewma" containing chart results. Includes signal_time and signal_detected (the first signal, kept for backward compatibility) and signal_times (an integer vector of every time point at which a signal was recorded; empty if none).
See Also
csb_ewma() for the higher-level convenience wrapper that dichotomizes continuous data, precomputes variance, calls this function, and performs post-hoc identification automatically.
Examples
bin_matrix <- matrix(rbinom(10*200, 1, 0.5), nrow = 10, ncol = 200)
for(i in 1:3) bin_matrix[i, ] <- rbinom(100, 1, 0.8)
var_cache <- precompute_variance(0.175, max_t = 200)
result <- run_csb_ewma(bin_matrix, lambda = 0.175, L = 1.375, var_cache)
print(paste("Signal at time:", result$signal_time))
# Continue monitoring past the first signal
result2 <- run_csb_ewma(bin_matrix, lambda = 0.175, L = 1.375, var_cache,
stop_at_signal = FALSE)
print(result2$signal_times)
Compute exact CSB-EWMA variance for a single time point
Description
This function implements the exact variance formula derived in Theorem 2. The variance is computed using double summation over the covariance structure of the standardized statistics. For a given smoothing parameter lambda and time point t, this returns Var(r_t) as defined in Equation (19) of the supplementary material.
Usage
var_rt_exact_single(lambda, t)
Arguments
lambda |
Smoothing parameter for EWMA. Must be between 0 and 1. Typical values range from 0.05 to 0.5. |
t |
Time point (positive integer). The variance is computed for this specific time point. |
Value
The exact variance Var(r_t) at time t. For t = 0, returns 0.
Examples
# Compute variance at time 10 for lambda = 0.175
var_rt_exact_single(lambda = 0.175, t = 10)
# Compute variance at time 50 for lambda = 0.15
var_rt_exact_single(lambda = 0.15, t = 50)