Skip to main content

pccpy

PyPI version Python License: MIT

Control Estadístico de Procesos (SPC) en Python, al estilo Minitab.

🇬🇧 English version

pccpy es una librería de SPC en español diseñada para ingenieros y técnicos que conocen Minitab y quieren hacer el mismo análisis desde Python. Todas las convenciones de nomenclatura (LCS/LCI, LEI/LES, Cp/Cpk/Pp/Ppk, Z.Bench, PPM, Anderson-Darling), los métodos de estimación de sigma y las 8 pruebas de causas especiales siguen lo que documenta Minitab.


Índice

  1. Instalación
  2. Inicio rápido
  3. Cartas de variables
  4. Cartas de atributos
  5. Cartas de tiempo ponderado
  6. Cartas avanzadas
  7. Cartas multivariadas
  8. Pruebas de causas especiales
  9. Visualización de las cartas
  10. Capacidad del proceso
  11. Normalidad, Pareto y constantes SPC
  12. Carta de corridas y pre-control
  13. EWMA y CUSUM para atributos
  14. Intervalos de tolerancia
  15. Muestreo de aceptación
  16. MSA / Gage R&R
  17. Acceso a los datos del resultado
  18. Validación
  19. Desarrollo
  20. Licencia

Instalación

pip install pccpy

Desde GitHub (versión de desarrollo):

pip install git+https://github.com/LeoSanta15/pccpy.git

Requiere Python ≥ 3.9 con NumPy, SciPy, pandas y matplotlib.

Idioma de los mensajes

Los textos de pccpy están en español. El idioma se puede cambiar (desde 0.11.0 —la documentación y este README también están en inglés— se traducen los mensajes de error y aviso, los summary(), las cabeceras de to_frame() —con stable=True para claves fijas—, los gráficos, el asistente wizard() y las demás tablas; el inglés está pendiente de revisión por una persona del dominio):

import pccpy as pp

pp.available_languages()   # ['en', 'es']
pp.set_language("en")      # o PCCPY_LANG=en; también: with pp.language("en"): ...

Inicio rápido

import numpy as np
import pccpy as pp

rng = np.random.default_rng(1)
x = rng.normal(100, 2, 60)
x[40:] += 3           # el proceso se desplaza en la observación 41

# Calcular carta I-MR con las 8 pruebas de causas especiales
carta = pp.imr_chart(x, tests=(1, 2, 3, 4, 5, 6, 7, 8))

print(carta.summary())        # resumen tipo sesión de Minitab
carta.violations()            # DataFrame: panel, punto, prueba, descripción
carta.to_frame()              # todos los valores, límites y pruebas fallidas
carta.plot()                  # figura de matplotlib (se muestra con plt.show())

Cartas de variables

Carta I-MR

La carta Individuales y Rango Móvil es la opción estándar cuando cada observación es una medición única (no hay subgrupos). Produce dos paneles:

  • Panel I: valores individuales con límites ±3σ en torno a la media.
  • Panel MR: rangos móviles sucesivos con su límite superior.
import numpy as np
import pccpy as pp

x = np.array([10.2, 10.5, 9.8, 10.1, 10.4, 9.9, 10.3, 10.6,
               9.7, 10.0, 10.2, 10.8, 9.6, 10.1, 10.3])

carta = pp.imr_chart(
    x,
    span=2,                # longitud del rango móvil (defecto: 2, igual que Minitab)
    sigma_method="mr",     # estimador de sigma: 'mr' (defecto), 'median_mr', 'mssd'
    tests=(1, 2, 3),       # qué pruebas de causas especiales activar
)
print(carta.summary())

Opciones de sigma_method:

Método Cálculo Cuándo usarlo
'mr' MR_prom / d2 por defecto; el mismo que Minitab
'median_mr' mediana(MR) / 0.9540 más robusto ante valores atípicos (solo span=2)
'mssd' √(MSSD / 2) cuando no hay observaciones consecutivas correladas

Cartas Xbar-R y Xbar-S

Cuando los datos se recogen en subgrupos (varias mediciones por muestra), las cartas Xbar-R (subgrupos pequeños, n ≤ 8) y Xbar-S (subgrupos grandes, n > 8) estiman sigma usando la variación dentro de cada subgrupo, lo que las hace más sensibles a desplazamientos de la media que la carta I-MR.

datos = np.array([
    [20.1, 20.3, 20.0, 20.2, 19.9],
    [20.4, 20.1, 20.5, 20.2, 20.3],
    # ... una fila por subgrupo
])

carta_r = pp.xbar_r_chart(
    datos,
    sigma_method="rbar",  # 'rbar' (defecto) o 'pooled'
    tests=(1, 2, 3, 4, 5, 6, 7, 8),
)

carta_s = pp.xbar_s_chart(
    datos,
    sigma_method="sbar",  # 'sbar' (defecto) o 'pooled'
)

Opciones de sigma_method para Xbar:

Método Cálculo Cuándo usarlo
'rbar' R_prom / d2(n) Xbar-R, subgrupos de tamaño fijo (igual que Minitab)
'pooled' S_pooled / c4(n_total) Xbar-R o Xbar-S, mejor estimación con muchos subgrupos
'sbar' S_prom / c4(n) Xbar-S, subgrupos de tamaño fijo (igual que Minitab)

Formatos de entrada de datos

Las cartas de subgrupos (xbar_r_chart, xbar_s_chart, ewma_chart, cusum_chart, ma_chart, imr_rs_chart, zone_chart, capability_analysis, capability_sixpack) aceptan cinco formatos equivalentes:

import pandas as pd

# 1. Matriz 2-D: cada fila es un subgrupo (formato clásico)
datos_2d = np.array([[20.1, 20.3, 20.0],
                     [20.4, 20.1, 20.5],
                     [19.9, 20.2, 20.1]])
pp.xbar_r_chart(datos_2d)

