pyMC ベイズ・確率プログラミング

統計の信頼性と予測的較正

注記

このノートブックは、PyMCに依存しないライブラリを使います。したがって、このノートブックで走らせるために、特別にインストールされる必要があります。追加のガイダンスは、以下のドロップダウンメニューを開いてください。

import os
import random

from io import StringIO

import arviz as az
import matplotlib.cm as cm
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import pymc as pm

from lifelines import KaplanMeierFitter, LogNormalFitter, WeibullFitter
from lifelines.utils import survival_table_from_events
from scipy.stats import binom, lognorm, norm, weibull_min
RANDOM_SEED = 8927
rng = np.random.default_rng(RANDOM_SEED)
az.style.use("arviz-darkgrid")
%config InlineBackend.figure_format = 'retina'

統計の信頼性

 私たちが、生産ラインで誤りを推論したいときは、大なり小なり工場、製品の原料、または、私たちが答えを探している疑問の特性に依存したデータセットのサンプルを取得するでしょう。しかし、全てのケースで、受け入れられる誤りの品質とコストの問題があります。
 信頼性の研究は、したがって、誤りが観測に重要であり、間違って特徴づけた調査で稼働するコストと誤りのコストの区間を説明する必要があります。疑問の定義の正確さと、実際のモデリングの性質の要求は、最重要です。

 時間と誤りのデータの鍵になる特徴は、この隔たった伝統的な統計的なまとめ方と推定技術と打ち切りのための方法です。このノートブックでは、私たちは、誤った時間の予測に焦点を当て、ベイジアンの見解の較正の予測区間を任意の頻度主義の代替と比較します。私たちは、『信頼データのための統計法」詳細は、(Meeker et al[2022]ハイパーリンク)の成果を書いて置きます。

予測の種類

 私たちは、次のような誤りの時間分布の異なる視点について知りたいでしょう。

  • 新しいアイテムの失敗のための時間
  • 将来のmユニットのサンプルのk個の失敗までの時間

 ノンパラメトリックで、これらの疑問の種類の評価に使うことができる説明する方法ですが、私たちが確率モデル、例えば、不定のθによってパラメータ化された誤り時間F(t:θ)の対数正規分布、を持つケースに焦点を当てるつもりです。

プレゼンテーションの構造

 私たちは、

  • 信頼データに関する累積密度関数CDFの非パラメトリックな推定を議論します。
  • いかに頻度主義また同じ関数のMLEが、私たちの予測区間に情報を与えることができるかを示します。
  • いかにベイジアンメソッドが稀な情報のケースで同じCDFの分析の引数のために使うことができるかを示します。

 いかにCDFの理解が信頼性設定の失敗のリスクを理解するのに役立つことができるかを理解することに焦点を当てます。特に、いかにCDFが、良く較正された予測区間を誘導するのに使うことができるかです。

誤り分布の例

 統計の信頼性の研究は、長いテイルの分布を基礎にした位置のスケールに焦点を当てることがあります。そこでは私たちは、正確に、どの分布が私たちの誤りの過程を説明するのかわかります、そして、次の誤りのための信頼区間は、正確に定義されます。

mu, sigma = 6, 0.3


def plot_ln_pi(mu, sigma, xy=(700, 75), title="Exact Prediction Interval for Known Lognormal"):
    failure_dist = lognorm(s=sigma, scale=np.exp(mu))
    samples = failure_dist.rvs(size=1000, random_state=100)
    fig, axs = plt.subplots(1, 3, figsize=(20, 8))
    axs = axs.flatten()
    axs[0].hist(samples, ec="black", color="slateblue", bins=30)
    axs[0].set_title(f"Failure Time Distribution: LN({mu}, {sigma})")
    count, bins_count = np.histogram(samples, bins=30)
    pdf = count / sum(count)
    cdf = np.cumsum(pdf)
    axs[1].plot(bins_count[1:], cdf, label="CDF", color="slateblue")
    axs[2].plot(bins_count[1:], 1 - cdf, label="Survival", color="slateblue")
    axs[2].legend()
    axs[1].legend()
    axs[1].set_title("Cumulative Density Function")
    axs[2].set_title("Survival Curve")

    lb = failure_dist.ppf(0.01)
    ub = failure_dist.ppf(0.99)
    axs[0].annotate(
        f"99% Prediction \nInterval: [{np.round(lb, 3)}, {np.round(ub, 3)}]",
        xy=(xy[0], xy[1] - 25),
        fontweight="bold",
    )
    axs[0].fill_betweenx(y=range(200), x1=lb, x2=ub, alpha=0.2, label="p99 PI", color="cyan")
    axs[1].fill_betweenx(y=range(2), x1=lb, x2=ub, alpha=0.2, label="p99 PI", color="cyan")
    axs[2].fill_betweenx(y=range(2), x1=lb, x2=ub, alpha=0.2, label="p99 PI", color="cyan")
    lb = failure_dist.ppf(0.025)
    ub = failure_dist.ppf(0.975)
    axs[0].annotate(
        f"95% Prediction \nInterval: [{np.round(lb, 3)}, {np.round(ub, 3)}]",
        xy=(xy[0], xy[1]),
        fontweight="bold",
    )
    axs[0].fill_betweenx(y=range(200), x1=lb, x2=ub, alpha=0.2, label="p95 PI", color="magenta")
    axs[1].fill_betweenx(y=range(2), x1=lb, x2=ub, alpha=0.2, label="p95 PI", color="magenta")
    axs[2].fill_betweenx(y=range(2), x1=lb, x2=ub, alpha=0.2, label="p95 PI", color="magenta")
    axs[0].legend()
    axs[1].legend()
    axs[2].legend()
    plt.suptitle(title, fontsize=20)


plot_ln_pi(mu, sigma)

データからの誤り分布の推定

 現実の世界では、私たちは、そうした正確な知識を持っていることは稀です。その代わりに、私たちは、はっきりしないデータとともに始めましょう。私たちは、最初に三つの工場間の熱交換に関する誤ったデータで実験します。そして、三つの工場の熱交換の時間の量の情報を集めます。

 データは故意に小さくしてあります。そのため、私たちは、時間ー誤りデータの評価を含む説明的な統計に焦点を当てることができます。特に、私たちは、生存関数と、実験に基づいたCDFを推定します。私たちは、その後、この分析のスタイルを大きなデータセットに一般化します。

