Qué contiene este artículo
Un ajuste completo, desde la muestra en bruto hasta la conclusión, sin nada oculto. Todos los números que aparecen abajo los produjo el script que cierra esta página, ejecutado con Phitter 1.0.4, NumPy 2.4 y Python 3.12. Al ejecutarlo se obtienen los mismos valores: la muestra tiene semilla fija.
El ejemplo está construido de forma deliberada para que la respuesta correcta se conozca de antemano. Eso permite comprobar no solo si el ajuste funciona, sino si el ranking que produce significa lo que sugiere una primera lectura.
Paso 1 — el conjunto de datos
La muestra representa tiempos de reparación: 500 observaciones, estrictamente positivas y con sesgo a la derecha. Proviene de una distribución lognormal con mean=1.2 y sigma=0.55, de modo que la respuesta verdadera es conocida.
import numpy as np
rng = np.random.default_rng(42)
data = rng.lognormal(mean=1.2, sigma=0.55, size=500)
np.savetxt("repair_times.csv", data, fmt="%.4f",
header="repair_time_hours", comments="")
El archivo repair_times.csv resultante es una sola columna con encabezado, que es el formato que Phitter Web acepta directamente.
Paso 2 — mirar antes de ajustar
| Medida | Valor |
|---|---|
| n | 500 |
| Media | 3.7963 |
| Desviación estándar | 2.2013 |
| Mínimo | 0.8092 |
| Mediana | 3.3260 |
| Máximo | 16.4879 |
| Asimetría | 1.9242 |
Esta tabla ya decide tres cosas. Los datos son continuos y estrictamente positivos, así que cualquier candidata cuyo soporte incluya valores negativos queda descartada sin importar su puntuación. La asimetría de 1.92 elimina los modelos simétricos. Y la media queda muy por encima de la mediana, la firma de una cola derecha que dominará cualquier simulación construida sobre esta muestra.
Paso 3 — el ajuste
import phitter
phi = phitter.Phitter(data=data, fit_type="continuous",
num_bins=20, confidence_level=0.95)
phi.fit(n_workers=4)
Una nota práctica para quien trabaje en Windows: un n_workers mayor que 1 abre un pool de procesos, y en Windows los procesos hijos vuelven a importar el módulo que los llamó. Si el ajuste se ejecuta a nivel de módulo, el pool muere con BrokenProcessPool. Conviene envolver la llamada en if __name__ == "__main__": — o usar n_workers=1.
Phitter ajustó 63 distribuciones continuas a esta muestra.
Paso 4 — el ranking
Estas son las primeras filas de phi.df_sorted_distributions, sin editar:
| Distribución | AIC | BIC | KS | KS p | AD | AD p | χ² p |
|---|---|---|---|---|---|---|---|
| inverse_gaussian | 1976.32 | 1984.75 | 0.0352 | 0.5541 | 0.5356 | 0.7107 | 0.0832 |
| lognormal | 1976.43 | 1984.86 | 0.0305 | 0.7273 | 0.3296 | 0.9143 | 0.1678 |
| inverse_gamma_3p | 1978.62 | 1991.27 | 0.0281 | 0.8149 | 0.3105 | 0.9301 | 0.3054 |
| beta_prime | 1983.41 | 1991.84 | 0.0418 | 0.3373 | 1.3122 | 0.2284 | 0.0619 |
| frechet | 1980.44 | 1993.08 | 0.0299 | 0.7527 | 0.3453 | 0.9006 | 0.3309 |
| burr_4p | 1976.44 | 1993.30 | 0.0266 | 0.8612 | 0.2124 | 0.9866 | 0.1856 |
Los datos se generaron a partir de una lognormal. La lognormal quedó segunda.
Esta es la parte del ejemplo que merece detenimiento. El modelo mejor clasificado no es incorrecto, pero tampoco es la respuesta. La diferencia de AIC entre el primer y el segundo puesto es de 0.11, sobre valores cercanos a 1976. Eso no es una diferencia: es ruido. Y en los tres estadísticos de bondad de ajuste, el segundo clasificado obtiene un resultado mejor que el primero.
El orden tampoco es un error. Phitter ordena por criterios de información, que contraponen ajuste y número de parámetros; los estadísticos de prueba miden otra cosa. Leer la tabla como una clasificación deportiva oculta esa distinción. Interpretar un ranking correctamente retoma este problema en detalle.
Paso 5 — revisar los parámetros
phi.sorted_distributions["lognormal"]["parameters"]
# {'mu': 1.1891, 'sigma': 0.5384}
Los parámetros generadores fueron mu=1.2 y sigma=0.55. La estimación los recuperó con un error cercano al 1% y al 2%. Esta es la comprobación más importante y la que más se omite: un modelo cuyos parámetros resultan inverosímiles para el proceso que se describe no se salva por tener una buena puntuación.
Paso 6 — revisar los diagnósticos, no solo los números
Veinte de las 63 distribuciones ajustadas no fueron rechazadas por ninguna de las tres pruebas al nivel del 95%. Ese es el dato más útil de toda la ejecución. «Pasa las pruebas» no es aquí una propiedad distintiva: es el caso habitual. Con n = 500, las pruebas simplemente carecen de potencia para separar modelos que solo difieren en sus colas.
Los gráficos hacen un trabajo que los estadísticos no pueden hacer:
phi.plot_histogram_distributions() # concordancia de forma en el cuerpo
phi.qq_plot_regression() # dónde se desvía el modelo y en qué dirección
phi.plot_ecdf_distribution() # concordancia acumulada
El gráfico Q–Q es el que conviene leer con atención. Muestra dónde falla un modelo, algo que los estadísticos de un solo número no pueden indicar.
Qué conviene registrar
Para que un ajuste sea reproducible seis meses después hay que conservar: la versión de Phitter, la muestra original o la semilla que la genera, fit_type, num_bins, confidence_level, la lista de candidatas si se restringió, y la tabla completa del ranking en lugar de solo la ganadora. num_bins merece atención especial: modifica el resultado de chi-cuadrado, y chi-cuadrado es la única de las tres pruebas que depende de él.
El script completo
import numpy as np
import phitter
def main():
rng = np.random.default_rng(42)
data = rng.lognormal(mean=1.2, sigma=0.55, size=500)
np.savetxt("repair_times.csv", data, fmt="%.4f",
header="repair_time_hours", comments="")
phi = phitter.Phitter(data=data, fit_type="continuous",
num_bins=20, confidence_level=0.95)
phi.fit(n_workers=4)
print(phi.best_distribution)
print(phi.df_sorted_distributions.head(10))
print(len(phi.df_not_rejected_distributions), "no rechazadas")
if __name__ == "__main__":
main()
Para ejecutar el mismo análisis sin instalar nada, basta con pegar el CSV en Phitter Web.