《Rick.C & Peter.R 共同創作》
在上一篇【Python學習筆記_賽事預測-MLB打擊率與勝負的相關?】中,我們研究了打擊率與勝負間的關聯性,究竟打擊率與勝負間是否存在著高度的相關性呢?有興趣的人可以回去看看這篇唷!!
那今日我們再換一個特徵值來觀察,那就是「防禦率」。先前的兩篇文章中,我們都是以打者的角度去觀察勝負,而今日我們換個角度,以投手的身分來研究看看,究竟防禦率是否與勝負有著密不可分的關係呢?。我們將探討以下幾點:
王建民的伸卡球,讓打者打不遠的球為何那麼誘人呢?我們先來解釋防禦率的定義:
單從字面上的意思來看,可能會覺得防禦率應該越高代表投手越不容易被擊球出去才對,但其實相反,防禦率又稱為「自責分率
」,指投手平均每場球所失的自責分,只要非野手失誤所造成打者上壘,並最終讓其回來得分,這就算是投手的自責分,因此防禦率是越低代表該隊投手越厲害
。
我們的資料來源一樣為FANGRAPHS,數據期間我們也一樣抓取2015賽季至2021賽季,共7年的期間作為我們研究的賽期。
主場優勢一直是所有競賽的迷思,因此我們每篇的第一個重點就是著重在,究竟主場優勢存不存在,今日探討的防禦率也是如此,我們就來看看在主場投球的投手是否防禦率就比較低呢?
首先,我們一樣導入套件
# 數據整理
import numpy as np
import pandas as pd
# 可視化
import matplotlib.pyplot as plt
import seaborn as sns
import plotly as py
import plotly.graph_objs as go
接著讀取資料
df = pd.read_excel(r"C:\Users\Guess365User\Desktop\MLB\fangraphs紀錄\fan_all.xlsx",index_col='date')
df.head()
我們所爬取的資料中,只有主隊以及客隊的投手防禦率(ERA),因此,我們要自行製作欄位「more_Pitcher_ERA」,為判斷主客隊誰的防禦率較低;「W/L」為判斷主客隊誰獲勝;「more_ERA_win」為判斷防禦率低者是否獲勝
df[' more_Pitcher_ERA'] = (df[' Home_Pitcher_ERA'] > df[' Away_Pitcher_ERA'])*1
df['W/L'] = (df['主隊得分'] > df['客隊得分']) *1
df["more_ERA_win"] = (df[' more_Pitcher_ERA'] == df['W/L'])*1
df.head(5)
這裡我們用到剛剛創建的欄位「more_Pitcher_ERA」來視覺化看看
trace0 = go.Pie(labels=df["more_Batting_ ERA"].value_counts().index,
values=df["more_Batting_ ERA"].value_counts().values,
hole=0.5,
opacity=0.9,
marker=dict(line=dict(color='white', width=1.3))
)
layout = go.Layout(title='主客防禦率')
data = [trace0]
fig = go.Figure(data, layout)
py.offline.plot(fig, filename=r'C:\Users\Guess365User\Desktop\IT邦寫文\圖表區\主客防禦率.xlsx')
藍色(1)表示主隊防禦率較低,機率為53.1%,橘色(0)表示客隊防禦率較低,機率為46.9%。我們可以發現,主隊防禦率的確有較高的機率低於客隊防禦率,但一樣從統計的角度來看,53.1.7%的機率其實還是偏向於各半,所以說,防禦率並無特別受到主場優勢的加持
。
依照我們一般的邏輯,防禦率的計算是直接牽扯到隊手的得分,因此防禦率低的隊伍應該要有較高的機率獲勝,但真實情況會如我們想的一樣嗎?我們來統計看看吧!!
這裡我們用到剛剛創建的欄位「more_ERA_win」來視覺化看看。
trace0 = go.Pie(labels=df["more_ERA_win"].value_counts().index,
values=df["more_ERA_win"].value_counts().values,
hole=0.5,
opacity=0.9,
marker=dict(line=dict(color='white', width=1.3))
)
layout = go.Layout(title='三振率與勝負關係')
data = [trace0]
fig = go.Figure(data, layout)
py.offline.plot(fig, filename=r'C:\Users\Guess365User\Desktop\IT邦寫文\圖表區\三振率與勝負關係圖.xlsx')
此處藍色(1)為防禦率低且獲勝者,佔比高達97.9%,而橘色(0)為防禦率低但卻輸者,佔筆僅2.13%,由此可知,防禦率較低的隊伍,幾乎可以確定就會拿下該場比賽
。
雖然確定防禦率低的隊伍幾乎就是勝者,但我們還挺好奇這剩下的2.13%究竟是怎麼回事,所以我們特別拉出來觀察看看。
我們總共的資料筆數為15439筆,而防禦率低卻落敗的場數共有329場。
df[df["more_ERA_win"] == 0]
我們想了一下,可能造成防禦率低卻輸掉的最直接原因,那應該就是「非自責分數」高吧,因為非自責分的失分不會記錄在防禦率中,所以在計算上才會出現低防禦率卻輸掉比賽,但這依舊是我們的猜想,所以我們來實證看看。
我們本身數據就擁有「主客隊得分」以及「投手自責分(ER)」這兩個數據,因此我們將敵隊的得分減去自家隊的自責分,那麼就可以得到自己的非自責分。
df['Home_NotER'] = df['客隊得分'] - df["Home_Pitcher_ER"]layout = go.Layout(title='三振率與勝負關係')
df['Away_NotER'] = df['主隊得分'] - df["Away_Pitcher_ER"]
df.head(5)
接著我們分別計算兩種狀況的非自責分平均為多少
正常的情況
下,防禦率低獲勝的隊伍,其非自責分平均每場為0.21分,而防禦率高落敗的隊伍,其非自責分平均每場為0.43分,其實都算相當的低,但很明顯防禦率低的隊伍非自責分相對較低
,因此能夠拿下比賽勝利。df_win = df[df["more_ERA_win"] == 1]
win_notera = 0
lose_notera = 0
for i in range(len(df_win)):
if df_win['W/L'][i] == 0:
win_notera += df_win['Away_NotER'][i]
lose_notera += df_win['Home_NotER'][i]
else:
win_notera += df_win['Home_NotER'][i]
lose_notera += df_win['Away_NotER'][i]
print("防禦率低獲勝的非自責分平均為 : " + str(round((win_notera/len(df_win)),2)) + " 分")
print("防禦率高落敗的非自責分平均為 : " + str(round((lose_notera/len(df_win)),2))+ " 分")
接著看例外狀況,沒想到在這些例外的場次中,防禦率高卻獲勝的隊伍,其非自責分僅0.08分,而防禦率低卻落敗的隊伍,其非自責分高達2.82分
,可想而知,這幾場比賽的投手真的是衰,一整個被隊友的守備能力拖累,但究竟是甚麼原因造成這些比賽有如此高的非自責分出現,這部分我們以後有機會再做更深入的探討。
df_ nowin = df[df["more_ERA_win"] == 0]
win_notera = 0
lose_notera = 0
for i in range(len(df_ nowin)):
if df_win['W/L'][i] == 0:
win_notera += df_ nowin['Away_NotER'][i]
lose_notera += df_ nowin['Home_NotER'][i]
else:
win_notera += df_ nowin['Home_NotER'][i]
lose_notera += df_ nowin['Away_NotER'][i]
print("防禦率低獲勝的非自責分平均為 : " + str(round((win_notera/len(df_ nowin)),2)) + " 分")
print("防禦率高落敗的非自責分平均為 : " + str(round((lose_notera/len(df_ nowin)),2))+ " 分")
看到這,應該大致了解防禦率是非野手失誤所造成對手擊球出去得分的呈現,但打擊出去就一定會得分嗎?有些球隊或許打擊率不高,但卻總能在關鍵時刻擊球得分,但照常理來說,被打擊率低的球隊,防禦率正常也會較低才對。不過沒數據的證實,終究只是我們紙上談兵,所以我們就來統計看看吧!!
被打擊率其實就是對手的打擊率,所以我們可以使用到上一篇研究打擊率的數值,我們分別將「主隊打擊率與客隊防禦率」、「客隊打擊率與主隊防禦率」做散點圖視覺化出來。
plt.rcParams['font.sans-serif'] = ['Microsoft JhengHei']
plt.rcParams['axes.unicode_minus'] = False
plt.figure(figsize=(10, 10), dpi=100)
plt.scatter(df['Away_Pitcher_ERA'],df['Home_Batting_AVG'], c='#FFA500', s=30, label='防禦率與被打擊率')
plt.xlabel("客隊防禦率", fontdict={'size': 10})
plt.ylabel("主隊打擊率", fontdict={'size': 10})
plt.title("防禦率與被打擊率關係", fontdict={'size': 20})
plt.legend(loc='best')
plt.show()
plt.rcParams['font.sans-serif'] = ['Microsoft JhengHei']
plt.rcParams['axes.unicode_minus'] = False
plt.figure(figsize=(10, 10), dpi=100)
plt.scatter(df[' Home_Pitcher_ERA'],df['Away_Batting_AVG'], c='#FFA500', s=30, label='防禦率與被打擊率')
plt.xlabel("主隊防禦率", fontdict={'size': 10})
plt.ylabel("客隊打擊率", fontdict={'size': 10})
plt.title("防禦率與被打擊率關係", fontdict={'size': 20})
plt.legend(loc='best')
plt.show()
由上圖可知,散點圖幾乎分布於左下角,且越右方有越高的趨勢,也就證明,防禦率與被打擊率的確呈現正向關係
,但可以看到仍然有些許的例外案例。
最終我們仍回歸到主場優勢身上,雖然上方我們統計出主客場誰的防禦率較低時,主隊僅略勝一籌,仍無法證實其有主場優勢,所以我們再來統計看看防禦率低且獲得勝利女神眷顧的隊伍究竟會是哪一方。
df_era_win = df[df['more_Pitcher_ERA'] == df['W/L']]
df_era_win.head()
不過從下方的統計圖來看,藍色(1)為防禦率低且獲勝的隊伍為主隊,佔比53.4%,橘色(0)為防禦率低且獲勝的隊伍為客隊,佔比為46.6%。可以看出主客的佔比與第一點的主客防禦率高低分布圖相似,因此可以說,只要哪方防禦率低,幾乎就可以確定哪方可以獲勝
。
trace0 = go.Pie(labels= df_era_win["W/L"].value_counts().index,
values= df_era_win["W/L"].value_counts().values,
hole=0.5,
opacity=0.9,
marker=dict(line=dict(color='white', width=1.3))
)
layout = go.Layout(title='防禦率勝負與主客關係')
data = [trace0]
fig = go.Figure(data, layout)
py.offline.plot(fig, filename=r'C:\Users\Guess365User\Desktop\IT邦寫文\圖表區\防禦率勝負與主客關係圖.xlsx')
根據今天的研究,防禦率真的是預測比賽勝負一個極其重要的因子,只要能擁有準確的防禦率將能夠大大提升預測勝負的準確度,而這正是將來要研究的議題之一,我們現階段就是希望能找到更多如同防禦率這樣明確區分勝負的重要因子,因此我們就繼續探討下去吧!!
[Guess官網平台-分析專欄] https://reurl.cc/yMOVE6
[資料視覺化 | 製作充滿說服力的資訊圖表]
[Python資料分析必備套件!Pandas資料清理、重塑、過濾、視覺化]
[Python 大數據專案 X 工程 X 產品 資料工程師的升級攻略]