web-dev-qa-db-fra.com

Adapter la distribution empirique aux distributions théoriques avec Scipy (Python)?

INTRODUCTION : J'ai une liste de plus de 30 000 valeurs entières allant de 0 à 47 inclus, par exemple .[0,0,0,0,..,1,1,1,1,...,2,2,2,2,...,47,47,47,...] échantillonné à partir d’une distribution continue. Les valeurs de la liste ne sont pas nécessairement dans l'ordre, mais l'ordre n'a pas d'importance pour ce problème.

PROBLÈME : En fonction de ma distribution, j'aimerais calculer la valeur p (la probabilité de voir des valeurs supérieures) pour une valeur donnée. Par exemple, comme vous pouvez le voir, la valeur p pour 0 approcherait 1 et la valeur p pour les nombres les plus élevés tendrait à 0.

Je ne sais pas si j'ai raison, mais pour déterminer les probabilités, je pense que je dois adapter mes données à une distribution théorique qui convient le mieux pour décrire mes données. Je suppose qu'un test de qualité de l'ajustement est nécessaire pour déterminer le meilleur modèle.

Existe-t-il un moyen d'implémenter une telle analyse dans Python (Scipy ou Numpy)?? Pouvez-vous présenter des exemples?

Je vous remercie!

110
s_sherly

Ajustement de distribution avec erreur de somme des carrés (SSE)

Ceci est une mise à jour et une modification de réponse de Saullo , qui utilise la liste complète du courant scipy.stats distributions et retourne la distribution avec le plus petit SSE entre l'histogramme de la distribution et l'histogramme des données.

Exemple de montage

En utilisant le jeu de données El Niño de statsmodels , les distributions sont ajustées et l’erreur est déterminée. La distribution avec la moindre erreur est renvoyée.

Toutes les distributions

All Fitted Distributions

Meilleure distribution

Best Fit Distribution

Exemple de code

%matplotlib inline

import warnings
import numpy as np
import pandas as pd
import scipy.stats as st
import statsmodels as sm
import matplotlib
import matplotlib.pyplot as plt

matplotlib.rcParams['figure.figsize'] = (16.0, 12.0)
matplotlib.style.use('ggplot')

# Create models from data
def best_fit_distribution(data, bins=200, ax=None):
    """Model data by finding best fit distribution to data"""
    # Get histogram of original data
    y, x = np.histogram(data, bins=bins, density=True)
    x = (x + np.roll(x, -1))[:-1] / 2.0

    # Distributions to check
    DISTRIBUTIONS = [        
        st.alpha,st.anglit,st.arcsine,st.beta,st.betaprime,st.bradford,st.burr,st.cauchy,st.chi,st.chi2,st.cosine,
        st.dgamma,st.dweibull,st.erlang,st.expon,st.exponnorm,st.exponweib,st.exponpow,st.f,st.fatiguelife,st.fisk,
        st.foldcauchy,st.foldnorm,st.frechet_r,st.frechet_l,st.genlogistic,st.genpareto,st.gennorm,st.genexpon,
        st.genextreme,st.gausshyper,st.gamma,st.gengamma,st.genhalflogistic,st.gilbrat,st.gompertz,st.gumbel_r,
        st.gumbel_l,st.halfcauchy,st.halflogistic,st.halfnorm,st.halfgennorm,st.hypsecant,st.invgamma,st.invgauss,
        st.invweibull,st.johnsonsb,st.johnsonsu,st.ksone,st.kstwobign,st.laplace,st.levy,st.levy_l,st.levy_stable,
        st.logistic,st.loggamma,st.loglaplace,st.lognorm,st.lomax,st.maxwell,st.mielke,st.nakagami,st.ncx2,st.ncf,
        st.nct,st.norm,st.Pareto,st.pearson3,st.powerlaw,st.powerlognorm,st.powernorm,st.rdist,st.reciprocal,
        st.rayleigh,st.rice,st.recipinvgauss,st.semicircular,st.t,st.triang,st.truncexpon,st.truncnorm,st.tukeylambda,
        st.uniform,st.vonmises,st.vonmises_line,st.wald,st.weibull_min,st.weibull_max,st.wrapcauchy
    ]

    # Best holders
    best_distribution = st.norm
    best_params = (0.0, 1.0)
    best_sse = np.inf

    # Estimate distribution parameters from data
    for distribution in DISTRIBUTIONS:

        # Try to fit the distribution
        try:
            # Ignore warnings from data that can't be fit
            with warnings.catch_warnings():
                warnings.filterwarnings('ignore')

                # fit dist to data
                params = distribution.fit(data)

                # Separate parts of parameters
                arg = params[:-2]
                loc = params[-2]
                scale = params[-1]

                # Calculate fitted PDF and error with fit in distribution
                pdf = distribution.pdf(x, loc=loc, scale=scale, *arg)
                sse = np.sum(np.power(y - pdf, 2.0))

                # if axis pass in add to plot
                try:
                    if ax:
                        pd.Series(pdf, x).plot(ax=ax)
                    end
                except Exception:
                    pass

                # identify if this distribution is better
                if best_sse > sse > 0:
                    best_distribution = distribution
                    best_params = params
                    best_sse = sse

        except Exception:
            pass

    return (best_distribution.name, best_params)

