2020年6月14日日曜日

渡辺澄夫

渡辺澄夫
http://watanabe-www.math.dis.titech.ac.jp/users/swatanab/index-j.html

データ解析
http://watanabe-www.math.dis.titech.ac.jp/users/swatanab/da2019.html

特異モデルにおけるベイズ検定と変化点発見への応用
http://watanabe-www.math.dis.titech.ac.jp/users/fujiwara/doc/fujiwara_ibis2006.ppt.pdf

Pythonや機械学習を学ぶ

機械学習の前に重要なデータ抽出・加工に便利なPythonライブラリ「pandas」の基本的な使い方のチュートリアル
https://www.atmarkit.co.jp/ait/articles/1802/13/news012.html


[Python入門]リストの操作 (1/4)
https://www.atmarkit.co.jp/ait/articles/1906/04/news009.html

時系列データへの回帰分析
https://logics-of-blue.com/time-series-regression/


【Python】pandasのDataframe操作
http://canisterism.hatenablog.com/entry/2018/01/07/150105


#特定のセルの値を取り出してseriesに
pythonでexcelファイル処理まとめ
https://qiita.com/hasepy/items/06d5d2e2b6495752442c


[Python] Excelで文字化けしないCSVファイルを書き出す
https://qiita.com/y4m3/items/674423b596284bbc7cf7

Pythonの日本語処理
http://www.wakayama-u.ac.jp/~kazama/lab/python/i18n.html
標準出力の文字コードと自動変換
https://www.javadrive.jp/python/japan/index2.html
文字コードの指定
https://www.javadrive.jp/python/japan/index1.html
Unicode文字列(ユニコード文字列)
https://www.javadrive.jp/python/string/index5.html

日本語文字列コード問題まとめ
http://python.matrix.jp/pages/tips/string/encoding.html

python3で日本語を含むURL(日本語URL)にurllibでアクセスする際、htmlで勝手にencodeされてエラーするので回避策をメモ
https://qiita.com/mix/items/87d094414e46f857de45

日本語を含むURLでつまづく
http://mankuro.xyz/blog/2017/04/25/japanese-url/
単一ドメインを走査する(URLに日本語を含む場合)
http://nnpo.hatenablog.com/entry/2016/12/13/002540
PythonでURLエンコード/デコード
http://shuzo-kino.hateblo.jp/entry/2016/10/22/224034

pythonでutf-8日本語文字列を、URIエンコード・URLデコードする
http://linux.oboe-gaki.com/archives/000333.html
Pythonでマルチバイト文字を扱う際に気をつける点。
https://gist.github.com/devlights/4561968
Python と文字コード
http://www.kabipan.com/computer/python/unicode.html


[Python] urllib.parseによるURLエンコード/デコードの方法
https://hibiki-press.tech/learn_prog/python/url-encoding/2804



機械学習

第1回 難しくない! PyTorchでニューラルネットワークの基本
https://www.atmarkit.co.jp/ait/articles/2002/06/news025.html

AI・機械学習のための数学超入門 ― 前提知識は四則演算だけ! (1/4)
https://www.atmarkit.co.jp/ait/articles/2003/02/news023.html

機械学習やディープラーニングってどんなもの? (1/2)
https://www.atmarkit.co.jp/ait/articles/2003/24/news016.html

「機械学習の最先端」を効率的に情報収集! おすすめのメルマガ3選
https://www.atmarkit.co.jp/ait/articles/2006/11/news016.html

機械学習の手法13選 ー 初級者、中級者別に解説!
https://ainow.ai/2020/06/04/222969/

スターバックスはコーヒー事業者ではない ― データテック企業なのだ
https://ainow.ai/2020/05/28/222388/


平均二乗誤差

pytorchにはMSELossがある

criterion = torch.nn.MSELoss()

y = f(x)
loss = criterion(y, t)
print(loss.data)

自作するとこう
def mycriterion(x, y):
    result = (x - y) ** 2
    return result.sum() / len(result)


myloss = mycriterion(y, t)
print(myloss)





2020年5月30日土曜日

SSブログとGoogleフォトのアルバム

So-netブログ改めSSブログは、Googleフォトの画像を埋め込むことができる。
ブログ側にてアップした画像は、以前は同じアルバムに入ってた。
2019年3月ごろからはアップしたタイミングごとに別のアルバムになった。

2020年3月ごろから、ブログ閲覧者はGoogleアカウントにサインインしないと画像が見れなくなっていた、ことに3ヶ月くらいしてから気づいた。

見れるようにするには、Googleフォトにて、アルバムを共有設定にする必要がどうやらあるらしい。。。



SSブログやBloggerからアップした画像を削除したい場合


Googleアルバムアーカイブというところにアクセスする必要があるらしい



2020年5月24日日曜日

Pythonで体験するベイズ推論

Pythonで体験するベイズ推論 PyMCによるMCMC入門
キャメロン デビッドソン=ピロン(著) 玉木徹(翻訳)

PyMC3のインストール方法

岩波データサイエンス Vol.1
[特集] ベイズ推論とMCMCのフリーソフト のサポートページ
https://sites.google.com/site/iwanamidatascience/vol1/support_tokushu#TOC-PyMC3-

Python ヒッチハイク・ガイド

https://python-guideja.readthedocs.io/ja/latest/

PcMC3のコード

https://github.com/CamDavidsonPilon/Probabilistic-Programming-and-Bayesian-Methods-for-Hackers

Chapter1の一部をやってみた

https://github.com/sasasakaz/bayesian/blob/main/bayesian_estimation_for_change_point_detection.ipynb

2020年4月12日日曜日

2群の平均値の差の検定そしてベイズ推定

背景
  • 2群の平均値に差はあるか?を簡便におこなうならt検定
  • 中心極限定理とt分布の頑健性のおかげで、たいていの場面において有用
  • 計算簡単でコスパに優れた方法だが、有意差がある/ない以上の情報はもたらしてくれない
そこでベイズ推定
  • ここでは2群の観測データそれぞれがt分布から生成されると仮定したモデルを作成する
  • そしてこのモデルにより、2群の平均値の差の事後分布を得る
  • これにより2群の平均の差が一定値以上である確率など、豊かな情報を得ることができる
結論
  • ベイズ推定においても、2群の平均の差の平均値や区間推定は、Welchのt検定とほぼ同じ結果が得られた
  • 弱情報事前分布の使用により事後分布はほぼ尤度だけで形作られ、しかも単峰だったため
  • 不確かさを扱いながらの推定は手間を要するが、手間をかけたぶん実務面での問いに答えやすい情報を得られた



シナリオ

  • 背景: カフェチェーンの店頭ディスプレイ改善施策
    • あるカフェチェーンで、店内POP(販促物)を刷新することで「ラテ」の売上拡大を企図している
    • 立地や客層、売上規模、ラテの販売動向が似通っている2店舗を対象に、ABテストを模した実験をおこなった
  • データ構造と検証項目
    • 2店それぞれの14日間のラテ注文単価データ(日次ラテ売上金額 / 日次ラテ注文数)
    • ラテのサイズやトッピングの有無などにより変動しやすい売上金額ではなく注文単価で比較
    • 処置(テスト店舗での新POP)は結果変数(ラテの日次注文単価)に影響してそうかを検証したい

観測データ

A群(Control)[465.0, 458.6, 466.5, 475.2, 457.7, 457.7, 475.8, 467.7, 455.3, 465.4, 455.4, 455.3, 462.4, 440.9]

B群(Test)[454.1, 471.6, 464.8, 484.7, 466.4, 458.8, 502.0, 476.6, 481.0, 458.6, 471.8, 481.7, 462.7, 485.6]

Welch t-test

Group A: mean=461.34, unbiased sample variance=81.62, n=14
Group B: mean=472.89, unbiased sample variance=174.83, n=14
calculated degree of freedom: 23.0

Welch's t_test Results t-value: 2.70 p-value: 0.0128 average diff: 11.55 95%CI: (2.70, 20.41)




