Чи підходить емпіричний розподіл теоретичним за допомогою Scipy (Python)?


139

ВСТУП : У мене є список з більш ніж 30 000 цілих значень, починаючи від 0 до 47, включно, наприклад [0,0,0,0,..,1,1,1,1,...,2,2,2,2,...,47,47,47,...]вибірки з деякого безперервного розподілу. Значення у списку не обов'язково в порядку, але порядок не має значення для цієї проблеми.

ПРОБЛЕМА : На підставі мого розподілу я б хотів обчислити p-значення (ймовірність побачити більші значення) для будь-якого заданого значення. Наприклад, як ви бачите, що значення p для 0 наближатиметься до 1, а значення p для більш високих чисел має тенденцію до 0.

Я не знаю, чи маю рацію, але, щоб визначити ймовірність, я думаю, що мені потрібно пристосувати свої дані до теоретичного розподілу, який є найбільш підходящим для опису моїх даних. Я припускаю, що для визначення найкращої моделі потрібен якийсь тест на придатність.

Чи є спосіб здійснити такий аналіз у Python ( Scipyабо Numpy)? Чи можете ви навести якісь приклади?

Дякую!


2
Ви маєте лише окремі емпіричні значення, але хочете безперервного розподілу? Я правильно це розумію?
Майкл Дж. Барбер

1
Це здається безглуздим. Що представляють числа? Вимірювання з обмеженою точністю?
Майкл Дж. Барбер

1
Майкл, я пояснив , що ці цифри становлять в моєму попередньому питанні: stackoverflow.com/questions/6615489 / ...
s_sherly

6
Це дані підрахунку. Це не безперервний розподіл.
Майкл Дж. Барбер

1
Перевірте прийняту відповідь на це питання stackoverflow.com/questions/48455018/…
Ахмад Суліман

Відповіді:


209

Розмір розподілу з сумою помилки квадрата (SSE)

Це оновлення та модифікація відповіді Салло , яка використовує повний список поточних scipy.statsдистрибутивів і повертає розподіл з найменшим SSE між гістограмою розподілу та гістограмою даних.

Приклад установки

Використовуючи набір даних El Niño відstatsmodels , розподіли підходять і визначається помилка. Повертається розподіл з найменшою помилкою.

Усі дистрибуції

Усі встановлені дистрибутиви

Найкращий придатний розподіл

Найкращий придатний розподіл

Приклад коду

%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
Дивовижно. Подумайте про використання density=Trueзамість normed=Trueв np.histogram(). ^^
Пеке

1
@tmthydvnprt Можливо, ви можете скасувати зміни .plot()методів, щоб уникнути плутанини в майбутньому. ^^
Пеке

10
Для того, щоб отримати імена розподілу: from scipy.stats._continuous_distns import _distn_names. Потім ви можете використовувати щось подібне getattr(scipy.stats, distname)для кожного distnameу _distn_names`. Корисно, оскільки дистрибутиви оновлюються різними версіями SciPy.
Бред Соломон

1
Чи можете ви поясніть, чому цей код перевіряє найкращу відповідність безперервних дистрибутивів і не може перевірити наявність дискретних або багатоваріантних розподілів. Дякую.
Адам Шредер

6
Дуже круто. Мені довелося оновити параметр кольору -ax = data.plot(kind='hist', bins=50, normed=True, alpha=0.5, color=list(matplotlib.rcParams['axes.prop_cycle'])[1]['color'])
басові хвилі

147

У SciPy 0.12.0 є 82 реалізованих функції розподілу . Ви можете перевірити, як деякі з них підходять до ваших даних, використовуючи їх fit()метод . Перевірте код нижче, щоб отримати докладнішу інформацію:

введіть тут опис зображення

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

Список літератури:

- Придатні розподіли, корисність придатності, р-значення. Чи можливо це зробити за допомогою Scipy (Python)?

- Поширення, що відповідає Scipy

А ось список із назвами всіх функцій розповсюдження, доступних у 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
Що робити, якщо normed = Trueбудувати гістограму? Ви б не помножили pdf_fittedна size, правда?
алоха

3
Дивіться цю відповідь, якщо ви хочете побачити, як виглядають усі дистрибутиви, або уявити, як отримати доступ до всіх.
tmthydvnprt

@SaulloCastro Що представляють 3 значення в парамі, у висновку dist.fit
shaifali Gupta

2
Для того, щоб отримати імена розподілу: from scipy.stats._continuous_distns import _distn_names. Потім ви можете використовувати щось подібне getattr(scipy.stats, distname)для кожного distnameу _distn_names`. Корисно, оскільки дистрибутиви оновлюються різними версіями SciPy.
Бред Соломон

1
Я б видалив color = 'w' з коду, інакше гістограма не відображається.
Еран

12

fit()Спосіб, згаданий @Saullo Castro, забезпечує максимальну оцінку ймовірності (MLE). Найкраще розповсюдження ваших даних - це найвищий рівень, який можна визначити декількома різними способами: наприклад