熱交換データ

打ち切りデータの注記:観測が打ち切りされるかどうか、言い換えれば、私たちが熱交換の期間の全コースを観測したかどうか、どのように誤ったデータがフラグされるか、以下見ていきましょう。これは、誤った時間データの極めて重大な特徴です。統計結果が非常に簡単なので、すべてのアイテムのライフサイクルの全コースに関する研究ではないという事実によって、誤りが蔓延する推定に隔たります。最も蔓延している打ち切りの様式は、"right censored"データを呼ばれます。それは、私たちが、観測値のサブセットに対して、誤った出来事を認識しないことです。データの収集がしかるべき時より早く終わっているため、私たちの履歴は不完全になります。

 Left censoring(私たちはそれらの履歴の最初からアイテムの観測をしない) と、interval censoring(right とleft censoringの両方)は、発生できますが、共通していません。

heat_exchange_df
Years LowerYears UpperCensoring IndicatorCountPlantyear_intervalfailedcensoredrisk_set
001Left110,110100
112Interval211,22099
223Interval212,32097
33Right9513,0950
401Left220,120100
512Interval321,23098
62Right9522,0950
701Left130,110100
81Right9931,0990
actuarial_table = heat_exchange_df.groupby(["Years Upper"])[["failed", "risk_set"]].sum()
actuarial_table = actuarial_table.tail(3)


def greenwood_variance(df):
    ### Used to estimate the variance in the CDF
    n = len(df)
    ps = [df.iloc[i]["p_hat"] / (df.iloc[i]["risk_set"] * df.iloc[i]["1-p_hat"]) for i in range(n)]
    s = [(df.iloc[i]["S_hat"] ** 2) * np.sum(ps[0 : i + 1]) for i in range(n)]
    return s


def logit_transform_interval(df):
    ### Used for robustness in the estimation of the Confidence intervals in the CDF
    df["logit_CI_95_lb"] = df["F_hat"] / (
        df["F_hat"]
        + df["S_hat"] * np.exp((1.960 * df["Standard_Error"]) / (df["F_hat"] * df["S_hat"]))
    )
    df["logit_CI_95_ub"] = df["F_hat"] / (
        df["F_hat"]
        + df["S_hat"] / np.exp((1.960 * df["Standard_Error"]) / (df["F_hat"] * df["S_hat"]))
    )
    df["logit_CI_95_lb"] = np.where(df["logit_CI_95_lb"] < 0, 0, df["logit_CI_95_lb"])
    df["logit_CI_95_ub"] = np.where(df["logit_CI_95_ub"] > 1, 1, df["logit_CI_95_ub"])
    return df


def make_actuarial_table(actuarial_table):
    ### Actuarial lifetables are used to describe the nature of the risk over time and estimate
    actuarial_table["p_hat"] = actuarial_table["failed"] / actuarial_table["risk_set"]
    actuarial_table["1-p_hat"] = 1 - actuarial_table["p_hat"]
    actuarial_table["S_hat"] = actuarial_table["1-p_hat"].cumprod()
    actuarial_table["CH_hat"] = -np.log(actuarial_table["S_hat"])
    ### The Estimate of the CDF function
    actuarial_table["F_hat"] = 1 - actuarial_table["S_hat"]
    actuarial_table["V_hat"] = greenwood_variance(actuarial_table)
    actuarial_table["Standard_Error"] = np.sqrt(actuarial_table["V_hat"])
    actuarial_table["CI_95_lb"] = (
        actuarial_table["F_hat"] - actuarial_table["Standard_Error"] * 1.960
    )
    actuarial_table["CI_95_lb"] = np.where(
        actuarial_table["CI_95_lb"] < 0, 0, actuarial_table["CI_95_lb"]
    )
    actuarial_table["CI_95_ub"] = (
        actuarial_table["F_hat"] + actuarial_table["Standard_Error"] * 1.960
    )
    actuarial_table["CI_95_ub"] = np.where(
        actuarial_table["CI_95_ub"] > 1, 1, actuarial_table["CI_95_ub"]
    )
    actuarial_table["ploting_position"] = actuarial_table["F_hat"].rolling(1).median()
    actuarial_table = logit_transform_interval(actuarial_table)
    return actuarial_table


actuarial_table_heat = make_actuarial_table(actuarial_table)
actuarial_table_heat = actuarial_table_heat.reset_index()
actuarial_table_heat.rename({"Years Upper": "t"}, axis=1, inplace=True)
actuarial_table_heat["t"] = actuarial_table_heat["t"].astype(int)
actuarial_table_heat
tfailedrisk_setp_hat1-p_hatS_hatCH_hatF_hatV_hatStandard_ErrorCI_95_lbCI_95_ubploting_positionlogit_CI_95_lblogit_CI_95_ub
0143000.0133330.9866670.9866670.0134230.0133330.0000440.0066220.0003540.0263130.0133330.0050130.034977
1251970.0253810.9746190.9616240.0391310.0383760.0001640.0128020.0132830.0634680.0383760.0198180.073016
232970.0206190.9793810.9417970.0599650.0582030.0003500.0187010.0215500.0948560.0582030.0306940.107629

 この例に時間をかけることは価値があります。なぜなら、時間ー誤りモデリングの鍵となる量の推定を作ります。

 最初に、いかに私たちが、離散間隔の系列として時間を取り扱うか注意してください。データフォーマットは、時間上で総誤りを記録するので、離散で終端するフォーマットです。私たちは、誤りデータの他のフォーマット-アイテム終端フォーマット-を下で認識します。それは、各個別のアイテムのすべての終端とそれらの対応する状態を記録します。このフォーマットで、鍵の量は、誤ったアイテムのセットと、各終端のrisk_setです。そのほかの全ては、これらの事実から派生します。

 最初に私たちは、生産されてその後に故障する熱交換の数を3年の連続した期間で、三つの企業にわたって作ります。これは、年間の故障率:p_hatとその逆1-p_hatの推定を与えます。生存曲線S_hat、それは累積のハザードCH_hatと累積の密度関数F_hatの推定を回復するためにさらに変換される、を推定するために年間のコースをさらに組み合わせます。
 次に、私たちは、CDFの私たちの推定の不確実性の拡張を計るために、汚れた方法を取ります。この目的のために、私たちは、推定F_hatのV_hatの変数を推定するために、グリーンウッドの公式を使います。これは、標準のエラーと文献で推薦される信頼区間の二つの変数を私たちに与えます。

 私たちは、同じ技術をより大きいデータセットに適用し、いくつかのこれらの量を下に図示します。