統計モデリングのスタンス

  • 統計や機械学習においては、今手元にある観測データは、なんらかの確率分布から生成されたという考え方をする。

  • 例えばこのカフェの場合、ラテ注文者の金額は確率的に決まる(例えば10人中5人くらいは400円の注文をする)という考え方。

  • 例えば実際の注文額(観測データ)は、平均400円、ばらつき50円くらいの正規分布から発生したのだろうといった捉え方ができる。

  • 確率分布とパラメータ

    • ただし真の確率分布は誰にもわからない(本当は平均380円だったのかも?本当はもっと複雑な形状の分布だったのかも?)。
    • 分析者はなるべく真の確率分布に近しそうな確率モデルを構築する。この確率モデルの形状を決めるのがパラメータ。
    • 真の確率分布がわからない以上、適切な確率モデルもパラメータも分析者にはわからない。
    • 一方、手元には観測データがある。事実としてここにあるからには確からしいであろうと思える。
    • そこで、観測データは確からしいであろうを出発点として、このデータを生み出したパラメータを(観測データの生成メカニズムとは)逆向きに探るアプローチをとる。
  • パラメータの選定

    • 手元の観測データを得るのにちょうどいいパラメータもあれば、そうでないものもある。
    • 良し悪しの判断に用いるのは、データとパラメータの適合度合いを表す指標(likelihood 尤度)。
    • データを固定して、パラメータをいろいろと変化させたときに、尤度がどう変わるかをグラフや数式で表現したのが尤度関数(Likelihood Function)。
  • 古典的統計学の場合

    • 尤度が一番しっくりくる箇所を特定してパラメータを推定する(例えば二次関数を微分して極値を求める最尤法など)。t検定の考え方もこれに準ずる。
    • 計算が速く推定結果がわかりやすい。しかしそのピンポイントのわかりやすさが仇になる場合もある。
  • 不確実性

    • Welchのt検定の結果、新POP導入でTest店のラテ注文単価はControl店を平均的に上回っていたことがわかった。
    • だが、14日間のテスト期間中には注文単価が同程度だった日や下回っている日もあった。
    • t検定が教えてくれるのは「平均して効果があったかどうか」という1点。
    • 14日間という限られた観測データから得られた推定値が、不確実性をどの程度含んでいるかは見えない。
    • テスト期間の傾向が今後も続くのか、他店導入時に裏目に出るリスク(単価が下がる確率)はどのくらいかなど実務面での問いには答えにくい。
  • パラメータの不確かさを含んだ推定

    • であれば、今手元にある限られた観測データから生じる「パラメータの不確かさ」を含んだままに推定してみる。
    • これにより、まだ見ぬ場面における不確かさを確率的に表現しやすい、使い勝手のいい推定結果を得ることができるのではないか。
    • そこで、尤度以外の情報(すでにわかっている知識、過去経験など)も駆使しながら不確かさをうまく扱えるベイズ推定をおこなう。
  • ベイズ推定の場合

    • ベイズ推定では探索によって得られるパラメータを確率分布(事後分布)で表す。
    • メリットとして、推定値の不確かさを自然に表現しやすく、実務面での問いにも答えやすい情報を得られる。
    1. まずパラメータの探索範囲を確率分布(事前分布)で仮定する。
    2. 次に手元の観測データと事前分布を足がかりにして探索する。
    3. こうしてパラメータの不確かさを考慮した推定値(事後分布)を得る。
       ※事前分布に、データから得られた尤度(情報)を掛け合わせる(更新する)ことで、事後分布を得る

なお尤度はあくまで分析者が仮定した確率モデルのパラメータとの適合度合いを表すだけ。観測データとパラメータとの辻褄がもっとも合う尤度だったからといって、最適解のパラメータであることを常時保証してくれるわけではない。




Bayesian Model Definition

  • ベイズ推定のモデリングで分析者が設定すること

    • 観測データの生成プロセス(尤度関数となる確率分布の選定): 観測データがどんなルール(確率モデル)から生まれたか
    • 選んだ確率モデルの形状を決めるパラメータの事前分布: 確率モデルの平均やバラツキをコントロールするパラメータの探索範囲
  • 観測データの生成プロセス

    • 2群の観測データそれぞれが「t分布」から生成されると想定
      • t分布は、平均mu 標準偏差std 自由度ν(ニュー)の3つのパラメータで形状が決まる
      • t分布は、正規分布よりも両端が厚いぶん、観測データに極端な値があった場合に、たまたま起きたノイズ(標本誤差)として処理しやすい
      • t分布は、νが大きくなるにつれて正規分布に近似していく(t分布を想定することで正規分布をも包含できる)
      • t分布にすることで、小サンプルでも標本誤差を軽減しやすく、不確かさを考慮した「頑健な」推定がしやすい
  • 各パラメータの事前分布

    • muとstd共通
      • muとstdの事前分布はかなり広めに設定している(弱情報事前分布 Weakly Informative Prior)
      • 真の値がどのあたりに位置するか確からしい情報がない(観測データを最優先したい、あまり影響与えたくない)との考えを反映
      • この場合、事後分布はほぼ観測データの情報(尤度)だけで形作られる(ので推定結果は検定の結果と近しくなりやすい)
    • mu
      • 観測データ全体での平均と、観測データ全体の標準偏差を2倍した標準偏差からなる、裾が長い正規分布としている
    • std
      • 観測データ全体のスケールから機械的に算出した、範囲広めの一様分布としている
      • 下限は0付近ギリギリまで探索できるように設定(標準偏差は0以下にはならない、0近辺に張り付く可能性ある)
      • 上限は現実的範囲に制限
    • ν(ニュー)
      • 頑健な推定のためにνの値が小さい(=両端が厚い分布になる)方に高い事前確率を割り当てたい
      • 0をピークに右に裾が長い指数分布は好都合だが、νは0より大きい必要がある。またt分布はνが30を超えるとほぼ正規分布になる(ν=1はCauchy分布と同じ)
      • 以上から平均29(ラムダλ=1/29)の指数分布を右に1だけシフトしている
      • これにより観測データの外れ値を許容しやすいν=1近辺(典型的なt分布)から、ν=30近辺(ほぼ正規分布)まで広く探索できる
  • 関心のある指標自体もモデリング

    • 観測データの生成プロセスを推定するついでに、結果の判断や意思決定に有用な情報もモデル化する
    • ここでは以下4指標をDeterministic (各種パラメータから導出される決定論的変数)に推定する
      • 平均の差分: リフト差分(Test群の平均 - Control群の平均)
      • 標準偏差の差分: バラツキの差(Test群の標準偏差 - Control群の標準偏差)
      • 効果量: 差の大きさがバラツキに対してどれだけインパクト(大きな効果、意味を持つ差)があるか
      • 平均の変化率: リフトの比率(Control群を基準としたときの相対比率 1=100%すなわち等倍)
# Model Definition

# x_A:Control, x_B:Test
x_A = [465.0, 458.6, 466.5, 475.2, 457.7, 457.7, 475.8, 467.7, 455.3, 465.4, 455.4, 455.3, 462.4, 440.9]
x_B = [454.1, 471.6, 464.8, 484.7, 466.4, 458.8, 502.0, 476.6, 481.0, 458.6, 471.8, 481.7, 462.7, 485.6]

# prior distributions of mu 観測データの全体から設定
combined_data = np.concatenate([x_A, x_B])
pooled_mean = combined_data.mean()
pooled_std = combined_data.std() * 2

# prior distributions of sigma データのスケールに応じて動的に設定
std_low = pooled_std * 0.001
std_high = pooled_std * 10

# prior distributions of nu クルシュケさんのベストプラクティスに準じる
lambda_kruschke = 29

# 平均と標準偏差それぞれの2群間差分、効果量、平均の相対比率についてもモデル作成

with pm.Model() as model_weakly:
    mu_A = pm.Normal('mu_A', mu=pooled_mean, sigma=pooled_std)
    mu_B = pm.Normal('mu_B', mu=pooled_mean, sigma=pooled_std)
    # muが負の値を取るのを完全に防ぐなら、TruncatedNormalを使いlowerを指定する方法もあり
    # lower=0よりは、ドメイン知識を反映して、ラテの最低価格にするのがより現実的
    # mu_A = pm.TruncatedNormal('mu_A', mu=pooled_mean, sigma=pooled_std, lower=0)
    # mu_B = pm.TruncatedNormal('mu_B', mu=pooled_mean, sigma=pooled_std, lower=0)

    std_A = pm.Uniform('std_A', lower=std_low, upper=std_high)
    std_B = pm.Uniform('std_B', lower=std_low, upper=std_high)
    nu_kruschke = pm.Exponential('nu', lam=1/lambda_kruschke) + 1

    obs_A = pm.StudentT('obs_A', mu=mu_A, sigma=std_A, nu=nu_kruschke, observed=x_A)
    obs_B = pm.StudentT('obs_B', mu=mu_B, sigma=std_B, nu=nu_kruschke, observed=x_B)

    mu_diff = pm.Deterministic('mean_diff', mu_B - mu_A)
    mu_diff_rr = pm.Deterministic('relative_mean_diff', mu_diff / mu_A)
    std_diff = pm.Deterministic('std_diff', std_B - std_A)
    effect_size = pm.Deterministic('effect_size', mu_diff / np.sqrt((std_A**2 + std_B**2) / 2))


# MCMC Sampling
with model_weakly:
    prior_weakly = pm.sample_prior_predictive()
    trace_weakly = pm.sample(tune=2000, draws=2000, random_seed=42, return_inferencedata=True, chains=4, 
                            #  target_accept=0.95,  # Uncomment if divergences occur (Raise to 0.9-0.95)
                             idata_kwargs={"log_likelihood": True})  # モデル比較に使用する対数尤度を保存
    posterior_predictive_weakly = pm.sample_posterior_predictive(trace_weakly)



