選手評価を偏差からパーセンタイルへ変更しました
ボールの代わりにペットボトルの蓋を投げて打つ野球盤型スポーツ、キャップ野球のデータ管理のお手伝いをしています。基本的には成績の計算方法は野球と同じです。
パーセンタイルの導入への経緯
以前選手成績のUIを刷新した記事の中でも触れているのですが、従来の選手成績評価では、標準偏差を用いたS、A~F評価を採用していました。しかし、この方法では、特定の指標の分布が偏ると、特定のクラスに選手が集中してしまうという問題がありました。例えば、打率が全体的に高いシーズンでは、S評価の選手が極端に少なくなってしまう可能性があります。
この問題を解決するため、より公平かつ客観的な評価方法として、パーセンタイルを導入しました。パーセンタイルは、データ全体の中での相対的な位置を示す指標であり、分布の偏りに左右されずに、選手の実力を正確に評価することができます。例えば、打率のパーセンタイルが90%の選手は、リーグの選手の中で上位10%に入る打力を持っていることを意味します。
どのようにパーセンタイルを計算するか
計算方法として真っ先に頭に浮かんだのはSQLでパーセンタイルを計算する方法です。ただし、現在運用しているデータベースの制約からパーセンタイルを計算するにはそれなりに労力がいることがわかりました。他のアプローチを検討するにあたり、まずパーセンタイルの定義からきちんと理解する必要がありました。
パーセンタイルとは
パーセンタイル順位とは、データを小さい順に並べたとき、値の順位を百分率(パーセント表示)で表したものをいいます。また、小さいほうから数えたパーセントに位置する値をパーセンタイルといいます。
(パーセンタイル順位)={(特定の値を下回る値の数)÷(全体の値の数)}×100
アプローチ
全体数と特定の値を下回る値の数がわかればよいので、既存のAPIで全成績を返しているものを用いて、パーセンタイルをフロント側で求めることにしました。SQLで全て計算しなくても何人が計算対象の成績より下にいるかは計算できます。
const calcPitchingPercentile = useCallback(
(key: string, value: number) => {
if (allPlayerData?.pitchingResultList?.length === 0) return 0;
// 30イニング以上投球した選手のみを対象とする。
// これは、十分なデータ量を確保し、より信頼性の高いパーセンタイルを計算するため。
const underRecords = allPlayerData?.pitchingResultList?.filter(
(result) => {
// 防御率(era)とWHIPは、値が小さいほど優秀とされるため、他の指標とは比較方法が異なる。
if (key === "era" || key === "whip") {
return Number(result?.[key]) > value && result.inning >= 30;
}
if (key === "win_avg") {
return (
Number(result.win / (result?.win + result.lose)) < value &&
result.inning >= 30
);
}
return Number(result?.[key]) < value && result.inning >= 30;
}
);
const allRecords = allPlayerData?.pitchingResultList?.filter((result) => {
return result.inning >= 30;
});
return (underRecords.length / allRecords.length) * 100;
},
[allPlayerData]
);
投手成績は
- 防御率(1イニングごとの平均失点)
- WHIP(1イニングに安打と四死球で平均何人の走者を出すか)
の指標では値が小さい方が優秀とされているので、場合分けして値の比較を逆転させる必要があります。
Discussion