# 2. Vector 1-D + tamaño de subgrupo fijo
vector = datos_2d.ravel()            # [20.1, 20.3, 20.0, 20.4, 20.1, 20.5, ...]
pp.xbar_r_chart(vector, subgroup_size=3)

# 3. Vector 1-D + identificador de subgrupo (permite tamaños desiguales)
ids = np.repeat(["S1", "S2", "S3"], 3)
pp.xbar_r_chart(vector, subgroup=ids)

# También acepta pandas Series y DataFrame directamente
df = pd.DataFrame({"medida": vector, "subgrupo": ids})
pp.xbar_r_chart(df["medida"], subgroup=df["subgrupo"])

# 4. DataFrame en formato largo + nombres de columna
#    (subgroup: columna de identificadores, value: columna de valores)
df_largo = pd.DataFrame({
    "lote":   [1, 1, 1, 2, 2, 2, 3, 3, 3],
    "medida": [20.1, 20.3, 20.0, 20.4, 20.1, 20.5, 19.9, 20.2, 20.1],
})
pp.xbar_r_chart(df_largo, subgroup="lote", value="medida")

# Si solo hay una columna numérica, 'value' se detecta automáticamente
pp.xbar_r_chart(df_largo, subgroup="lote")

# 5. Vector 1-D con subgroup_size cuando el total no es divisible exactamente
#    El último subgrupo incompleto se grafica con sus propios límites
#    pero NO entra en el cálculo de sigma ni de la media global
import warnings
datos_11 = np.array([20.1, 20.3, 20.0, 20.4, 20.1, 20.5, 19.9, 20.2, 20.1, 20.3, 20.4])
with warnings.catch_warnings(record=True):
    carta = pp.xbar_r_chart(datos_11, subgroup_size=4)
# UserWarning: 11 observaciones no forman un número exacto de subgrupos de tamaño 4.
# El último subgrupo tiene 3 de 4 observaciones: se incluye en la gráfica
# pero NO en el cálculo de los límites de control.

Parámetros históricos y etapas

Equivalente a Opciones de estimación y Etapas en Minitab:

# Parámetros conocidos (no se estiman de los datos)
pp.imr_chart(x, mu=100.0, sigma=2.0)

# Etapas: los límites se calculan de forma independiente por etapa
etapas = ["Antes"] * 30 + ["Después"] * 30
carta = pp.imr_chart(x, stages=etapas)
carta.params        # lista de dicts, uno por etapa
carta.params[0]     # {'stage': 'Antes', 'media': ..., 'sigma': ..., ...}
carta.params[1]     # {'stage': 'Después', 'media': ..., 'sigma': ..., ...}

# Combinación: parámetros históricos + etapas (los mismos parámetros para todas)
pp.imr_chart(x, mu=100, sigma=2, stages=etapas)

Fase I iterativa

phase_one() calcula la carta, excluye los puntos con señal, recalcula los límites y repite hasta que no queda ninguna; phase2() aplica esos límites congelados a datos nuevos. Funciona con imr_chart, xbar_r_chart, xbar_s_chart, p_chart, np_chart, c_chart y u_chart.

rng = np.random.default_rng(3)
x_historico = rng.normal(100, 2, 60)
x_historico[[10, 33]] += 15           # dos puntos con causa especial
x_nuevo = rng.normal(100, 2, 20)

fase1 = pp.phase_one(pp.imr_chart, x_historico, tests=(1, 2, 3))
print(fase1.summary())     # puntos excluidos, límites congelados y cada pasada
fase1.excluded             # posiciones (base 0) de los puntos excluidos
fase1.chart.plot()         # carta con los límites definitivos

carta_ii = fase1.phase2(x_nuevo, tests=(1, 2))   # Fase II con los límites de la Fase I

Salvaguardas: max_iterations (10), min_points (20) y max_excluded (25 % de los puntos). Si saltan, el resultado no converge y se avisa: un proceso que necesita descartar tantos puntos no es estable. Excluye puntos solo si has encontrado y corregido su causa especial. En imr_chart solo cuentan las señales del panel I (un valor atípico dispara también el rango móvil de su vecino); cámbialo con exclude_panels=.


Fechas en el eje x

Con una serie de pandas con índice de fechas (o de texto, p. ej. números de lote) la carta conserva las etiquetas: el gráfico rotula el eje x con ellas, to_frame() y violations() añaden la columna etiqueta y summary() muestra la fecha junto al punto. Los puntos se grafican por orden de muestreo, como en Minitab.

import pandas as pd

x = np.random.default_rng(1).normal(10, 1, 30)
serie = pd.Series(x, index=pd.date_range("2026-03-01", periods=len(x), freq="D"))
carta = pp.imr_chart(serie, tests=(1, 2))
carta.plot()                     # eje «Fecha»
carta.violations()               # con la fecha de cada señal
carta.with_labels([f"L{i}" for i in range(30)])   # etiquetas propias (una por punto graficado)

Cartas de atributos

Cuando la característica de calidad es discreta (piezas defectuosas, número de defectos) se usan cartas de atributos. Las cartas de Laney corrigen la sobredispersión (variabilidad mayor a la esperada por el modelo teórico), un problema frecuente con tamaños de muestra grandes.

# P: fracción defectuosa (n puede variar entre muestras)
defectuosos = np.array([3, 5, 2, 4, 6, 1, 3, 5, 2, 4])
n_muestral  = np.full(10, 200)    # tamaño de cada muestra
pp.p_chart(defectuosos, n=n_muestral)

# NP: número de defectuosos (requiere n constante)
pp.np_chart(defectuosos, n=200)

# C: número de defectos por unidad (n fijo = 1 unidad por muestra)
defectos = np.array([2, 1, 3, 0, 4, 2, 1, 3, 2, 1])
pp.c_chart(defectos)

# U: tasa de defectos por unidad (n puede variar)
n_unidades = np.array([1, 1, 2, 1, 2, 1, 1, 2, 1, 2])
pp.u_chart(defectos, n=n_unidades)