Sampling Quality Check

  • ベイズ推定においては、サンプリング(マルコフ連鎖)が正常に機能し、事後分布に正しく収束しているかを確認する必要がある

  • trace_plotとsummaryを確認し、基準値をクリアしていれば適切な推定結果と解釈しやすい

  • trace_plotによる視覚的チェック

    • 左側はKDE(カーネル密度推定)による事後分布の形状、右側はサンプリングの軌跡(トレースライン)。いずれのグラフもchainsで指定した本数(例: 4本)の線がプロットされる
    • 左側のグラフでは4本の線がだいたい同じ形に、右側のグラフでは毛虫のような見た目(縦軸の値が一定範囲を何度も行き来して変動幅がだいたい同じくらい)になっていれば概ね問題ない
    • もしも左右グラフの下部に縦のバーが表示されている場合は、divergence 発散が起きていることを表す(縦バーの箇所ではサンプラーが空間の急斜面で足を踏み外してる、シミュレーションが真の軌道から大きく外れてる、つまりサンプリングがうまくいっていないことを表す)
      • 対処策1: target_acceptの値を0.90〜0.95程度に上げてみる(歩幅を細かくして慎重に探索させる)
      • 対処策2: tuneの回数を増やしてみる(ウォーミングアップ期間を伸ばして、地形の学習を確実にする)
  • summaryによる数値的チェック

    • r_hat
      • chain間の不一致度を表す
      • 目安として1.05以下(理想は1.00)であれば問題ない
      • そうなってない=各chainが同じ分布にたどり着いていない(収束していない)、つまりサンプリングがうまくいっていない
    • mcse_mean, mcse_sd(マルコフ連鎖モンテカルロ誤差)
      • サンプリングのブレが原因で生じる推定値の誤差(の平均と標準偏差)を表す
      • 目安としてmcse_meanが0.01以下(パラメータの変動範囲が0〜1の場合)、またはパラメータの標準偏差の5%以下(できれば2%以下)と十分に小さければ問題ない
    • ess_bulk, ess_tail(有効サンプルサイズ)
      • 自己相関を考慮した実質的なサンプル数
      • 事後分布の形状やHDIを安定して推定できているかを表す
      • 目安として400以上(できれば数千)確保されていれば問題ない
  • plot_autocorrによる自己相関チェック(任意)

    • summaryでess_bulkとr_hatが基準値をクリアしていれば自己相関は心配無用なので実行せずともよい
    • 問題ありの場合は、該当のパラメータのみを指定して実行(することでグラフが少ないぶん見やすい)
    • どこに問題あるかの原因の切り分けに役立つ
      • 特定のchainだけ自己相関が高い→tuneを増やす、サンプリングの初期化オプションを調整するなど
      • どのchainも自己相関が高い→モデルの構造を変える(パラメータ間の相関などが疑われる)
# trace_plot
az.plot_trace(trace_weakly)
plt.tight_layout();

# summary
az.summary(trace_weakly)

# mcse_meanが各パラメーターの標準偏差の5%以下(できれば2%以下)という基準値に収まっているかをチェック
summary_df = az.summary(trace_weakly)
summary_df['mcse_mean / sd'] = summary_df['mcse_mean'] / summary_df['sd']
summary_df['mcse_mean / sd (%)'] = (summary_df['mcse_mean'] / summary_df['sd']) * 100 
output_df = summary_df[['sd', 'mcse_mean', 'mcse_mean / sd', 'mcse_mean / sd (%)']]
rounded_df = output_df.round({'sd': 1, 'mcse_mean': 3, 'mcse_mean / sd': 4, 'mcse_mean / sd (%)': 2 })
rounded_df

# # 自己相関
# az.plot_autocorr(trace_weakly, var_names=["nu"], figsize=(8,3), textsize=10)
# plt.tight_layout();

# forestplot 
# HDIを一覧で視覚的に解釈しやすい(太線は50%HDI)
az.plot_forest(trace_weakly, figsize=(6,4), textsize=10);



PPC

  1. 事後分布のchainから任意のパラメータをランダムに取り出す(例: mu_A=450 std_A=20 nu=5)
  2. 取り出したパラメータから成る分布から、観測データと同じサンプルサイズの擬似データを生成する(t分布から14サンプル取り出す)
  3. このKDE(カーネル密度推定)をプロットしたのが青線
  4. ここまでを指定の数(num_pp_samples=100)繰り返す。観測データのサンプルサイズが多い場合や分布次第では、青線が帯のように密集状態になる
  5. 青線の平均をとったのがオレンジ点線(=モデルの平均的な予測)
  • 読み解き方

    • 黒線(観測データのKDE)が青線の変動範囲やオレンジ線と同じような位置、形であれば、モデルは観測データをよく近似していると解釈できる
    • 大きくズレていたら、分布の選定や事前分布の見直し、例えばt分布以外がよかったかな?事前分布の探索範囲を変えた方がよい?など、モデル修正方針の検討材料になる
    • あくまで今手元にある観測データの発生メカニズムを矛盾なくシミュレーションできているかを見てるだけ。観測データやモデル自体の確からしさを裏付けるものではない(観測データが偏ってる可能性や過学習リスクは残る)
  • 本件の場合

    • 観測データのサンプルサイズが少ないため、青線はあまり密集していない
    • 黒線がモデルの予測範囲(青線たちの広がり)の中に収まっており、いい感じのモデルといえそう
    • ダメなモデルだったら、黒線が青線たちの外側に大きく飛び出していたり、山の位置が全くズレてたりするだろう
    • muの事前分布をNormalにしたことで、ごく僅かに左端がマイナスの値を取る。これを100サンプルが引き当てた場合には、違和感のあるプロットになる。2群の平均値の差の事後分布にはほぼ影響ないが、どうにも気になる場合はTruncatedモデルを採用する手もある
az.plot_ppc(posterior_predictive_weakly, num_pp_samples=100, figsize=(10, 4));



Interpretation

  • Posterior Inference and Interpretation: 推論された事後分布の解釈
  • plot_posterior
    • 事後分布のKDE, mean,HDIを一覧表示。視覚的に判断しやすい
    • ref_valで指定した値を基準に、その値を上回る(下回る)確率を表示することも可能
  • 2群の平均値の差について詳しく
    • もっとも関心のあるところなので加工してグラフにいろいろ装飾している
# posterior
az.plot_posterior(trace_weakly, figsize=(10,6), textsize=10)
plt.tight_layout();

# posterior
# 差分、効果量、平均の変化率のmeanやhdiの表示

az.plot_posterior(trace_weakly, # hdi_prob=0.95,
                  var_names=["mean_diff", "relative_mean_diff"],
                  ref_val=0, color='#87ceeb', figsize=(8,2), textsize=10);

az.plot_posterior(trace_weakly, # hdi_prob=0.95,
                  var_names=["std_diff", "effect_size"],
                  ref_val=0, color='#87ceeb', figsize=(8,2), textsize=10);



# 下準備

# 多次元配列を一次元のサンプル配列として抽出
mu_diff_samples = az.extract(trace_weakly, var_names="mean_diff").values

# posterior_meanと94%HDIをグラフに表示するために値取得
posterior_mean = mu_diff_samples.mean()
hdi_lower = np.percentile(mu_diff_samples, 3)
hdi_upper = np.percentile(mu_diff_samples, 97)


# 2群間の平均値の差分の事後分布を表示
fig, ax = plt.subplots(figsize=(6,4))
ax.hist(mu_diff_samples, bins='auto', density=True,  # 縦軸を確率密度(全体の面積が1)にする
        color="tab:orange", alpha=0.5)
# bins='auto'にすると、データの四分位範囲とサンプルサイズから、外れ値に影響されにくい最適なビン幅を自動計算してくれる
# density=Trueにすると、ヒストグラムの面積の合計が1になる(確率密度関数と同じ状態)になる!

ax.axvline(0, lw=0.8, linestyle="dashed")  # ゼロのタテ線

# observed_meanと95%CIの描画(観測データの平均値と95%信頼区間)
# y位置を決める(グラフの一番下から少し上げた高さ)
ymin, ymax = ax.get_ylim()
ci_y = ymin + 0.07 * (ymax - ymin)
ax.scatter(diff_mean, ci_y, s=30, facecolors="white", edgecolors='gray', linewidths=2, label= f"Observed Average: {diff_mean:.2f}" , zorder=5)  # mean の点
ax.hlines(y=ci_y, xmin=ci_lower_w, xmax=ci_upper_w, color='gray', linewidth=3, label=f"95% CI: [{ci_lower_w:.2f}, {ci_upper_w:.2f}]")  # 95%CIの横線

# posterior_meanと94%HDIの描画(ベイズ推定での事後分布)
# y位置を決める(グラフの一番下から少し上げた高さ)
ymin, ymax = ax.get_ylim()
hdi_y = ymin + 0.03 * (ymax - ymin)
ax.scatter(posterior_mean, hdi_y, s=30, facecolors="white", edgecolors='#7f2704', linewidths=2, label= f"Posterior Average: {posterior_mean:.2f}" , zorder=5)  # mean の点
ax.hlines(y=hdi_y, xmin=hdi_lower, xmax=hdi_upper, color='#cc5500', linewidth=3, label=f"94% HDI: [{hdi_lower:.2f}, {hdi_upper:.2f}]")  # 94%HDIの横線

ax.legend(loc='upper right')
ax.set_xlabel("Difference in Average (JPY, Test - Control)", fontsize=10)
ax.set_ylabel("Probability Density (Area integrates to 1)", fontsize=10)
ax.set_yticklabels([])  # y軸の目盛りラベルを非表示にする(確率密度関数の値自体に意味を見出さなくていいように)
ax.tick_params(axis='y', which='both', length=0)  # y軸の目盛り線の長さを0にして非表示状態にする
# ax.get_yaxis().set_visible(False)  # これだとlabelも非表示になる
ax.set_title("Estimated Lift of Latte \n(t_dist Weakly Prior Model)", fontsize=12)
plt.tight_layout()
plt.show()

