Подгонять эмпирическое распределение к теоретическому с помощью Scipy (Python)? - PullRequest
104 голосов
/ 08 июля 2011

ВВЕДЕНИЕ: У меня есть список из более чем 30 000 значений в диапазоне от 0 до 47, например, [0,0,0,0, .., 1,1,1,1, ..., 2,2,2, 2, ..., 47 и т. Д.], Который является непрерывным распределением.

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

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

Есть ли способ реализовать такой анализ в Python (Scipy или Numpy)?Не могли бы вы привести примеры?

Спасибо!

Ответы [ 6 ]

156 голосов
/ 03 июня 2016

Распределительная арматура с ошибкой суммы квадратов (SSE)

Это обновление и модификация ответа Саулло , в котором используется полный список текущих scipy.stats распределений и возвращает распределение с наименьшим SSE между гистограммой распределения и гистограммой данных.

Пример подгонки

Использование набора данных Эль-Ниньо из statsmodels, распределения соответствуют и определяется ошибка.Распределение с наименьшей ошибкой возвращается.

Все дистрибутивы

All Fitted Distributions

Best Fit Distribution

Best Fit Distribution

Пример кода

%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')
122 голосов
/ 20 мая 2013

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

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

Ссылки:

- Распределение фитингов, качество посадки, значение p. Возможно ли это сделать с помощью 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'] 
10 голосов
/ 12 сентября 2013

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

1, то есть то, которое дает вам наибольшую вероятность записи.

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

3, тот, который максимизирует байесовскую апостериорную вероятность.(см. вики: http://en.wikipedia.org/wiki/Posterior_probability)

Конечно, если у вас уже есть дистрибутив, который должен описывать ваши данные (основанные на теориях в вашей конкретной области) и хотите придерживаться этого, вы пропустите шагопределение наиболее подходящего распределения.

scipy не содержит функции для расчета вероятности записи в журнал (хотя предусмотрен метод MLE), но жесткий код один прост: см. Вероятность встраиванияфункции плотности `scipy.stat.distributions` медленнее, чем предоставленная пользователем?

4 голосов
/ 08 июля 2011

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 голосов
/ 20 сентября 2011

Для меня это похоже на проблему оценки плотности вероятности.

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.

0 голосов
/ 08 июля 2011

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

...