# Laney P': igual que P pero corrige sobredispersión (phi != 1)
pp.laney_p_chart(defectuosos, n=n_muestral)

# Laney U': igual que U pero corrige sobredispersión
pp.laney_u_chart(defectos, n=n_unidades)

Cuando n es un escalar se aplica el mismo tamaño a todas las muestras. Con n variable los límites se calculan individualmente para cada punto.


Cartas de tiempo ponderado

EWMA

La carta Media Móvil Exponencialmente Ponderada es más sensible que I-MR a pequeños desplazamientos de la media (típicamente ≤ 1.5σ). Los límites exactos se ensanchan en los primeros puntos hasta estabilizarse, igual que Minitab.

carta = pp.ewma_chart(
    x,
    weight=0.2,    # λ: factor de suavizado (0 < λ ≤ 1); menor = más memoria
    k=3.0,         # multiplicador de sigma para los límites (defecto: 3)
)
# EWMA aplica solo la prueba 1 (no tiene parámetro tests)

Guía de selección de λ:

λ Detecta mejor Memoria
0.05 – 0.10 desplazamientos muy pequeños (¼σ) mucha
0.20 desplazamientos moderados (½σ – 1σ) moderada (recomendado Minitab)
0.40+ parecido a carta Shewhart poca

CUSUM

La carta Suma Acumulada también detecta pequeños desplazamientos. Usa el esquema tabular de Hawkins: dos acumuladores (superior C⁺ e inferior C⁻) que parten de 0 y se comparan con el límite de decisión H.

carta = pp.cusum_chart(
    x,
    h=4.0,    # límite de decisión H (en unidades de sigma); defecto: 4 (= 4σ)
    k=0.5,    # zona de referencia K (en unidades de sigma); defecto: 0.5
    # Con H=4, K=0.5 el ARL en control es ≋370 (estándar de la industria)
)

Regla práctica: H = 4σ y K = 0.5σ equilibran la detección de un desplazamiento de 1σ con un ARL fuera de control ≈ 10 y un ARL en control ≈ 370.


Media móvil

La carta MA promedia los últimos length puntos para suavizar el ruido. Los primeros length - 1 puntos usan el promedio de los datos disponibles, por lo que sus límites son más amplios.

pp.ma_chart(
    x,
    length=5,   # ventana de la media móvil; defecto: 3
)

Cartas avanzadas

Z-MR (corridas cortas)

Cuando se fabrican piezas con especificaciones distintas (distintas medias o variabilidades nominales) no se pueden trazar en la misma carta I-MR estándar. La carta Z-MR estandariza cada observación con los parámetros de su parte, permitiendo visualizar todas en una sola carta con límites ±3.

partes   = ["A"] * 10 + ["B"] * 10 + ["A"] * 10
medidas  = np.r_[
    rng.normal(50, 1, 10),   # parte A: media 50, sigma 1
    rng.normal(80, 2, 10),   # parte B: media 80, sigma 2
    rng.normal(50, 1, 10),   # parte A de nuevo
]

carta = pp.zmr_chart(
    medidas,
    partes,
    sigma_method="by_part",  # 'constant', 'relative', 'by_part', 'by_run'
    # mu y sigma: dict {parte: valor} para parámetros históricos o nominales
    # mu={"A": 50, "B": 80}, sigma={"A": 1, "B": 2}
)

Opciones de sigma_method para Z-MR:

Método Descripción
'constant' misma sigma para todas las partes
'relative' sigma proporcional a la media de la parte
'by_part' sigma estimada por rango móvil dentro de cada parte (defecto)
'by_run' sigma estimada por rango móvil dentro de cada corrida (bloque consecutivo)

I-MR-R/S (variación entre/dentro)

Cuando los subgrupos tienen una variación entre subgrupos grande (diferencias de operario, turno, lote), la carta Xbar-R genera falsas alarmas porque su límite de control solo refleja la variación dentro. La carta I-MR-R/S descompone es dos fuentes y las trata por separado.

# sub: matriz (n_subgrupos x tamaño_subgrupo)
sub = rng.normal(20, 1, (25, 5)) + rng.normal(0, 1.5, (25, 1))  # variación entre grupos

carta = pp.imr_rs_chart(
    sub,
    within="r",   # 'r' (rango, defecto) o 's' (desviación estándar)
)
carta.params[0]
# {'sigma_dentro': 0.97, 'sigma_entre': 1.48, 'sigma_entre_dentro': 1.79, ...}

Eventos raros (G y T)

Cuando los eventos son tan infrecuentes que las cartas P o C darían muchos puntos en cero, se usan las cartas G (número de casos/piezas entre eventos) y T (tiempo entre eventos).

# G: número de casos entre eventos (distribución geométrica)
entre_eventos = rng.geometric(0.02, 40) - 1   # p = probabilidad del evento
pp.g_chart(entre_eventos)

# T: tiempo entre eventos (distribución Weibull o exponencial)
tiempos = rng.weibull(1.5, 40) * 30
pp.t_chart(tiempos)          # Weibull (defecto)
pp.t_chart(tiempos, distribution="exponential")  # exponencial

Los límites de la carta G se basan en percentiles exactos de la distribución geométrica; los de la carta T en los de la Weibull o exponencial ajustada por máxima verosimilitud.


Cartas multivariadas

T² de Hotelling

Equivalente multivariado de la carta I-MR o Xbar. Detecta desplazamientos simultáneos en varias variables correlacionadas. El método contributions() identifica qué variable(s) causaron la señal.

Sigma = [[1, .6, .3], [.6, 1, .2], [.3, .2, 1]]
hist  = rng.multivariate_normal([0, 0, 0], Sigma, 100)   # datos de Fase I
nuev  = rng.multivariate_normal([0, 0, 0], Sigma, 50)
nuev[35:, 2] += 4     # variable 3 se desplaza en el punto 36

