Identificar el tipo de distribución que tiene a una variable es un paso fundamental en prácticamente todos los estudios que implican datos, desde los contrastes de hipótesis hasta la creación de modelos por aprendizaje estadístico y machine learning.
En Python existen varias librerías que permiten ajustar distribuciones. En este documento se muestran las funcionalidades del módulo scipy.stats, haciendo hincapié en cómo comparar múltiples distribuciones con el objetivo de identificar a cuál de ellas se ajustan mejor los datos.
Ajustar una distribución paramétrica a partir de un conjunto de datos consiste en encontrar el valor de los parámetros con los que, con mayor probabilidad, dicha distribución puede haber generado los datos observados. Por ejemplo, la distribución normal tiene dos parámetros (media y varianza), una vez conocidos estos dos parámetros, se conoce toda la distribución.
Existen varios métodos que permiten encontrar los parámetros óptimos que mejor se ajustan a los datos, uno de los más utilizados, y el que implementa scipy.stats, es el método de Maximum Likelihood Estimation (MLE) (máxima verosimilitud). scipy.stats dispone de más de 90 distribuciones, puede encontrarse un listado de todas ellas en:
from scipy import stats
import pandas as pd
Además de diferenciar entre distribuciones continuas y discretas, es útil poder seleccionarlas por el rango de valores sobre el que está definida cada distribución (dominio). Por ejemplo, si se quiere modelar la velocidad del viento, aunque no se conozca el tipo exacto de distribución, se puede acotar a aquellas cuyo rango de valores está limitado entre $0$ y $+\inf$.
# Distribuciones agrupadas por dominio
# ==============================================================================
distribuciones = [getattr(stats,d) for d in dir(stats) \
if isinstance(getattr(stats,d), (stats.rv_continuous, stats.rv_discrete))]
distribucion = []
dominio_1 = []
dominio_2 = []
for dist in distribuciones:
distribucion.append(dist.name)
dominio_1.append(dist.a)
dominio_2.append(dist.b)
info_distribuciones = pd.DataFrame({
'distribucion': distribucion,
'dominio_1': dominio_1,
'dominio_2': dominio_2
})
info_distribuciones = info_distribuciones \
.sort_values(by=['dominio_1', 'dominio_2'])\
.reset_index(drop=True)
print("-------------------------------------")
print("Información distribuciones scipy.stat")
print("-------------------------------------")
display(info_distribuciones)
El método de ajuste por Maximum Likelihood Estimation (MLE) consigue, dada una determinada distribución, encontrar el valor sus parámetros con los que mejor se ajusta a los datos. En la práctica, es frecuente no conocer de antemano qué tipo de distribución siguen los datos. Aunque con la experiencia, el analista suele poder acotar las distribuciones candidatas (distribuciones continuas o discretas, distribuciones solo positivas, etc), es necesaria una forma de cuantificar la bondad de ajuste de cada distribución y, además, poder comparar entre distintas distribuciones candidatas.
Un libro en el que se explican las características de las principales distribuciones probabilísticas es Distributions for Modeling Location, Scale, and Shape Using GAMLSS in R By Robert A. Rigby, Mikis D. Stasinopoulos, Gillian Z. Heller, Fernanda De Bastiani.
Generalized Akaike information criterion (GAIC)
En distribuciones paramétricas que han sido ajustadas siguiendo una estrategia de Maximum Likelihood Estimation (MLE), una forma de cuantificar la bondad de ajuste del modelo, es decir, lo bien que se ajusta a los datos, es empleando el propio valor de likelihood conseguido en el ajuste. ¿Qué significa esto?
El valor de likelihood devuelto por una distribución para una observación $x_i$ es una medida de la probabilidad con la que esa distribución podría haber generado dicha observación. Para facilitar los cálculos matemáticos, normalmente se utiliza el logaritmo ( log likelihood), pero la interpretación es la misma, a mayor log likelihood mayor probabilidad.
Si se suma el log likelihood de todas las observaciones con las que se ha ajustado la distribución, se dispone de un valor que mide la "compatibilidad" entre la distribución y los datos. Siguiendo esta idea es como se define el término fitted global deviance (GDEV), que no es más que el log likelihood del modelo multiplicado por $-2$.
$$\text{GDEV} = − 2 \log(likelihood)$$El problema de emplear el GDEV para comparar distribuciones es que no tiene en cuenta los grados de libertad de cada una (su flexibilidad). En términos generales, cuantos más parámetros tenga una distribución, con más facilidad se ajusta a los datos y mayor es su log likelihood. Esto significa que utilizar el GDEV o el log likelihood para comparar distribuciones con distinto número de parámetros no es una estrategia justa, casi siempre ganará la que más parámetros tenga aunque no sea realmente la que mejor describe el comportamiento de los datos.
Una forma de mitigar este problema es mediante el uso del GAIC (generalized Akaike information criterion), que incorpora una penalización $k$ por cada parámetro que tenga la distribución:
$$\text{GAIC} = \text{GDEV} + k \times \text{nº parámetros}= $$$$= −2 \log(likelihood) + k \times \text{nº parámetros}$$
Dependiendo del grado de penalización, se favorece más o menos la sencillez del modelo. Dos estándares de esta métrica son el AIC (Criterio de información de Akaike) y BIC (Bayesian information criterion) también conocida como SBC. El primero utiliza como valor de penalización $k=2$ y el segundo $k=\log(\text{nº observaciones)}$.
$$\text{AIC} = −2 \log(\text{likelihood}) + 2 \times \text{nº parámetros}$$$$\text{BIC} = −2 \log(\text{likelihood}) + log (\text{nº observaciones}) \times \text{nº parámetros}$$En la práctica, AIC suele favorecer distribuciones con un más parámetros (overfitting), mientras BIC/SBC tiende al contrario underfitting. Los autores del paquete del libro Distributions for Modeling Location, Scale, and Shape Using GAMLSS in R recomiendan el uso de valores de $k$ en el rango $2.5 \leq k \leq 4$ o de $k = \sqrt{\log(n)}$ cuando $n \geq 1000$.
Para todas ellas, cuanto menor sea el valor, mejor el ajuste. El cambio en la dirección de selección respecto al log likelihood se debe a que en ambas métricas se multiplica por -2, por lo tanto, si interesan valores elevados de log likelihood, al multiplicar por -2, interesan valores muy negativos.
Es importante tener en cuenta que, ninguna de estas métricas, sirven para cuantificar la calidad del ajuste en un sentido absoluto, sino para comparar la calidad relativa entre distribuciones. Si todas las distribuciones ajustadas son malas, no proporcionan ningún aviso de ello.
En este ejemplo se procede a ajustar dos distribuciones, normal y gamma, con el objetivo de modelizar la distribución del precio de venta de diamantes. Además de realizar los ajustes, se representan gráficamente los resultados y se calculan las métricas de bondad de ajuste AIC, BIC y Log-Likelihood con el objetivo de comparar e identificar el mejor que distribución se ajusta mejor.
# Tratamiento de datos
# ==============================================================================
import pandas as pd
import numpy as np
import seaborn as sns
# Gráficos
# ==============================================================================
import matplotlib.pyplot as plt
from matplotlib import style
# Ajuste de distribuciones
# ==============================================================================
from scipy import stats
import inspect
from statsmodels.distributions.empirical_distribution import ECDF
# Configuración matplotlib
# ==============================================================================
#plt.rcParams['image.cmap'] = "bwr"
#plt.rcParams['figure.dpi'] = "100"
plt.rcParams['savefig.bbox'] = "tight"
style.use('ggplot') or plt.style.use('ggplot')
# Configuración warnings
# ==============================================================================
import warnings
warnings.filterwarnings('ignore')
Para esta demostración se emplean como datos el precio de los diamantes disponible en data set diamonds de la librería seaborn
, en concreto, la columna price.
# Datos
# ==============================================================================
datos = sns.load_dataset('diamonds')
datos = datos.loc[datos.cut == 'Fair', 'price']
Dos de los primeros pasos a la hora de analizar una variable son: calcular los principales estadísticos descriptivos y representar las distribuciones observadas (empíricas).
Si los datos se almacenan en un Serie
de Pandas
, pueden obtenerse los principales estadísticos descriptivos con el método describe()
.
# Estadísticos descriptivos
# ==============================================================================
datos.describe()
# Gráficos distribución observada (empírica)
# ==============================================================================
fig, axs = plt.subplots(nrows=1, ncols=2, figsize=(10, 4))
# Histograma
axs[0].hist(x=datos, bins=30, color="#3182bd", alpha=0.5)
axs[0].plot(datos, np.full_like(datos, -0.01), '|k', markeredgewidth=1)
axs[0].set_title('Distribución empírica del precio diamantes')
axs[0].set_xlabel('precio')
axs[0].set_ylabel('counts')
# Función de Distribución Acumulada
# ecdf (empirical cumulative distribution function)
ecdf = ECDF(x=datos)
axs[1].plot(ecdf.x, ecdf.y, color="#3182bd")
axs[1].set_title('Función de distribución empírica')
axs[1].set_xlabel('precio')
axs[1].set_ylabel('CDF')
plt.tight_layout();
# Ajuste distribución normal
#===============================================================================
# 1) Se define el tipo de distribución
distribucion = stats.norm
# 2) Con el método fit() se obtienen los parámetros
parametros = distribucion.fit(data=datos)
# 3) Se crea un diccionario que incluya el nombre de cada parámetro
nombre_parametros = [p for p in inspect.signature(distribucion._pdf).parameters \
if not p=='x'] + ["loc","scale"]
parametros_dict = dict(zip(nombre_parametros, parametros))
# 3) Se calcula el log likelihood
log_likelihood = distribucion.logpdf(datos.to_numpy(), *parametros).sum()
# 4) Se calcula el AIC y el BIC
aic = -2 * log_likelihood + 2 * len(parametros)
bic = -2 * log_likelihood + np.log(datos.shape[0]) * len(parametros)
# 5) Gráfico
x_hat = np.linspace(min(datos), max(datos), num=100)
y_hat = distribucion.pdf(x_hat, *parametros)
fig, ax = plt.subplots(figsize=(7,4))
ax.plot(x_hat, y_hat, linewidth=2, label=distribucion.name)
ax.hist(x=datos, density=True, bins=30, color="#3182bd", alpha=0.5)
ax.plot(datos, np.full_like(datos, -0.01), '|k', markeredgewidth=1)
ax.set_title('Distribución precio diamantes')
ax.set_xlabel('precio')
ax.set_ylabel('Densidad de probabilidad')
ax.legend();
#6) Información del ajuste
print('---------------------')
print('Resultados del ajuste')
print('---------------------')
print(f"Distribución: {distribucion.name}")
print(f"Dominio: {[distribucion.a, distribucion.b]}")
print(f"Parámetros: {parametros_dict}")
print(f"Log likelihood: {log_likelihood}")
print(f"AIC: {aic}")
print(f"BIC: {bic}")
Se repite el proceso, pero esta vez con la distribución gamma.
# Ajuste distribución normal
#===============================================================================
# 1) Se define el tipo de distribución
distribucion = stats.gamma
# 2) Con el método fit() se obtienen los parámetros
parametros = distribucion.fit(data=datos)
# 3) Se crea un diccionario que incluya el nombre de cada parámetro
nombre_parametros = [p for p in inspect.signature(distribucion._pdf).parameters \
if not p=='x'] + ["loc","scale"]
parametros_dict = dict(zip(nombre_parametros, parametros))
# 3) Se calcula el log likelihood
log_likelihood = distribucion.logpdf(datos.to_numpy(), *parametros).sum()
# 4) Se calcula el AIC y el BIC
aic = -2 * log_likelihood + 2 * len(parametros)
bic = -2 * log_likelihood + np.log(datos.shape[0]) * len(parametros)
# 5) Gráfico
x_hat = np.linspace(min(datos), max(datos), num=100)
y_hat = distribucion.pdf(x_hat, *parametros)
fig, ax = plt.subplots(figsize=(7,4))
ax.plot(x_hat, y_hat, linewidth=2, label=distribucion.name)
ax.hist(x=datos, density=True, bins=30, color="#3182bd", alpha=0.5)
ax.plot(datos, np.full_like(datos, -0.01), '|k', markeredgewidth=1)
ax.set_title('Distribución precio diamantes')
ax.set_xlabel('precio')
ax.set_ylabel('Densidad de probabilidad')
ax.legend();
#6) Información del ajuste
print('---------------------')
print('Resultados del ajuste')
print('---------------------')
print(f"Distribución: {distribucion.name}")
print(f"Dominio: {[distribucion.a, distribucion.b]}")
print(f"Parámetros: {parametros_dict}")
print(f"Log likelihood: {log_likelihood}")
print(f"AIC: {aic}")
print(f"BIC: {bic}")
Tanto la métrica AIC como la BIC coinciden en que la distribución gamma se ajusta mejor a los datos (valores de AIC y BIC más bajos). Esto se puede corroborar fácilmente con la inspección gráfica de los resultados.
En este caso, dado que los valores de precio solo pueden ser positivos y se tienen una notable cola derecha, la distribución gamma era una candidata mucho mejor que la normal. Sin embargo, hay otras posibles distribuciones, algunas de las cuales podrían ser mejores. En el siguiente ejemplo, se muestra cómo automatizar la búsqueda.
En el siguiente ejemplo se muestra cómo automatizar el ajuste y comparación de las múltiples distribuciones disponibles es scipy.stats. El código ha de permitir:
Ajustar todas las distribuciones disponibles en scipy.stats.
Poder preseleccionar un subconjunto de distribuciones candidatas en función de su dominio.
Mostrar los parámetros de cada ajuste.
Calcular los valores AIC y BIC para poder seleccionar la distribución con mejor ajuste.
Representación gráfica de los resultados
Funciones empleadas para comparar múltiples distribuciones.
from scipy import stats
import pandas as pd
import numpy as np
import tqdm
import inspect
import warnings
warnings.filterwarnings('ignore')
def seleccionar_distribuciones(familia='realall', verbose=True):
'''
Esta función selecciona un subconjunto de las distribuciones disponibles
en scipy.stats
Parameters
----------
familia : {'realall', 'realline', 'realplus', 'real0to1', 'discreta'}
realall: distribuciones de la familia `realline` + `realplus`
realline: distribuciones continuas en el dominio (-inf, +inf)
realplus: distribuciones continuas en el dominio [0, +inf)
real0to1: distribuciones continuas en el dominio [0,1]
discreta: distribuciones discretas
verbose : bool
Si se muestra información de las distribuciones seleccionadas
(the default `True`).
Returns
-------
distribuciones: list
listado con las distribuciones (los objetos) seleccionados.
Raises
------
Exception
Si `familia` es distinto de 'realall', 'realline', 'realplus', 'real0to1',
o 'discreta'.
Notes
-----
Las distribuciones levy_stable y vonmises han sido excluidas por el momento.
'''
distribuciones = [getattr(stats,d) for d in dir(stats) \
if isinstance(getattr(stats,d), (stats.rv_continuous, stats.rv_discrete))]
exclusiones = ['levy_stable', 'vonmises']
distribuciones = [dist for dist in distribuciones if dist.name not in exclusiones]
dominios = {
'realall' : [-np.inf, np.inf],
'realline': [np.inf,np.inf],
'realplus': [0, np.inf],
'real0to1': [0, 1],
'discreta': [None, None],
}
distribucion = []
tipo = []
dominio_inf = []
dominio_sup = []
for dist in distribuciones:
distribucion.append(dist.name)
tipo.append(np.where(isinstance(dist, stats.rv_continuous), 'continua', 'discreta'))
dominio_inf.append(dist.a)
dominio_sup.append(dist.b)
info_distribuciones = pd.DataFrame({
'distribucion': distribucion,
'tipo': tipo,
'dominio_inf': dominio_inf,
'dominio_sup': dominio_sup
})
info_distribuciones = info_distribuciones \
.sort_values(by=['dominio_inf', 'dominio_sup'])\
.reset_index(drop=True)
if familia in ['realall', 'realline', 'realplus', 'real0to1']:
info_distribuciones = info_distribuciones[info_distribuciones['tipo']=='continua']
condicion = (info_distribuciones['dominio_inf'] == dominios[familia][0]) & \
(info_distribuciones['dominio_sup'] == dominios[familia][1])
info_distribuciones = info_distribuciones[condicion].reset_index(drop=True)
if familia in ['discreta']:
info_distribuciones = info_distribuciones[info_distribuciones['tipo']=='discreta']
seleccion = [dist for dist in distribuciones \
if dist.name in info_distribuciones['distribucion'].values]
if verbose:
print("---------------------------------------------------")
print(" Distribuciones seleccionadas ")
print("---------------------------------------------------")
with pd.option_context('display.max_rows', None, 'display.max_columns', None):
print(info_distribuciones)
return seleccion
def comparar_distribuciones(x, familia='realall', ordenar='aic', verbose=True):
'''
Esta función selecciona y ajusta un subconjunto de las distribuciones
disponibles en scipy.stats. Para cada distribución calcula los valores de
Log Likelihood, AIC y BIC.
Parameters
----------
x : array_like
datos con los que ajustar la distribución.
familia : {'realall', 'realline', 'realplus', 'real0to1', 'discreta'}
realall: distribuciones de la familia `realline` + `realplus`
realline: distribuciones continuas en el dominio (-inf, +inf)
realplus: distribuciones continuas en el dominio [0, +inf)
real0to1: distribuciones continuas en el dominio [0,1]
discreta: distribuciones discretas
ordenar : {'aic', 'bic'}
criterio de ordenación de mejor a peor ajuste.
verbose : bool
Si se muestra información de las distribuciones seleccionadas
(the default `True`).
Returns
-------
resultados: data.frame
distribucion: nombre de la distribución.
log_likelihood: logaritmo del likelihood del ajuste.
aic: métrica AIC.
bic: métrica BIC.
n_parametros: número de parámetros de la distribución de la distribución.
parametros: parámetros del tras el ajuste
Raises
------
Exception
Si `familia` es distinto de 'realall', 'realline', 'realplus', 'real0to1',
o 'discreta'.
Notes
-----
'''
distribuciones = seleccionar_distribuciones(familia=familia, verbose=verbose)
distribucion_ = []
log_likelihood_= []
aic_ = []
bic_ = []
n_parametros_ = []
parametros_ = []
for i, distribucion in enumerate(distribuciones):
print(f"{i+1}/{len(distribuciones)} Ajustando distribución: {distribucion.name}")
try:
parametros = distribucion.fit(data=x)
nombre_parametros = [p for p in inspect.signature(distribucion._pdf).parameters \
if not p=='x'] + ["loc","scale"]
parametros_dict = dict(zip(nombre_parametros, parametros))
log_likelihood = distribucion.logpdf(x, *parametros).sum()
aic = -2 * log_likelihood + 2 * len(parametros)
bic = -2 * log_likelihood + np.log(x.shape[0]) * len(parametros)
distribucion_.append(distribucion.name)
log_likelihood_.append(log_likelihood)
aic_.append(aic)
bic_.append(bic)
n_parametros_.append(len(parametros))
parametros_.append(parametros_dict)
resultados = pd.DataFrame({
'distribucion': distribucion_,
'log_likelihood': log_likelihood_,
'aic': aic_,
'bic': bic_,
'n_parametros': n_parametros_,
'parametros': parametros_,
})
resultados = resultados.sort_values(by=ordenar).reset_index(drop=True)
except Exception as e:
print(f"Error al tratar de ajustar la distribución {distribucion.name}")
print(e)
print("")
return resultados
De nuevo se emplean como datos el precio de los diamantes disponible en data set diamonds de la librería seaborn
, en concreto, la columna price.
# Datos
# ==============================================================================
datos = sns.load_dataset('diamonds')
datos = datos.loc[datos.cut == 'Fair', 'price']
# Ajuste y comparación de distribuciones
# ==============================================================================
resultados = comparar_distribuciones(
x=datos.to_numpy(),
familia='realall',
ordenar='aic',
verbose=False
)
resultados
def plot_distribucion(x, nombre_distribucion, ax=None):
'''
Esta función superpone la curva de densidad de una distribución con el
histograma de los datos.
Parameters
----------
x : array_like
datos con los que ajustar la distribución.
nombre_distribuciones : str
nombre de una de las distribuciones disponibles en `scipy.stats`.
Returns
-------
resultados: matplotlib.ax
gráfico creado
Raises
------
Notes
-----
'''
distribucion = getattr(stats, nombre_distribucion)
parametros = distribucion.fit(data=x)
nombre_parametros = [p for p in inspect.signature(distribucion._pdf).parameters \
if not p=='x'] + ["loc","scale"]
parametros_dict = dict(zip(nombre_parametros, parametros))
log_likelihood = distribucion.logpdf(x, *parametros).sum()
aic = -2 * log_likelihood + 2 * len(parametros)
bic = -2 * log_likelihood + np.log(x.shape[0]) * len(parametros)
x_hat = np.linspace(min(x), max(x), num=100)
y_hat = distribucion.pdf(x_hat, *parametros)
if ax is None:
fig, ax = plt.subplots(figsize=(7,4))
ax.plot(x_hat, y_hat, linewidth=2, label=distribucion.name)
ax.hist(x=x, density=True, bins=30, color="#3182bd", alpha=0.5);
ax.plot(x, np.full_like(x, -0.01), '|k', markeredgewidth=1)
ax.set_title('Ajuste distribución')
ax.set_xlabel('x')
ax.set_ylabel('Densidad de probabilidad')
ax.legend();
print('---------------------')
print('Resultados del ajuste')
print('---------------------')
print(f"Distribución: {distribucion.name}")
print(f"Dominio: {[distribucion.a, distribucion.b]}")
print(f"Parámetros: {parametros_dict}")
print(f"Log likelihood: {log_likelihood}")
print(f"AIC: {aic}")
print(f"BIC: {bic}")
return ax
def plot_multiple_distribuciones(x, nombre_distribuciones, ax=None):
'''
Esta función superpone las curvas de densidad de varias distribuciones
con el histograma de los datos.
Parameters
----------
x : array_like
datos con los que ajustar la distribución.
nombre_distribuciones : list
lista con nombres de distribuciones disponibles en `scipy.stats`.
Returns
-------
resultados: matplotlib.ax
gráfico creado
Raises
------
Notes
-----
'''
if ax is None:
fig, ax = plt.subplots(figsize=(7,4))
ax.hist(x=x, density=True, bins=30, color="#3182bd", alpha=0.5)
ax.plot(x, np.full_like(x, -0.01), '|k', markeredgewidth=1)
ax.set_title('Ajuste distribuciones')
ax.set_xlabel('x')
ax.set_ylabel('Densidad de probabilidad')
for nombre in nombre_distribuciones:
distribucion = getattr(stats, nombre)
parametros = distribucion.fit(data=x)
nombre_parametros = [p for p in inspect.signature(distribucion._pdf).parameters \
if not p=='x'] + ["loc","scale"]
parametros_dict = dict(zip(nombre_parametros, parametros))
log_likelihood = distribucion.logpdf(x, *parametros).sum()
aic = -2 * log_likelihood + 2 * len(parametros)
bic = -2 * log_likelihood + np.log(x.shape[0]) * len(parametros)
x_hat = np.linspace(min(x), max(x), num=100)
y_hat = distribucion.pdf(x_hat, *parametros)
ax.plot(x_hat, y_hat, linewidth=2, label=distribucion.name)
ax.legend();
return ax
Se muestra la mejor distribución acorde al criterio AIC.
fig, ax = plt.subplots(figsize=(8,5))
plot_distribucion(
x=datos.to_numpy(),
nombre_distribucion=resultados['distribucion'][0],
ax=ax
);
Las curvas de densidad de probabilidad para las top 5 distribuciones.
fig, ax = plt.subplots(figsize=(8,5))
plot_multiple_distribuciones(
x=datos.to_numpy(),
nombre_distribuciones=resultados['distribucion'][:5],
ax=ax
);
Acorde al criterio AIC, las dos distribuciones que mejor se adaptan a los datos son: johnsonsu y norminvgauss.
Todas las funciones implementadas en scipy.stats disponen de los métodos pdf()
, logpdf()
, cdf()
, ppf()
y rvs()
con los que calcular la densidad, logaritmo de densidad, probabilidad acumulada, cuantiles, y muestreo de nuevos valores. Por ejemplo, se pueden simular 5 nuevos valores de diamantes acorde a la distribución johnsonsu.
# Definición de la distribución
distribucion = stats.johnsonsu
# Ajuste para obtener el valor de los parámetros
parametros = distribucion.fit(datos.to_numpy())
# Muestreo aleatorio
distribucion.rvs(*parametros, size=5)
from sinfo import sinfo
sinfo()
Distributions for Modeling Location, Scale, and Shape Using GAMLSS in R By Robert A. Rigby, Mikis D. Stasinopoulos, Gillian Z. Heller, Fernanda De Bastiani.
Delignette-Muller, M., & Dutang, C. (2015). fitdistrplus: An R Package for Fitting Distributions. doi:http://dx.doi.org/10.18637/jss.v064.i04
¿Cómo citar este documento?
Ajuste y selección de distribuciones con Python por Joaquín Amat Rodrigo, disponible con licencia CC BY-NC-SA 4.0 en https://www.cienciadedatos.net/documentos/pystats01-ajuste-distribuciones-python.html
¿Te ha gustado el artículo? Tu ayuda es importante
Mantener un sitio web tiene unos costes elevados, tu contribución me ayudará a seguir generando contenido divulgativo gratuito. ¡Muchísimas gracias! 😊
Este contenido, creado por Joaquín Amat Rodrigo, tiene licencia Attribution-NonCommercial-ShareAlike 4.0 International.
Se permite:
Compartir: copiar y redistribuir el material en cualquier medio o formato.
Adaptar: remezclar, transformar y crear a partir del material.
Bajo los siguientes términos:
Atribución: Debes otorgar el crédito adecuado, proporcionar un enlace a la licencia e indicar si se realizaron cambios. Puedes hacerlo de cualquier manera razonable, pero no de una forma que sugiera que el licenciante te respalda o respalda tu uso.
NoComercial: No puedes utilizar el material para fines comerciales.
CompartirIgual: Si remezclas, transformas o creas a partir del material, debes distribuir tus contribuciones bajo la misma licencia que el original.