Вопрос-Ответ

Fitting empirical distribution to theoretical ones with Scipy (Python)?

Подгонка эмпирического распределения к теоретическому с помощью Scipy (Python)?

ВВЕДЕНИЕ: У меня есть список из более чем 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)? Не могли бы вы привести какие-нибудь примеры?

Переведено автоматически
Ответ 1

Подгонка распределения с суммой квадратической ошибки (SSE)

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

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

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

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

Все подобранные распределения

Наиболее подходящее распределение

Наиболее подходящее распределение

Пример кода

%matplotlib inline

import warnings
import numpy as np
import pandas as pd
import scipy.stats as st
import statsmodels.api as sm
from scipy.stats._continuous_distns import _distn_names
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

# Best holders
best_distributions = []

# Estimate distribution parameters from data
for ii, distribution in enumerate([d for d in _distn_names if not d in ['levy_stable', 'studentized_range']]):

print("{:>3} / {:<3}: {}".format( ii+1, len(_distn_names), distribution ))

distribution = getattr(st, distribution)

# 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
best_distributions.append((distribution, params, sse))

except Exception:
pass


return sorted(best_distributions, key=lambda x:x[2])

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, density=True, alpha=0.5, color=list(matplotlib.rcParams['axes.prop_cycle'])[1]['color'])

# Save plot limits
dataYLim = ax.get_ylim()

# Find best fit distribution
best_distibutions = best_fit_distribution(data, 200, ax)
best_dist = best_distibutions[0]

# 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[0], best_dist[1])

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

param_names = (best_dist[0].shapes + ', loc, scale').split(', ') if best_dist[0].shapes else ['loc', 'scale']
param_str = ', '.join(['{}={:0.2f}'.format(k,v) for k,v in zip(param_names, best_dist[1])])
dist_str = '{}({})'.format(best_dist[0].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

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

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

import matplotlib.pyplot as plt
import numpy as np
import scipy
import scipy.stats
size = 30000
x = np.arange(size)
y = scipy.int_(np.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)
params = dist.fit(y)
arg = params[:-2]
loc = params[-2]
scale = params[-1]
if arg:
pdf_fitted = dist.pdf(x, *arg, loc=loc, scale=scale) * size
else:
pdf_fitted = dist.pdf(x, loc=loc, scale=scale) * size
plt.plot(pdf_fitted, label=dist_name)
plt.xlim(0,47)
plt.legend(loc='upper right')
plt.show()

Ссылки:

А вот список с названиями всех функций распределения, доступных в 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'] 
Ответ 3

Вы можете попробовать библиотеку distfit. Если у вас возникнут дополнительные вопросы, дайте мне знать, я также являюсь разработчиком этой библиотеки с открытым исходным кодом.

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

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

# 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 .

Более подробную информацию и примеры можно найти на страницах документации.

Ответ 4

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

1, тот, который дает вам наибольшую логарифмическую вероятность.

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

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

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

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

python numpy scipy