1, той, що дає вам найбільшу ймовірність журналу.

2, той, що дає найменші значення AIC, BIC або BICc (див. Wiki: http://en.wikipedia.org/wiki/Akaike_information_criterion) , в основному можна розглядати як вірогідність журналу, скориговану на кількість параметрів, як розподіл з більш очікується, що параметри краще підходять)

3, той, що максимізує байєсівську задню ймовірність. (див. вікі: http://en.wikipedia.org/wiki/Posterior_probability )

Звичайно, якщо у вас вже є дистрибутив, який повинен описувати ваші дані (виходячи з теорій у вашій конкретній галузі) і хочете дотримуватися цього, ви пропустите етап визначення найкращого розподілу.

scipyне має функції для обчислення ймовірності журналу (хоча надається метод MLE), але жорсткий код один простий: див. Чи функціонування щільності ймовірності вбудовування `scipy.stat.distributions` повільніше, ніж надано користувачем?


1
Як би я застосував цей метод до ситуації, коли дані вже поширювались, тобто це вже гістограма, а не генерування гістограми з даних?
Піт

@pete, це була б ситуація з інтервальною цензурою даних, існує максимальна ймовірність методу, але вона наразі не реалізована вscipy
КТ Чжу

Не забудьте докази
jtlz2

5

AFAICU, ваш розподіл дискретний (і нічого, крім дискретного). Тому просто підрахунок частот різних значень та їх нормалізація має бути достатнім для ваших цілей. Отож, приклад для демонстрації цього:

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

Таким чином, ймовірність побачити значення вище, ніж 1просто (відповідно до функції додаткового накопичувального розподілу (ccdf) :

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

Зверніть увагу, що ccdf тісно пов'язаний з функцією виживання (sf) , але він також визначається дискретними розподілами, тоді як sf визначається лише для суміжних розподілів.


2

Мені це звучить як проблема оцінки щільності ймовірності.

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

Також дивіться http://jpktd.blogspot.com/2009/03/using-gaussian-kernel-density.html .


1
Для майбутніх читачів: це рішення (або, принаймні, ідея) дає найпростішу відповідь на питання ОП («що таке p-значення») - було б цікаво дізнатися, як це порівнюється з деякими більш залученими методами, які підходять відомий розподіл.
Грег

Чи працюють регресії ядра Гаусса для всіх дистрибутивів?

@mikey Як правило, жодна регресія не працює для всіх дистрибутивів. Вони не погано , хоча
TheEnvironmentalist

2

Спробуйте distfitбібліотеку.

pip встановити 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()

введіть тут опис зображення

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

Найкраще підходить

Зауважте, що в цьому випадку всі бали будуть суттєвими через рівномірний розподіл. Ви можете фільтрувати за допомогою dist.y_pred, якщо потрібно.


1

З OpenTURNS я б використовував критерії BIC, щоб вибрати найкращий розподіл, який відповідає таким даним. Це тому, що цей критерій не дає надто переваги дистрибутивам, які мають більше параметрів. Дійсно, якщо дистрибутив має більше параметрів, пристосований розподіл простіше наблизитись до даних. Більше того, Колмогоров-Смірнов може не мати сенсу в цьому випадку, оскільки невелика похибка вимірюваних значень матиме величезний вплив на p-значення.

Щоб проілюструвати процес, я завантажую дані El-Nino, які містять 732 вимірювання температури за місяць з 1950 по 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

Легко отримати 30 вбудованих універсальних заводів розподілу GetContinuousUniVariateFactoriesстатичним методом. Після закінчення BestModelBICстатичний метод повертає найкращу модель та відповідний бал BIC.

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

який друкує:

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

Для графічного порівняння пристосування до гістограми я використовую drawPDFметоди найкращого розподілу.

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)

Це дає:

Бета підходить до температури Ель-Ніно

Детальніше з цієї теми представлено в документі BestModelBIC . Можна було б включити дистрибутив Scipy в SciPyDistribution або навіть з дистрибутивами ChaosPy з ChaosPyDistribution , але я думаю, що поточний сценарій виконує більшість практичних цілей.


2
Ви, мабуть, повинні заявити про зацікавлення?
jtlz2

0

Пробачте, якщо я не розумію вашої потреби, але як бути із збереженням ваших даних у словнику, де ключами будуть цифри від 0 до 47 і значення кількості зустрічей їх пов’язаних ключів у вашому первинному списку?
Таким чином, ваша ймовірність p (x) буде сумою всіх значень для ключів, більшим за x, поділеною на 30000.


У цьому випадку p (x) буде однаковим (дорівнює 0) для будь-якого значення, що перевищує 47. Мені потрібно безперервний розподіл ймовірностей.
s_sherly

2
@s_sherly - Це, мабуть, було б добре, якщо ви могли б відредагувати та уточнити своє питання краще, як і справді "ймовірність побачити більші значення" - як ви сказали, - це нуль для значень, що перевищують найвище значення в пулі .
мак
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.