# --- Fase I: parámetros estimados de los mismos datos ---
carta1 = pp.t2_chart(hist)
print(carta1.summary())

# --- Fase II: parámetros tomados del periodo histórico ---
carta2 = pp.t2_chart(
    nuev,
    mu=hist.mean(axis=0),
    cov=np.cov(hist, rowvar=False),
    n_hist=100,     # n de la estimación histórica (afecta el límite)
)

# Diagnosticar: ¿qué variable causó la señal en el punto 36?
carta2.contributions(36)     # pandas Series con la contribución de cada variable

# Con subgrupos (una fila = una observación; subgroup_size agrupa de a n filas)
pp.t2_chart(nuev, subgroup_size=5)

# Con DataFrame: los nombres de columna aparecen en contributions()
import pandas as pd
df = pd.DataFrame(nuev, columns=["Temperatura", "Presión", "Flujo"])
pp.t2_chart(df)

Tipo de límite según lo que se provea:

mu / cov n_hist Distribución Fase
No dados — Beta (individuales) / F (subgrupos) I
Dados Dado F ajustada a n_hist II
Dados No dado χ² parámetros conocidos

Varianza generalizada

Panel complementario al T² para datos en subgrupos: monitorea la dispersión multivariada mediante el determinante de la matriz de covarianza muestral |S|.

pp.generalized_variance_chart(
    nuev,
    subgroup_size=5,
    cov=np.cov(hist, rowvar=False),   # Sigma histórica (sin media: solo mide dispersión)
)

MEWMA

Versión multivariada de la carta EWMA. Muy sensible a desplazamientos pequeños y sostenidos en el vector de medias.

carta = pp.mewma_chart(
    nuev,
    mu=hist.mean(axis=0),
    cov=np.cov(hist, rowvar=False),
    weight=0.1,    # λ (defecto: 0.1)
    arl=200,       # ARL objetivo en control (el límite H se calcula automáticamente)
)

# Calcular el límite H por separado
H = pp.mewma_limit(p=3, weight=0.1, arl=200)
print(f"H = {H:.3f}")   # p: número de variables

MCUSUM

Versión multivariada de la carta CUSUM (esquema de Crosier). También detecta pequeños desplazamientos sostenidos.

carta = pp.mcusum_chart(
    nuev,
    mu=hist.mean(axis=0),
    cov=np.cov(hist, rowvar=False),
    k=0.5,     # zona de referencia (defecto: 0.5)
    arl=200,   # el límite H se calcula automáticamente
)

H = pp.mcusum_limit(p=3, k=0.5, arl=200)

Etapas y Box-Cox en cartas multivariadas

Las 4 cartas multivariadas (T², varianza generalizada, MEWMA y MCUSUM) aceptan stages y boxcox, igual que las cartas univariadas:

etapas = [1] * 40 + [2] * 40

# Sin mu/cov: cada etapa re-estima sus parámetros
c = pp.t2_chart(datos, stages=etapas)
c.stage_mean[1]     # vector de medias de la etapa 1
c.stage_mean[2]     # vector de medias de la etapa 2
c.contributions(45) # usa la media/covarianza de la etapa del punto 45

# Con mu/cov: los mismos parámetros en todas las etapas
pp.t2_chart(datos, mu=mu, cov=cov, n_hist=100, stages=etapas)

# MEWMA y MCUSUM reinician el acumulador al empezar cada etapa
pp.mewma_chart(datos, stages=etapas)
pp.mcusum_chart(datos, stages=etapas)

# Box-Cox: estima una lambda por variable (no compatible con mu/cov históricos)
c = pp.t2_chart(datos_positivos, boxcox=True)
c.params[0]["lambda_boxcox"]    # {'Var1': 0.42, 'Var2': 1.13, ...}
pp.mewma_chart(datos_positivos, boxcox=True)
pp.mcusum_chart(datos_positivos, boxcox=True)

Pruebas de causas especiales

pccpy implementa las 8 pruebas de Western Electric / Nelson tal como las documenta Minitab, con sus parámetros K por defecto:

Nú Descripción K defecto Equivalente Minitab
1 1 punto más allá de Kσ 3 Prueba 1
2 K puntos consecutivos en el mismo lado de la LC 9 Prueba 2
3 K puntos consecutivos en tendencia (subiendo o bajando) 6 Prueba 3
4 K puntos alternando arriba/abajo 14 Prueba 4
5 2 de 3 puntos más allá de 2σ en el mismo lado 2 Prueba 5
6 4 de 5 puntos más allá de 1σ en el mismo lado 4 Prueba 6
7 K puntos consecutivos entre ±1σ 15 Prueba 7
8 K puntos consecutivos en lados alternos de la LC más allá de 1σ 8 Prueba 8
# Seleccionar pruebas y ajustar parámetros
carta = pp.imr_chart(
    x,
    tests=(1, 2, 5),            # solo activar las pruebas 1, 2 y 5
    test_params={2: 7, 5: 3},   # cambiar el K de la prueba 2 a 7 y el de la 5 a 3
)

# Activar todas las pruebas
carta = pp.imr_chart(x, tests="all")

# Sin pruebas (solo calcular los límites)
carta = pp.imr_chart(x, tests=None)

# Ver los puntos fuera de control
carta.violations()          # DataFrame: panel, punto, prueba, valor, descripcion
carta.in_control            # True si ninguna prueba falló
carta.panels[0].flagged     # índices (base 0) de puntos marcados en el panel I

Cada tipo de carta aplica el subconjunto que corresponde:

  • Completo (1-8): carta I, Xbar, Z
  • Básicas (1-4): carta MR, R, S, G, T, atributos (P, NP, C, U, Laney)
  • Solo prueba 1: EWMA, CUSUM, MA, T², MEWMA, MCUSUM

Visualización de las cartas

