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