# print('observed average: {0:,.2f} 95%CI: ({1:,.2f}, {2:,.2f})'.format(diff_mean, ci_lower_w, ci_upper_w))
# print('posterior average: {0:,.2f} 94%HDI: ({1:,.2f}, {2:,.2f})'.format(posterior_mean, hdi_lower, hdi_upper))





Informative Model

  • ベイズ推定っぽく、事前分布に事前情報を適用したモデル
  • 他チェーンでは新POPで単価が50円上がったらしいという事前情報を反映した事前分布に書き換える
  • 「Test店はControl店よりもあらかじめ50円くらい高くなるポテンシャルを秘めているはずだ」との考えを反映したモデル
  • 尤度関数は引き続きt分布を想定
  • 想定される弱事前情報分布モデルからの変化
    • Test店の平均値と94%HDIが上ブレする。ベイズ推定は「他チェーンでは50円上がったらしいけど、今回のデータは11円か…じゃあ間をとってこれくらいが真の実力かな」という、データの不確実性と過去の信頼性を天秤にかけた、いわゆる「収縮効果(Shrinkage)」を起こす。
    • ひょっとすると94%HDIが狭まるかも。ベイズ推定は「情報が増えて、より確信を持って(不確かさを減らして)真の値を予測できるぞ」という状態になるので、事後分布の山が尖って、94%HDIの横棒が95%CIに比べて狭く引き締まる可能性がある。
# Model Definition

with pm.Model() as model_informative:
    # A店は、手元のデータの全体平均をベースに緩やかに置いておく
    mu_A = pm.Normal('mu_A', mu=pooled_mean, sigma=pooled_std)
    # B店(Test群)の事前分布に「50円プラス」の知識を組み込む。
    # 過去の実績(50円アップ)を信じつつ、ブレ(sigma)も考慮する
    prior_B_mean = pooled_mean + 50 
    mu_B = pm.Normal('mu_B', mu=prior_B_mean, sigma=pooled_std) 

    std_A = pm.Uniform('std_A', lower=std_low, upper=std_high)
    std_B = pm.Uniform('std_B', lower=std_low, upper=std_high)
    nu_kruschke = pm.Exponential('nu', lam=1/lambda_kruschke) + 1

    obs_A = pm.StudentT('obs_A', mu=mu_A, sigma=std_A, nu=nu_kruschke, observed=x_A)
    obs_B = pm.StudentT('obs_B', mu=mu_B, sigma=std_B, nu=nu_kruschke, observed=x_B)

    mu_diff = pm.Deterministic('mean_diff', mu_B - mu_A)
    mu_diff_rr = pm.Deterministic('relative_mean_diff', mu_diff / mu_A)
    std_diff = pm.Deterministic('std_diff', std_B - std_A)
    effect_size = pm.Deterministic('effect_size', mu_diff / np.sqrt((std_A**2 + std_B**2) / 2))



Truncated Model

  • 結果変数が負の値をとらないようにするモデル
  • 弱情報事前分布モデルも事前情報を組み込んだモデルも、muの事前分布をNormalにしていた
  • 推定への影響は軽微だが、現実世界のドメイン知識をより厳密に反映したモデルにする
  • 尤度関数は引き続きt分布を想定しつつ、muの事前分布をTruncated Normalに変更する
  • ドメイン知識を反映したモデルで納得感高めることを企図(副次的にPPCでの違和感ある出力回避可能)
with pm.Model() as model_truncated:
    # muが負の値を取るのを完全に防ぐなら、TruncatedNormalを使いlowerを指定する方法もあり
    # lower=0よりは、ドメイン知識を反映して、ラテの最低価格にするのがより現実的
    mu_A = pm.TruncatedNormal('mu_A', mu=pooled_mean, sigma=pooled_std, lower=0)
    mu_B = pm.TruncatedNormal('mu_B', mu=pooled_mean, sigma=pooled_std, lower=0)

    std_A = pm.Uniform('std_A', lower=std_low, upper=std_high)
    std_B = pm.Uniform('std_B', lower=std_low, upper=std_high)
    nu_kruschke = pm.Exponential('nu', lam=1/lambda_kruschke) + 1

    obs_A = pm.StudentT('obs_A', mu=mu_A, sigma=std_A, nu=nu_kruschke, observed=x_A)
    obs_B = pm.StudentT('obs_B', mu=mu_B, sigma=std_B, nu=nu_kruschke, observed=x_B)

    mu_diff = pm.Deterministic('mean_diff', mu_B - mu_A)
    mu_diff_rr = pm.Deterministic('relative_mean_diff', mu_diff / mu_A)
    std_diff = pm.Deterministic('std_diff', std_B - std_A)
    effect_size = pm.Deterministic('effect_size', mu_diff / np.sqrt((std_A**2 + std_B**2) / 2))



Gamma Model

  • Truncated Model同様に、結果変数が負の値をとらないようにするモデル
  • 尤度関数はガンマ分布を想定
  • 負の値をとらない連続値であれば、t分布と切断分布の組み合わせよりは、こちらの方が汎用性は高いかも
# Model Definition

with pm.Model() as model_gamma:
    # ガンマ分布のパラメータは「正の実数」でないとなので弱情報事前分布としてExponentialやHalfNormalを使うことが多い
    alpha_A = pm.Exponential('alpha_A', lam=0.1)
    beta_A = pm.Exponential('beta_A', lam=0.1)
    alpha_B = pm.Exponential('alpha_B', lam=0.1)
    beta_B = pm.Exponential('beta_B', lam=0.1)

    obs_A = pm.Gamma('obs_A', alpha=alpha_A, beta=beta_A, observed=x_A)
    obs_B = pm.Gamma('obs_B', alpha=alpha_B, beta=beta_B, observed=x_B)
    
    # ガンマ分布の数理特性から、事後分布の平均と標準偏差を逆算する(deterministic用)
    # 各群の期待値(平均値 = alpha / beta)の計算
    mu_A_calc = pm.Deterministic('mu_A_calc', alpha_A / beta_A)
    mu_B_calc = pm.Deterministic('mu_B_calc', alpha_B / beta_B)    
    # 各群の標準偏差(sd = sqrt(alpha) / beta)の計算
    std_A_calc = pm.Deterministic('std_A_calc', np.sqrt(alpha_A) / beta_A)
    std_B_calc = pm.Deterministic('std_B_calc', np.sqrt(alpha_B) / beta_B)

    # 平均と標準偏差それぞれの2群間差分、効果量、平均の相対比率についてもモデル作成
    mu_diff = pm.Deterministic('mean_diff', mu_B_calc - mu_A_calc)
    mu_diff_rr = pm.Deterministic('relative_mean_diff', mu_diff / mu_A_calc)
    std_diff = pm.Deterministic('std_diff', std_B_calc - std_A_calc)
    effect_size = pm.Deterministic('effect_size', mu_diff / np.sqrt((std_A_calc**2 + std_B_calc**2) / 2))



Compare Models

  • どの尤度関数(モデル)が優れているかを比較
  • 事後分布を丸ごと使って比較する
  • LOOやWAICといったベイズ専用の指標を計算する。
  • 単に手元のデータへのフィット度を見るだけでなく、「パラメータの不確実性も含めた上で、未来の予測能力が一番高いのはどのモデルか?」を厳密にスコアリングしてくれる。
  • 基本的には WAIC よりも LOO を使う方が推奨とされている。
    • 理由は数理的な頑健性(ロバストさ)にある。
    • WAIC: 計算がめちゃくちゃ高速だけど、データが少なかったり、極端な外れ値があったりすると、ペナルティ項(p_waic)の計算が少し不安定になって、スコアがブレやすいという弱点がある。
    • LOO(正確にはPSIS-LOO): WAIC とほぼ同じ予測力を測る指標なんだけど、内部で「外れ値に対する警告システム(Pareto k 統計量)」が動いてくれるから、14サンプルみたいな少サンプルデータに対して、より安全で正確に機能する。
# 各モデルのサンプリング結果を格納して、どの観測値の尤度で比較するかを変数名で指定する
model_compare = az.compare({
    "t_dist Weakly Prior Model": trace_weakly,
    "t_dist Informative Prior Model": trace_informative,
    "t_dist Truncated Model": trace_truncated,
    "Gamma Weakly Prior Model": trace_gamma
    },
    var_name="obs_B",
    # ic="waic"  # デフォルトはloo
)
az.plot_compare(model_compare);


model_compare