Todas las cartas se grafican con plot_control_chart(). Los gráficos incluyen por defecto las líneas de zonas ±1σ y ±2σ (en gris punteado), igual que Minitab, y marcan en rojo los puntos que fallan alguna prueba con el número de la prueba.

import matplotlib.pyplot as plt

carta = pp.imr_chart(x, tests=(1, 2, 3))

# Con zonas σ visibles (por defecto)
fig = pp.plot_control_chart(carta)
plt.show()

# Sin zonas (solo LCS, LC, LCI)
fig = pp.plot_control_chart(carta, zones=False)

# Con título personalizado y tamaño de figura
fig = pp.plot_control_chart(carta, title="Diámetro del eje (mm)", figsize=(14, 7))

# Acceso directo desde el objeto carta
carta.plot(title="Mi carta")

Las zonas sigma en la carta permiten aplicar visualmente las pruebas 5, 6, 7 y 8:

  • Zona A (±2σ a ±3σ): p. ej. prueba 5 — 2 de 3 puntos en zona A
  • Zona B (±1σ a ±2σ): p. ej. prueba 6 — 4 de 5 puntos en zona B o más allá
  • Zona C (0 a ±1σ): p. ej. prueba 7 — 15 puntos consecutivos en zona C

Capacidad del proceso

Capacidad normal

datos = rng.normal(10, 0.1, 100)

res = pp.capability_analysis(
    datos,
    lsl=9.7,           # Límite de Especificación Inferior
    usl=10.3,          # Límite de Especificación Superior
    target=10.0,       # objetivo (para Cpm; opcional)
    subgroup_size=5,   # con subgrupos: sigma_dentro ≠ sigma_general
)
print(res.summary())
# Cp, CPL, CPU, Cpk, Pp, PPL, PPU, Ppk, Cpm, Z.Bench sup/inf, PPM total

res.to_frame()   # todos los índices como DataFrame
res.plot()       # histograma con curvas dentro / general y especificaciones

Diferencia Cp/Cpk vs Pp/Ppk:

Índice σ usada Interpreta
Cp, Cpk sigma dentro de subgrupos capacidad potencial del proceso estable
Pp, Ppk sigma general (todos los datos) rendimiento real incluyendo variación entre subgrupos

Si el proceso está en control, Cpk ≈ Ppk. Una diferencia grande indica inestabilidad o variación entre subgrupos.


Capacidad no normal

Cuando los datos no siguen una distribución normal (confirmado con normality_test) se pueden usar 9 distribuciones alternativas:

res_nn = pp.capability_nonnormal(
    datos,
    lsl=1.0,
    usl=20.0,
    distribution="weibull",   # ver lista abajo
)
print(res_nn.summary())

Distribuciones disponibles: "normal", "lognormal", "weibull", "gamma", "exponential", "loglogistic", "logistic", "largest_extreme", "smallest_extreme"

Los parámetros se ajustan por máxima verosimilitud. Los índices Pp/Ppk se calculan por el método de percentiles (ISO 22514-2), que es compatible con Minitab.


Capacidad con transformación Box-Cox

res_bc = pp.capability_boxcox(
    datos,
    lsl=1.0,
    usl=20.0,
    # lam=None  # si es None, se estima por máxima verosimilitud
)
print(res_bc.summary())
res_bc.transform     # {'lambda': 0.42}

Capacidad para atributos (binomial y Poisson)

Cuando el dato es «defectuoso / bueno» o un conteo de defectos, no hay Cp ni Cpk: la capacidad es la tasa de defectos con su intervalo exacto. capability_binomial da el % defectivo (Clopper-Pearson), el PPM y el nivel Z; capability_poisson da los defectos por unidad (Garwood) y, con opportunities=, el DPMO y el Z. Ambas incluyen la prueba chi-cuadrado de que la tasa es constante entre muestras: si falla, el proceso no es estable y la capacidad no es fiable (revise la carta P o U).

defectuosas = [3, 5, 2, 4, 6, 1, 3, 5]
defectos = [3, 5, 2, 4, 6, 1, 3, 5]
r = pp.capability_binomial(defectuosas, n=200)        # n constante o uno por muestra
print(r.summary())
r.p_ci, r.ppm, r.z, r.homogeneous
r.plot()                                               # tasa por muestra y estimación acumulada

q = pp.capability_poisson(defectos, units=10, opportunities=20)
q.dpu, q.dpu_ci, q.dpmo, q.z

Capability Sixpack

Genera las 6 vistas del Capability Sixpack de Minitab en una sola figura: carta de control (dentro), rango/desviación (dentro), últimas 25 obs/subgrupos, histograma de capacidad, gráfico de probabilidad normal y gráfico de capacidad.

fig, res, carta = pp.capability_sixpack(
    datos,
    lsl=9.7,
    usl=10.3,
    subgroup_size=5,
    tests=(1,),
)
plt.show()
# fig: figura de matplotlib
# res: CapabilityResult con todos los índices
# carta: ControlChart (Xbar-R, Xbar-S o I-MR según el tamaño de subgrupo)

Normalidad, Pareto y constantes SPC

Prueba de normalidad

result = pp.normality_test(
    datos,
    method="anderson",      # 'anderson' (defecto), 'shapiro', 'dagostino'
)
print(result)
# Anderson-Darling: estadístico = 0.5302, valor p = 0.165 (n = 40)

result.statistic    # valor del estadístico
result.p_value      # valor p
result.reject       # True si se rechaza normalidad a α = 0.05

# Gráfico de probabilidad normal con Anderson-Darling
pp.probability_plot(datos)

Pareto

defectos = ["Rayado", "Abolladura", "Rayado", "Pintura", "Rayado",
             "Abolladura", "Otro", "Rayado", "Pintura", "Abolladura"]

tabla = pp.pareto(defectos)
print(tabla)
#       categoria  frecuencia  porcentaje  acumulado
# 0       Rayado           4        40.0       40.0
# 1  Abolladura           3        30.0       70.0
# 2      Pintura           2        20.0       90.0
# 3         Otro           1        10.0      100.0