衝撃吸収データ:頻度主義の信頼分析

 区間のある衝撃吸収データは、打ち切りされる一つのアイテム上で、または各時間間隔で失敗することで、言い換えると正常にテストされて削除されるか、誤りのために削除されるか、時間上で一定間隔でリスクセットが減少されて記録します。これは、終端のあるフォーマットデータの特別なケースです。

actuarial_table_shock = make_actuarial_table(shockabsorbers_events)
actuarial_table_shock

t
removedfailedcensoredentrancerisk_setp_hat1-p_hatS_hatCH_hatF_hatV_hatStandard_ErrorCI_95_lbCI_95_ubploting_positionlogit_CI_95_lblogit_CI_95_ub
00.000038380.0000001.0000001.000000-0.0000000.0000000.0000000.0000000.0000000.0000000.000000NaNNaN
16700.01100380.0263160.9736840.9736840.0266680.0263160.0006740.0259670.0000000.0772120.0263160.0036940.164570
26950.01010370.0000001.0000000.9736840.0266680.0263160.0006740.0259670.0000000.0772120.0263160.0036940.164570
37820.01010360.0000001.0000000.9736840.0266680.0263160.0006740.0259670.0000000.0772120.0263160.0036940.164570
48790.01010350.0000001.0000000.9736840.0266680.0263160.0006740.0259670.0000000.0772120.0263160.0036940.164570
59120.01100340.0294120.9705880.9450460.0565210.0549540.0014310.0378310.0000000.1291030.0549540.0137550.195137
69660.01010330.0000001.0000000.9450460.0565210.0549540.0014310.0378310.0000000.1291030.0549540.0137550.195137
79820.01010320.0000001.0000000.9450460.0565210.0549540.0014310.0378310.0000000.1291030.0549540.0137550.195137
811310.01010310.0000001.0000000.9450460.0565210.0549540.0014310.0378310.0000000.1291030.0549540.0137550.195137
911690.01010300.0000001.0000000.9450460.0565210.0549540.0014310.0378310.0000000.1291030.0549540.0137550.195137
1011850.01010290.0000001.0000000.9450460.0565210.0549540.0014310.0378310.0000000.1291030.0549540.0137550.195137
1111880.01010280.0000001.0000000.9450460.0565210.0549540.0014310.0378310.0000000.1291030.0549540.0137550.195137
1212140.01010270.0000001.0000000.9450460.0565210.0549540.0014310.0378310.0000000.1291030.0549540.0137550.195137
1312200.01100260.0384620.9615380.9086980.0957420.0913020.0025940.0509270.0000000.1911190.0913020.0292850.250730
1412870.01010250.0000001.0000000.9086980.0957420.0913020.0025940.0509270.0000000.1911190.0913020.0292850.250730
1513150.01100240.0416670.9583330.8708360.1383020.1291640.0037560.0612850.0090460.2492820.1291640.0485100.301435
1613330.01010230.0000001.0000000.8708360.1383020.1291640.0037560.0612850.0090460.2492820.1291640.0485100.301435
1713470.01010220.0000001.0000000.8708360.1383020.1291640.0037560.0612850.0090460.2492820.1291640.0485100.301435
1814040.01010210.0000001.0000000.8708360.1383020.1291640.0037560.0612850.0090460.2492820.1291640.0485100.301435
1914300.01100200.0500000.9500000.8272940.1895950.1727060.0051910.0720470.0314950.3139170.1727060.0720980.359338
2017520.01100190.0526320.9473680.7837520.2436620.2162480.0064550.0803420.0587780.3737170.2162480.0982540.411308
2117540.01010180.0000001.0000000.7837520.2436620.2162480.0064550.0803420.0587780.3737170.2162480.0982540.411308
2217890.01010170.0000001.0000000.7837520.2436620.2162480.0064550.0803420.0587780.3737170.2162480.0982540.411308
2318450.01010160.0000001.0000000.7837520.2436620.2162480.0064550.0803420.0587780.3737170.2162480.0982540.411308
2418960.01010150.0000001.0000000.7837520.2436620.2162480.0064550.0803420.0587780.3737170.2162480.0982540.411308
2518980.01010140.0000001.0000000.7837520.2436620.2162480.0064550.0803420.0587780.3737170.2162480.0982540.411308
2619410.01010130.0000001.0000000.7837520.2436620.2162480.0064550.0803420.0587780.3737170.2162480.0982540.411308
2720100.02110120.0833330.9166670.7184400.3306730.2815600.0093340.0966130.0921990.4709220.2815600.1332120.499846
2820150.01010100.0000001.0000000.7184400.3306730.2815600.0093340.0966130.0921990.4709220.2815600.1332120.499846
2920320.0101090.0000001.0000000.7184400.3306730.2815600.0093340.0966130.0921990.4709220.2815600.1332120.499846
3020900.0110080.1250000.8750000.6286350.4642050.3713650.0142030.1191770.1377780.6049530.3713650.1784420.616380
3122700.0110070.1428570.8571430.5388300.6183560.4611700.0173480.1317110.2030160.7193240.4611700.2324530.707495
3223490.0101060.0000001.0000000.5388300.6183560.4611700.0173480.1317110.2030160.7193240.4611700.2324530.707495
3326510.0110050.2000000.8000000.4310640.8414990.5689360.0203930.1428060.2890370.8488350.5689360.2965510.805150
3427410.0101040.0000001.0000000.4310640.8414990.5689360.0203930.1428060.2890370.8488350.5689360.2965510.805150
3527490.0110030.3333330.6666670.2873761.2469640.7126240.0228280.1510890.4164901.0000000.7126240.3686830.913267
3627890.0101020.0000001.0000000.2873761.2469640.7126240.0228280.1510890.4164901.0000000.7126240.3686830.913267
3728100.0101010.0000001.0000000.2873761.2469640.7126240.0228280.1510890.4164901.0000000.7126240.3686830.91326

