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.
- {statslib → statslibx}/__init__.py +5 -4
- {statslib → statslibx}/descriptive.py +182 -31
- statslibx/inferential.py +974 -0
- statslibx/utils.py +1180 -0
- {statslibx-0.1.0.dist-info → statslibx-0.1.2.dist-info}/METADATA +34 -3
- statslibx-0.1.2.dist-info/RECORD +8 -0
- statslibx-0.1.2.dist-info/top_level.txt +1 -0
- statslib/inferential.py +0 -547
- statslib/utils.py +0 -889
- statslibx-0.1.0.dist-info/RECORD +0 -8
- statslibx-0.1.0.dist-info/top_level.txt +0 -1
- {statslibx-0.1.0.dist-info → statslibx-0.1.2.dist-info}/WHEEL +0 -0
statslibx/inferential.py
ADDED
|
@@ -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)
|