表の見方

  • rank: どのモデルが優秀か。
    • 予測性能がいい順番。
  • elpd_loo(elpd_waic): 予測性能のトータルスコア。
    • 期待対数予測密度(対数予測密度の期待値 Expected Log Pointwise Predictive Density)。値が大きい、つまり0に近いほど予測性能が優秀。未来のデータをどれだけ精度よく予測できるかを表す。
    • t分布たちはほぼ互角だけど、ガンマ分布はかなり低いスコア。つまり「14サンプルの手元のデータに対しては、t分布のほうが圧倒的に座りが良かった」ということを意味している
  • p_loo(p_waic): 有効パラメータ数。
    • モデルの複雑さ(過学習のしやすさ、ペナルティの大きさ)を表す。
    • パラメータ数が多かったり、観測データに無理やり形を合わせよう(過剰適合)とすると、この数値が大きくなりelpdが減点される仕組み。
    • t分布勢に対してガンマ分布は小さい。ガンマ分布モデルは無駄な複雑さがなく、シンプルにデータを表現していることを示している
  • elpd_diff: 1位との予測スコアの差分。
    • 差が4以上あると有意な差とみなされることが多い
    • t分布たちの差分は微量なので1位とほぼ同等と言える。ガンマ分布はずいぶん離れており、今回データにおいては予測力不足と言える。
  • weight: 予測ブレンドの重み付け
    • もしこれらのモデルを合体させて未来を予測するなら、どのモデルの意見を何割信じるべき?というブレンド比率(0〜1)。
    • LOOの場合(内部ではスタッキング法という技術が使われることが多い)、モデルの実力が伯仲していたときに0.6と0.4みたいに綺麗に分散して出力されやすい(複数モデルの予測パワーのバランスをリアルに反映しやすい。重み付けの計算がより安定的になる)
    • WAICの場合は微差であっても1位のモデルに 1.0(100%)と極端に全振りされやすい。
  • se: トータルスコアの標準誤差
    • 各モデルのelpd_loo(elpd_waic)が、サンプリングのブレによってどれくらいバラつく可能性があるかを表す不確実性の幅(標準誤差)。
  • dse: 差分の標準誤差(elpd_diff自体の標準誤差)。
    • elpd_diff ÷ dseの計算結果が2を超えているかどうかが、実務上の超重要ラインになる。
    • 2未満であれば、1位のモデルと数理的には完全に互角・誤差の範囲内。
    • ガンマ分布は2を大きく超えており、ガンマ分布が負けているのはサンプリングの偶然ではなく明確に実力差であると言い切れる。
  • warning: 計算が破綻していないかの警告
    • データの中にLOO(WAIC)の前提を揺るがすような過度なばらつき(極端な値)があり、指標の計算が数理的に変になってないかのフラグ。
    • True だと数値が信用できない、False だと健康に計算できたという意味。
    • LOOのwarning=Trueは、
      • 内部で パレートk統計量(Pareto k diagnostics)という厳密なデバッグシステムが動いて警告を出す。
      • データ1件1件に対して、このデータはモデルにとって予測しづらい外れ値になってないか?を全件チェックして、基準値(k>0.7)を超えたデータが1件でもあるとwarning=Trueになる。
    • WAICのwarningは、
      • データ全体のバラつきをなんとなく見て、なんか破綻してそうってアバウトに警告を出すだけ。
      • WAICではt分布勢はサンプルの少なさや裾の厚さのせいでTrueだが、ガンマ分布モデルは Falseだった。
      • ガンマ分布は非負のデータ構造を数理的に正しく捉えているから、警告を出さずにものすごく健康に計算できたという、ガンマ分布の構造的な美しさが証明されている。
  • scale: 計算の単位空間。
    • 対数尤度をどのスケールで計算したか。ArviZでは一律でlog(対数空間)が使われる

モデル評価

  • 4モデルの中ではTruncated Modelが最も予測能力が高いと評価された(僅差のためサンプリング次第でTOP3の並びは変わる可能性あり)
  • t分布の3モデルはほぼ同程度に対し、ガンマ分布モデルは予測能力が劣ると評価された
  • ガンマ分布モデルは微量ながらdivergenceが発生していた。また94%HDIの幅が広すぎる推定だった
  • 以上からすると、ガンマ分布モデルを採用するのはなさそう。t分布についてはどれを採用してもよさそうとの結論

2020年4月11日土曜日

ブランディングの科学

『How Brands Grow : What Marketers Don't Know』バイロン・シャープ

 
✅ 3行まとめ
  • ブランドの成長には、購入者数の拡大が重要である
  • 特に消費者の大多数を占めるライト/ノンユーザーへのアプローチが欠かせない
  • ブランドの独自性を追求してブランドを識別しやすくすることが購入につながり、ブランドを成長させる
⚠️ 背景事情
  • 人間の脳は注意力散漫でスルースキルが高い。顕著な手がかりがないブランドや広告は存在しないも同然。
  • 人間の記憶は不確かで不安定である。明瞭で一貫したマーケティングによる記憶強化が購入を後押しする。
  • 消費者が購買行動時に非合理な選択をするのは珍しくない。新規獲得による購入者数拡大なくしてブランドは成長しない。

概要

マーケティング界では常識のように語られている法則や理論の中には、実はなんの裏付けもなかったり、誇大表現がまかり通っているものがある。

著者バイロン・シャープ Byron Sharpは、従来のマーケティング理論をやみくもに盲信するのではなく、エビデンスに基づいたマーケティングを提唱。実際の販売データや市場調査データの裏付けとともに新たな法則・理論を提示しながら、より効率的なマーケティング活動をおこなうことの重要性を説く。

エビデンスに欠ける従来思想に固執して出血大サービスのマーケティングを続けるのか、それともデータで検証された確からしい見識を携えてブランドを有意義に成長させるのか。見なかったフリをして進化を止めるのか、それとも積極的に取り込んで自らをも成長させるのか。後者でいたい方には有益な書籍。




Ch1. エビデンスに基づいたマーケティング

Evidence-based Marketing

  • マーケティングの教科書に書かれた理論の大半は、エビデンスの記述がない
  • マーケティングの歴史は浅いため、表層的な印象や根拠なき言い伝えに頼っている面がある
  • 誤った仮説を立てるから、方向を見誤ってしまう

Ch2. ブランドはどのように成長するか

How Brands Grow

  • 小規模ブランドは、大規模ブランドよりも購入者数がはるかに少ない
  • 小規模ブランドは購入頻度も少ない、これらを合わせて「二重苦の法則」と呼ぶ
  • ブランドの成長には購入者数の拡大が重要である

Ch3. 顧客基盤を拡大させる

How to Grow Your Customer Base

  • 「1:5の法則」「5:25の法則」を裏付けるデータはない
  • 顧客離反率をゼロに抑えられたとしても、ブランド成長度はたかが知れている
  • 新規顧客獲得に成功するとブランドは成長し、失敗すると衰退する

Ch4. ブランドにとってもっとも重要や顧客を探す

Which Customers Matter Most?

  • ヘビーユーザーはありがたい、だが習慣化した購買行動を変えるのは難しい
  • 2割のヘビーユーザーが売上に占める割合はせいぜい6割である
  • ライトユーザーは、ブランドの大きな売上、大きな成長をもたらす可能性を秘めている

Ch5. 顧客のパーソナリティプロファイルを知る

Our Buyers Are Different

  • 自社ブランド顧客と競合ブランド顧客の属性は似通っている
  • 競合ブランドの現顧客は、自社ブランドの将来顧客になる可能性がある
  • 各ブランドの購入者は別人ではあっても、属性が異なるわけではない

Ch.6 真の競合ブランドを探す

Who Do You Really Compete With?

  • 各ブランドは顧客を共有している、各ブランドの顧客はセグメントされていない
  • どのブランドも、大規模ブランドとの顧客重複率は高く、小規模ブランドとの顧客重複率は低い
  • 市場には高品質/高価格帯のような境界線は存在するが、完全に分断された市場とは捉えない方がよい

Ch7. 消費者のコミットメントを知る

Passionate Consumer Commitment

  • ブランドの購入体験がブランド選好を高め、ロイヤルティにつながる
  • 大規模ブランドほどライトユーザー比率が高いがために、ロイヤルティも高く見える
  • ブランドの熱狂的信者は確かに存在するが、マーケティング的な重要性は低い

Ch8. 差別化か、それとも独自性か

Differentiation versus Distinctiveness

  • 差別化は長続きしない、差別化よりも独自性を追い求めるべきである
  • 消費者はブランド間の差異に気づいてはいる、だがお気に入りブランドの購入を止めるほどではない
  • 独自のブランド資産は、消費者のブランドへの気づきやすさ、想起しやすさ、購入促進につながる

Ch9. 広告の機能

How Advertising Really Works

  • 広告は売上に寄与している、ただしすぐ売上に効くことも、止めて売上がすぐ落ち込むこともほぼない
  • 広告は以前の記憶を思い起こさせたり、時に記憶に刷り込まれることで機能する
  • 広告は独創的な内容によって、ブランドの独自性を維持、構築する

Ch10. 価格販促の役割

What Price Promotions Really Do

  • 価格販促はすぐ売上に効く、だが長続きしないし、非常に大きな犠牲をともなう
  • 価格販促で新規獲得はできない、購入者の大半はそのブランドの過去購入経験あり
  • 消費者は幅広い価格帯から商品を選んでいる、低価格志向者だけを狙った価格販促は困難である

Ch11. ロイヤルティプログラムが失敗する理由

Why Loyalty Programs Don't Work

  • ロイヤルティプログラムの効果は少しはある、だが利益やブランド成長への寄与はないに等しい
  • 大多数を占めるライトユーザーは低購入頻度なぶんプログラム参加可能性が低く、効き目も薄い
  • ヘビーユーザーはプログラムに参加しても購買行動に変化はない、つまり多額の投資に見合わない