誤ったデータのための最大尤度の適合

 私たちのデータの描写的な結果を得ることを追加して、私たちは、単一変数のモデルを誤った時間の分布への適合を推定するためにlifeテーブルデータを使うことができます。そうした適合は、もしよければ、予測区間の特別なセットの予測分布の明快な視界を持つことができます。ここで、私たちは、right-censoredデータのMLE適合を推定するために、Lifelinesパッケージから関数を使います。

lnf = LogNormalFitter().fit(actuarial_table_shock["t"] + 1e-25, actuarial_table_shock["failed"])
lnf.print_summary()
modellifelines.LogNormalFitter
number of observations38
number of events observed11
log-likelihood-124.20
hypothesismu_ != 0, sigma_ != 1
coefse(coef)coef lower 95%coef upper 95%cmp tozp-log2(p)
mu_10.130.149.8510.410.0071.08<0.005inf
sigma_0.530.110.310.741.00-4.26<0.00515.58
AIC252.41

 このモデルは一時的に実行するものですが、制限したデータにケースに注意する必要があります。例えば、熱交換データでは、私たちは、全体で11の不具合を持つ3年分のデータを持っています。非常に簡単なモデルなので、私たちは、完全に間違います。すぐに、私たちは、衝撃吸収データ、それはノンパラメトリックな描写で簡単な単一の変数でこのデータに適合します、に焦点を当てるでしょう。

plot_cdfs(actuarial_table_shock, title="Shock Absorber");

 これは、データへの正しい適合を示し、あなたが予期したように、衝撃吸収の不具合の変動がそれらを身につける年齢によって増加するということを示唆します。しかし、どのように私たちは、与えられた推定モデルで、予測を計測しましょう。

統計予測区間の近似の計算のためのプラグイン手続き

 私たちは、衝撃吸収データのCDFのために対数正規適合を推定するので、私たちは、それらの予測区間近似を図示することができます。私たちは、生産者として、低い境界が低すぎる場合、支払のリスクエクスポージャーと保証の請求を認識するかもしれないので、ここでの関心は、予測間隔の短い境界になる可能性が高いことです。

plot_ln_pi(
    10.128,
    0.526,
    xy=(40000, 120),
    title="Plug-in Estimate of Shock Absorber Failure Prediction Interval",
)

ブートストラップ較正とカバレッジの推定

 私たちは、ここで、信頼区間によって示唆されるカバレッジの推定を求めます。そうすることで、私たちは、95%の信頼区間で上位側と下位側のブートストラップ推定を行います。そして、最後にMLE適合のカバレッジ条件を評価します。私たちは、変動する重みの(ベイジアン)を使います。私たちは、カバレッジ統計を推定する二つの方法を報告します。最初は、知られた範囲からランダムな値のサンプリングを基礎にした実証的なカバレッジ。もし、それが上位と下位の境界の95%MLの間で、誤まれば、評価します。

 2番目の方法は、私たちは、評価カバレッジを使います。それは、95%上位と下位の境界でブートストラップ推定します。その後、どのくらいそれらのブートストラップ値が、MLE適合の条件をカバーするか評価します。

def bayes_boot(df, lb, ub, seed=100):
    w = np.random.dirichlet(np.ones(len(df)), 1)[0]
    lnf = LogNormalFitter().fit(df["t"] + 1e-25, df["failed"], weights=w)
    rv = lognorm(s=lnf.sigma_, scale=np.exp(lnf.mu_))
    ## Sample random choice from implied bootstrapped distribution
    choices = rv.rvs(1000)
    future = random.choice(choices)
    ## Check if choice is contained within the MLE 95% PI
    contained = (future >= lb) & (future <= ub)
    ## Record 95% interval of bootstrapped dist
    lb = rv.ppf(0.025)
    ub = rv.ppf(0.975)
    return lb, ub, contained, future, lnf.sigma_, lnf.mu_
CIs = [bayes_boot(actuarial_table_shock, 8928, 70188, seed=i) for i in range(1000)]
draws = pd.DataFrame(
    CIs, columns=["Lower Bound PI", "Upper Bound PI", "Contained", "future", "Sigma", "Mu"]
)
draws

Lower Bound PI
Upper Bound PIContainedfutureSigmaMu
011272.61399849582.094766True17292.8704340.37787810.070758
17991.81678993161.345993False70864.3134940.62652010.214131
210873.71596161376.105119True25782.8876360.44150610.159440
35752.613566125975.249573True32697.6060930.78736910.200625
410599.31144875336.965736True19192.3781600.50031110.249135
.....................
9959642.58546259419.909295True28284.6689180.46389610.083165
9966032.79157581430.341065False89564.0563790.66392510.006234
9978594.30002065307.015356True54894.0798650.51735710.072855
99810491.09245554912.865230True42479.6089210.42225810.085892
99910301.39570161648.310891True44760.0377350.45642810.134618

1000 rows × 6 columns

 私たちは、さらに予測分布の量を計算するために、これらのブートストラップ統計を使うことができます。私たちのケースでは、簡単なパラメトリックモデルのために、パラメトリックCDFを使います。しかし、私たちは私たちがもっと複雑なモデルの時にも、どのようにこの技術を使うことができるか示すために、私たちは、ここで、実証的なcdfを適合します。

def ecdf(sample):

    # convert sample to a numpy array, if it isn't already
    sample = np.atleast_1d(sample)

    # find the unique values and their corresponding counts
    quantiles, counts = np.unique(sample, return_counts=True)

    # take the cumulative sum of the counts and divide by the sample size to
    # get the cumulative probabilities between 0 and 1
    cumprob = np.cumsum(counts).astype(np.double) / sample.size

    return quantiles, cumprob


fig, axs = plt.subplots(1, 2, figsize=(20, 6))
axs = axs.flatten()
ax = axs[0]
ax1 = axs[1]
hist_data = []
for i in range(1000):
    samples = lognorm(s=draws.iloc[i]["Sigma"], scale=np.exp(draws.iloc[i]["Mu"])).rvs(1000)
    qe, pe = ecdf(samples)
    ax.plot(qe, pe, color="skyblue", alpha=0.2)
    lkup = dict(zip(pe, qe))
    hist_data.append([lkup[0.05]])