pp.plot_pareto(tabla)   # diagrama de Pareto con curva acumulada

Constantes SPC

Calculadas por integración numérica para cualquier n ≥ 2 —no solo para los valores tabulados hasta n = 25 que suelen publicarse en libros.

pp.d2(5)   # 2.3259  (factor para estimar sigma desde R; n=5)
pp.d3(5)   # 0.8641  (para el límite del rango móvil)
pp.c4(5)   # 0.9400  (factor para estimar sigma desde s)
pp.c5(5)   # 0.3412  (para el límite de la desviación estándar)

# Tabla completa de constantes para n=5
pp.control_chart_constants(5)
# {'n': 5, 'd2': 2.326, 'd3': 0.864, 'c4': 0.940, 'c5': 0.341,
#  'A2': 0.577, 'A3': 1.342, 'D3': 0.0, 'D4': 2.115, 'B3': 0.0, 'B4': 2.089}

Carta de corridas y pre-control

Carta de corridas

Detecta patrones no aleatorios en los datos mediante cuatro pruebas de hipótesis (agrupamiento, mezclas, tendencias, oscilación) usando la distribución de rachas.

rc = pp.run_chart(x)
print(rc.summary())
# Mediana: 100.12
# p agrupamiento: 0.23  p mezclas: 0.77  p tendencias: 0.04  p oscilación: 0.96
# → Tendencia detectada (p < 0.05)

rc.to_frame()   # punto, valor, lado (arriba/abajo), racha
rc.plot()       # carta con la mediana y puntos coloreados por lado
Prueba Detecta
Agrupamiento demasiado pocas rachas → patrón por ciclos o mezcla de distribuciones
Mezclas demasiadas rachas → dos distribuciones alternando
Tendencias rachas largas consecutivas → deriva del proceso
Oscilación alternancia excesiva arriba/abajo → sobrecontrol

Pre-control (Shainin)

Semáforo de cuatro zonas basado en el ancho de tolerancia. No requiere estimación de sigma: usa solo LSL/USL y marca cada observación con el color de su zona.

pc = pp.precontrol(x, lsl=94, usl=106)
print(pc.summary())
# Verde (G): 45   Amarillo bajo (Y-): 3   Amarillo alto (Y+): 2   Rojo: 0
# Señales de ajuste: 0

pc.zones          # lista de etiquetas: 'G', 'Y-', 'Y+', 'R-', 'R+'
pc.signals        # lista de índices donde se detectó señal de ajuste
pc.to_frame()     # DataFrame con punto, valor y zona
pc.plot()

Zonas pre-control:

Zona Rango Acción
Verde (G) USL/4 alrededor del centro (50% de la tolerancia) Continuar
Amarilla baja (Y−) [LSL, LSL + 25% tol] Precaución
Amarilla alta (Y+) [USL − 25% tol, USL] Precaución
Roja (R−/R+) Por debajo de LSL o por encima de USL Detener

EWMA y CUSUM para atributos

Versiones de cartas de tiempo ponderado para datos discretos (fracciones defectuosas, tasas de defectos). Los límites transitorios son exactos y se ensanchan en las primeras muestras hasta estabilizarse.

defectuosos = [3, 5, 2, 4, 6, 1, 3, 5, 2, 4]
n_muestral  = 200   # escalar o array de longitud igual

# EWMA-P: fracción defectuosa
carta = pp.ewma_p_chart(defectuosos, n=n_muestral, weight=0.2)

# EWMA-U: tasa de defectos por unidad
defectos   = [2, 1, 3, 0, 4, 2, 1, 3, 2, 1]
n_unidades = [1, 1, 2, 1, 2, 1, 1, 2, 1, 2]
carta_u = pp.ewma_u_chart(defectos, n=n_unidades)

# CUSUM-P: suma acumulada para fracción defectuosa
carta_cp = pp.cusum_p_chart(defectuosos, n=n_muestral, h=4, k=0.5)

# CUSUM-C: suma acumulada para número de defectos
carta_cc = pp.cusum_c_chart(defectos, h=4, k=0.5)

Con n variable (array), los límites se ajustan punto a punto al tamaño de muestra.


Intervalos de tolerancia

Un intervalo de tolerancia (α, p) garantiza —con confianza 1−α— que al menos una fracción p de la población cae dentro de los límites calculados.

Normal bilateral y unilateral

rng = np.random.default_rng(1)
x50 = rng.normal(100, 2, 50)

# Bilateral: con 95% de confianza, ≥95% de la población en (LI, LS)
res = pp.tolerance_interval(x50, coverage=0.95, confidence=0.95)
print(res.summary())
# LI = 93.7, LS = 106.3, k = 2.382 (normal bilateral, n=50, 95/95)

res.lower          # límite inferior
res.upper          # límite superior
res.k_factor       # factor k
res.achieved_confidence  # confianza lograda (si el método es no paramétrico)
res.to_frame()
res.plot()

# Unilateral superior: P(X ≤ LS) ≥ 0.95 con 95% de confianza
res_u = pp.tolerance_interval(x50, sides="upper")

# Unilateral inferior: P(X ≥ LI) ≥ 0.95 con 95% de confianza
res_l = pp.tolerance_interval(x50, sides="lower")

No paramétrico

x300 = rng.normal(100, 2, 300)
res_np = pp.tolerance_interval(x300, method="nonparametric", coverage=0.95)
# Los límites son estadísticos de orden; n≈300 para 95/95 bilateral

Desde estadísticos resumen

Cuando solo se tienen la media, desviación típica y n (sin datos individuales):

res_s = pp.tolerance_interval_summary(
    mean=100.0, std=2.0, n=50,
    coverage=0.95, confidence=0.95,
    sides="two",
)
print(res_s.summary())   # Mismos índices que tolerance_interval

Muestreo de aceptación

