Adattare la distribuzione empirica a quelli teorici con Scipy (Python)?


139

INTRODUZIONE : ho un elenco di oltre 30.000 valori interi compresi tra 0 e 47, inclusi, ad esempio, [0,0,0,0,..,1,1,1,1,...,2,2,2,2,...,47,47,47,...]campionati da una distribuzione continua. I valori nell'elenco non sono necessariamente in ordine, ma l'ordine non ha importanza per questo problema.

PROBLEMA : in base alla mia distribuzione, vorrei calcolare il valore p (la probabilità di vedere valori maggiori) per ogni dato valore. Ad esempio, come puoi vedere il valore p per 0 si avvicina a 1 e il valore p per numeri più alti tende a 0.

Non so se ho ragione, ma per determinare le probabilità penso di dover adattare i miei dati a una distribuzione teorica più adatta a descrivere i miei dati. Presumo che per determinare il modello migliore sia necessario un qualche tipo di test di idoneità.

C'è un modo per implementare tale analisi in Python ( Scipyo Numpy)? Potresti presentare qualche esempio?

Grazie!


2
Hai solo valori empirici discreti ma vuoi una distribuzione continua? Lo capisco correttamente?
Michael J. Barber,

1
Sembra senza senso. Cosa rappresentano i numeri? Misure con precisione limitata?
Michael J. Barber,

1
Michael, ho spiegato quello che i numeri rappresentano nella mia domanda precedente: stackoverflow.com/questions/6615489/...
s_sherly

6
Questi sono i dati di conteggio. Non è una distribuzione continua.
Michael J. Barber,

1
Controlla la risposta accettata a questa domanda stackoverflow.com/questions/48455018/…
Ahmad Suliman

Risposte:


209

Raccordo di distribuzione con somma di errore quadrato (SSE)

Questo è un aggiornamento e una modifica alla risposta di Saullo , che utilizza l'elenco completo delle scipy.statsdistribuzioni correnti e restituisce la distribuzione con il minimo SSE tra l'istogramma della distribuzione e l'istogramma dei dati.

Esempio di raccordo

Utilizzando il set di dati El Niño distatsmodels , le distribuzioni sono adeguate e l'errore viene determinato. Viene restituita la distribuzione con il minimo errore.

Tutte le distribuzioni

Tutte le distribuzioni adattate

Distribuzione più adatta

Distribuzione più adatta

Codice di esempio

%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')

2
Eccezionale. Prendi in considerazione l'utilizzo density=Trueinvece di normed=Truein np.histogram(). ^^
Peque,

1
@tmthydvnprt Forse potresti annullare i cambiamenti nei .plot()metodi per evitare confusioni future. ^^
Peque,

10
Per ottenere i nomi di distribuzione: from scipy.stats._continuous_distns import _distn_names. È quindi possibile utilizzare qualcosa di simile getattr(scipy.stats, distname)per ciascuno distnamein _distn_names`. Utile perché le distribuzioni vengono aggiornate con diverse versioni di SciPy.
Brad Solomon,

1
Puoi spiegare perché questo codice verifica solo la migliore adattabilità delle distribuzioni continue e non può verificare la presenza di distribuzioni discrete o multivariate. Grazie.
Adam Schroeder,

6
Molto bello. Ho dovuto aggiornare il parametro color -ax = data.plot(kind='hist', bins=50, normed=True, alpha=0.5, color=list(matplotlib.rcParams['axes.prop_cycle'])[1]['color'])
basswaves

147

Ci sono 82 funzioni di distribuzione implementate in SciPy 0.12.0 . È possibile verificare come alcuni di loro si adattano ai dati usando il loro fit()metodo . Controlla il codice qui sotto per maggiori dettagli:

inserisci qui la descrizione dell'immagine

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()

Riferimenti:

- Distribuzioni adeguate, bontà di adattamento, valore p. È possibile farlo con Scipy (Python)?

- Raccordo di distribuzione con Scipy

E qui un elenco con i nomi di tutte le funzioni di distribuzione disponibili in 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'] 

7
E se normed = Truenel tracciare l'istogramma? Non ti moltiplicheresti pdf_fittedper size, giusto?
aloha,

3
Vedi questa risposta se desideri vedere come appaiono tutte le distribuzioni o per avere un'idea di come accedervi.
tmthydvnprt,

@SaulloCastro Cosa rappresentano i 3 valori in param, nell'output di dist.fit
shaifali Gupta

2
Per ottenere i nomi di distribuzione: from scipy.stats._continuous_distns import _distn_names. È quindi possibile utilizzare qualcosa di simile getattr(scipy.stats, distname)per ciascuno distnamein _distn_names`. Utile perché le distribuzioni vengono aggiornate con diverse versioni di SciPy.
Brad Solomon,