def make_pdf(dist, params, size=10000):
    """Generate distributions's Probability Distribution Function """

    # Separate parts of parameters
    arg = params[:-2]
    loc = params[-2]
    scale = params[-1]

    # Get sane start and end points of distribution
    start = dist.ppf(0.01, *arg, loc=loc, scale=scale) if arg else dist.ppf(0.01, loc=loc, scale=scale)
    end = dist.ppf(0.99, *arg, loc=loc, scale=scale) if arg else dist.ppf(0.99, loc=loc, scale=scale)

    # Build PDF and turn into pandas Series
    x = np.linspace(start, end, size)
    y = dist.pdf(x, loc=loc, scale=scale, *arg)
    pdf = pd.Series(y, x)

    return pdf

# Load data from statsmodels datasets
data = pd.Series(sm.datasets.elnino.load_pandas().data.set_index('YEAR').values.ravel())

# Plot for comparison
plt.figure(figsize=(12,8))
ax = data.plot(kind='hist', bins=50, normed=True, alpha=0.5, color=plt.rcParams['axes.color_cycle'][1])
# Save plot limits
dataYLim = ax.get_ylim()

# Find best fit distribution
best_fit_name, best_fit_params = best_fit_distribution(data, 200, ax)
best_dist = getattr(st, best_fit_name)

# Update plots
ax.set_ylim(dataYLim)
ax.set_title(u'El Niño sea temp.\n All Fitted Distributions')
ax.set_xlabel(u'Temp (°C)')
ax.set_ylabel('Frequency')

# Make PDF with best params 
pdf = make_pdf(best_dist, best_fit_params)

# Display
plt.figure(figsize=(12,8))
ax = pdf.plot(lw=2, label='PDF', legend=True)
data.plot(kind='hist', bins=50, normed=True, alpha=0.5, label='Data', legend=True, ax=ax)

param_names = (best_dist.shapes + ', loc, scale').split(', ') if best_dist.shapes else ['loc', 'scale']
param_str = ', '.join(['{}={:0.2f}'.format(k,v) for k,v in Zip(param_names, best_fit_params)])
dist_str = '{}({})'.format(best_fit_name, param_str)

ax.set_title(u'El Niño sea temp. with best fit distribution \n' + dist_str)
ax.set_xlabel(u'Temp. (°C)')
ax.set_ylabel('Frequency')
162
tmthydvnprt

Il y a 82 fonctions de distribution implémentées dans SciPy 0.12. . Vous pouvez tester leur adaptation à vos données à l'aide de leur méthode fit() . Vérifiez le code ci-dessous pour plus de détails:

enter image description here

import matplotlib.pyplot as plt
import scipy
import scipy.stats
size = 30000
x = scipy.arange(size)
y = scipy.int_(scipy.round_(scipy.stats.vonmises.rvs(5,size=size)*47))
h = plt.hist(y, bins=range(48))

dist_names = ['gamma', 'beta', 'rayleigh', 'norm', 'Pareto']

for dist_name in dist_names:
    dist = getattr(scipy.stats, dist_name)
    param = dist.fit(y)
    pdf_fitted = dist.pdf(x, *param[:-2], loc=param[-2], scale=param[-1]) * size
    plt.plot(pdf_fitted, label=dist_name)
    plt.xlim(0,47)
plt.legend(loc='upper right')
plt.show()

Références:

- Distributions d'ajustement, qualité de l'ajustement, valeur p. Est-il possible de le faire avec Scipy (Python)?

- Raccord de distribution avec Scipy

Et voici une liste avec les noms de toutes les fonctions de distribution disponibles dans Scipy 0.12.0 (VI):