Ch12. メンタル・アベイラビリティとフィジカル・アベイラビリティ

Mental and Physical Availability

  • 消費者は限られたお気に入りブランドを購入しがち、そもそも大半のブランドは眼中にない
  • ブランドを想起してくれる人数と、購入候補に加えてもらえる回数、この2点を増やす努力を
  • ブランドの配荷率と店頭での目立ちやすさを確保する、これが購入を後押しする

Ch13. 最後に一言

A Final Word

  • 7つのシンプルなマーケティングの法則
  1. ブランドのサービス/製品カテゴリー内のすべての消費者に、配荷およびマーケティングコミュニケーションの両面から継続的にリーチする
  2. ブランドの買い求めやすさを確保する
  3. 目立つ。その過程を誤るとブランドコミュニケーションに費やす費用が無駄になる
  4. ブランドが目立ち、買いやすくなるためにも、ブランド記憶の構造を構築/刷新する
  5. 独自のコミュニケーション資産を創造する
  6. 一貫性を保ちながら、新鮮さと興味を失わない
  7. 競合力を維持しつつ、多くの人に受け入れられる。ブランドを買わない理由を与えてはならない

ブランドが成長することは可能だ。新しいブランドである必要はない。優れた広告、優れたブランディング、優れたメディア戦略、優れた店内ディスプレイ、そして前述の7つのルールに従うこと、これらすべてが成長への道だ。

本書に登場した法則

  • ダブルジョパディの法則 Ch2.
  • リテンションダブルジョパディの法則 Ch3.
  • パレートの法則(60/20) Ch4.
  • 購買行動適正化の法則 Ch4.
  • 自然独占の法則 Ch7.
  • 顧客基盤が類似する Ch5.
  • ブランドに対する態度と思いが行動ロイヤルティに反映される Ch7.
  • ブランド使用体験が消費者の態度に影響を与える Ch5.
  • プロトタイプの法則 Ch8.
  • 購買重複の法則 Ch6.
  • NBDディリクレ Ch4.

関連情報


2019年8月16日金曜日

QuickTimeで音声入りで画面を動画キャプチャ

macの場合の話
  1. サウンドで、Soundflower(2ch)を選択
  2. LadioCastを起動し、入力1:Soundflower(2ch) 出力メイン:内蔵出力 に設定する
  3. QuickTime Playerを起動、メニューバーのファイル→新規画面収録で録画ボタン横の下向き矢印クリックでSoundflower(2ch)を選択
  4. 収録したい画面範囲をドラッグで選択、決まったら録画ボタン押す
  5. 録画終了はタスクバーの停止ボタンクリック、内容よければファイル保存
  6. サウンド設定を元(内蔵スピーカー設定)に戻して終わり

音声なしの動画保存ならサウンド周り設定不要なのでQuickTimeで新規画面収録でOK

https://qiita.com/taki_tflare/items/a03514b93a1bba9cdc01



M1 Macの場合
soundflowerが使えない
  1. BlackHoleをインストール
  2. GitHub – ExistentialAudio/BlackHole: BlackHole is a modern macOS virtual audio driver that allows applications to pass audio to other applications with zero additional latency.
  3. Audio MIDI設定を開く
  4. 左下の+ボタン押下して「複数出力機器を作成」を選択
  5. MacBook AirのスピーカーとBlackHole 16chにチェック入れる
  6. 左カラムのBlackHole 16chを右クリックして「このサウンド入力装置を使用」を選択して、チャンネルの音量はとりあえず最大にしておく
  7. サウンド設定を、複数出力装置にしておく
  8. QuickTime Player起動して、新規画面収録
  9. https://support.apple.com/ja-jp/HT208721
  10. shift + command + 5
  11. オプション右側あたりを右クリックで、マイクをBlackHole 16chにする
  12. 収録開始
  13. 「command + control + esc」キーで収録終了
  14. サウンド設定は戻しておく(とfunctionキーで音量調節できる)
  15. uninstallしたいときは
  16. https://github.com/ExistentialAudio/BlackHole/wiki/Uninstallation
  17. https://pc.watch.impress.co.jp/docs/column/yajiuma-mini-review/1262744.html

2019年1月7日月曜日

書評

途上国の人々との話し方

和田信明 中田豊一

国際援助においては、現地の人々の合意を得ながら開発プログラムが進む。だが後で振り返ってみると、プログラム内容が最適な解決策になっていなかったり、一過性に終わっている場合もある。その原因の一つに、援助する側とされる側の認識のズレが挙げられる。

例えば「この村では何が問題で困ってますか?」「水汲みが大変です」「井戸はないんですか?」「自分たちでは作れません」「では作るのを手伝いましょう」井戸が作られたおかげで水汲みに遠出しなくて済むようになりました、めでたしめでたし。 ところが数年後に村を訪れると井戸が使われていない。井戸のレバーが壊れたらしい。住民は以前のように水汲みをしている。井戸の修理方法がわからないし、水汲みはもともとやっていたし、そこまで不便とも思っていないらしい。井戸作成プログラムは一定の役には立った。だがさして困っていない様子を見ると、井戸はあれば便利かなぐらいの要望であって、真に解決すべき重大な問題が他にあったのかもしれない。

認識のズレをなくすにはどうすればよいのか?そのカギは相手への質問の仕方にあった。NGOとして途上国に赴いた著者達が、現地の住民やスタッフとの会話で実践しながら編み出したコミュニケーション方法を体系化。実際の出来事を紹介しながら具体的な手順、注意点について解説している。

ポイント

  • 認識のズレをなくすには「事実」をたずねる
  • 「いつ」「どこで」「誰が」「何が」のように事実を答えやすい投げかけをする
  • 「なぜ」「どうして」と尋ねてしまうと、事実ではなく「感情」や「解釈」を答えてしまう
  • 「事実」「感情」「解釈」を切り分けることで、俯瞰で状況把握するメタ認知が醸成される
  • 自分たちを客観的に捉えることができると、課題の整理がしやすい

詳細(引用含む)

想起しやすい事実を口にしながら過去の出来事を整理してもらうことで、相手の記憶の棚卸を手伝う。 「その問題が最初に起こったのはいつ?」「最近その問題が発生したのはどこで?」 「その問題で誰が困っている?」「その問題に対してこれまでに対処したことは何?」 事実を重ねる対話を続けることで、相手は気づきを得やすくなる。

「なぜ」「どうして」は相手の負担が大きい、詰問されてるようにも感じる。回答が難しい。 空気を読んで、質問者が欲しがっている答えを口にしてしまう。質問する側も自分の思い込みで質問してしまう。 そうすると事実から遠ざかる。対話の焦点が揃わない。問題が的確に定義されない。解決方法も的外れになる。 事実をたずねることで、本当に解決したい問題の定義、そして的確な答えへとつながる。

結論と展望

書籍タイトルからは遠い途上国の話に見える。だがその応用範囲は広い。日常のあらゆるコミュニケーションに通じる。ビジネスにおけるファシリテーション、問題解決にも使える。

答えを急がない。問題を取り違えない。相手と違う景色を見ない。なぜをたずねるよりも「事実」を着々と積み重ねる。 地味に見えるこの対話術は、途上国に限らずあらゆる場面で有効に機能する。

関連書籍

同著者の手により、実社会において適用しやすいようにエッセンスを抽出した後続書籍あり。おそらくこちらの方が入手しやすく、かつ読みやすいと思われる。

「良い質問」を40年磨き続けた対話のプロがたどり着いた 「なぜ」と聞かない質問術 中田 豊一




ベイズモデリングによるマーケティング分析

照井伸彦

タイトル通り、ベイズ統計モデリングの手法をマーケティング場面に活用することを目指した本

難解な点は、

  • ベイズやモデリングに明るい人には、マーケティングの説明が薄い
  • マーケティングに明るい人には、ベイズやモデリングの説明が薄い
  • 両方明るい人にはちょうどいいが、そんな人はそうそういない

違う表現をすると、

  • 数式がバンバン出てくるので苦手意識がある人には向いてない
  • プログラミングのコードは出てこないので写経したい人には向いてない

消費者行動場面を、ベイズ的観点からモデリングしてみる際の手順をちょっと知ってみたい人にはおすすめ 書かれてることを自分の中で咀嚼して応用が効かせられる人にもおすすめ




広告マネジメント

木戸 茂

マーケティングをデータに基づいて科学的に意思決定する際の手法や応用手順を体系的に紹介するシリーズ書籍。

テレビや紙の媒体でかつて実際におこなわれてきた、以下のような統計的、計量的手法によるモデル構築と検証結果を説明

  • 広告予算配分の最適化
  • 各媒体のリーチ推定モデル
  • 広告認知率の推定モデル
  • 広告効果の因果モデル
  • ブランド連想と広告評価の因果モデル

広告の短期的効果と長期的成果を区別したり、企業ブランドと製品ブランドを区別するなど、シンプルなモデルを目指してるのは好感持てる。広告の継続出稿有意義性を後押しする材料にも使いやすそう

GRPのような広告につきものの説明変数だけでなく、同一市場内での競合との広告シェア、自社の値引率、自社の相対的なシェア、平均購入個数、継続購入率、市場自体の成長率、市場が寡占的か激戦区かなども変数として採用しており、広告出稿に閉じた話に終わらず、ソリューション全体の提案として使いやすい

