RのWeb制作

Webサービス制作のための技術情報を。データ分析(Python、機械学習コンペ他)や自作野球ゲームMeisyoのこと中心。

Web制作 Python データサイエンス

野球ゲームデータで遊ぶデータサイエンス(正規分布の検定編)

投稿日:2019年2月5日 更新日:

名将と呼ばれた者達のデータを使って、データサイエンスを学んでみましょう!

生きた&整えられたデータは中々公開されていないので、今回の野球ゲームのデータは分析に適していると思われます。もちろん、Kaggle等のデータもありだと思います!

今回やること

野球ゲームの選手について、能力のミートが正規分布であるか(排出設定通りか)確認します。その理由としては、運営の観点から、バグなどの問題がなく、うまく選手が生成できているか?を答えたいと思います。

ゲームデータ(試合結果)

野手データ 20190204.csv
投手データ 20190204.csv

実施内容

はじめに

Jypyter Notebookで分析を進めていきたいと思います。

まずは、コマンドプロンプトを開いて、基本的なパッケージをインポートしておきましょう。

import os, sys, csv
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

次に、ゲームデータがあるディレクトリ直下まで行って、データを読み込ませておきます。

dfB = pd.read_csv('meisyo_b_20190204.csv', encoding='utf-16')
dfP = pd.read_csv('meisyo_p_20190204.csv', encoding='utf-16')

* 文字化け防止のためcsvはutf-16で出力しています。

データの確認

データの状況把握のためにdf.info()を表示します。

dfB.info()
dfP.info()

つづいて、野手(dfB)のデータをメインで見ていきましょう。
Lvのデータをグラフ化しましょう。

plt.hist(dfB['lv'], bins=100)
plt.show()

* bins=100にしているのは、Lvが1~100の100分割で設定されているからです。

Lv0が多すぎるので、クエリでLv > 10と制限しちゃいます。

plt.hist(dfB.query('lv > 10')['lv'], bins=10)
plt.show()

dfは「Lv > 10 & Lv <= 100」などSQLみたいに書けるので、応用がいろいろできそうですね。 * 次回以降、plt.show()を省略します。

能力値の確認

それでは、ミート(b_mt)を解析します。
まずはshape(データの行数・列数)とmean(平均値)、std(標準偏差)を表示します。

dfB.shape
dfB['b_mt'].mean()
dfB['b_mt'].std()
(7893, 96)
32.214493855314835
11.483961920286394

7893データ(n = 7893)、96カラムがありました。これだけでは、ミートがどのようなデータ分布をしているのかいまいちわかりませんね。

今回は(設定通り)正規分布してるのか?ということが気になるので、b_mtをヒストグラムにしてみます。

plt.hist(dfB['b_mt'], bins=30)

右と左のバランス感から、これは見た限りでは正規分布していないですね。

正規分布の検定

見た限りという主観的な意見ではなく、正規分布であるかどうか判断できる方法論である検定を行います。

正規Q-Qプロット

正規分布の検定によく使われる手法です。こちらは可視化の方法論となります。

import scipy.stats as stats
stats.probplot(dfB['b_mt'], dist="norm", plot=plt)
plt.show()

直線上に乗ってることが正規分布を示すと考えてください。

低い値の範囲が正規分布していないようですね。

シャピロウィルク検定

他にも正規分布の検定方法はあります。定量化できるので、数値での比較が可能となります。

stats.shapiro(dfB['b_mt'])
C:\Users\saito\AppData\Local\Programs\Python\Python37-32\lib\site-packages\scipy\stats\morestats.py:1653: UserWarning: p-value may not be accurate for N > 5000.
  warnings.warn("p-value may not be accurate for N > 5000.")
(0.9833155870437622, 1.85650483425366e-29)

N>5000は正確でないよとアラートが出ていますね。データ量が多すぎたのかもしれません。

結果は、(検定統計量(W), p値)と出力されます。

今回は、p値 < 0.05のため、正規分布であることが棄却(≒正規分布ではない)されています。

歪度と尖度の確認

歪度と尖度が正規分布に近い値か確認します。0になると正規分布ですが・・・

>>> pd.Series(dfB['b_mt']).skew() #歪度
0.364141699620992
>>> pd.Series(dfB['b_mt']).kurtosis() #尖度
-0.3620100293286428

今回は正規分布でないですね。

その他データ加工方法を試す

能力値をある条件下で区切って、正規分布であるかどうかを確認します。

stats.probplot(dfB.query('b_mt > 0 & b_mt < 60')['b_mt'], dist="norm", plot=plt)


加工したほうが直線への乗りが悪くなってますね。

他のステータスb_pw(パワー)も見てみましょう。

 plt.hist(dfB.query('b_pw >= 10 & b_pw <= 60')['b_pw'], bins=50)


これは・・・ダメかもしれません。左すそは分布が急減していて、右すそは謎の山ができている。

上記を検定してみましょう!

>>> stats.shapiro(dfB['b_pw'])
(0.9832108020782471, 1.5380268896554336e-29)