dist_names = [ 'alpha', 'anglit', 'arcsine', 'beta', 'betaprime', 'bradford', 'burr', 'cauchy', 'chi', 'chi2', 'cosine', 'dgamma', 'dweibull', 'erlang', 'expon', 'exponweib', 'exponpow', 'f', 'fatiguelife', 'fisk', 'foldcauchy', 'foldnorm', 'frechet_r', 'frechet_l', 'genlogistic', 'genpareto', 'genexpon', 'genextreme', 'gausshyper', 'gamma', 'gengamma', 'genhalflogistic', 'gilbrat', 'gompertz', 'gumbel_r', 'gumbel_l', 'halfcauchy', 'halflogistic', 'halfnorm', 'hypsecant', 'invgamma', 'invgauss', 'invweibull', 'johnsonsb', 'johnsonsu', 'ksone', 'kstwobign', 'laplace', 'logistic', 'loggamma', 'loglaplace', 'lognorm', 'lomax', 'maxwell', 'mielke', 'nakagami', 'ncx2', 'ncf', 'nct', 'norm', 'Pareto', 'pearson3', 'powerlaw', 'powerlognorm', 'powernorm', 'rdist', 'reciprocal', 'rayleigh', 'rice', 'recipinvgauss', 'semicircular', 't', 'triang', 'truncexpon', 'truncnorm', 'tukeylambda', 'uniform', 'vonmises', 'wald', 'weibull_min', 'weibull_max', 'wrapcauchy'] 
127

La méthode fit() mentionnée par @Saullo Castro fournit des estimations du maximum de vraisemblance (MLE). La meilleure distribution pour vos données est celle qui vous donne le plus grand nombre d’éléments pouvant être déterminés de différentes manières:

1, celui qui vous donne la plus grande probabilité de log.

2, celui qui vous donne les plus petites valeurs AIC, BIC ou BICc (voir wiki: http://en.wikipedia.org/wiki/Akaike_information_criterion , peut être considéré fondamentalement comme une vraisemblance de journal ajustée pour le nombre de paramètres, car une distribution avec plus de paramètres devrait s’ajuster mieux)

3, celui qui maximise la probabilité bayésienne postérieure. (voir wiki: http://en.wikipedia.org/wiki/Posterior_probability )

Bien sûr, si vous avez déjà une distribution qui devrait décrire vos données (sur la base des théories de votre domaine) et que vous souhaitez vous y tenir, vous passerez à l'étape d'identification de la distribution la mieux adaptée.

scipy ne comporte pas de fonction permettant de calculer la vraisemblance du journal (bien que la méthode MLE soit fournie), mais le code le plus simple est facile: voir Les fonctions de densité de probabilité intégrées de `scipy.stat. distributions` plus lentes qu’un utilisateur en a fourni une?

10
CT Zhu

AFAICU, votre distribution est discrète (et rien que discrète). Par conséquent, le simple fait de compter les fréquences de différentes valeurs et de les normaliser devrait suffire à vos fins. Donc, un exemple pour démontrer ceci:

In []: values= [0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 3, 3, 4]
In []: counts= asarray(bincount(values), dtype= float)
In []: cdf= counts.cumsum()/ counts.sum()

Ainsi, la probabilité de voir des valeurs supérieures à 1 est simplement (selon la fonction de distribution cumulative complémentaire (ccdf) :

In []: 1- cdf[1]
Out[]: 0.40000000000000002

Veuillez noter que ccdf est étroitement lié à fonction de survie (sf) , mais il est également défini avec des distributions discrètes, alors que sf est défini uniquement pour les contigu les distributions.

4
eat

Cela ressemble à un problème d'estimation de densité de probabilité pour moi.

from scipy.stats import gaussian_kde
occurences = [0,0,0,0,..,1,1,1,1,...,2,2,2,2,...,47]
values = range(0,48)
kde = gaussian_kde(map(float, occurences))
p = kde(values)
p = p/sum(p)
print "P(x>=1) = %f" % sum(p[1:])

Voir aussi http://jpktd.blogspot.com/2009/03/using-gaussian-kernel-density.html .

2
emre

Pardonnez-moi si je ne comprends pas votre besoin, mais qu'en est-il de stocker vos données dans un dictionnaire où les clés seraient les nombres compris entre 0 et 47 et valoriseraient le nombre d'occurrences de leurs clés associées dans votre liste d'origine?
Ainsi, votre probabilité p(x) sera la somme de toutes les valeurs des clés supérieures à x divisées par 30000.

0
PierrOz