1
Vorrei rimuovere color = 'w' dal codice, altrimenti l'istogramma non viene visualizzato.
Eran,

12

fit()il metodo citato da @Saullo Castro fornisce le stime di massima verosimiglianza (MLE). La migliore distribuzione per i tuoi dati è quella che ti dà il massimo che può essere determinata in diversi modi: come

1, quello che ti dà la più alta probabilità di log.

2, quello che ti dà i valori AIC, BIC o BICc più piccoli (vedi wiki: http://en.wikipedia.org/wiki/Akaike_information_criterion , in pratica può essere visto come probabilità di log adattata per il numero di parametri, come distribuzione con più i parametri dovrebbero adattarsi meglio)

3, quello che massimizza la probabilità posteriore bayesiana. (vedi wiki: http://en.wikipedia.org/wiki/Posterior_probability )

Naturalmente, se disponi già di una distribuzione che dovrebbe descrivere i tuoi dati (in base alle teorie nel tuo campo specifico) e desideri attenersi a ciò, salterai il passaggio per identificare la distribuzione più adatta.

scipynon viene fornito con una funzione per calcolare la verosimiglianza dei log (anche se viene fornito il metodo MLE), ma uno hard code è facile: vedi Le funzioni di densità di probabilità incorporate di `scipy.stat.distributions` sono più lente di quelle fornite da un utente?


1
Come applicherei questo metodo a una situazione in cui i dati sono già stati raggruppati, ovvero è già un istogramma anziché generare un istogramma dai dati?
Pete,

@pete, quella sarebbe una situazione di dati censurati per intervallo, ci sono metodi di massima verosimiglianza per questo, ma attualmente non è implementato inscipy
CT Zhu

Non dimenticare le prove
jtlz2,

5

AFAICU, la tua distribuzione è discreta (e nient'altro che discreta). Pertanto, il solo conteggio delle frequenze di valori diversi e la loro normalizzazione dovrebbe essere sufficiente per i tuoi scopi. Quindi, un esempio per dimostrarlo:

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()

Pertanto, la probabilità di vedere valori più alti di 1è semplicemente (secondo la funzione di distribuzione cumulativa complementare (ccdf) :

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

Si noti che ccdf è strettamente correlato alla funzione di sopravvivenza (sf) , ma è anche definito con distribuzioni discrete, mentre sf è definito solo per distribuzioni contigue.


2

Mi sembra un problema di stima della densità di probabilità.

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:])

Vedi anche http://jpktd.blogspot.com/2009/03/using-gaussian-kernel-density.html .


1
Per i futuri lettori: questa soluzione (o almeno l'idea) fornisce la risposta più semplice alle domande dei PO ('qual è il valore p') - sarebbe interessante sapere come si confronta con alcuni dei metodi più coinvolti che si adattano una distribuzione nota.
Greg,

Le regressioni del kernel gaussiano funzionano per tutte le distribuzioni?

@mikey Come regola generale, nessuna regressione funziona per tutte le distribuzioni. Non sono male però
TheEnvironmentalist,

2

Prova la distfitbiblioteca.

pip install distfit

# Create 1000 random integers, value between [0-50]
X = np.random.randint(0, 50,1000)

# Retrieve P-value for y
y = [0,10,45,55,100]

# From the distfit library import the class distfit
from distfit import distfit

# Initialize.
# Set any properties here, such as alpha.
# The smoothing can be of use when working with integers. Otherwise your histogram
# may be jumping up-and-down, and getting the correct fit may be harder.
dist = distfit(alpha=0.05, smooth=10)

# Search for best theoretical fit on your empirical data
dist.fit_transform(X)

> [distfit] >fit..
> [distfit] >transform..
> [distfit] >[norm      ] [RSS: 0.0037894] [loc=23.535 scale=14.450] 
> [distfit] >[expon     ] [RSS: 0.0055534] [loc=0.000 scale=23.535] 
> [distfit] >[pareto    ] [RSS: 0.0056828] [loc=-384473077.778 scale=384473077.778] 
> [distfit] >[dweibull  ] [RSS: 0.0038202] [loc=24.535 scale=13.936] 
> [distfit] >[t         ] [RSS: 0.0037896] [loc=23.535 scale=14.450] 
> [distfit] >[genextreme] [RSS: 0.0036185] [loc=18.890 scale=14.506] 
> [distfit] >[gamma     ] [RSS: 0.0037600] [loc=-175.505 scale=1.044] 
> [distfit] >[lognorm   ] [RSS: 0.0642364] [loc=-0.000 scale=1.802] 
> [distfit] >[beta      ] [RSS: 0.0021885] [loc=-3.981 scale=52.981] 
> [distfit] >[uniform   ] [RSS: 0.0012349] [loc=0.000 scale=49.000] 

# Best fitted model
best_distr = dist.model
print(best_distr)

# Uniform shows best fit, with 95% CII (confidence intervals), and all other parameters
> {'distr': <scipy.stats._continuous_distns.uniform_gen at 0x16de3a53160>,
>  'params': (0.0, 49.0),
>  'name': 'uniform',
>  'RSS': 0.0012349021241149533,
>  'loc': 0.0,
>  'scale': 49.0,
>  'arg': (),
>  'CII_min_alpha': 2.45,
>  'CII_max_alpha': 46.55}

# Ranking distributions
dist.summary

# Plot the summary of fitted distributions
dist.plot_summary()

inserisci qui la descrizione dell'immagine

# Make prediction on new datapoints based on the fit
dist.predict(y)

# Retrieve your pvalues with 
dist.y_pred
# array(['down', 'none', 'none', 'up', 'up'], dtype='<U4')
dist.y_proba
array([0.02040816, 0.02040816, 0.02040816, 0.        , 0.        ])

# Or in one dataframe
dist.df

# The plot function will now also include the predictions of y
dist.plot()

Il più adatto

Si noti che in questo caso, tutti i punti saranno significativi a causa della distribuzione uniforme. È possibile filtrare con dist.y_pred, se necessario.


1

Con OpenTURNS , utilizzerei i criteri BIC per selezionare la migliore distribuzione adatta a tali dati. Questo perché questi criteri non danno troppo vantaggio alle distribuzioni che hanno più parametri. Infatti, se una distribuzione ha più parametri, è più facile che la distribuzione adattata sia più vicina ai dati. Inoltre, il Kolmogorov-Smirnov potrebbe non avere senso in questo caso, perché un piccolo errore nei valori misurati avrà un impatto enorme sul valore p.

Per illustrare il processo, carico i dati El-Nino, che contengono 732 misurazioni mensili di temperatura dal 1950 al 2010:

import statsmodels.api as sm
dta = sm.datasets.elnino.load_pandas().data
dta['YEAR'] = dta.YEAR.astype(int).astype(str)
dta = dta.set_index('YEAR').T.unstack()
data = dta.values

È facile ottenere 30 delle fabbriche univariate integrate di distribuzioni con il GetContinuousUniVariateFactoriesmetodo statico. Una volta fatto, il BestModelBICmetodo statico restituisce il modello migliore e il punteggio BIC corrispondente.

sample = ot.Sample(data, 1)
tested_factories = ot.DistributionFactory.GetContinuousUniVariateFactories()
best_model, best_bic = ot.FittingTest.BestModelBIC(sample,
                                                   tested_factories)
print("Best=",best_model)

che stampa:

Best= Beta(alpha = 1.64258, beta = 2.4348, a = 18.936, b = 29.254)

Per confrontare graficamente l'adattamento all'istogramma, utilizzo i drawPDFmetodi della migliore distribuzione.

import openturns.viewer as otv
graph = ot.HistogramFactory().build(sample).drawPDF()
bestPDF = best_model.drawPDF()
bestPDF.setColors(["blue"])
graph.add(bestPDF)
graph.setTitle("Best BIC fit")
name = best_model.getImplementation().getClassName()
graph.setLegends(["Histogram",name])
graph.setXTitle("Temperature (°C)")
otv.View(graph)

Questo produce:

Beta adatta alle temperature di El-Nino

Maggiori dettagli su questo argomento sono presentati nel documento BestModelBIC . Sarebbe possibile includere la distribuzione di Scipy in SciPyDistribution o anche con le distribuzioni di ChaosPy con ChaosPyDistribution , ma immagino che lo script attuale soddisfi gli scopi più pratici.


2
Probabilmente dovresti dichiarare un interesse?
jtlz2,

0

Scusami se non capisco le tue necessità, ma che dire di memorizzare i tuoi dati in un dizionario in cui le chiavi sarebbero i numeri tra 0 e 47 e valuti il ​​numero di occorrenze delle loro chiavi correlate nell'elenco originale?
Quindi la tua probabilità p (x) sarà la somma di tutti i valori per le chiavi maggiori di x divisa per 30000.


In questo caso p (x) sarà lo stesso (uguale a 0) per qualsiasi valore maggiore di 47. Ho bisogno di una distribuzione di probabilità continua.
s_sherly

2
@s_sherly - Sarebbe probabilmente una buona cosa se potessi modificare e chiarire meglio la tua domanda, come in effetti la "probabilità di vedere valori maggiori" - come dici tu - È zero per valori che sono al di sopra del valore più alto nel pool .
mac
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.