LoginSignup
15
16

More than 5 years have passed since last update.

とりあえず経験分布になんらかのモデルをフィットさせたい Python/Scipy

Last updated at Posted at 2019-04-02

とりあえず、分布にフィットさせたい

image.png

ガウス分布、t分布、いろいろありますが、正直どの分布を使ってモデリングすれば良いのかわからないときや、手っ取り早くそれっぽい分析をしたいときがあるかと思います。
そんなときに使えるコードを見つけたので、紹介します。

ほぼこれなんです→ https://stackoverflow.com/a/37616966

scipyの分布は82種類

調べてみたところ、scipyには82種類の分布が用意されてあり、どれを使ってもfit()で再尤度推定によるパラメータ調整ができるとのこと。

    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
    ]

image.png

↑どれにすれば良いんだ!?

答え、全部試して1番良いのを使う

残差平方和でフィットした82種類のモデルを評価して、それぞれの[分布名,分布パラメータ,誤差]を格納したリストを誤差の少ない順に返す、魔法の関数がありました。

image.png

#データからモデルを作る
def best_fit_distribution(data, bins=200, ax=None):
    """Model data by finding best fit distribution to data"""
    # データからヒストグラムを作成する
    y, x = np.histogram(data, bins=bins, density=True)
    x = (x + np.roll(x, -1))[:-1] / 2.0

    # 以下の分布でフィッティングする
    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
    ]

    # [分布名,分布パラメータ,誤差]を格納するためのリスト
    l = []
    i = 0
    print('starting|----------')
    print("progress|", end="")
    # 分布毎にそれぞれパラメータを推測してみる
    for distribution in DISTRIBUTIONS:

        # プログレスバー
        i += 1 
        progress = i/len(DISTRIBUTIONS)*10
        number = int(progress)
        if number > int((i-1)/len(DISTRIBUTIONS)*10):
            print("#", end="")
        # 分布によってはフィットできないこともあるので、
        # フィットできなければ、passする
        try:
            with warnings.catch_warnings():
                warnings.filterwarnings('ignore')

                # 分布をフィットさせる
                params = distribution.fit(data)

                # わかりやすい用にパラメータをばらばらにする
                arg = params[:-2]
                loc = params[-2]
                scale = params[-1]

                # フィットさせた確率密度関数を計算して、分布にフィットさせる
                pdf = distribution.pdf(x, loc=loc, scale=scale, *arg)

                # 残差平方和を誤差として計算する
                sse = np.sum(np.power(y - pdf, 2.0))

                # l
                l.append([
                    distribution.name,
                    params,
                    sse,
                ])
        except Exception:
            pass
    l.sort(key=lambda x: x[2])
    return l

使用例

楽にプロットするための関数とimportね↓

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
import sys

matplotlib.style.use('ggplot')

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

    # わかりやすい用にパラメータをばらばらにする
    arg = params[:-2]
    loc = params[-2]
    scale = params[-1]

    # 分布の開始と終わりを指定する
    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=locb, scale=scale) if arg else dist.ppf(0.99, loc=loc, scale=scale)

    # 確率密度関数を作って、Seriesに変換する
    x = np.linspace(start, end, size)
    y = dist.pdf(x, loc=loc, scale=scale, *arg)
    pdf = pd.Series(y, x)

    return pdf

# プロットするための関数
# めっちゃてきとうでごめんなさいw
def plot(i):
    fit_name, fit_params, fit_sse = fit_list[i]
    dist = getattr(st, fit_name)
    # Make PDF with best params 
    pdf = make_pdf(dist, fit_params)

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

    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')
    plt.show()

例として使うデータはstatsmodelsのエルニーニョデータセット。
こんな感じです。

# エルニーニョデータをロード
data = pd.Series(sm.datasets.elnino.load_pandas().data.set_index('YEAR').values.ravel())

# 確率密度関数と比較する用にヒストグラムを作成する
plt.figure(figsize=(12,8))
ax = data.plot(kind='hist', bins=50, density=True, alpha=0.5)

# あとで使うので、Y軸の情報を保存する
dataYLim = ax.get_ylim()

image.png

次に全部のモデルを試す関数を使います。ちょっと時間かかりますが、我慢強く待ってください。
僕は待ちきれなくて(というか動いているかどうか心配で)、stackoverflowにあったオリジナルにプログレスバーを加えました。笑

# 作った関数を使って、最適な分布を探す
fit_list = best_fit_distribution(data, 20, ax)
> starting|----------
> progress|##########

そして、プロット。
いくつか例としてぷろっとしますね。

plot(0)

image.png

plot(15)

image.png

plot(67)

image.png

15
16
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
16