だめですね!正規分布ではない。(‘A`)ンアー

検証結果

正規分布していないという結論になりました。

なぜ正規分布していないかというと3点理由があります。
1・投手と野手では計算式が違うから
2・能力値値が低い選手は削除されるから
3・生成した結果のデータではなく、選手データをそのまま使っているから

1点目については、ミートもパワーも(結果的に打力が)高い投手が出現するなら、野手が要らなくなるよね?それは阻止したい。ということで、「野手能力は野手>投手」「投手能力は投手>野手」になるように生成の制限をかけています。
それは後述のヒストグラムで確認可能です。

2点目については、ゲームをプレイしているユーザー側の都合ですね。
相対的に弱い選手は淘汰される。ただそれだけです。

そのため、正規Q-Qプロットでミートが低い値の範囲が正規分布していなかったことが予想されます。

3点目については、分析すべき対象データを間違っていたという致命的な内容です。

つまり、今回のデータや分析では何も言えないということです。答えがこれでは出せませんというのが答えという・・・。

さて、頭を切り替えて、野手と投手データを分けると、どう分布してるのか?を見ていきましょう。

plt.hist(dfB.query('type == "P"')['b_mt'], bins=10, alpha=0.3, histtype='stepfilled', color='r') # 投手
plt.hist(dfB.query('type == "B"')['b_mt'], bins=10, alpha=0.3, histtype='stepfilled', color='b') # 野手
plt.hist(dfB['b_mt'], bins=10, alpha=0.3, histtype='stepfilled', color='g') # 全体

ヒストグラムを半透明にして、緑=全体、青=野手、赤=投手で表示しています。

投手と野手の分布が違うことがわかりやすいのではないでしょうか。とはいえ、このように前提知識がないと効果的な洞察は得られません。

野手と投手の分布が違うことをさらに裏付けましょう。
マン・ホイットニーのU検定(中央値の検定)では、投手と野手ではやはり中央値は異なると出ますね。これこそ設定通りですね。

stats.mannwhitneyu(dfB.query('type == "P"')['b_mt'], dfB.query('type == "B"')['b_mt'])
MannwhitneyuResult(statistic=3865093.0, pvalue=4.898308872342808e-180)

加えて、one-way ANOVA(1元配置分散分析)を試します。それぞれの集団が正規分布であることを前提にする検定です。平均値が同じであるかどうか?を分散の違いで検定しています。

よくやりがちなんですが、2群間の検定であるt検定を3群に対してそれぞれ行う(A=B、A=C、B=CだからOK!)という暴挙はしてはなりません。こちらは、統計の適用ミスとして失敗例によく挙げられます。

まずは、3群(全体、野手群、投手群)が正規分布であるかどうかの検定を行います。

>>> stats.shapiro(dfB['b_mt'])
(0.9833155870437622, 1.85650483425366e-29)
>>> stats.shapiro(dfB.query('type == "P"')['b_mt'])
(0.984963059425354, 4.804016817054439e-15)
>>> stats.shapiro(dfB.query('type == "B"')['b_mt'])
(0.9849120378494263, 6.929926115617653e-24)

全然正規分布ではありませんでした。つまり、one-way ANOVAは使っちゃいけない分布です。

続いて、分散分析を行います。(使い方は間違っています)

>>> stats.f_oneway(dfB['b_mt'],dfB.query('type == "P"')['b_mt'],dfB.query('type == "B"')['b_mt'])
F_onewayResult(statistic=447.17232845769377, pvalue=1.25435911612848e-189)

一致するわけがないのでした・・・。

おまけ:追加検証

正規性を検定するシャピロウィルク検定(stats.shapiro())で、標準正規分布を生成するnumpy.random.randn()を検定してみましょう。正規分布を正規分布(正を正)と判断できるかをチェックしています。

>>> for i in range(1, 10):
...   temp = np.random.randn(10 ** i)
...   print("10^" + str(i) + ":" + str(stats.shapiro(temp)))
...
10^1:(0.9447452425956726, 0.6069143414497375)
10^2:(0.994074285030365, 0.9436165690422058)
10^3:(0.9983760118484497, 0.4763585329055786)
10^4:(0.9998458623886108, 0.7608375549316406)
10^5:(0.9999829530715942, 0.9663644433021545)
10^6:(0.999995768070221, 0.9852367043495178)
10^7:(0.9999997615814209, 1.0)
Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
  File "C:\Users\saito\AppData\Local\Programs\Python\Python37-32\lib\site-packages\scipy\stats\morestats.py", line 1644, in shapiro
    a = zeros(N, 'f')
MemoryError

10^n個の標準正規分布を作らせて検定してみたところ、数が多くなればなるほどp値は1.0に近づきますね。

ただし、統計学では「正規分布していることを棄却できない(=判定を保留している)」だけであって、積極的に「正規分布している」とは言えないことに注意が必要です。

さいごに

今回はゲームデータを用いて正規分布の検定を試してみました。

今回の結論からしても、やはり分析技術の理解とドメイン知識の両輪が必要ですね。

よく読まれるようなら、また別の内容を投稿したいと思います。

-Web制作, Python, データサイエンス

執筆者:


comment

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

関連記事

[SQL]URLから正規表現でホストの抽出

他のサイトの説明が釈然としなかったので投稿。 以下のref0~2番のホストを抽出したいとする。 ref[0] = "http://www.other.com/path1/index …

[Meisyo]大型アップデート予定のお知らせ

名将と呼ばれた者達よりお知らせです。 下記アップデートを予定しています。 1・サーバーの移転(高速化) 今まで練習試合等で非常に時間がかかっていた問題が解消されます。 2・チーム名等の仕様変更 チーム …

[Meisyo]試合一球分析用DBの追加

試合で一球ごとの分析用DBを追加しました。 なぜかというと、試合での投手vs打者における投球と打撃結果の因果関係が分からなかったからです。 もっとカンタンニシテ・・・ 例えば・・・ どのコースは打たれ …

(VPSでつくる) 総括・変更履歴

長らくお疲れ様でした。 最後までご覧いただき誠にありがとうございました。 今回は、VPSの良い点、悪い点、引っかかりやすいポイントなどなど。 総括を行いたいと思います。 この記事は、VPSで作るPyt …

[Meisyo]速球の地位向上

[Meisyo]ビッグデータから学ぶ試合の基礎設計4から早4か月。 「速球の上方修正してほしいな」というコメントが届きました。 すみません! 制球に比べ、速球は蔑ろにされてきたことはわかっております。 …