hist_data = pd.DataFrame(hist_data, columns=["p05"])
samples = lognorm(s=draws["Sigma"].mean(), scale=np.exp(draws["Mu"].mean())).rvs(1000)
qe, pe = ecdf(samples)
ax.plot(qe, pe, color="red", label="Expected CDF for Shock Absorbers Data")
ax.set_xlim(0, 30_000)
ax.set_title("Bootstrapped CDF functions for the Shock Absorbers Data", fontsize=20)
ax1.hist(hist_data["p05"], color="slateblue", ec="black", alpha=0.4, bins=30)
ax1.set_title("Estimate of Uncertainty in the 5% Failure Time", fontsize=20)
ax1.axvline(
    hist_data["p05"].quantile(0.025), color="cyan", label="Lower Bound CI for 5% failure time"
)
ax1.axvline(
    hist_data["p05"].quantile(0.975), color="cyan", label="Upper Bound CI for 5% failure time"
)
ax1.legend()
ax.legend();

 次に、私たちは、ブートストラップしたデータとカバレッジの二つの推定を図示します。私たちはMLE適合の条件でアーカイブします。言い換えると、私たちが、MLE適合を基礎にした予測区間のカバレッジを評価する時に、私たちは、量のためにブートストラップ推定することができます。

 これらのシミュレーションは、私たちがここで実行したよりももっと大きい数の時間を繰り返すべきです。必要とするカバレッジレベルをアーカイブするために、いかにMLE区間を変更できるかわかるのが明白になるでしょう。

ベアリングケージデータ:ベイジアン信頼分析の研究

 次に、私たちは、わずかにクリアでないパラメトリック適合を持つデータセットをみましょう。このデータの最も明瞭な特徴は、誤り記録の小さい量です。データは、各ピリオドのrisk_setの拡張を表示する係数の終端の様式が記録されます。

 私たちは、衝撃吸収データを推定するために、頻度主義の技術がどのようにうまく働くか示すために、この例で時間をかけます。ベアリングケージデータのケースで議論できます。特に、私たちは、ベイジアンアプローチで解決できる問題がどのように発生するか示します。

actuarial_table_bearings = make_actuarial_table(bearing_cage_events)

actuarial_table_bearings

tremovedfailedcensoredentrancerisk_setp_hat1-p_hatS_hatCH_hatF_hatV_hatStandard_ErrorCI_95_lbCI_95_ubploting_positionlogit_CI_95_lblogit_CI_95_ub
00.0000170317030.0000001.0000001.000000-0.0000000.0000000.000000e+000.0000000.00.0000000.000000NaNNaN
150.02880288017030.0000001.0000001.000000-0.0000000.0000000.000000e+000.0000000.00.0000000.000000NaNNaN
2150.01480148014150.0000001.0000001.000000-0.0000000.0000000.000000e+000.0000000.00.0000000.000000NaNNaN
3230.0110012670.0007890.9992110.9992110.0007900.0007896.224491e-070.0007890.00.0023360.0007890.0001110.005581
4250.01240124012660.0000001.0000000.9992110.0007900.0007896.224491e-070.0007890.00.0023360.0007890.0001110.005581
5334.0110011420.0008760.9991240.9983360.0016660.0016641.386254e-060.0011770.00.0039720.0016640.0004150.006641
6350.01110111011410.0000001.0000000.9983360.0016660.0016641.386254e-060.0011770.00.0039720.0016640.0004150.006641
7423.0110010300.0009710.9990290.9973670.0026370.0026332.322113e-060.0015240.00.0056200.0026330.0008460.008165
8450.01060106010290.0000001.0000000.9973670.0026370.0026332.322113e-060.0015240.00.0056200.0026330.0008460.008165
9550.09909909230.0000001.0000000.9973670.0026370.0026332.322113e-060.0015240.00.0056200.0026330.0008460.008165
10650.0110011008240.0000001.0000000.9973670.0026370.0026332.322113e-060.0015240.00.0056200.0026330.0008460.008165
11750.0114011407140.0000001.0000000.9973670.0026370.0026332.322113e-060.0015240.00.0056200.0026330.0008460.008165
12850.0119011906000.0000001.0000000.9973670.0026370.0026332.322113e-060.0015240.00.0056200.0026330.0008460.008165
13950.0127012704810.0000001.0000000.9973670.0026370.0026332.322113e-060.0015240.00.0056200.0026330.0008460.008165
14990.011003540.0028250.9971750.9945490.0054660.0054511.022444e-050.0031980.00.0117180.0054510.0017220.017117
151009.011003530.0028330.9971670.9917320.0083030.0082681.808196e-050.0042520.00.0166030.0082680.0030080.022519
161050.0123012303520.0000001.0000000.9917320.0083030.0082681.808196e-050.0042520.00.0166030.0082680.0030080.022519
171150.09309302290.0000001.0000000.9917320.0083030.0082681.808196e-050.0042520.00.0166030.0082680.0030080.022519
181250.04704701360.0000001.0000000.9917320.0083030.0082681.808196e-050.0042520.00.0166030.0082680.0030080.022519
191350.0410410890.0000001.0000000.9917320.0083030.0082681.808196e-050.0042520.00.0166030.0082680.0030080.022519
201450.0270270480.0000001.0000000.9917320.0083030.0082681.808196e-050.0042520.00.0166030.0082680.0030080.022519
211510.01100210.0476190.9523810.9445060.0570930.0554942.140430e-030.0462650.00.1461730.0554940.0103080.248927
221550.0110110200.0000001.0000000.9445060.0570930.0554942.140430e-030.0462650.00.1461730.0554940.0103080.248927
231650.0606090.0000001.0000000.9445060.0570930.0554942.140430e-030.0462650.00.1461730.0554940.0103080.248927
241850.0101030.0000001.0000000.9445060.0570930.0554942.140430e-030.0462650.00.1461730.0554940.0103080.248927
252050.0202020.0000001.0000000.9445060.0570930.0554942.140430e-030.0462650.00.1461730.0554940.0103080.248927

 単一の変数または、ノンパラメトリックCDFを推定するために、私たちは、ピリオドフォーマットデータをアイテムーピリオドフォーマットに分解する必要があります。

アイテムピリオドフォーマット