Z1.4 — Atributos (ANSI/ASQ Z1.4)

Selecciona el plan de muestreo de la norma Z1.4 a partir del tamaño de lote y el AQL:

# Lote N=1000, AQL=1%
plan = pp.acceptance_sampling_attributes(N=1000, aql=1.0)
print(plan.summary())
# n=80, Ac=2, Re=3, LTPD=6.5%, AOQL=0.87%

plan.n          # tamaño de muestra
plan.c          # número de aceptación (Ac)
plan.ltpd       # LTPD (fracción que da Pa ≈ 0.10)
plan.aoq_max    # AOQL

plan.pa(0.01)   # P(aceptar | p=1%) — curva OC
plan.oc_curve() # DataFrame con columnas p_defectivo, P(aceptar) (stable=True: defective_fraction, p_accept)
plan.aoq_curve()# DataFrame con columnas p_defectivo, AOQ (stable=True: defective_fraction, aoq)
plan.plot()     # curva OC + curva AOQ en una figura

Z1.9 — Variables (ANSI/ASQ Z1.9)

Requiere que la característica se distribuya normalmente; usa la media y la desviación para estimar la fracción defectuosa mediante el estadístico de calidad Q:

plan_v = pp.acceptance_sampling_variables(N=1000, aql=1.0, spec_type="one")
# spec_type='one'  → especificación unilateral (solo USL o solo LSL)
# spec_type='two'  → especificación bilateral

muestra = rng.normal(10.5, 0.2, plan_v.n)
dec = plan_v.evaluate(muestra, usl=11.0)
print(dec)
# {'xbar': 10.49, 's': 0.19, 'Q_usl': 2.68, 'accept': True}

plan_v.oc_curve()   # curva OC del plan

Dodge-Romig

Planes que minimizan la inspección total media (ATI):

# Plan LTPD: diseñado para LTPD=5% con proceso promedio p̄=1%
plan_ltpd = pp.dodge_romig(N=1000, ltpd=0.05, process_avg=0.01)
print(plan_ltpd.summary())   # n, Ac, AOQL

# Plan AOQL: diseñado para AOQL=1%
plan_aoql = pp.dodge_romig(N=1000, aoql=0.01, process_avg=0.005)
print(plan_aoql.summary())   # n, Ac, LTPD

plan_ltpd.n        # tamaño de muestra
plan_ltpd.c        # número de aceptación
plan_ltpd.aoql     # AOQL
plan_ltpd.ltpd     # LTPD
plan_ltpd.to_frame()

MSA / Gage R&R

Cruzado (ANOVA y Xbar-R)

El diseño cruzado mide cada parte con todos los operadores. Es el más común cuando la medición no destruye la pieza.

# 10 partes × 3 operadores × 2 réplicas (orden del array: parte0-op0, parte0-op1, ...)
data = rng.normal(0, 1, 10 * 3 * 2)

# Método ANOVA (más preciso)
grr = pp.gage_rr(data, parts=10, operators=3, replicates=2, tolerance=20.0)
print(grr.summary())
# Repetibilidad: 14.3%  Reproducibilidad: 3.1%  %R&R: 14.6%  NDC: 9

grr.pct_gage            # % Estudio de variación total del sistema de medición
grr.var_repeatability   # varianza de repetibilidad
grr.var_reproducibility # varianza de reproducibilidad
grr.var_part            # varianza de partes
grr.var_total           # total (suma de los tres anteriores)
grr.ndc                 # número de categorías distintas
grr.anova_table         # DataFrame con GL, SC, CM, F, p-valor (siempre en español)
grr.anova_frame()       # la misma tabla en el idioma activo (stable=True: df, ss, ms, f, p_value)
grr.to_frame()
grr.plot()

# Método Xbar-R (clásico AIAG)
grr_xr = pp.gage_rr(data, parts=10, operators=3, replicates=2,
                    method="xbar_r", tolerance=20.0)
# grr_xr.anova_table es None (el método Xbar-R no produce tabla ANOVA)

Anidado

Cuando los operadores miden partes distintas (la medición destruye la muestra o las partes no son intercambiables entre operadores):

grr_n = pp.gage_rr_nested(data, parts=10, operators=3, replicates=2)
print(grr_n.summary())

Estudio Tipo 1 — sesgo y repetibilidad

Mide una pieza de referencia varias veces con un solo operador para estimar el sesgo y la repetibilidad:

ref_meas = rng.normal(10.02, 0.05, 25)   # 25 mediciones de referencia = 10.0
t1 = pp.gage_type1(ref_meas, reference=10.0, tolerance=0.5)
print(t1.summary())
# Sesgo: 0.02 mm  t = 2.0  p = 0.057  Cg = 1.67  Cgk = 1.33

t1.bias      # sesgo = media - referencia
t1.t_stat    # estadístico t para H₀: sesgo = 0
t1.p_value   # valor p de la prueba bilateral
t1.cg        # capacidad del instrumento (0.2×tolerancia / 6s)
t1.cgk       # capacidad considerando sesgo

# Desde estadísticos resumen (sin datos individuales)
ts = pp.gage_type1_summary(
    mean=10.02, std=0.05, n=25,
    reference=10.0, tolerance=0.5,
)

Linealidad y sesgo

Evalúa si el sesgo es constante en todo el rango de operación del instrumento:

refs       = np.repeat([2, 4, 6, 8, 10], 5)    # 5 referentes × 5 réplicas
mediciones = refs + rng.normal(0.05, 0.1, 25)

lin = pp.gage_linearity(mediciones, refs, tolerance=10.0)
print(lin.summary())
# Pendiente: 0.001  Intercepto: 0.048  R²: 0.002  Linealidad: 0.01%

lin.slope      # pendiente de la regresión sesgo ~ referencia
lin.intercept
lin.r_squared
lin.to_frame()
lin.plot()

Concordancia por atributos (Kappa)

