statslibx 0.1.0__py3-none-any.whl → 0.1.1__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,788 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+ from typing import Optional, Union, Literal, List
4
+ from datetime import datetime
5
+
6
+ class InferentialStats:
7
+ """
8
+ Clase para estadística inferencial (pruebas de hipótesis, intervalos de confianza, etc.)
9
+ """
10
+
11
+ def __init__(self, data: Union[pd.DataFrame, np.ndarray],
12
+ backend: Literal['pandas', 'polars'] = 'pandas'):
13
+ """
14
+ Inicializar con DataFrame o array numpy
15
+ """
16
+ if isinstance(data, np.ndarray):
17
+ if data.ndim == 1:
18
+ data = pd.DataFrame({'var': data})
19
+ else:
20
+ data = pd.DataFrame(data, columns=[f'var_{i}' for i in range(data.shape[1])])
21
+
22
+ self.data = data
23
+ self.backend = backend
24
+ self._numeric_cols = data.select_dtypes(include=[np.number]).columns.tolist()
25
+
26
+ # ============= INTERVALOS DE CONFIANZA =============
27
+
28
+ def confidence_interval(self, column: str, confidence: float = 0.95,
29
+ statistic: Literal['mean', 'median', 'proportion'] = 'mean') -> tuple:
30
+ """
31
+ Intervalo de confianza para diferentes estadísticos
32
+
33
+ Parameters:
34
+ -----------
35
+ column : str
36
+ Columna a analizar
37
+ confidence : float
38
+ Nivel de confianza (default 0.95 = 95%)
39
+ statistic : str
40
+ 'mean', 'median' o 'proportion'
41
+
42
+ Returns:
43
+ --------
44
+ tuple : (lower_bound, upper_bound, point_estimate)
45
+ """
46
+ from scipy import stats
47
+
48
+ data = self.data[column].dropna()
49
+ n = len(data)
50
+ alpha = 1 - confidence
51
+
52
+ if statistic == 'mean':
53
+ point_est = data.mean()
54
+ se = stats.sem(data)
55
+ margin = se * stats.t.ppf((1 + confidence) / 2, n - 1)
56
+ return (point_est - margin, point_est + margin, point_est)
57
+
58
+ elif statistic == 'median':
59
+ # Bootstrap para mediana
60
+ point_est = data.median()
61
+ n_bootstrap = 10000
62
+ bootstrap_medians = []
63
+ for _ in range(n_bootstrap):
64
+ sample = np.random.choice(data, size=n, replace=True)
65
+ bootstrap_medians.append(np.median(sample))
66
+
67
+ lower = np.percentile(bootstrap_medians, (alpha/2) * 100)
68
+ upper = np.percentile(bootstrap_medians, (1 - alpha/2) * 100)
69
+ return (lower, upper, point_est)
70
+
71
+ elif statistic == 'proportion':
72
+ # Asume datos binarios (0/1)
73
+ point_est = data.mean()
74
+ se = np.sqrt(point_est * (1 - point_est) / n)
75
+ z_critical = stats.norm.ppf((1 + confidence) / 2)
76
+ margin = z_critical * se
77
+ return (point_est - margin, point_est + margin, point_est)
78
+
79
+ # ============= PRUEBAS DE HIPÓTESIS =============
80
+
81
+ def t_test_1sample(self, column: str, popmean: float = None,
82
+ popmedian: float = None,
83
+ alternative: Literal['two-sided', 'less', 'greater'] = 'two-sided') -> 'TestResult':
84
+ """
85
+ Prueba t de una muestra (para media o mediana)
86
+
87
+ Parameters:
88
+ -----------
89
+ column : str
90
+ Columna a analizar
91
+ popmean : float, optional
92
+ Media poblacional hipotética
93
+ popmedian : float, optional
94
+ Mediana poblacional hipotética (usa signed-rank test)
95
+ alternative : str
96
+ Hipótesis alternativa
97
+ """
98
+ from scipy import stats
99
+
100
+ data = self.data[column].dropna()
101
+
102
+ if popmean is not None:
103
+ statistic, pvalue = stats.ttest_1samp(data, popmean, alternative=alternative)
104
+
105
+ return TestResult(
106
+ test_name='T-Test de Una Muestra (Media)',
107
+ statistic=statistic,
108
+ pvalue=pvalue,
109
+ alternative=alternative,
110
+ params={
111
+ 'popmean': popmean,
112
+ 'sample_mean': data.mean(),
113
+ 'n': len(data),
114
+ 'df': len(data) - 1
115
+ }
116
+ )
117
+
118
+ elif popmedian is not None:
119
+ # Wilcoxon signed-rank test para mediana
120
+ statistic, pvalue = stats.wilcoxon(data - popmedian, alternative=alternative)
121
+
122
+ return TestResult(
123
+ test_name='Wilcoxon Signed-Rank Test (Mediana)',
124
+ statistic=statistic,
125
+ pvalue=pvalue,
126
+ alternative=alternative,
127
+ params={
128
+ 'popmedian': popmedian,
129
+ 'sample_median': data.median(),
130
+ 'n': len(data)
131
+ }
132
+ )
133
+
134
+ else:
135
+ raise ValueError("Debe especificar popmean o popmedian")
136
+
137
+ def t_test_2sample(self, column1: str, column2: str,
138
+ equal_var: bool = True,
139
+ alternative: Literal['two-sided', 'less', 'greater'] = 'two-sided') -> 'TestResult':
140
+ """
141
+ Prueba t de dos muestras independientes
142
+
143
+ Parameters:
144
+ -----------
145
+ column1, column2 : str
146
+ Columnas a comparar
147
+ equal_var : bool
148
+ Asumir varianzas iguales
149
+ alternative : str
150
+ Hipótesis alternativa
151
+ """
152
+ from scipy import stats
153
+
154
+ data1 = self.data[column1].dropna()
155
+ data2 = self.data[column2].dropna()
156
+
157
+ statistic, pvalue = stats.ttest_ind(data1, data2, equal_var=equal_var, alternative=alternative)
158
+
159
+ return TestResult(
160
+ test_name='T-Test de Dos Muestras',
161
+ statistic=statistic,
162
+ pvalue=pvalue,
163
+ alternative=alternative,
164
+ params={
165
+ 'mean1': data1.mean(), 'mean2': data2.mean(),
166
+ 'std1': data1.std(), 'std2': data2.std(),
167
+ 'n1': len(data1), 'n2': len(data2),
168
+ 'equal_var': equal_var
169
+ }
170
+ )
171
+
172
+ def t_test_paired(self, column1: str, column2: str,
173
+ alternative: Literal['two-sided', 'less', 'greater'] = 'two-sided') -> 'TestResult':
174
+ """
175
+ Prueba t pareada
176
+ """
177
+ from scipy import stats
178
+
179
+ data1 = self.data[column1].dropna()
180
+ data2 = self.data[column2].dropna()
181
+
182
+ statistic, pvalue = stats.ttest_rel(data1, data2, alternative=alternative)
183
+
184
+ return TestResult(
185
+ test_name='T-Test Pareado',
186
+ statistic=statistic,
187
+ pvalue=pvalue,
188
+ alternative=alternative,
189
+ params={'mean_diff': (data1 - data2).mean(), 'n': len(data1)}
190
+ )
191
+
192
+ def mann_whitney_test(self, column1: str, column2: str,
193
+ alternative: Literal['two-sided', 'less', 'greater'] = 'two-sided') -> 'TestResult':
194
+ """
195
+ Prueba de Mann-Whitney U (alternativa no paramétrica al t-test)
196
+
197
+ Parameters:
198
+ -----------
199
+ column1, column2 : str
200
+ Columnas a comparar
201
+ alternative : str
202
+ Hipótesis alternativa
203
+ """
204
+ from scipy import stats
205
+
206
+ data1 = self.data[column1].dropna()
207
+ data2 = self.data[column2].dropna()
208
+
209
+ statistic, pvalue = stats.mannwhitneyu(data1, data2, alternative=alternative)
210
+
211
+ return TestResult(
212
+ test_name='Mann-Whitney U Test',
213
+ statistic=statistic,
214
+ pvalue=pvalue,
215
+ alternative=alternative,
216
+ params={
217
+ 'median1': data1.median(),
218
+ 'median2': data2.median(),
219
+ 'n1': len(data1),
220
+ 'n2': len(data2)
221
+ }
222
+ )
223
+
224
+ def chi_square_test(self, column1: str, column2: str) -> 'TestResult':
225
+ """
226
+ Prueba Chi-cuadrado de independencia
227
+
228
+ Parameters:
229
+ -----------
230
+ column1, column2 : str
231
+ Variables categóricas a probar
232
+ """
233
+ from scipy import stats
234
+
235
+ contingency_table = pd.crosstab(self.data[column1], self.data[column2])
236
+ chi2, pvalue, dof, expected = stats.chi2_contingency(contingency_table)
237
+
238
+ return TestResult(
239
+ test_name='Prueba Chi-Cuadrado de Independencia',
240
+ statistic=chi2,
241
+ pvalue=pvalue,
242
+ alternative='two-sided',
243
+ params={'dof': dof, 'contingency_table': contingency_table}
244
+ )
245
+
246
+ def anova_oneway(self, column: str, groups: str) -> 'TestResult':
247
+ """
248
+ ANOVA de un factor
249
+
250
+ Parameters:
251
+ -----------
252
+ column : str
253
+ Variable dependiente (numérica)
254
+ groups : str
255
+ Variable de agrupación (categórica)
256
+ """
257
+ from scipy import stats
258
+
259
+ groups_data = [group[column].values for name, group in self.data.groupby(groups)]
260
+ statistic, pvalue = stats.f_oneway(*groups_data)
261
+
262
+ return TestResult(
263
+ test_name='ANOVA de Un Factor',
264
+ statistic=statistic,
265
+ pvalue=pvalue,
266
+ alternative='two-sided',
267
+ params={
268
+ 'groups': len(groups_data),
269
+ 'n_total': sum(len(g) for g in groups_data)
270
+ }
271
+ )
272
+
273
+ def kruskal_wallis_test(self, column: str, groups: str) -> 'TestResult':
274
+ """
275
+ Prueba de Kruskal-Wallis (ANOVA no paramétrico)
276
+
277
+ Parameters:
278
+ -----------
279
+ column : str
280
+ Variable dependiente (numérica)
281
+ groups : str
282
+ Variable de agrupación (categórica)
283
+ """
284
+ from scipy import stats
285
+
286
+ groups_data = [group[column].values for name, group in self.data.groupby(groups)]
287
+ statistic, pvalue = stats.kruskal(*groups_data)
288
+
289
+ return TestResult(
290
+ test_name='Kruskal-Wallis Test',
291
+ statistic=statistic,
292
+ pvalue=pvalue,
293
+ alternative='two-sided',
294
+ params={
295
+ 'groups': len(groups_data),
296
+ 'n_total': sum(len(g) for g in groups_data)
297
+ }
298
+ )
299
+
300
+ def normality_test(self, column: str,
301
+ method: Literal['shapiro', 'ks', 'anderson', 'jarque_bera', 'all'] = 'shapiro',
302
+ test_statistic: Literal['mean', 'median', 'mode'] = 'mean') -> Union['TestResult', dict]:
303
+ """
304
+ Prueba de normalidad con múltiples métodos y estadísticos
305
+
306
+ Parameters:
307
+ -----------
308
+ column : str
309
+ Columna a analizar
310
+ method : str
311
+ 'shapiro' (Shapiro-Wilk)
312
+ 'ks' (Kolmogorov-Smirnov)
313
+ 'anderson' (Anderson-Darling)
314
+ 'jarque_bera' (Jarque-Bera)
315
+ 'all' (ejecutar todos los tests)
316
+ test_statistic : str
317
+ 'mean', 'median' o 'mode' - estadístico para centrar la distribución
318
+
319
+ Returns:
320
+ --------
321
+ TestResult o dict
322
+ Si method='all', retorna dict con todos los resultados
323
+ """
324
+ from scipy import stats
325
+
326
+ data = self.data[column].dropna().values
327
+ n = len(data)
328
+
329
+ # Centrar los datos según el estadístico elegido
330
+ if test_statistic == 'mean':
331
+ loc = np.mean(data)
332
+ scale = np.std(data, ddof=1)
333
+ elif test_statistic == 'median':
334
+ loc = np.median(data)
335
+ # MAD (Median Absolute Deviation) como escala
336
+ scale = np.median(np.abs(data - loc)) * 1.4826
337
+ elif test_statistic == 'mode':
338
+ from scipy.stats import mode as scipy_mode
339
+ mode_result = scipy_mode(data, keepdims=True)
340
+ loc = mode_result.mode[0]
341
+ scale = np.std(data, ddof=1)
342
+ else:
343
+ raise ValueError(f"test_statistic '{test_statistic}' no reconocido")
344
+
345
+ if method == 'all':
346
+ results = {}
347
+
348
+ # Shapiro-Wilk
349
+ if n <= 5000: # Shapiro tiene límite de muestra
350
+ stat_sw, p_sw = stats.shapiro(data)
351
+ results['shapiro'] = TestResult(
352
+ test_name=f'Shapiro-Wilk ({test_statistic})',
353
+ statistic=stat_sw,
354
+ pvalue=p_sw,
355
+ alternative='two-sided',
356
+ params={'n': n, 'test_statistic': test_statistic, 'loc': loc, 'scale': scale}
357
+ )
358
+
359
+ # Kolmogorov-Smirnov
360
+ stat_ks, p_ks = stats.kstest(data, 'norm', args=(loc, scale))
361
+ results['kolmogorov_smirnov'] = TestResult(
362
+ test_name=f'Kolmogorov-Smirnov ({test_statistic})',
363
+ statistic=stat_ks,
364
+ pvalue=p_ks,
365
+ alternative='two-sided',
366
+ params={'n': n, 'test_statistic': test_statistic, 'loc': loc, 'scale': scale}
367
+ )
368
+
369
+ # Anderson-Darling
370
+ anderson_result = stats.anderson(data, dist='norm')
371
+ results['anderson_darling'] = {
372
+ 'test_name': f'Anderson-Darling ({test_statistic})',
373
+ 'statistic': anderson_result.statistic,
374
+ 'critical_values': anderson_result.critical_values,
375
+ 'significance_levels': anderson_result.significance_level,
376
+ 'params': {'n': n, 'test_statistic': test_statistic, 'loc': loc, 'scale': scale}
377
+ }
378
+
379
+ # Jarque-Bera
380
+ stat_jb, p_jb = stats.jarque_bera(data)
381
+ results['jarque_bera'] = TestResult(
382
+ test_name=f'Jarque-Bera ({test_statistic})',
383
+ statistic=stat_jb,
384
+ pvalue=p_jb,
385
+ alternative='two-sided',
386
+ params={
387
+ 'n': n,
388
+ 'test_statistic': test_statistic,
389
+ 'skewness': stats.skew(data),
390
+ 'kurtosis': stats.kurtosis(data)
391
+ }
392
+ )
393
+
394
+ return results
395
+
396
+ elif method == 'shapiro':
397
+ if n > 5000:
398
+ raise ValueError("Shapiro-Wilk requiere n <= 5000. Use otro método o 'all'")
399
+ statistic, pvalue = stats.shapiro(data)
400
+ test_name = f'Shapiro-Wilk ({test_statistic})'
401
+ params = {'n': n, 'test_statistic': test_statistic, 'loc': loc, 'scale': scale}
402
+
403
+ elif method == 'ks':
404
+ statistic, pvalue = stats.kstest(data, 'norm', args=(loc, scale))
405
+ test_name = f'Kolmogorov-Smirnov ({test_statistic})'
406
+ params = {'n': n, 'test_statistic': test_statistic, 'loc': loc, 'scale': scale}
407
+
408
+ elif method == 'anderson':
409
+ anderson_result = stats.anderson(data, dist='norm')
410
+ return {
411
+ 'test_name': f'Anderson-Darling ({test_statistic})',
412
+ 'statistic': anderson_result.statistic,
413
+ 'critical_values': anderson_result.critical_values,
414
+ 'significance_levels': anderson_result.significance_level,
415
+ 'params': {'n': n, 'test_statistic': test_statistic, 'loc': loc, 'scale': scale},
416
+ 'interpretation': self._interpret_anderson(anderson_result)
417
+ }
418
+
419
+ elif method == 'jarque_bera':
420
+ statistic, pvalue = stats.jarque_bera(data)
421
+ test_name = f'Jarque-Bera ({test_statistic})'
422
+ params = {
423
+ 'n': n,
424
+ 'test_statistic': test_statistic,
425
+ 'skewness': stats.skew(data),
426
+ 'kurtosis': stats.kurtosis(data)
427
+ }
428
+
429
+ else:
430
+ raise ValueError(f"Método '{method}' no reconocido")
431
+
432
+ return TestResult(
433
+ test_name=test_name,
434
+ statistic=statistic,
435
+ pvalue=pvalue,
436
+ alternative='two-sided',
437
+ params=params
438
+ )
439
+
440
+ def _interpret_anderson(self, anderson_result):
441
+ """Interpreta resultados de Anderson-Darling"""
442
+ interpretations = []
443
+ for i, (crit_val, sig_level) in enumerate(zip(anderson_result.critical_values,
444
+ anderson_result.significance_level)):
445
+ if anderson_result.statistic < crit_val:
446
+ interpretations.append(f"No se rechaza normalidad al {sig_level}% de significancia")
447
+ else:
448
+ interpretations.append(f"Se RECHAZA normalidad al {sig_level}% de significancia")
449
+ return interpretations
450
+
451
+ def help(self):
452
+ """
453
+ Muestra ayuda completa de la clase InferentialStats
454
+ """
455
+ help_text = """
456
+ ╔════════════════════════════════════════════════════════════════════════════╗
457
+ ║ 🔬 CLASE InferentialStats - AYUDA COMPLETA ║
458
+ ╚════════════════════════════════════════════════════════════════════════════╝
459
+
460
+ 📝 DESCRIPCIÓN:
461
+ Clase para estadística inferencial: pruebas de hipótesis, intervalos de
462
+ confianza y pruebas de normalidad. Permite realizar inferencias sobre
463
+ poblaciones a partir de muestras de datos.
464
+
465
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
466
+
467
+ 📋 MÉTODOS PRINCIPALES:
468
+
469
+ ┌────────────────────────────────────────────────────────────────────────────┐
470
+ │ 1. 📊 INTERVALOS DE CONFIANZA │
471
+ └────────────────────────────────────────────────────────────────────────────┘
472
+
473
+ • .confidence_interval(column, confidence=0.95, statistic='mean')
474
+
475
+ Calcula intervalos de confianza para diferentes estadísticos
476
+
477
+ Parámetros:
478
+ column : Columna a analizar (str)
479
+ confidence : Nivel de confianza (float, default 0.95 = 95%)
480
+ statistic : 'mean', 'median' o 'proportion'
481
+
482
+ Retorna: (lower_bound, upper_bound, point_estimate)
483
+
484
+ ┌────────────────────────────────────────────────────────────────────────────┐
485
+ │ 2. 🧪 PRUEBAS DE HIPÓTESIS - UNA MUESTRA │
486
+ └────────────────────────────────────────────────────────────────────────────┘
487
+
488
+ • .t_test_1sample(column, popmean=None, popmedian=None,
489
+ alternative='two-sided')
490
+
491
+ Prueba t de una muestra (o Wilcoxon para mediana)
492
+
493
+ Parámetros:
494
+ column : Columna a analizar
495
+ popmean : Media poblacional hipotética (para t-test)
496
+ popmedian : Mediana poblacional hipotética (para Wilcoxon)
497
+ alternative : 'two-sided', 'less', 'greater'
498
+
499
+ ┌────────────────────────────────────────────────────────────────────────────┐
500
+ │ 3. 🧪 PRUEBAS DE HIPÓTESIS - DOS MUESTRAS │
501
+ └────────────────────────────────────────────────────────────────────────────┘
502
+
503
+ 🔹 Pruebas Paramétricas:
504
+
505
+ • .t_test_2sample(column1, column2, equal_var=True,
506
+ alternative='two-sided')
507
+ Prueba t de dos muestras independientes
508
+
509
+ • .t_test_paired(column1, column2, alternative='two-sided')
510
+ Prueba t pareada (muestras dependientes)
511
+
512
+ 🔹 Pruebas No Paramétricas:
513
+
514
+ • .mann_whitney_test(column1, column2, alternative='two-sided')
515
+ Alternativa no paramétrica al t-test de dos muestras
516
+
517
+ ┌────────────────────────────────────────────────────────────────────────────┐
518
+ │ 4. 🧪 PRUEBAS PARA MÚLTIPLES GRUPOS │
519
+ └────────────────────────────────────────────────────────────────────────────┘
520
+
521
+ 🔹 Pruebas Paramétricas:
522
+
523
+ • .anova_oneway(column, groups)
524
+ ANOVA de un factor para comparar múltiples grupos
525
+
526
+ 🔹 Pruebas No Paramétricas:
527
+
528
+ • .kruskal_wallis_test(column, groups)
529
+ Alternativa no paramétrica a ANOVA
530
+
531
+ ┌────────────────────────────────────────────────────────────────────────────┐
532
+ │ 5. 🧪 PRUEBAS PARA VARIABLES CATEGÓRICAS │
533
+ └────────────────────────────────────────────────────────────────────────────┘
534
+
535
+ • .chi_square_test(column1, column2)
536
+ Prueba Chi-cuadrado de independencia entre variables categóricas
537
+
538
+ ┌────────────────────────────────────────────────────────────────────────────┐
539
+ │ 6. 📈 PRUEBAS DE NORMALIDAD │
540
+ └────────────────────────────────────────────────────────────────────────────┘
541
+
542
+ • .normality_test(column, method='shapiro', test_statistic='mean')
543
+
544
+ Prueba si los datos siguen una distribución normal
545
+
546
+ Métodos disponibles:
547
+ 'shapiro' : Shapiro-Wilk (mejor para n ≤ 5000)
548
+ 'ks' : Kolmogorov-Smirnov
549
+ 'anderson' : Anderson-Darling
550
+ 'jarque_bera' : Jarque-Bera (basado en asimetría y curtosis)
551
+ 'all' : Ejecuta todos los tests
552
+
553
+ test_statistic: 'mean', 'median' o 'mode' para centrar la distribución
554
+
555
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
556
+
557
+ 💡 EJEMPLOS DE USO:
558
+
559
+ ┌─ Ejemplo 1: Intervalos de Confianza ────────────────────────────────────┐
560
+ │ from inferential import InferentialStats │
561
+ │ import pandas as pd │
562
+ │ │
563
+ │ df = pd.read_csv('datos.csv') │
564
+ │ inf_stats = InferentialStats(df) │
565
+ │ │
566
+ │ # IC para la media (95%) │
567
+ │ lower, upper, mean = inf_stats.confidence_interval( │
568
+ │ 'salario', │
569
+ │ confidence=0.95, │
570
+ │ statistic='mean' │
571
+ │ ) │
572
+ │ print(f"IC 95%: [{lower:.2f}, {upper:.2f}]") │
573
+ │ │
574
+ │ # IC para la mediana (bootstrap) │
575
+ │ lower, upper, median = inf_stats.confidence_interval( │
576
+ │ 'edad', │
577
+ │ confidence=0.99, │
578
+ │ statistic='median' │
579
+ │ ) │
580
+ └──────────────────────────────────────────────────────────────────────────┘
581
+
582
+ ┌─ Ejemplo 2: Prueba t de Una Muestra ────────────────────────────────────┐
583
+ │ # H0: μ = 50000 (la media salarial es 50000) │
584
+ │ # H1: μ ≠ 50000 (prueba bilateral) │
585
+ │ │
586
+ │ resultado = inf_stats.t_test_1sample( │
587
+ │ column='salario', │
588
+ │ popmean=50000, │
589
+ │ alternative='two-sided' │
590
+ │ ) │
591
+ │ │
592
+ │ print(resultado) │
593
+ │ # Muestra: estadístico t, valor p, interpretación │
594
+ │ │
595
+ │ # Prueba unilateral │
596
+ │ resultado = inf_stats.t_test_1sample( │
597
+ │ column='salario', │
598
+ │ popmean=50000, │
599
+ │ alternative='greater' # H1: μ > 50000 │
600
+ │ ) │
601
+ └──────────────────────────────────────────────────────────────────────────┘
602
+
603
+ ┌─ Ejemplo 3: Comparación de Dos Grupos ──────────────────────────────────┐
604
+ │ # Prueba t independiente │
605
+ │ resultado = inf_stats.t_test_2sample( │
606
+ │ column1='salario_hombres', │
607
+ │ column2='salario_mujeres', │
608
+ │ equal_var=True, │
609
+ │ alternative='two-sided' │
610
+ │ ) │
611
+ │ print(resultado) │
612
+ │ │
613
+ │ # Prueba Mann-Whitney (no paramétrica) │
614
+ │ resultado = inf_stats.mann_whitney_test( │
615
+ │ column1='salario_grupo_a', │
616
+ │ column2='salario_grupo_b', │
617
+ │ alternative='two-sided' │
618
+ │ ) │
619
+ │ │
620
+ │ # Prueba t pareada (mediciones antes/después) │
621
+ │ resultado = inf_stats.t_test_paired( │
622
+ │ column1='peso_antes', │
623
+ │ column2='peso_despues', │
624
+ │ alternative='two-sided' │
625
+ │ ) │
626
+ └──────────────────────────────────────────────────────────────────────────┘
627
+
628
+ ┌─ Ejemplo 4: ANOVA y Kruskal-Wallis ─────────────────────────────────────┐
629
+ │ # ANOVA para comparar múltiples grupos │
630
+ │ resultado = inf_stats.anova_oneway( │
631
+ │ column='rendimiento', │
632
+ │ groups='departamento' │
633
+ │ ) │
634
+ │ print(resultado) │
635
+ │ │
636
+ │ # Kruskal-Wallis (alternativa no paramétrica) │
637
+ │ resultado = inf_stats.kruskal_wallis_test( │
638
+ │ column='satisfaccion', │
639
+ │ groups='categoria' │
640
+ │ ) │
641
+ └──────────────────────────────────────────────────────────────────────────┘
642
+
643
+ ┌─ Ejemplo 5: Chi-Cuadrado ───────────────────────────────────────────────┐
644
+ │ # Probar independencia entre variables categóricas │
645
+ │ resultado = inf_stats.chi_square_test( │
646
+ │ column1='genero', │
647
+ │ column2='preferencia_producto' │
648
+ │ ) │
649
+ │ print(resultado) │
650
+ │ │
651
+ │ # El resultado incluye la tabla de contingencia │
652
+ └──────────────────────────────────────────────────────────────────────────┘
653
+
654
+ ┌─ Ejemplo 6: Pruebas de Normalidad ──────────────────────────────────────┐
655
+ │ # Shapiro-Wilk (recomendado para n ≤ 5000) │
656
+ │ resultado = inf_stats.normality_test( │
657
+ │ column='edad', │
658
+ │ method='shapiro', │
659
+ │ test_statistic='mean' │
660
+ │ ) │
661
+ │ print(resultado) │
662
+ │ │
663
+ │ # Kolmogorov-Smirnov │
664
+ │ resultado = inf_stats.normality_test( │
665
+ │ column='salario', │
666
+ │ method='ks' │
667
+ │ ) │
668
+ │ │
669
+ │ # Ejecutar todos los tests │
670
+ │ resultados = inf_stats.normality_test( │
671
+ │ column='ingresos', │
672
+ │ method='all', │
673
+ │ test_statistic='median' │
674
+ │ ) │
675
+ │ │
676
+ │ # Acceder a cada test │
677
+ │ print(resultados['shapiro']) │
678
+ │ print(resultados['kolmogorov_smirnov']) │
679
+ │ print(resultados['anderson_darling']) │
680
+ │ print(resultados['jarque_bera']) │
681
+ └──────────────────────────────────────────────────────────────────────────┘
682
+
683
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
684
+
685
+ 📊 GUÍA DE SELECCIÓN DE PRUEBAS:
686
+
687
+ ┌─ Comparar Una Muestra vs Valor de Referencia ───────────────────────────┐
688
+ │ Datos normales → t_test_1sample (con popmean) │
689
+ │ Datos no normales → t_test_1sample (con popmedian, usa Wilcoxon) │
690
+ └──────────────────────────────────────────────────────────────────────────┘
691
+
692
+ ┌─ Comparar Dos Grupos Independientes ────────────────────────────────────┐
693
+ │ Datos normales → t_test_2sample │
694
+ │ Datos no normales → mann_whitney_test │
695
+ └──────────────────────────────────────────────────────────────────────────┘
696
+
697
+ ┌─ Comparar Dos Grupos Pareados ──────────────────────────────────────────┐
698
+ │ Datos normales → t_test_paired │
699
+ │ Datos no normales → (use scipy.stats.wilcoxon directamente) │
700
+ └──────────────────────────────────────────────────────────────────────────┘
701
+
702
+ ┌─ Comparar Múltiples Grupos ─────────────────────────────────────────────┐
703
+ │ Datos normales → anova_oneway │
704
+ │ Datos no normales → kruskal_wallis_test │
705
+ └──────────────────────────────────────────────────────────────────────────┘
706
+
707
+ ┌─ Probar Independencia entre Categóricas ────────────────────────────────┐
708
+ │ Variables categóricas → chi_square_test │
709
+ └──────────────────────────────────────────────────────────────────────────┘
710
+
711
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
712
+
713
+ 🎯 CARACTERÍSTICAS CLAVE:
714
+
715
+ ✓ Pruebas paramétricas y no paramétricas
716
+ ✓ Intervalos de confianza con múltiples métodos
717
+ ✓ Pruebas de normalidad completas
718
+ ✓ Interpretación automática de resultados
719
+ ✓ Manejo automático de valores faltantes
720
+ ✓ Salidas formateadas profesionales
721
+ ✓ Soporte para análisis bilateral y unilateral
722
+
723
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
724
+
725
+ ⚠️ INTERPRETACIÓN DE RESULTADOS:
726
+
727
+ • Valor p < 0.05: Se rechaza H0 (evidencia significativa)
728
+ • Valor p ≥ 0.05: No se rechaza H0 (evidencia insuficiente)
729
+ • IC que no incluye el valor nulo: Evidencia contra H0
730
+
731
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
732
+
733
+ 📚 DOCUMENTACIÓN ADICIONAL:
734
+ Para más información sobre métodos específicos, use:
735
+ help(InferentialStats.nombre_metodo)
736
+
737
+ ╚════════════════════════════════════════════════════════════════════════════╝
738
+ """
739
+ print(help_text)
740
+
741
+ class TestResult:
742
+ """Clase para resultados de pruebas de hipótesis"""
743
+
744
+ def __init__(self, test_name: str, statistic: float, pvalue: float,
745
+ alternative: str, params: dict):
746
+ self.test_name = test_name
747
+ self.statistic = statistic
748
+ self.pvalue = pvalue
749
+ self.alternative = alternative
750
+ self.params = params
751
+
752
+ def __repr__(self):
753
+ return self._format_output()
754
+
755
+ def _format_output(self):
756
+ """Formato de salida para pruebas de hipótesis"""
757
+ output = []
758
+ output.append("=" * 80)
759
+ output.append(self.test_name.center(80))
760
+ output.append("=" * 80)
761
+ output.append(f"Fecha: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
762
+ output.append(f"Hipótesis Alternativa: {self.alternative}")
763
+ output.append("-" * 80)
764
+
765
+ output.append("\nRESULTADOS:")
766
+ output.append("-" * 80)
767
+ output.append(f"{'Estadístico':<40} {self.statistic:>20.6f}")
768
+ output.append(f"{'Valor p':<40} {self.pvalue:>20.6e}")
769
+
770
+ # Interpretación
771
+ alpha = 0.05
772
+ if self.pvalue < alpha:
773
+ interpretation = "❌ Se RECHAZA la hipótesis nula"
774
+ else:
775
+ interpretation = "✔️ No hay evidencia suficiente para rechazar la hipótesis nula"
776
+
777
+ output.append("\nINTERPRETACIÓN:")
778
+ output.append("-" * 80)
779
+ output.append(f"Alpha = {alpha}")
780
+ output.append(interpretation)
781
+
782
+ output.append("\nPARÁMETROS:")
783
+ output.append("-" * 80)
784
+ for k, v in self.params.items():
785
+ output.append(f"{k:<40} {str(v):>20}")
786
+
787
+ output.append("=" * 80)
788
+ return "\n".join(output)