item_period = bearing_cage_df["Hours"].to_list() * bearing_cage_df["Count"].sum()
ids = [[i] * 25 for i in range(bearing_cage_df["Count"].sum())]
ids = [int(i) for l in ids for i in l]
item_period_bearing_cage = pd.DataFrame(item_period, columns=["t"])
item_period_bearing_cage["id"] = ids
item_period_bearing_cage["failed"] = np.zeros(len(item_period_bearing_cage))

## Censor appropriate number of ids
unique_ids = item_period_bearing_cage["id"].unique()
censored = bearing_cage_df[bearing_cage_df["Censoring Indicator"] == "Censored"]
i = 0
stack = []
for hour, count, idx in zip(censored["Hours"], censored["Count"], censored["Count"].cumsum()):
    temp = item_period_bearing_cage[
        item_period_bearing_cage["id"].isin(unique_ids[i:idx])
        & (item_period_bearing_cage["t"] == hour)
    ]
    stack.append(temp)
    i = idx

censored_clean = pd.concat(stack)

### Add  appropriate number of failings
stack = []
unique_times = censored_clean["t"].unique()
for id, fail_time in zip(
    [9999, 9998, 9997, 9996, 9995, 9994],
    bearing_cage_df[bearing_cage_df["failed"] == 1]["t"].values,
):
    temp = pd.DataFrame(unique_times[unique_times < fail_time], columns=["t"])
    temp["id"] = id
    temp["failed"] = 0
    temp = pd.concat([temp, pd.DataFrame({"t": [fail_time], "id": [id], "failed": [1]}, index=[0])])
    stack.append(temp)

failed_clean = pd.concat(stack).sort_values(["id", "t"])
censored_clean
item_period_bearing_cage = pd.concat([failed_clean, censored_clean])
## Transpose for more concise visual
item_period_bearing_cage.head(30).T

0
123456789...4567890012
t50.0150.0250.0350.0450.0550.0650.0750.0850.0950.0...450.0550.0650.0750.0850.0950.01009.050.0150.0250.0
id9994.09994.09994.09994.09994.09994.09994.09994.09994.09994.0...9995.09995.09995.09995.09995.09995.09995.09996.09996.09996.0
failed0.00.00.00.00.00.00.00.00.00.0...0.00.00.00.00.00.01.00.00.00.0

3 rows × 30 columns

assert item_period_bearing_cage["id"].nunique() == 1703
assert item_period_bearing_cage["failed"].sum() == 6
assert item_period_bearing_cage[item_period_bearing_cage["t"] >= 1850]["id"].nunique() == 3

 私たちは、実証的なCDFを図示するので、私たちは、最大の高さ0.05に達しないy軸だけをみます。単純な(ナイーブ)MLE適合は、データの観測範囲の座標の外で、大きく間違います。

ax = plot_cdfs(
    actuarial_table_bearings,
    title="Bearings",
    dist_fits=False,
    xy=(20, 0.7),
    item_period=item_period_bearing_cage,
)

確率図示:制限された線形範囲のCDFの比較

 このセクションでは、私たちは、視覚的に"適合の正しさ"のチェックを実行できるように、線形のMLE適合の技術を使います。これらの図示の種類は、CDFを線形空間に変えるために、位置とスケールの分布に適用することができる変換に依拠します。

 対数正規とWeibull適合の両方、私たちは、ログの値tと、適当なCDF-1の関係として、それらのCDFを線形空間に表します。

 私たちは、ここでどのようにMLE適合が観測データの範囲をカバーしないか見ることができます。

信頼データのベイジアンモデリング

 私たちは、ここで、頻度主義とMLEフレームワークを使って、モデルの仕方とパラメトリックモデルの適合が信頼性の希薄さを視覚化するのを見ました。私たちはここで、同じスタイルの推論が、いかにベイジアンパラダイムをアーカイブすることができるか示します。
 MLEパラダイムのように、私たちは、打ち切りした尤度のモデルを必要とします。私たちが上で見たほとんどの対数位置分布にとって、尤度は、データポイントが時間ウィンドウで完全に観測されるか、または打ち切りされるかどうかに適切に依存するものとして、適用されるcdfΦと、pdfφの分布の組み合わせの関数として説明されます。

 ここで、δiは、観測が失敗するか正しい計測の観測であるかのインジケーターです。もっと複雑な計測の種類は計測した観測の性質に依存するCDFの同様の修正を含むことができます。

Weibull生存の直接のPyMC実装

 私たちは、事前分布の仕様の異なる二つのバージョンのモデルを適合します。一つは、データを覆う不明瞭な一様事前分布をとり、もう一つは、MLE適合に近接した事前分布を規定します。私たちは、下の観測データとモデルの較正にためにもっている事前分布の仕様の影響を示します。

def weibull_lccdf(y, alpha, beta):
    """Log complementary cdf of Weibull distribution."""
    return -((y / beta) ** alpha)


item_period_max = item_period_bearing_cage.groupby("id")[["t", "failed"]].max()
y = item_period_max["t"].values
censored = ~item_period_max["failed"].values.astype(bool)


priors = {"beta": [100, 15_000], "alpha": [4, 1, 0.02, 8]}
priors_informative = {"beta": [10_000, 500], "alpha": [2, 0.5, 0.02, 3]}


def make_model(p, info=False):
    with pm.Model() as model:

        if info:
            beta = pm.Normal("beta", p["beta"][0], p["beta"][1])
        else:
            beta = pm.Uniform("beta", p["beta"][0], p["beta"][1])
        alpha = pm.TruncatedNormal(
            "alpha", p["alpha"][0], p["alpha"][1], lower=p["alpha"][2], upper=p["alpha"][3]
        )

        y_obs = pm.Weibull("y_obs", alpha=alpha, beta=beta, observed=y[~censored])
        y_cens = pm.Potential("y_cens", weibull_lccdf(y[censored], alpha, beta))
        idata = pm.sample_prior_predictive()
        idata.extend(pm.sample(random_seed=100, target_accept=0.95))
        idata.extend(pm.sample_posterior_predictive(idata))

    return idata, model


idata, model = make_model(priors)
idata_informative, model = make_model(priors_informative, info=True)
idata

arviz.InferenceData