Cuando la característica es categórica (conforme/no conforme, grados, colores):

import pandas as pd

clasificaciones = pd.DataFrame({
    "Op1": ["G", "D", "G", "G", "D"] * 4,
    "Op2": ["G", "D", "G", "D", "D"] * 4,
    "Op3": ["G", "G", "G", "G", "D"] * 4,
})
referencia = np.array(["G", "D", "G", "G", "D"] * 2)

atr = pp.attribute_agreement(clasificaciones, reference=referencia, replicates=2)
print(atr.summary())
# Kappa Fleiss: 0.84
# Op1 vs ref: κ=0.90  Op2 vs ref: κ=0.80  Op3 vs ref: κ=0.82

atr.fleiss_kappa           # Kappa de Fleiss (acuerdo entre todos los operadores)
atr.kappa_vs_reference     # DataFrame con kappa, EE y p-valor por operador
atr.to_frame()

Acceso a los datos del resultado

Todas las cartas de control devuelven un objeto ControlChart (las multivariadas devuelven MultivariateChart). Se puede acceder a todos los datos calculados:

carta = pp.imr_chart(x, tests=(1, 2, 3))

# --- Acceso por nombre de panel ---
panel_i  = carta["I"]    # panel de individuales
panel_mr = carta["MR"]   # panel de rango móvil

# Arrays NumPy directos
panel_i.values    # valores graficados
panel_i.center    # línea central (LC)
panel_i.ucl       # límite de control superior (LCS)
panel_i.lcl       # límite de control inferior (LCI)
panel_i.sigma     # sigma estimada punto a punto
panel_i.stage     # etiqueta de etapa de cada punto
panel_i.flagged   # índices base-0 de puntos marcados

# --- DataFrame de un panel ---
panel_i.to_frame()
# columnas: punto, etapa, valor, LC, LCS, LCI, pruebas_fallidas

# --- DataFrame ancho (todos los paneles) ---
carta.to_frame()
# columnas: punto, etapa, I_valor, I_LC, I_LCS, I_LCI, I_pruebas_fallidas,
#            MR_valor, MR_LC, MR_LCS, MR_LCI, MR_pruebas_fallidas

# --- Puntos fuera de control ---
carta.violations()
# columnas: panel, punto, prueba, valor, descripcion

# --- Parámetros estimados ---
carta.params        # lista de dicts, uno por etapa
carta.params[0]     # {'stage': 1, 'media': 10.2, 'sigma': 0.31, 'MR_prom': 0.35, 'n': 30}

# --- Estado del proceso ---
carta.in_control    # True si no hay ningún punto fuera de control
carta.summary()     # resumen en texto

# --- Acceso programatico a violaciones por prueba ---
carta.panels[0].violations   # dict {prueba: array de índices}

Validación

Más de 1 800 pruebas automatizadas. Las referencias son independientes de pccpy:

  • Constantes d2, d3, c4 frente a tablas publicadas (Montgomery).
  • Límites I-MR, Xbar-R y Xbar-S frente al cálculo manual con A2, D3, D4, A3, B3, B4.
  • Anderson-Darling frente a statsmodels (coincide a 9 decimales).
  • EWMA y CUSUM frente a la recursión manual.
  • Las 8 pruebas de causas especiales frente a casos construidos a mano, incluidos los casos límite.
  • T²: valores frente a scipy.spatial.distance.mahalanobis; límites de Fase I y II por simulación Monte Carlo.
  • Varianza generalizada: constantes b1 y b2 por simulación de matrices de covarianza.
  • MEWMA: el límite para p=2, λ=0.1, ARL=200 da 8.63 (publicado: 8.64, Prabhu y Runger 1997).
  • G y T: cuantiles frente a scipy.stats, y máxima verosimilitud Weibull por su ecuación de score.

Nota: pccpy sigue las fórmulas y convenciones que Minitab documenta, pero no se ha comparado corrida a corrida contra el software Minitab por ausencia de licencia. Si encuentras una diferencia, abre un issue.


Desarrollo

git clone https://github.com/LeoSanta15/pccpy.git
cd pccpy
pip install -e ".[dev]"

pytest --cov=pccpy          # pruebas + cobertura
ruff check src/              # estilo y errores comunes
mypy src/pccpy               # verificación de tipos

Para generar la documentación localmente:

pip install -e ".[docs]"
sphinx-build -b html docs/source docs/build
# Abrir docs/build/index.html

Ver CHANGELOG.md para el historial de versiones y CONTRIBUTING.md para las normas de contribución.


Licencia

MIT — ver LICENSE.

Metadata

Release files for pccpy 0.12.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pccpy 0.12.0
File Size Uploaded Provenance
pccpy-0.12.0.tar.gz 237.6 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for pccpy 0.12.0
File Interpreter ABI Platform
pccpy-0.12.0-py3-none-any.whl Python 3 none any Details

Total release size: 410.5 kB

Release files / pccpy-0.12.0.tar.gz

Download URL pccpy-0.12.0.tar.gz
Size 237.6 kB
Tags Source
SHA-256 checksum
How to use checksums
e680db1d3d37e4dc4d3e3e5d30974f19438012dfe4a1ce80e413e0738a8ff74f
BLAKE2b-256 checksum
How to use checksums
b1edb4be9db9c56ea503d8c1d54c0473cff52d423a5f5807b982d735d30b8aef
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 5, 2026.

Transparency log

Release files / pccpy-0.12.0-py3-none-any.whl

Download URL pccpy-0.12.0-py3-none-any.whl
Size 172.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
87b484162aad9562532e33f2a83cc0492881ad9b0b06fc333f4316f3fcbb83f1
BLAKE2b-256 checksum
How to use checksums
25c14699e5d16c7914f22ac328a6daa6491e46c59f98e2e297d1c3c9842eb6b3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 5, 2026.

Transparency log

Security for pccpy 0.12.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for pccpy-0.12.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 5, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.12.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page