pccpy 0.4.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,319 @@
1
+ Metadata-Version: 2.4
2
+ Name: pccpy
3
+ Version: 0.4.3
4
+ Summary: Control Estadístico de Procesos (SPC) en Python: cartas de control, capacidad y pruebas al estilo Minitab
5
+ Author-email: LeoSanta15 <angelsanta1@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/LeoSanta15/pccpy
8
+ Project-URL: Issues, https://github.com/LeoSanta15/pccpy/issues
9
+ Project-URL: Changelog, https://github.com/LeoSanta15/pccpy/blob/main/CHANGELOG.md
10
+ Keywords: spc,control estadístico de procesos,cartas de control,capacidad,minitab,six sigma
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Intended Audience :: Science/Research
19
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
20
+ Requires-Python: >=3.9
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: numpy>=1.22
24
+ Requires-Dist: scipy>=1.9
25
+ Requires-Dist: pandas>=1.4
26
+ Requires-Dist: matplotlib>=3.5
27
+ Provides-Extra: dev
28
+ Requires-Dist: pytest>=7; extra == "dev"
29
+ Requires-Dist: pytest-cov>=4; extra == "dev"
30
+ Requires-Dist: ruff>=0.6; extra == "dev"
31
+ Requires-Dist: mypy>=1.10; extra == "dev"
32
+ Provides-Extra: docs
33
+ Requires-Dist: sphinx>=7; extra == "docs"
34
+ Requires-Dist: sphinx-rtd-theme>=2; extra == "docs"
35
+ Requires-Dist: myst-parser>=2; extra == "docs"
36
+ Dynamic: license-file
37
+
38
+ # spyc
39
+
40
+ **Control Estadístico de Procesos (SPC) en Python, al estilo Minitab.**
41
+
42
+ Cartas de control, pruebas de causas especiales, análisis de capacidad, prueba de
43
+ normalidad y Pareto, con salidas y convenciones (LCS/LCI, LEI/LES, Cp/Cpk/Pp/Ppk,
44
+ Z.Bench, PPM, Anderson-Darling) pensadas para quien viene de Minitab.
45
+
46
+ Todo en español: nombres de columnas, mensajes, descripciones de pruebas y gráficos.
47
+
48
+ ## Instalación
49
+
50
+ Desde GitHub:
51
+
52
+ ```bash
53
+ pip install git+https://github.com/TU_USUARIO/spyc.git
54
+ ```
55
+
56
+ Desarrollo local:
57
+
58
+ ```bash
59
+ git clone https://github.com/TU_USUARIO/spyc.git
60
+ cd spyc
61
+ pip install -e ".[dev]"
62
+ pytest --cov=spyc # pruebas + cobertura (97% al momento de escribir esto)
63
+ ruff check src/ # estilo y errores comunes
64
+ mypy src/spyc # tipos
65
+ ```
66
+
67
+ Ver [`CHANGELOG.md`](CHANGELOG.md) para el historial de versiones.
68
+
69
+ ## Documentación
70
+
71
+ Hay documentación construida con Sphinx en `docs/`, con una página por cada grupo
72
+ de cartas (generadas a partir de los docstrings, así que siempre coinciden con el
73
+ código instalado). Para generarla y abrirla en tu máquina:
74
+
75
+ ```bash
76
+ pip install -e ".[docs]"
77
+ sphinx-build -b html docs/source docs/build
78
+ ```
79
+
80
+ Abre `docs/build/index.html`. Una vez el repositorio esté en GitHub, puedes conectarlo
81
+ en [readthedocs.org](https://readthedocs.org) para tenerla publicada automáticamente
82
+ en cada push; el archivo `.readthedocs.yaml` ya está listo para eso.
83
+
84
+ Requiere Python ≥ 3.9 (numpy, scipy, pandas, matplotlib).
85
+
86
+ ## Inicio rápido
87
+
88
+ ```python
89
+ import numpy as np
90
+ import spyc
91
+
92
+ rng = np.random.default_rng(1)
93
+ x = rng.normal(100, 2, 60)
94
+ x[40:] += 3 # el proceso se desplaza
95
+
96
+ carta = spyc.imr_chart(x, tests=(1, 2, 3, 4, 5, 6, 7, 8))
97
+ print(carta.summary()) # resumen tipo sesión de Minitab
98
+ carta.violations() # DataFrame: panel, punto, prueba, descripción
99
+ carta.to_frame() # todos los valores, límites y pruebas fallidas
100
+ carta.plot() # figura de matplotlib
101
+ ```
102
+
103
+ ## Cartas de control
104
+
105
+ | Minitab | Función | Notas |
106
+ |---|---|---|
107
+ | I-MR | `imr_chart(x)` | `sigma_method`: `'mr'`, `'median_mr'`, `'mssd'`; `span` configurable |
108
+ | Xbar-R | `xbar_r_chart(datos)` | `sigma_method`: `'rbar'`, `'pooled'` |
109
+ | Xbar-S | `xbar_s_chart(datos)` | `sigma_method`: `'sbar'`, `'pooled'` |
110
+ | P | `p_chart(defectuosos, n)` | límites variables con `n` variable |
111
+ | NP | `np_chart(defectuosos, n)` | |
112
+ | C | `c_chart(defectos)` | |
113
+ | U | `u_chart(defectos, n)` | |
114
+ | Laney P' / U' | `laney_p_chart`, `laney_u_chart` | corrige sobredispersión |
115
+ | EWMA | `ewma_chart(datos, weight=0.2, k=3)` | límites exactos (se ensanchan al inicio) |
116
+ | CUSUM | `cusum_chart(datos, h=4, k=0.5)` | tabular, sumas superior e inferior |
117
+ | Media móvil | `ma_chart(datos, length=3)` | límites más anchos en los primeros `length-1` puntos |
118
+ | Z-MR | `zmr_chart(x, partes)` | corridas cortas; ver más abajo |
119
+ | I-MR-R/S (entre/dentro) | `imr_rs_chart(datos, within='r')` | variación entre y dentro de subgrupos |
120
+ | G | `g_chart(entre_eventos)` | eventos raros, geométrica |
121
+ | T | `t_chart(tiempos)` | eventos raros, Weibull o exponencial |
122
+ | T² de Hotelling | `t2_chart(matriz)` | Fase I / Fase II, con diagnóstico por variable |
123
+ | Varianza generalizada | `generalized_variance_chart(matriz, subgroup_size=n)` | dispersión multivariada, `\|S\|` |
124
+ | MEWMA | `mewma_chart(matriz)` | límite por ARL (por defecto 200) |
125
+
126
+ Los datos de variables aceptan una matriz 2D (filas = subgrupos), un vector con
127
+ `subgroup_size=5`, o un vector con `subgroup=identificadores` (subgrupos de tamaño desigual).
128
+
129
+ ```python
130
+ datos = rng.normal(50, 1, size=(25, 5))
131
+ spyc.xbar_r_chart(datos, tests=(1, 2, 3, 4)).summary()
132
+
133
+ vector = datos.ravel() # mismos datos en un solo vector
134
+ ids = np.repeat(np.arange(25), 5) # identificador de subgrupo de cada dato
135
+ spyc.xbar_r_chart(vector, subgroup_size=5)
136
+ spyc.xbar_s_chart(vector, subgroup=ids)
137
+ ```
138
+
139
+ **Parámetros históricos y etapas** (equivalentes a *Opciones de estimación* y *Etapas* de Minitab):
140
+
141
+ ```python
142
+ spyc.imr_chart(x, mu=100, sigma=2) # parámetros conocidos
143
+ spyc.imr_chart(x, stages=["antes"] * 40 + ["después"] * 20) # límites por etapa
144
+ ```
145
+
146
+ **Atributos:**
147
+
148
+ ```python
149
+ defectuosos = rng.binomial(200, 0.05, 30)
150
+ spyc.p_chart(defectuosos, n=200)
151
+ spyc.laney_p_chart(defectuosos, n=200)
152
+ ```
153
+
154
+ ## Cartas avanzadas
155
+
156
+ ```python
157
+ # Z-MR: partes con medias y variaciones distintas en una sola carta (corridas cortas)
158
+ partes = ["A"] * 10 + ["B"] * 10 + ["A"] * 10
159
+ medidas = np.r_[rng.normal(50, 1, 10), rng.normal(80, 2, 10), rng.normal(50, 1, 10)]
160
+ spyc.zmr_chart(medidas, partes, sigma_method="by_part") # 'constant', 'relative', 'by_part', 'by_run'
161
+
162
+ # I-MR-R/S: la variación entre subgrupos no genera falsas alarmas como en Xbar-R
163
+ sub = rng.normal(20, 1, (25, 5)) + rng.normal(0, 1.5, (25, 1))
164
+ c = spyc.imr_rs_chart(sub, within="s")
165
+ c.params[0] # sigma_dentro, sigma_entre, sigma_entre_dentro
166
+
167
+ # Eventos raros
168
+ spyc.g_chart(rng.geometric(0.02, 40) - 1) # casos entre eventos
169
+ spyc.t_chart(rng.weibull(1.5, 40) * 30) # tiempo entre eventos
170
+
171
+ # Media móvil
172
+ spyc.ma_chart(x, length=5)
173
+ ```
174
+
175
+ `zmr_chart` estandariza cada observación con la media de su parte y una sigma estimada
176
+ con el rango móvil *dentro de cada corrida* (bloque consecutivo de la misma parte);
177
+ `mu={parte: valor}` y `sigma=` aceptan valores históricos o nominales.
178
+
179
+ ## Cartas multivariadas
180
+
181
+ ```python
182
+ Sigma = [[1, .6, .3], [.6, 1, .2], [.3, .2, 1]]
183
+ historico = rng.multivariate_normal([0, 0, 0], Sigma, 100)
184
+ nuevos = rng.multivariate_normal([0, 0, 0], Sigma, 40)
185
+ nuevos[25:, 2] += 4 # la variable 3 se desplaza
186
+
187
+ # Fase I: parámetros estimados de los mismos datos
188
+ spyc.t2_chart(historico).summary()
189
+
190
+ # Fase II: parámetros históricos (n_hist = observaciones con que se estimaron)
191
+ c = spyc.t2_chart(nuevos, mu=historico.mean(axis=0), cov=np.cov(historico, rowvar=False),
192
+ n_hist=100)
193
+ c.violations()
194
+ c.contributions(31) # ¿qué variable explica la señal?
195
+
196
+ # Subgrupos: matriz N x p con subgroup_size, o array 3-D (subgrupos x n x p)
197
+ spyc.t2_chart(nuevos[:40], subgroup_size=4)
198
+ spyc.generalized_variance_chart(nuevos[:40], subgroup_size=8)
199
+
200
+ # MEWMA: cambios pequeños y sostenidos
201
+ spyc.mewma_chart(nuevos, mu=historico.mean(axis=0), cov=np.cov(historico, rowvar=False))
202
+ spyc.mewma_limit(p=2, weight=0.1, arl=200) # límite H para un ARL dado (≈ 8.64)
203
+ ```
204
+
205
+ Los datos pueden ser un `DataFrame` (los nombres de columna se usan en `contributions`).
206
+ El límite de T² usa por defecto α = 0.00135 (cola de 3 sigmas, como Minitab). Sin `mu`/`cov`
207
+ se usan límites de **Fase I** (Beta para individuales, F para subgrupos); con `mu`/`cov` y
208
+ `n_hist`, de **Fase II**; con `mu`/`cov` sin `n_hist` los parámetros se toman como conocidos (χ²).
209
+
210
+ ### Etapas y Box-Cox
211
+
212
+ Las 4 cartas multivariadas (T², varianza generalizada, MEWMA y MCUSUM) aceptan `stages`
213
+ y `boxcox`, igual que las cartas univariadas:
214
+
215
+ ```python
216
+ etapas = [1] * 40 + [2] * 40 # p. ej. antes/después de un ajuste
217
+
218
+ # Sin mu/cov: la media y la covarianza se vuelven a estimar dentro de cada etapa
219
+ c = spyc.t2_chart(nuevos_con_dos_etapas, stages=etapas)
220
+ c.stage_mean[1], c.stage_mean[2] # medias distintas por etapa
221
+ c.contributions(45) # usa la media/cov de la etapa del punto 45
222
+
223
+ # Con mu/cov históricos: los mismos parámetros en todas las etapas
224
+ spyc.t2_chart(nuevos_con_dos_etapas, mu=mu, cov=cov, n_hist=100, stages=etapas)
225
+
226
+ # MEWMA y MCUSUM: el acumulador reinicia al empezar cada etapa
227
+ spyc.mewma_chart(nuevos_con_dos_etapas, stages=etapas)
228
+ spyc.mcusum_chart(nuevos_con_dos_etapas, stages=etapas)
229
+
230
+ # Box-Cox: una lambda por variable (no se puede combinar con mu/cov históricos)
231
+ c = spyc.t2_chart(datos_positivos, boxcox=True)
232
+ c.params[0]["lambda_boxcox"] # {'X1': ..., 'X2': ..., 'X3': ...}
233
+ spyc.generalized_variance_chart(datos_positivos, subgroup_size=6, boxcox=True)
234
+ spyc.mewma_chart(datos_positivos, boxcox=True)
235
+ spyc.mcusum_chart(datos_positivos, boxcox=True)
236
+ ```
237
+
238
+ ## Pruebas de causas especiales
239
+
240
+ Las 8 pruebas de Minitab (1 a 8) con sus parámetros por defecto
241
+ (`K` = 3, 9, 6, 14, 2, 4, 15, 8). Se piden con `tests=(...)` y se ajustan con `test_params`:
242
+
243
+ ```python
244
+ spyc.imr_chart(x, tests=(1, 2, 5), test_params={2: 7}) # prueba 2 con 7 puntos
245
+ ```
246
+
247
+ Cada tipo de carta aplica el subconjunto que corresponde (completo en I, Xbar y Z;
248
+ básicas 1-4 en MR, R, S, G, T y cartas de atributos; solo la 1 en EWMA, CUSUM, MA y las
249
+ multivariadas). En G y T la prueba 1 usa los percentiles de su distribución.
250
+ Las funciones individuales están en `spyc.rules`.
251
+
252
+ ## Capacidad del proceso
253
+
254
+ ```python
255
+ datos = rng.normal(10, 0.1, 100)
256
+ res = spyc.capability_analysis(datos, lsl=9.7, usl=10.3, subgroup_size=5)
257
+ print(res.summary()) # Cp, CPL, CPU, Cpk, Pp, PPL, PPU, Ppk, Cpm, Z.Bench, PPM, IC
258
+ res.to_frame()
259
+ res.plot()
260
+ ```
261
+
262
+ Como en Minitab, **Cp/Cpk usan la desviación estándar dentro de subgrupos** y
263
+ **Pp/Ppk la desviación general**; por eso Cpk ≠ Ppk cuando el proceso se desplaza o hay variación entre subgrupos.
264
+
265
+ Datos no normales:
266
+
267
+ ```python
268
+ spyc.capability_nonnormal(x, lsl=1, usl=20, distribution="weibull")
269
+ spyc.capability_boxcox(x, lsl=1, usl=20)
270
+ ```
271
+
272
+ Distribuciones: `normal`, `lognormal`, `weibull`, `gamma`, `exponential`,
273
+ `loglogistic`, `logistic`, `largest_extreme`, `smallest_extreme` (método de percentiles).
274
+
275
+ `spyc.capability_sixpack(datos, lsl, usl, subgroup_size=5)` genera el **Capability Sixpack**.
276
+
277
+ ## Normalidad, Pareto y constantes
278
+
279
+ ```python
280
+ spyc.normality_test(x) # Anderson-Darling (por defecto), 'shapiro', 'dagostino'
281
+ spyc.probability_plot(x)
282
+
283
+ tabla = spyc.pareto(["rayón", "abolladura", "rayón", "otro"])
284
+ spyc.plot_pareto(tabla)
285
+
286
+ spyc.control_chart_constants(5) # d2, d3, c4, c5, A2, A3, D3, D4, B3, B4
287
+ ```
288
+
289
+ Las constantes se calculan por integración numérica para **cualquier** `n ≥ 2` (no solo tablas hasta 25).
290
+
291
+ ## Validación
292
+
293
+ pruebas automatizadas. Las referencias son independientes de spyc:
294
+
295
+ - Constantes d2, d3, c4 frente a las tablas publicadas (Montgomery).
296
+ - Límites I-MR, Xbar-R y Xbar-S frente al cálculo manual con A2, D3, D4, A3, B3, B4.
297
+ - Anderson-Darling frente a `statsmodels.stats.diagnostic.normal_ad` (coincide a 9 decimales).
298
+ - EWMA y CUSUM frente a la recursión manual.
299
+ - Las 8 pruebas de causas especiales frente a casos construidos a mano, incluidos los casos límite.
300
+ - T²: valores frente a `scipy.spatial.distance.mahalanobis`; los límites de Fase I y II
301
+ (Beta y F) por simulación Monte Carlo (tasa de falsa alarma y valor esperado).
302
+ - Varianza generalizada: constantes b1 y b2 por simulación de matrices de covarianza.
303
+ - MEWMA: el límite para p=2, λ=0.1, ARL=200 da 8.63 (publicado: 8.64, Prabhu y Runger 1997)
304
+ y el ARL se comprueba por simulación.
305
+ - G y T: cuantiles frente a `scipy.stats`, y máxima verosimilitud Weibull por su ecuación de score.
306
+ - Constantes de Z-MR (1.128 y 3.686) frente a las que documenta Minitab.
307
+
308
+ > **Importante:** spyc no se ha comparado corrida a corrida contra el software Minitab
309
+ > (no hay licencia disponible en el desarrollo). Sigue las fórmulas y convenciones que
310
+ > Minitab documenta. Si encuentras una diferencia, abre un issue (ver `CONTRIBUTING.md`).
311
+
312
+ ## Fuera de alcance (por ahora)
313
+
314
+ Cartas multivariadas (T² de Hotelling), cartas de corridas cortas, Z-MR, I-MR-R/S
315
+ (entre/dentro), transformación de Johnson, MSA/Gage R&R.
316
+
317
+ ## Licencia
318
+
319
+ MIT.
@@ -0,0 +1,22 @@
1
+ pccpy-0.4.3.dist-info/licenses/LICENSE,sha256=nHoS7zkyH9dODTc-wkgXuXUTdSSqPNTrFz6gBO0WgVk,1072
2
+ spyc/__init__.py,sha256=H9V4zs5zsVbHyooRXcRvN4u-j8fb-8zNsFmEWxcBnm4,1900
3
+ spyc/_constants.py,sha256=jsOTg062VXtxUn3K5SjZ2fhrOFbc4zOmMQFFIqWnN_A,2729
4
+ spyc/_data.py,sha256=jjIjGZn_fYvYH2dWyMzavkoSEVpS7zU6gUWYTWRJaYg,4011
5
+ spyc/_sigma.py,sha256=7cUFK2Z48RzYFXR82llm7ERS7Xv1AP4Pbe-5jaLmrtM,3445
6
+ spyc/capability.py,sha256=-3aVL1V-43agzfVUYWwAVR91K7hlMGzaRca4Am4-wuo,15944
7
+ spyc/multivariate.py,sha256=pZfkc6HVnbxF83vRaMOZjbu8U1PuFNTew1DBUD5vDjs,24031
8
+ spyc/normality.py,sha256=QCHFeLqTn10xsvX2IRPoS9e-qWIlRxqIRtjSYq5k92Y,2712
9
+ spyc/plotting.py,sha256=ljaorhxaTh55bMla0i3i1J5jYzkq2ZOYFsBI4crzQXI,10037
10
+ spyc/quality_tools.py,sha256=FTNkkXrwzgynMvW_xY_qU-Q53jS9IyRyBGp39Iv9Q_E,2397
11
+ spyc/results.py,sha256=tR9FVakg2-oZ6QGUTGou41p5DwOKExxWGuQYKhUppZU,7580
12
+ spyc/rules.py,sha256=rlGlzlVh_gHJYBvFoTEpTcgB2qMzMMn0D8oBpyi6aeI,6086
13
+ spyc/charts/__init__.py,sha256=U0u3Dw3i-AZZ7fGgzQLmCXp9HqjD6-vN6JNa-hQRjMc,600
14
+ spyc/charts/_engine.py,sha256=tvsQUuuedGAvqxgxeiAV8cAM8ebR0AEkKdiWg2BLv_w,3618
15
+ spyc/charts/advanced.py,sha256=y0Fxu4WE78BwM65tEzh47FEE9D1WNb9WCDzqEoqot-I,21558
16
+ spyc/charts/attributes.py,sha256=JvJQe3iLJSTWCqgJP0yKNKtsQpLXwyACi8hMvDY3D9s,5387
17
+ spyc/charts/timeweighted.py,sha256=M3r4O5QKjRVXuIgqLzqnUok5R9k5VpTAocmzRPeKWLM,4205
18
+ spyc/charts/variables.py,sha256=ORuEfaipD7t8rJfX1C8GoAO1GbBY2U0M7qYGNYxTnFo,5599
19
+ pccpy-0.4.3.dist-info/METADATA,sha256=9gWY6z7syZ1HqWw703ziSSXlStmh1iP3OcTVKuRlWEU,13363
20
+ pccpy-0.4.3.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
21
+ pccpy-0.4.3.dist-info/top_level.txt,sha256=LTiaF0UW38bvJzBsmdnooNa5uCV1C_tb6vkbv0p3T_k,5
22
+ pccpy-0.4.3.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Autores de spyc
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ spyc
spyc/__init__.py ADDED
@@ -0,0 +1,89 @@
1
+ """spyc - Control Estadístico de Procesos (SPC) en Python, al estilo Minitab."""
2
+ from ._constants import c4, c5, control_chart_constants, d2, d3
3
+ from .capability import (
4
+ CapabilityResult,
5
+ NonNormalCapabilityResult,
6
+ capability_analysis,
7
+ capability_boxcox,
8
+ capability_nonnormal,
9
+ )
10
+ from .charts import (
11
+ c_chart,
12
+ cusum_chart,
13
+ ewma_chart,
14
+ g_chart,
15
+ imr_chart,
16
+ imr_rs_chart,
17
+ laney_p_chart,
18
+ laney_u_chart,
19
+ ma_chart,
20
+ np_chart,
21
+ p_chart,
22
+ t_chart,
23
+ u_chart,
24
+ xbar_r_chart,
25
+ xbar_s_chart,
26
+ zmr_chart,
27
+ zone_chart,
28
+ )
29
+ from .multivariate import (
30
+ generalized_variance_chart,
31
+ mcusum_chart,
32
+ mcusum_limit,
33
+ mewma_chart,
34
+ mewma_limit,
35
+ t2_chart,
36
+ )
37
+ from .normality import NormalityResult, normality_test
38
+ from .plotting import capability_sixpack, plot_capability, plot_control_chart, probability_plot
39
+ from .quality_tools import pareto, plot_pareto
40
+ from .results import ControlChart, MultivariateChart, Panel
41
+
42
+ __version__ = "0.4.3"
43
+
44
+ __all__ = [
45
+ "CapabilityResult",
46
+ "ControlChart",
47
+ "MultivariateChart",
48
+ "NonNormalCapabilityResult",
49
+ "NormalityResult",
50
+ "Panel",
51
+ "c4",
52
+ "c5",
53
+ "c_chart",
54
+ "capability_analysis",
55
+ "capability_boxcox",
56
+ "capability_nonnormal",
57
+ "capability_sixpack",
58
+ "control_chart_constants",
59
+ "cusum_chart",
60
+ "d2",
61
+ "d3",
62
+ "ewma_chart",
63
+ "g_chart",
64
+ "generalized_variance_chart",
65
+ "imr_chart",
66
+ "imr_rs_chart",
67
+ "laney_p_chart",
68
+ "laney_u_chart",
69
+ "ma_chart",
70
+ "mcusum_chart",
71
+ "mcusum_limit",
72
+ "mewma_chart",
73
+ "mewma_limit",
74
+ "normality_test",
75
+ "np_chart",
76
+ "p_chart",
77
+ "pareto",
78
+ "plot_capability",
79
+ "plot_control_chart",
80
+ "plot_pareto",
81
+ "probability_plot",
82
+ "t2_chart",
83
+ "t_chart",
84
+ "u_chart",
85
+ "xbar_r_chart",
86
+ "xbar_s_chart",
87
+ "zmr_chart",
88
+ "zone_chart",
89
+ ]
spyc/_constants.py ADDED
@@ -0,0 +1,101 @@
1
+ """Constantes de control estadístico de procesos (d2, d3, c4, c5, A2, D3, D4, ...).
2
+
3
+ Se calculan numéricamente para *cualquier* tamaño de subgrupo n >= 2, en lugar
4
+ de depender de una tabla finita. Los valores coinciden con las tablas
5
+ publicadas (p. ej. Montgomery, Apéndice VI) a los decimales publicados.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import math
10
+ from functools import cache
11
+
12
+ import numpy as np
13
+ from scipy import integrate
14
+ from scipy.special import gammaln
15
+
16
+ _SQRT2PI = math.sqrt(2.0 * math.pi)
17
+
18
+
19
+ def _phi(t: float) -> float:
20
+ return math.exp(-0.5 * t * t) / _SQRT2PI
21
+
22
+
23
+ def _Phi(t: float) -> float:
24
+ return 0.5 * math.erfc(-t / math.sqrt(2.0))
25
+
26
+
27
+ def _check_n(n: int) -> int:
28
+ n = int(n)
29
+ if n < 2:
30
+ raise ValueError(f"El tamaño de subgrupo debe ser >= 2 (recibido: {n}).")
31
+ return n
32
+
33
+
34
+ @cache
35
+ def d2(n: int) -> float:
36
+ """Media del rango relativo W = R/sigma para n observaciones normales."""
37
+ n = _check_n(n)
38
+
39
+ def f(x: float) -> float:
40
+ p = _Phi(x)
41
+ return 1.0 - p**n - (1.0 - p) ** n
42
+
43
+ val, _ = integrate.quad(f, -np.inf, np.inf, epsabs=1e-13, epsrel=1e-13, limit=200)
44
+ return float(val)
45
+
46
+
47
+ @cache
48
+ def d3(n: int) -> float:
49
+ """Desviación estándar del rango relativo W = R/sigma."""
50
+ n = _check_n(n)
51
+ lim = 9.0 + math.log(n)
52
+
53
+ def joint(y: float, x: float) -> float:
54
+ # densidad conjunta (mínimo x, máximo y) por (y - x)^2
55
+ return (
56
+ (y - x) ** 2
57
+ * n
58
+ * (n - 1)
59
+ * _phi(x)
60
+ * _phi(y)
61
+ * (_Phi(y) - _Phi(x)) ** (n - 2)
62
+ )
63
+
64
+ ex2, _ = integrate.dblquad(
65
+ joint, -lim, lim, lambda x: x, lambda x: lim, epsabs=1e-11, epsrel=1e-11
66
+ )
67
+ return math.sqrt(max(ex2 - d2(n) ** 2, 0.0))
68
+
69
+
70
+ @cache
71
+ def c4(n: int) -> float:
72
+ """Factor de corrección de sesgo de la desviación estándar muestral."""
73
+ n = _check_n(n)
74
+ return math.sqrt(2.0 / (n - 1)) * math.exp(gammaln(n / 2.0) - gammaln((n - 1) / 2.0))
75
+
76
+
77
+ @cache
78
+ def c5(n: int) -> float:
79
+ """Desviación estándar de s / sigma: sqrt(1 - c4^2)."""
80
+ return math.sqrt(1.0 - c4(n) ** 2)
81
+
82
+
83
+ def control_chart_constants(n: int) -> dict[str, float]:
84
+ """Devuelve todas las constantes para un tamaño de subgrupo ``n``.
85
+
86
+ Claves: d2, d3, c4, c5, A2, A3, D3, D4, B3, B4.
87
+ """
88
+ n = _check_n(n)
89
+ _d2, _d3, _c4, _c5 = d2(n), d3(n), c4(n), c5(n)
90
+ return {
91
+ "d2": _d2,
92
+ "d3": _d3,
93
+ "c4": _c4,
94
+ "c5": _c5,
95
+ "A2": 3.0 / (_d2 * math.sqrt(n)),
96
+ "A3": 3.0 / (_c4 * math.sqrt(n)),
97
+ "D3": max(0.0, 1.0 - 3.0 * _d3 / _d2),
98
+ "D4": 1.0 + 3.0 * _d3 / _d2,
99
+ "B3": max(0.0, 1.0 - 3.0 * _c5 / _c4),
100
+ "B4": 1.0 + 3.0 * _c5 / _c4,
101
+ }
spyc/_data.py ADDED
@@ -0,0 +1,106 @@
1
+ """Conversión y validación de datos de entrada."""
2
+ from __future__ import annotations
3
+
4
+ import numpy as np
5
+ import pandas as pd
6
+
7
+
8
+ def as_1d(x, name: str = "x", allow_nan: bool = False) -> np.ndarray:
9
+ """Convierte ``x`` (lista, array, Series) a un array float 1-D."""
10
+ arr = np.asarray(x, dtype=float)
11
+ if arr.ndim == 0:
12
+ arr = arr.reshape(1)
13
+ if arr.ndim != 1:
14
+ raise ValueError(f"'{name}' debe ser unidimensional (recibido: {arr.ndim}-D).")
15
+ if arr.size == 0:
16
+ raise ValueError(f"'{name}' está vacío.")
17
+ if not allow_nan and not np.all(np.isfinite(arr)):
18
+ raise ValueError(f"'{name}' contiene valores faltantes o infinitos.")
19
+ return arr
20
+
21
+
22
+ def to_subgroups(
23
+ data,
24
+ subgroup_size: int | None = None,
25
+ subgroup=None,
26
+ ) -> np.ndarray:
27
+ """Devuelve una matriz (k subgrupos x m columnas) rellena con NaN.
28
+
29
+ Formatos aceptados (los mismos que ofrece Minitab):
30
+
31
+ * ``data`` 2-D (array o DataFrame): cada **fila** es un subgrupo.
32
+ * ``data`` 1-D + ``subgroup_size``: observaciones consecutivas se agrupan de
33
+ a ``subgroup_size``.
34
+ * ``data`` 1-D + ``subgroup`` (identificadores, misma longitud): cada valor
35
+ distinto de ``subgroup`` define un subgrupo, en orden de aparición. Permite
36
+ subgrupos de tamaño desigual.
37
+ """
38
+ arr = np.asarray(data, dtype=float)
39
+ if arr.ndim == 2:
40
+ if subgroup_size is not None or subgroup is not None:
41
+ raise ValueError(
42
+ "Con datos 2-D no se debe indicar 'subgroup_size' ni 'subgroup'."
43
+ )
44
+ mat = arr
45
+ elif arr.ndim == 1:
46
+ if subgroup is not None:
47
+ ids = np.asarray(subgroup)
48
+ if ids.shape != arr.shape:
49
+ raise ValueError("'subgroup' debe tener la misma longitud que los datos.")
50
+ groups = [g.to_numpy(dtype=float) for _, g in pd.Series(arr).groupby(ids, sort=False)]
51
+ m = max(len(g) for g in groups)
52
+ mat = np.full((len(groups), m), np.nan)
53
+ for i, g in enumerate(groups):
54
+ mat[i, : len(g)] = g
55
+ elif subgroup_size is not None:
56
+ m = int(subgroup_size)
57
+ if m < 1:
58
+ raise ValueError("'subgroup_size' debe ser >= 1.")
59
+ if arr.size % m != 0:
60
+ raise ValueError(
61
+ f"{arr.size} observaciones no se pueden dividir en subgrupos de {m}."
62
+ )
63
+ mat = arr.reshape(-1, m)
64
+ else:
65
+ raise ValueError(
66
+ "Datos 1-D: indique 'subgroup_size' o 'subgroup', o pase una matriz 2-D."
67
+ )
68
+ else:
69
+ raise ValueError("Los datos deben ser 1-D o 2-D.")
70
+
71
+ if mat.shape[0] == 0 or mat.shape[1] == 0:
72
+ raise ValueError("No hay datos.")
73
+ if np.any(np.isinf(mat)):
74
+ raise ValueError("Los datos contienen valores infinitos.")
75
+ if np.any(np.all(np.isnan(mat), axis=1)):
76
+ raise ValueError("Hay subgrupos sin ninguna observación válida.")
77
+ return mat
78
+
79
+
80
+ def stage_slices(n_points: int, stages) -> list:
81
+ """Lista de (etiqueta, índices) con las etapas en orden de aparición.
82
+
83
+ Cada etapa debe ser un bloque contiguo (como exige Minitab).
84
+ """
85
+ if stages is None:
86
+ return [(1, np.arange(n_points))]
87
+ st = np.asarray(stages)
88
+ if st.shape != (n_points,):
89
+ raise ValueError(
90
+ f"'stages' debe tener una etiqueta por punto graficado ({n_points}); "
91
+ f"recibido: {st.shape}."
92
+ )
93
+ out, seen = [], set()
94
+ start = 0
95
+ for i in range(1, n_points + 1):
96
+ if i == n_points or st[i] != st[start]:
97
+ label = st[start].item() if hasattr(st[start], "item") else st[start]
98
+ if label in seen:
99
+ raise ValueError(
100
+ f"La etapa {label!r} aparece en bloques separados; "
101
+ "cada etapa debe ser contigua."
102
+ )
103
+ seen.add(label)
104
+ out.append((label, np.arange(start, i)))
105
+ start = i
106
+ return out