az.plot_trace(idata, kind="rank_vlines");
az.plot_trace(idata_informative, kind="rank_vlines");
az.summary(idata)
meansdhdi_3%hdi_97%mcse_meanmcse_sdess_bulkess_tailr_hat
beta8149.8352916.3783479.92913787.447111.29378.729612.0371.01.01
alpha2.6260.5581.7793.6310.0300.025616.0353.01.01
az.summary(idata_informative)
meansdhdi_3%hdi_97%mcse_meanmcse_sdess_bulkess_tailr_hat
beta10027.680526.9929013.85610964.30111.8488.4001979.02241.01.0
alpha2.1830.1681.8652.4850.0040.0032102.01870.01.0
joint_draws = az.extract(idata, num_samples=1000)[["alpha", "beta"]]
alphas = joint_draws["alpha"].values
betas = joint_draws["beta"].values

joint_draws_informative = az.extract(idata_informative, num_samples=1000)[["alpha", "beta"]]
alphas_informative = joint_draws_informative["alpha"].values
betas_informative = joint_draws_informative["beta"].values

mosaic = """AAAA
            BBCC"""
fig, axs = plt.subplot_mosaic(mosaic=mosaic, figsize=(20, 13))
axs = [axs[k] for k in axs.keys()]
ax = axs[0]
ax1 = axs[2]
ax2 = axs[1]
hist_data = []
for i in range(1000):
    draws = pm.draw(pm.Weibull.dist(alpha=alphas[i], beta=betas[i]), 1000)
    qe, pe = ecdf(draws)
    lkup = dict(zip(pe, qe))
    hist_data.append([lkup[0.1], lkup[0.05]])
    ax.plot(qe, pe, color="slateblue", alpha=0.1)
hist_data_info = []
for i in range(1000):
    draws = pm.draw(pm.Weibull.dist(alpha=alphas_informative[i], beta=betas_informative[i]), 1000)
    qe, pe = ecdf(draws)
    lkup = dict(zip(pe, qe))
    hist_data_info.append([lkup[0.1], lkup[0.05]])
    ax.plot(qe, pe, color="pink", alpha=0.1)
hist_data = pd.DataFrame(hist_data, columns=["p10", "p05"])
hist_data_info = pd.DataFrame(hist_data_info, columns=["p10", "p05"])
draws = pm.draw(pm.Weibull.dist(alpha=np.mean(alphas), beta=np.mean(betas)), 1000)
qe, pe = ecdf(draws)
ax.plot(qe, pe, color="purple", label="Expected CDF Uninformative Prior")
draws = pm.draw(
    pm.Weibull.dist(alpha=np.mean(alphas_informative), beta=np.mean(betas_informative)), 1000
)
qe, pe = ecdf(draws)
ax.plot(qe, pe, color="magenta", label="Expected CDF Informative Prior")
ax.plot(
    actuarial_table_bearings["t"],
    actuarial_table_bearings["logit_CI_95_ub"],
    "--",
    label="Non-Parametric CI UB",
    color="black",
)
ax.plot(
    actuarial_table_bearings["t"],
    actuarial_table_bearings["F_hat"],
    "-o",
    label="Non-Parametric CDF",
    color="black",
    alpha=1,
)
ax.plot(
    actuarial_table_bearings["t"],
    actuarial_table_bearings["logit_CI_95_lb"],
    "--",
    label="Non-Parametric CI LB",
    color="black",
)
ax.set_xlim(0, 2500)
ax.set_title(
    "Bayesian Estimation of Uncertainty in the Posterior Predictive CDF \n Informative and Non-Informative Priors",
    fontsize=20,
)
ax.set_ylabel("Fraction Failing")
ax.set_xlabel("Time")
ax1.hist(
    hist_data["p10"], bins=30, ec="black", color="skyblue", alpha=0.4, label="Uninformative Prior"
)
ax1.hist(
    hist_data_info["p10"],
    bins=30,
    ec="black",
    color="slateblue",
    alpha=0.4,
    label="Informative Prior",
)
ax1.set_title("Distribution of 10% failure Time", fontsize=20)
ax1.legend()
ax2.hist(
    hist_data["p05"], bins=30, ec="black", color="cyan", alpha=0.4, label="Uninformative Prior"
)
ax2.hist(
    hist_data_info["p05"], bins=30, ec="black", color="pink", alpha=0.4, label="Informative Prior"
)
ax2.legend()
ax2.set_title("Distribution of 5% failure Time", fontsize=20)
wbf = WeibullFitter().fit(item_period_bearing_cage["t"] + 1e-25, item_period_bearing_cage["failed"])
wbf.plot_cumulative_density(ax=ax, color="cyan", label="Weibull MLE Fit")
ax.legend()
ax.set_ylim(0, 0.25);

 私たちは、故意にぼんやりした事前分布によって駆動させたベイジアン不確実性の推定が、いかに、私たちのMLE適合より、もっと不確実性を包むか見ることができます。そして、情報のない事前分布は、5%と10%の誤り時間の幅広い予測分布を示唆します。情報のない事前分布のベイジアンモデルは、私たちのCDFのノンパラメトリック推定で不確実性を取り扱うのに良い仕事をするように見えます。しかし、さらなる情報なしで、もっと適切なモデルを教えることは困難です。

 各モデル適合の時間上の誤り率の具体的な推定は、私たちが希薄なデータを持つこの状況で特に極めて重要です。私たちは、いかに推定と10%の誤り時間の範囲がそれらの生産物にとって、最もらしいかについて、その問題の専門家たちに助言を求めることができる、感覚チェックは有意義です。

区間の誤り数の予測

 観測されたデータの誤りが極端に希薄であるため、私たちは、時間の観測範囲に対して、推定について非常に注意深くなければなりません。しかし、私たちは、私たちのcdfの低位のテイルの誤りの予測数について尋ねることができます。これは、このデータに他の視点を与えます。問題に関して専門家と議論するのに役立つことができます。

プラグイン推定

 サービスの基礎になる150と600時間の間に、幾つのベアリングが不良なるか知りたいことを想像してくだい。私たちは、推定したCDFと新しい将来のベアリング数を元にして計算することができます。私たちは、最初に、次を計算します。

 期間で発生する不良品の確率を設定するために、その後、期間の不良品の数のための予測ポイントは、risk_set*ρ で与えられます。