プリファレンスという1つの説明変数で購買予測するUSJ森岡モデルに比べて、本書はプリファレンスに集約してしまう前の段階、すなわちブランド・エクイティの説明変数が具体的でわかりやすい

またマーケティング全般というよりは広告業界視点で語られている点は、広告業界関係者にはとっつきやすく感じられるだろう

数学的方面からのアプローチや、サイエンス方面からの理解を深めたい人にはおすすめ




現場主義統計学のすすめ: 野外調査のデータ解析

統計スポットライト・シリーズ

内容

野外の生き物を観察して得たデータを分析しながら統計手法を学ぶ本。 -クマさんにGPSをつけて計測した移動軌跡をクラスター解析でデータ集約して生態環境を研究

  • 樹木の年輪幅から成長量を推定する状態空間モデルを作成、ベイズで推定、平滑化して森林の長期動態を研究
  • スズランの果実が実る割合を目的変数に、花粉散布をロジスティック式でモデル化、対数尤度やAICでモデル検討、
  • ランダムウォークモデルを作成してシミュレーション

といった事例を紹介しながら、データの計測の仕方、記録方法、データ集約手順や解析を説明。

ここはよかった

自然観察から得られる複雑な事象を数値化、図示化するのはなかなか手間に思えますが、そういうのができるソフトの紹介やデータ加工手順については参考になりそう。

ここはちょっと

具体的なコードなどの記載まではなく、さらっと概要を紹介した薄い本なので、そういうのが欲しい場合は緑本の方が向いてそう。




ポアソン分布・ポアソン回帰・ポアソン過程

統計スポットライト・シリーズ

内容

ポアソン分布の理解を深めることを目的にした本。

  • 式だけ見ても直感的に理解しにくい。二項分布ならまだしも、ごく稀に発生する事象の分布と言われても想像しにくい。
  • ならばエクセルでシミュレーションしてポアソン分布を作りながら不安感を解消しよう。
  • その延長で最尤法,AIC,非定常ポアソン過程を学びましょう。

ここはよかった

へーエクセルでそういうこともできるんだ

ここはちょっと

上の野外調査ともども、緑本と内容がカブってる。緑本は実践に重きを置いてるので原理や理由の詳細説明はあまりない。それを補えると捉えればこの2冊は有用なんだが、副読本の立ち位置でよさそう。




社会シミュレーション ―世界を「見える化」する―

内容

シミュレーションで社会問題を解決しよう、その取り組みや事例を提示、入門となる本。

  • 現代は学問・研究領域の専門化、細分化が進んでおり、研究分野が違うと共通言語が乏しくて相互理解が大変。
  • 一方で、世の中の事象は様々な要因が絡んで複雑化しており、単一領域の学問・技術だけでは解決が難しい。
  • そこでシミュレーションが共通言語となり、社会を解明する、課題解決の役割を担うと期待されている。

共有地の悲劇がもたらす環境問題、エージェント・ベース・モデリング、データ分析でシミュレーション、デマツイートとウィルス感染、都市形成、居住民族の構成変化、三次元物体の透視可視化などを紹介しながら、シミュレーションの楽しさや難しさを語る。

ここはよかった

いろんなテーマをちょっとずつ紹介してあり、また実生活とも関わり合いの多い事象を取り上げてるので、読みやすかった。

ここはちょっと

興味を持ってもらうための導入とわりきってもともと作ってあるため、詳しくは参考文献見て自助努力でと放り出される。

放送大学のソーシャルシティ('17)講座は続きとしてよいかも。 http://www.ouj.ac.jp/hp/kamoku/H30/kyouyou/C/seikatu/1519077.html

放送大学のははシミュレーション関係ない。都市問題つながり。福岡の繁華街、天神に新しい商業施設がオープン、駐車場は足りるの?大分市の駅高架化で、駅ビルがきれいになったら商店街との人の行き来はどう変わった?梅田とか落合氏とかきゅんくんなども登場。




なぜベイズを使わないのか! ? _臨床試験デザインのために

内容

臨床試験での実験計画において従来使われてきた頻度流統計の利点と欠点を述べつつ、それを補う形でベイズ流を説明する本。

ここはよかった

頻度流との対比でベイズ流を説明してるので違いやそれぞれのよさがわかりやすい

ここはちょっと

残念ポイントは特に見当たらない。じゃあNPSが高いかというとそういう気もしない。なんでだろう、これ一冊では完結しないからだろうか。うまく転用することで有効活用できそうな気はする(ちゃんと咀嚼できてない)






2019年1月5日土曜日

画像の圧縮

画像の大きさはそのままに、ファイルサイズを小さくする
ImageMagickでできる

$mogrify -quality 85 *.jpg


ただし一度プレビューで品質下げて書き出すなどしたのを改めてやると、ファイルサイズが大きくなることもある


コマンドで画像を一括処理! ImageMagickの便利な使い方
https://liginc.co.jp/394506

画像を最適化する 
https://developers.google.com/speed/docs/insights/OptimizeImages?hl=ja



フォルダ内のjpegファイルをjpgにする
for file in *.jpeg ; do mv $file ${file/%.jpeg/.jpg} ; done


2018年12月29日土曜日

PythonでMCA(コレスポンデンス分析)

Pythonでコレポンをやる

コレスポンデンス分析

  • 略してコレポン

    • 数量化III類(Quantification Theory III)、対応分析(CA: Correspondence Analysis)、多重対応分析(MCA: Multiple Correspondence Analysis)、双対尺度法(Dual Scaling)など、呼び方や近縁手法が複数存在し、数理的には非常に近い関係にある
    • ここでは「コレポン」表現をメインに据えている。実際にはCAだったりMCAだったりするが数理的背景は非常に近いため細かい区別は気にしない
  • point

    • 観測データを単純化することで、データの背後にある潜在的な要素を解釈しやすくする
    • 質的データ(カテゴリデータ)の関係構造を探索するのに適している
    • 主成分分析(Principal Component Analysis)と数理的に近い構造を持つ
    • PCA同様にデータを縮約して、2次元プロットなどで可視化できる
    • 統計的厳密性よりは、結果解釈のしやすさや仮説探索的手法として喜ばれやすい(コレポンに限らない)
    • 大雑把な傾向把握にとどめるのが望ましい(細かいことを気にしないことこそコレポンらしさかも)
  • Rの場合

    1. インストールして library(ca)
    2. 読み込んで実行して ca(table.T)
    3. 可視化 plot(ca(table.T))
  • Pythonの場合

    • ここではmcaライブラリを使用 python -m pip install mca
    • 単機能でシンプルではあるが、新規に始めるならprinceライブラリの方がいろんなことに使いやすいかも

コレポンとPCA

  • コレポンの特徴
    • カテゴリデータや頻度データの関連構造を少数の軸で表現する手法。期待頻度からのズレ(カイ二乗統計量)が大きい方向を探す。
    • カイ二乗距離に基づく幾何を前提としている。行・列の周辺度数で基準化した行列を特異値分解する。
    • 行と列を双対的に扱い、同一空間上に同時布置できる。
  • PCA(主成分分析)の特徴
    • 数値データの分散構造を少数の軸で表現する手法。データのばらつき(分散)が最大になる方向を探す。
    • ユークリッド距離に基づく幾何を前提としている。共分散行列または相関行列を固有値分解する。
    • 行(サンプル)は主成分スコア、列(変数、特徴量)は負荷量として表現される。
  • コレポンとPCAの共通点
    • 多変量データの構造を少数の軸で要約・可視化する次元圧縮手法(データの背後にある構造を捉えるためのもの)
    • データ中の「似たパターン」や「関係構造」を低次元空間へ埋め込む(低次元に圧縮して、似た回答傾向を可視化する)
    • 元データを座標空間へ変換し、近い点ほど似た性質を持つと解釈できる
    • 軸ごとの構造を使って、クラスタ傾向や潜在的な特徴を読み解ける
  • コレポンとPCAの相違点
    • コレポンとPCAは少なくとも数理的な骨格は同じなんだが
    • 軸の解釈に対する考え方の違い、解析結果の安定性など運用上は多くの相違点が存在する(PCAにおける軸は量的な増減方向として解釈しやすい。コレポンにおける軸はカテゴリの対立軸として表れやすい)
    • だがコレポンは適用範囲が広い。理論的前提から多少外れていても、大まかな構造把握用途では有用な場合が多い。特に順序尺度のアンケートデータなどでは、PCA的に見るかコレポン的に見るかの境界が曖昧になることも多い

コレポンの注意点

  • 仮説探索や構造把握に向いた手法であり、推定や検定のような厳密性を主目的とする分析とは少し性格が異なる
  • 適用範囲が広い(深く捉えなくともなんでも実行しやすい)とはいえ、本来持っていた情報を相当捨てていることは気に留めておく
  • 変数間で尺度の意味が大きく異なるデータには向かない(例えば年収と身長と気温を同列の変数として扱うのは不適切)
  • PCA同様に異常値や希少カテゴリは空間構造をゆがめやすい

コレポンの実情

  • 特にマーケティング分野におけるアンケート調査データにおいて重宝されやすい
  • 「結果の見た目がわかりやすい」「直感的に理解しやすい」「解釈やストーリーを考察しやすいので話が弾む」などが主な理由として考えられる
  • 他に「クロス数表があればできちゃう」「たいていの設問形式に対応できちゃう(ように感じやすい)」「簡単な操作で簡単なアウトプットが得られちゃう」もありそう
  • 理論的美しさよりも、実行しやすさや適用範囲の広さ、データの背後構造の見えやすさが魅力と言える

コレポンの守備範囲

  • 理論的には、行・列ともにカテゴリデータの頻度表(人数、出現数などのcountデータ)を対象としている

    • 例えば、行に「カフェのメニュー」、列に「性年代区分」を置いたデータセット(各メニューを好きと回答した人数の性年代別集計値)でおこなうのが王道
    • だがクロス数表を読み慣れている場合はコレポンやる意義を感じにくいかも
    • 性別や年代による違いは想像がしやすいため(ある程度わかってる顕在的要素をなぞってるだけに見えるぶんコレポンの利点が活かされにくい)
  • コレポンをよく使う場面

    • 例えば、行に「カフェのメニュー」、列に「イメージワード」を置いたデータセット(各メニューの印象にあてはまるワードを選択した人数のワード別集計値)
    • イメージが似てるメニューはどれとどれなのか、逆に似ていないのはどれかといった解釈がしやすい(クロス数表からは読み解きにくい)
    • コレポンの理論的枠組みからはやや外れるが、コレポンやる意義を感じさせやすい
  • 他に、

    • 行に「回答者」、列に「イメージワード」を置いたデータセット(回答者各自がカフェの印象にあてはまるワードに5段階評価したローデータ)
    • これも理論的枠組みからは外れる(PCAっぽいともいえる)が、ある人がどんなワードを重視してるか、似た者同士は誰かといった解釈がしやすい
  • 比率表(回答者数全体に占める割合、%データ)のデータセット

    • コレポン自体は実行できるし、頻度表よりも入手しやすい場面もあるなどこちらでおこなう人が多数派かもしれない
    • これまた理論的枠組みからは外れるが、単純化するという性質からさほど弊害が表れにくい(頻度表での実行結果と大差ないなど)
    • 同様理由で、表の縦%や横%が100を超える場合(複数回答形式設問)でもおこなわれているだろう

コレポン結論

  • 理論を超えて使いやすいが、わかりやすさに引きづられない
  • 簡単に得られる情報と引き換えに、捨てている情報も多いことを忘れない

コレポン実行例

  • あるカフェのお客様アンケート結果をコレポンしてみる
  • メニュー × イメージワード
    • 実務においてありがちなコレポンの使い方
    • お店のメニュー(8品)それぞれに対して、印象にあてはまるイメージワード(8項目)をMA(複数回答可能形式)で聴取
    • 行(メニュー)、列(イメージワード)のクロス数表(選択した人数の頻度表)
    • MA選択率よりはイメージワード各5段階評価のTOP2人数の方がコレポン理論には近い。とはいえ回答者負荷の高い設問形式を推奨したくない意図
# import
import pandas as pd
import numpy as np
from matplotlib import pyplot as plt
import mca

# version
import sys;print("Python", sys.version.split()[0])
import platform;print("OS", platform.platform())
print("is_google_colab:", "google.colab" in sys.modules)
from importlib.metadata import version
packages = [
"pandas",
"numpy",
"matplotlib",
"mca"
]
for pkg in packages:
    try:
        print(pkg, version(pkg))
    except Exception as e:
        print(pkg, "not installed")
Python 3.11.15
OS macOS-26.5-arm64-arm-64bit
is_google_colab: False
pandas 2.2.0
numpy 1.26.4
matplotlib 3.10.9
mca 1.0.4
# サンプルのdataframeを生成する

# メニュー(行)
menus = ['Coffee', 'Tea', 'Latte', 'Espresso', 'Cake', 'Sandwich', 'Cookie', 'Toast']

# イメージワード(列)
words = ['香り高い', 'コスパが良い', '見た目が映える', '濃厚な味わい', 
         'ボリューム満点', '手軽に食べれる', '贅沢な気分', '健康的なイメージ']

# プロットがある程度バラつくように、『Tea』『Cookie』『手軽に食べれる』が中心近辺にくるように平均的な値に寄せている
data = [
    [50, 25,  5, 15,  5, 30, 10,  5], # Coffee
    [25, 20, 15, 10, 10, 35, 15, 15], # Tea
    [10, 10, 45, 35, 10, 20, 30,  5], # Latte
    [40, 10, 10, 55,  5, 15, 25,  5], # Espresso
    [10, 10, 50, 30, 10, 15, 45,  5], # Cake
    [ 5, 25, 10,  5, 50, 40,  5, 30], # Sandwich
    [20, 20, 18, 15, 15, 30, 15, 12], # Cookie
    [10, 40,  5,  5, 35, 40,  5, 20], # Toast
]

df = pd.DataFrame(data, index=menus, columns=words)


# MCAの実行
ncol = df.shape[1]
mca_ben = mca.MCA(df, ncols=ncol, benzecri=False, TOL=1e-8)


# 行スコア(=座標)
result_by_row = pd.DataFrame(mca_ben.fs_r(N=2))
result_by_row.index = list(df.index)
print ("Score by row:")
print(result_by_row.round(3))
Score by row:
              0      1
Coffee    0.010 -0.686
Tea       0.132 -0.137
Latte    -0.513  0.348
Espresso -0.535 -0.413
Cake     -0.594  0.440
Sandwich  0.771  0.284
Cookie    0.080 -0.020
Toast     0.710  0.047
# 列スコア(=座標)
result_by_column = pd.DataFrame(mca_ben.fs_c(N=2))
result_by_column.index = list(df.columns)
print ("Score by column:")
print(result_by_column.round(3))
Score by column:
              0      1
香り高い     -0.184 -0.727
コスパが良い    0.433 -0.130
見た目が映える  -0.533  0.537
濃厚な味わい   -0.629 -0.123
ボリューム満点   0.724  0.326
手軽に食べれる   0.339 -0.065
贅沢な気分    -0.579  0.227
健康的なイメージ  0.642  0.160
# 固有値と寄与率

# MCAでは「行と列のどちらか少ない方の数-1」 が固有値(eigenvalue)の最大数になるためそれを算出
num_of_eigenvalue = min(len(df.index), len(df.columns)) - 1

data = {
    'Factor': range(1, len(mca_ben.L) + 1),
    'value': pd.Series(mca_ben.L),
    'ratio': mca_ben.expl_var(greenacre=False, N=num_of_eigenvalue)
}
columns = ['Factor', 'value', 'ratio']
table2 = pd.DataFrame(data=data, columns=columns).fillna(0)

# ratioの累計(cumulative_ratio)列を追加
table2['cum_ratio'] = table2['ratio'].cumsum()

print("Principal & Inertia(固有値 寄与率):")
print(table2.round(3).to_string(index=False))
Principal & Inertia(固有値 寄与率):
 Factor  value  ratio  cum_ratio
      1  0.266  0.598      0.598
      2  0.131  0.296      0.894
      3  0.033  0.075      0.969
      4  0.007  0.016      0.986
      5  0.004  0.008      0.994
      6  0.003  0.006      1.000
      7  0.000  0.000      1.000

# 上記Factor1,2でプロット
plt.figure(figsize=(7, 6))

# plt.rcParams['font.family'] = ['sans-serif', 'Meiryo']  # windows
plt.rcParams['font.family'] = ['sans-serif', 'Hiragino Sans']  # mac

# plot by row
plt.scatter(result_by_row[0], result_by_row[1], color='tab:blue', s=20, marker="o", label="Row")
for k, v in result_by_row.iterrows():
    plt.annotate(k, v, xytext=(2, 2), textcoords="offset points")

# plot by column
plt.scatter(result_by_column[0], result_by_column[1], color='tab:orange', s=20, marker='s', facecolors="none", label="Column")
for k, v in result_by_column.iterrows():
    plt.annotate(k, v, xytext=(2, 2), textcoords="offset points")

plt.axhline(0, color='gray', linestyle='dashed')
plt.axvline(0, color='gray', linestyle='dashed')
plt.xlabel('Factor 1')
plt.ylabel('Factor 2')
plt.title("Correspondence Analysis\n(Customer Satisfaction Survey Example)")
plt.legend(loc='best')
# plt.gca().set_aspect("equal")
plt.show()

結果の解釈例

  • 左上のCakeとLatteは似たイメージでお客様から捉えられている
  • CakeとLatteはともに「映える」「贅沢」といったリッチなイメージが強そう
  • これらとは対極の右側にはSandwich, Toastが「手軽に食べれる」日常的なMenuとの評価
  • 横軸(第1軸)はハレと日常を表すような背後要因が考えられる
  • 下方のCoffeeはハレと日常の中間的ポジションながらも、Espressoともども「香り高い」イメージ
  • 縦軸(第2軸)は寄与率が1軸ほど大きくなく、背後要因はやや読み解きにくい(今回使用したイメージワードでは不足だった可能性も考えられる)
  • 中心付近にプロットされた「Cookie」「Tea」は他のMenuほどには強い特徴がみられなかったと解釈できる

(2026年5月更新)