mle_fit = weibull_min(c=2, scale=10_000)
rho = mle_fit.cdf(600) - mle_fit.cdf(150) / (1 - mle_fit.cdf(150))
print("Rho:", rho)
print("N at Risk:", 1700)
print("Expected Number Failing in between 150 and 600 hours:", 1700 * rho)
print("Lower Bound 95% PI :", binom(1700, rho).ppf(0.05))
print("Upper Bound 95% PI:", binom(1700, rho).ppf(0.95))
Rho: 0.0033685024546080927
N at Risk: 1700
Expected Number Failing in between 150 and 600 hours: 5.7264541728337575
Lower Bound 95% PI : 2.0
Upper Bound 95% PI: 10.0

ベイズ事後分布の同じ手続きを適用

 私たちは、一様なモデルの分布を事後予測に使います。私たちは、ここで、時間間隔の不良品の数の95%予測期間の推定の不確実性を推論する方法を示します。この手続きの代替的な上のMLEで見たように、ブートストラップサンプリングから予測分布を生成することです。ブートストラップ手続きはMLE推定を使ってプラグイン手続きを承認し、そして事前分布の情報を規定することで柔軟性が欠如する傾向があります。

import xarray as xr

from xarray_einstats.stats import XrContinuousRV, XrDiscreteRV


def PI_failures(joint_draws, lp, up, n_at_risk):
    fit = XrContinuousRV(weibull_min, joint_draws["alpha"], scale=joint_draws["beta"])
    rho = fit.cdf(up) - fit.cdf(lp) / (1 - fit.cdf(lp))
    lub = XrDiscreteRV(binom, n_at_risk, rho).ppf([0.05, 0.95])
    lb, ub = lub.sel(quantile=0.05, drop=True), lub.sel(quantile=0.95, drop=True)
    point_prediction = n_at_risk * rho
    return xr.Dataset(
        {"rho": rho, "n_at_risk": n_at_risk, "lb": lb, "ub": ub, "expected": point_prediction}
    )


output_ds = PI_failures(joint_draws, 150, 600, 1700)
output_ds

xarray.Dataset

def cost_func(failures, power):
    ### Imagined cost function for failing item e.g. refunds required
    return np.power(failures, power)


mosaic = """AAAA
            BBCC"""
fig, axs = plt.subplot_mosaic(mosaic=mosaic, figsize=(20, 12))
axs = [axs[k] for k in axs.keys()]
ax = axs[0]
ax1 = axs[1]
ax2 = axs[2]

ax.scatter(
    joint_draws["alpha"],
    output_ds["expected"],
    c=joint_draws["beta"],
    cmap=cm.cool,
    alpha=0.3,
    label="Coloured by function of Beta values",
)
ax.legend()
ax.set_ylabel("Expected Failures")
ax.set_xlabel("Alpha")
ax.set_title(
    "Posterior Predictive Expected Failure Count between 150-600 hours \nas a function of Weibull(alpha, beta)",
    fontsize=20,
)

ax1.hist(
    output_ds["lb"],
    ec="black",
    color="slateblue",
    label="95% PI Lower Bound on Failure Count",
    alpha=0.3,
)
ax1.axvline(output_ds["lb"].mean(), label="Expected 95% PI Lower Bound on Failure Count")
ax1.axvline(output_ds["ub"].mean(), label="Expected 95% PI Upper Bound on Failure Count")
ax1.hist(
    output_ds["ub"],
    ec="black",
    color="cyan",
    label="95% PI Upper Bound on Failure Count",
    bins=20,
    alpha=0.3,
)
ax1.hist(
    output_ds["expected"], ec="black", color="pink", label="Expected Count of Failures", bins=20
)
ax1.set_title("Uncertainty in the Posterior Prediction Interval of Failure Counts", fontsize=20)
ax1.legend()

ax2.set_title("Expected Costs Distribution(s)  \nbased on implied Failure counts", fontsize=20)
ax2.hist(
    cost_func(output_ds["expected"], 2.3),
    label="Cost(failures,2)",
    color="royalblue",
    alpha=0.3,
    ec="black",
    bins=20,
)
ax2.hist(
    cost_func(output_ds["expected"], 2),
    label="Cost(failures,2.3)",
    color="red",
    alpha=0.5,
    ec="black",
    bins=20,
)
ax2.set_xlabel("$ cost")
# ax2.set_xlim(-60, 0)
ax2.legend();

 そのようなケースでモデルの選択は、極めて重大です。不良のプロファイルに関する決定は、問題の専門家によって情報を与えられなければならない傾向があります。なぜなら、そうした希薄なデータからの推定は、常に危険が伴います。もし、実際の不良品に付随するコストがあり、問題の専門知識が通常、サービスの600時間内で、2または7の不良品が推定されることがもっともらしいと言うことが、良い適切な値であれば、不確実性を理解することは、極めて重要です。

結論

 私たちは、頻度主義とベイジアンの視点からモデルの信頼性と分析の方法を見てきました。そしてノンパラメトリック推定に対して比較しました。私たちは、どのように予測期間がブートストラッピングとベイジアンの両方によって、鍵となる統計の数を推論できるかを示しました。私たちは、再サンプリングメソッドと事前の情報規定を通してこれらの予測区間を較正するためのアプローチを見てきました。問題へのこれらの視点は、補完的であり、その技術の選択は、イデオロギー上の約束ではなく、関心の疑問の要因によって駆動されるのが妥当です。

 特に、私たちは、どのようにベアリングデータへのMLE適合が、ベイズ分析の事前分布を作るために、満足できる最初の考えのアプローチを提供するのかを見てきました。私たちはまた、いかにその問題の専門知識が、綿密な検査のためのこれらの事前の影響と予測モデルから鍵となる量を推論することによって、誘導することができるかを見てきました。ベイズ予測区間の選択は、私たちは事前分布の推定を較正します。そして、これらの事前分布は、再度チェックされて適当な費用関数に対して分析されます。

製作者

著者:Nathaniel Forde 2022年1月9日 

参考文献

  • W.Q. Meeker, L.A. Escobar, and F.G Pascual. Statistical Methods for Reliability Data. Wiley, 2022.

-pyMC, ベイズ・確率プログラミング
-