Le 18 juin 2014, dans le match des Colorado Rockies contre les Dodgers de Los Angeles, le lanceur des Dodgers Clayton Kershaw a lancé neuf manches, réalisant 15 prises et aucun coup sûr. Cette fois, nous allons le comparer avec le lanceur de l'adversaire Rockies et analyser pourquoi le lanceur Clayton Kershaw a réussi à réaliser un no-run sans coup sûr.
・ Python 3.7.5 ・ Windows10 ・ Cahier Jupyter (Anaconda3)
$ jupyter notebook
baseball_analysis.ipynb
%matplotlib inline
import requests
import xml.etree.ElementTree as ET
import os
import pandas as pd
baseball_analysis.ipynb
#Création de trame de données
pitchDF = pd.DataFrame(columns = ['pitchIdx', 'inning', 'frame', 'ab', 'abIdx', 'batter', 'stand', 'speed',
'pitchtype', 'px', 'pz', 'szTop', 'szBottom', 'des'], dtype=object)
#Création d'un dictionnaire de type boule
pitchDictionary = { "FA":"fastball", "FF":"4-seam fb", "FT": "2-seam fb", "FC": "fb-cutter", "":"unknown", None: "none",
"FS":"fb-splitter", "SL":"slider", "CH":"changeup","CU":"curveball","KC":"knuckle-curve",
"KN":"knuckleball","EP":"eephus", "UN":"unidentified", "PO":"pitchout", "SI":"sinker", "SF":"split-finger"
}
# top=Table, bas=retour
frames = ["top", "bottom"]
baseball_analysis.ipynb
#Lire les informations du lecteur distribuées par MLB Advanced Media
url = 'https://gd2.mlb.com/components/game/mlb/year_2014/month_06/day_18/gid_2014_06_18_colmlb_lanmlb_1/players.xml'
resp = requests.get(url)
xmlfile = "myplayers.xml"
with open(xmlfile, mode='wb') as f:
f.write(resp.content)
statinfo = os.stat(xmlfile)
#Analyser le fichier xml
tree = ET.parse(xmlfile)
game = tree.getroot()
teams = game.findall("./team")
playerDict = {}
for team in teams:
players = team.findall("./player")
for player in players:
#Ajouter l'identifiant et le nom du joueur au dictionnaire
playerDict[ player.attrib.get("id") ] = player.attrib.get("first") + " " + player.attrib.get("last")
baseball_analysis.ipynb
#Lire les données de chaque manche distribuées par MLB Advanced Media
url = 'https://gd2.mlb.com/components/game/mlb/year_2014/month_06/day_18/gid_2014_06_18_colmlb_lanmlb_1/inning/inning_all.xml'
resp = requests.get(url)
xmlfile = "mygame.xml"
with open(xmlfile, 'wb') as f:
f.write(resp.content)
statinfo = os.stat(xmlfile)
#Analyser le fichier xml
tree = ET.parse(xmlfile)
root = tree.getroot()
innings = root.findall("./inning")
totalPitchCount = 0
topPitchCount = 0
bottomPitchCount = 0
for inning in innings:
for i in range(len(frames)):
fr = inning.find(frames[i])
if fr is not None:
for ab in fr.iter('atbat'):
battername = playerDict[ab.get('batter')]
standside = ab.get('stand')
abIdx = ab.get('num')
abPitchCount = 0
pitches = ab.findall("pitch")
for pitch in pitches:
if pitch.attrib.get("start_speed") is None:
speed == 0
else:
speed = float(pitch.attrib.get("start_speed"))
pxFloat = 0.0 if pitch.attrib.get("px") == None else float('{0:.2f}'.format(float(pitch.attrib.get("px"))))
pzFloat = 0.0 if pitch.attrib.get("pz") == None else float('{0:.2f}'.format(float(pitch.attrib.get("pz"))))
szTop = 0.0 if pitch.attrib.get("sz_top") == None else float('{0:.2f}'.format(float(pitch.attrib.get("sz_top"))))
szBot = 0.0 if pitch.attrib.get("sz_bot") == None else float('{0:.2f}'.format(float(pitch.attrib.get("sz_bot"))))
abPitchCount = abPitchCount + 1
totalPitchCount = totalPitchCount + 1
if frames[i]=='top':
topPitchCount = topPitchCount + 1
else:
bottomPitchCount = bottomPitchCount + 1
inn = inning.attrib.get("num")
verbosePitch = pitchDictionary[pitch.get("pitch_type")]
desPitch = pitch.get("des")
#Ajouter au bloc de données
pitchDF.loc[totalPitchCount] = [float(totalPitchCount), inn, frames[i], abIdx, abPitchCount, battername, standside, speed,
verbosePitch, pxFloat, pzFloat, szTop, szBot, desPitch]
baseball_analysis.ipynb
pitchDF
# pitchIdx=numéro de série
# inning=tour de batte
# frame=Avant et arrière
# ab=ID de la batterie
# abIdx=Nombre de balles par bâton
# batter=Nom de la pâte
# stand=Au bâton(R → droitier, L → gaucher)
# speed=Vitesse de balle
# pitchtype=Type de balle
# px=Position de dépassement de la base d'origine(Gauche et droite)(Droite → positive, gauche → négative)
# pz=Position de dépassement de la base d'origine(Haut bas)
# szTop=Distance du sol à la valeur la plus élevée de la zone de frappe du frappeur
# szBottom=Distance du sol à la valeur la plus basse de la zone de frappe du frappeur
# des=résultat
baseball_analysis.ipynb
import matplotlib.pyplot as plt
import matplotlib.patches as patches
#Dessinez une nouvelle fenêtre
fig1 = plt.figure()
#Ajouter un sous-tracé
ax1 = fig1.add_subplot(111, aspect='equal')
#La largeur de la zone de frappe est de 17 pouces= 1.4 pieds
#La hauteur de la zone de frappe est de 1.5~3.5 pieds
#La taille de la balle de baseball est de 3 pouces= 0.25 pieds
#Comment trouver des pieds=pouce/ 12
#Création de zone de grève
#Le cadre bleu est la zone de frappe
platewidthInFeet = 17 / 12
szHeightInFeet = 3.5 - 1.5
#Créer une zone de frappe à l'extérieur d'une balle
#Le cadre bleu clair est une zone de frappe à l'extérieur d'une balle
expandedPlateInFeet = 20 / 12
ballInFeet = 3 / 12
halfBallInFeet = ballInFeet / 2
ax1.add_patch(patches.Rectangle((expandedPlateInFeet/-2, 1.5 - halfBallInFeet), expandedPlateInFeet, szHeightInFeet + ballInFeet, color='lightblue'))
ax1.add_patch(patches.Rectangle((platewidthInFeet/-2, 1.5), platewidthInFeet, szHeightInFeet))
plt.ylim(0, 5)
plt.xlim(-2, 2)
plt.show()
baseball_analysis.ipynb
uniqDesList = pitchDF.des.unique()
ballColList = []
strikeColList = []
ballCount = 0
strikeCount = 0
for index, row in pitchDF.iterrows():
des = row['des']
if row['abIdx'] == 1:
ballCount = 0
strikeCount = 0
ballColList.append(ballCount)
strikeColList.append(strikeCount)
if 'Ball' in des:
ballCount = ballCount + 1
elif 'Foul' in des:
if strikeCount is not 2:
strikeCount = strikeCount + 1
elif 'Strike' in des:
strikeCount = strikeCount + 1
#Ajouter au bloc de données
pitchDF['ballCount'] = ballColList
pitchDF['strikeCount'] = strikeColList
baseball_analysis.ipynb
pitchDF
baseball_analysis.ipynb
df= pitchDF.loc[pitchDF['frame']=='top']
ax1 = df.plot(kind='scatter', x='px', y='pz', marker='o', color='red', figsize=[8,8], ylim=[0,4], xlim=[-2,2])
ax1.set_xlabel('horizontal location')
ax1.set_ylabel('vertical location')
ax1.set_title('La tendance au lancer de Clayton Kershaw')
ax1.set_aspect(aspect=1)
platewidthInFeet = 17 / 12
expandedPlateInFeet = 20 / 12
szTop = df["szTop"].iloc[0]
szBottom = df["szBottom"].iloc[0]
szHeightInFeet = szTop - szBottom
ballInFeet = 3 / 12
halfBallInFeet = ballInFeet / 2
outrect = ax1.add_patch(patches.Rectangle((expandedPlateInFeet/-2, szBottom - halfBallInFeet), expandedPlateInFeet, szHeightInFeet + ballInFeet, color='lightblue'))
rect = ax1.add_patch(patches.Rectangle((platewidthInFeet/-2, szBottom), platewidthInFeet, szHeightInFeet))
outrect.zorder=-2
rect.zorder=-1
plt.ylim(0, 5)
plt.xlim(-2.5, 2.5)
plt.show()
baseball_analysis.ipynb
df= pitchDF.loc[pitchDF['frame']=='bottom']
ax1 = df.plot(kind='scatter', x='px', y='pz', marker='o', color='red', figsize=[8,8], ylim=[0,4], xlim=[-2,2])
ax1.set_xlabel('horizontal location')
ax1.set_ylabel('vertical location')
ax1.set_title('Tendance au lancer des Rocheuses')
ax1.set_aspect(aspect=1)
platewidthInFeet = 17 / 12
expandedPlateInFeet = 20 / 12
szTop = df["szTop"].iloc[0]
szBottom = df["szBottom"].iloc[0]
szHeightInFeet = szTop - szBottom
ballInFeet = 3 / 12
halfBallInFeet = ballInFeet / 2
outrect = ax1.add_patch(patches.Rectangle((expandedPlateInFeet/-2, szBottom - halfBallInFeet), expandedPlateInFeet, szHeightInFeet + ballInFeet, color='lightblue'))
rect = ax1.add_patch(patches.Rectangle((platewidthInFeet/-2, szBottom), platewidthInFeet, szHeightInFeet))
outrect.zorder=-2
rect.zorder=-1
plt.ylim(0, 5)
plt.xlim(-2.5, 2.5)
plt.show()
Comparer les deux pichets, ** Taux de grève Clayton: 65% ** ** Taux de grève des Rocheuses: 56% ** J'ai découvert que. Comparé aux lanceurs des Rocheuses, Clayton estime qu'il y a moins de balles qui sortent sur le côté. Est-ce l'influence du curseur ou de la ligne droite qui sautille qu'il y a beaucoup de variation dans la direction verticale?
Ensuite, regardons la tendance de la première balle.
baseball_analysis.ipynb
df= pitchDF.loc[pitchDF['frame']=='top'].loc[pitchDF['abIdx']==1]
ax1 = df.plot(kind='scatter', x='px', y='pz', marker='o', color='red', figsize=[8,8], ylim=[0,4], xlim=[-2,2])
ax1.set_xlabel('horizontal location')
ax1.set_ylabel('vertical location')
ax1.set_title('La première tendance au ballon de Clayton Kershaw')
ax1.set_aspect(aspect=1)
platewidthInFeet = 17 / 12
expandedPlateInFeet = 20 / 12
szTop = df["szTop"].iloc[0]
szBottom = df["szBottom"].iloc[0]
szHeightInFeet = szTop - szBottom
ballInFeet = 3 / 12
halfBallInFeet = ballInFeet / 2
outrect = ax1.add_patch(patches.Rectangle((expandedPlateInFeet/-2, szBottom - halfBallInFeet), expandedPlateInFeet, szHeightInFeet + ballInFeet, color='lightblue'))
rect = ax1.add_patch(patches.Rectangle((platewidthInFeet/-2, szBottom), platewidthInFeet, szHeightInFeet))
outrect.zorder=-2
rect.zorder=-1
plt.ylim(0, 5)
plt.xlim(-2.5, 2.5)
plt.show()
baseball_analysis.ipynb
df= pitchDF.loc[pitchDF['frame']=='bottom'].loc[pitchDF['abIdx']==1]
ax1 = df.plot(kind='scatter', x='px', y='pz', marker='o', color='red', figsize=[8,8], ylim=[0,4], xlim=[-2,2])
ax1.set_xlabel('horizontal location')
ax1.set_ylabel('vertical location')
ax1.set_title('La première tendance de balle des Rocheuses')
ax1.set_aspect(aspect=1)
platewidthInFeet = 17 / 12
expandedPlateInFeet = 20 / 12
szTop = df["szTop"].iloc[0]
szBottom = df["szBottom"].iloc[0]
szHeightInFeet = szTop - szBottom
ballInFeet = 3 / 12
halfBallInFeet = ballInFeet / 2
outrect = ax1.add_patch(patches.Rectangle((expandedPlateInFeet/-2, szBottom - halfBallInFeet), expandedPlateInFeet, szHeightInFeet + ballInFeet, color='lightblue'))
rect = ax1.add_patch(patches.Rectangle((platewidthInFeet/-2, szBottom), platewidthInFeet, szHeightInFeet))
outrect.zorder=-2
rect.zorder=-1
plt.ylim(0, 5)
plt.xlim(-2.5, 2.5)
plt.show()
Comparer les deux pichets, ** Taux de frappe de Clayton à la première balle: 71% ** ** Taux de frappe à la première balle des Rocheuses: 64% ** J'ai découvert que.
Le lanceur Clayton a un petit nombre de balles et est en avance sur la frappe.
Ensuite, regardons le changement de vitesse de balle.
baseball_analysis.ipynb
df = pitchDF.loc[(pitchDF['frame']=='top')]
speed = df['speed']
print(sum(speed) / len(speed))
print(max(speed))
print(min(speed))
print(max(speed) - min(speed))
ax = df.plot(x='pitchIdx', y='speed', color='blue', figsize=[12,6])
ax.set_ylabel('speed')
ax.set_title('Changement de vitesse de balle dans les Rocheuses')
plt.savefig('pitch_rockies_speed.png')
plt.show()
>>>>>>>>>>>>>>>>>>>>>>>>>
#Vitesse moyenne de la balle: 87.88504672897201
#Le plus rapide: 95.0
#Le plus tardif: 72.4
#Différence lente / rapide: 22.599999999999994
baseball_analysis.ipynb
df = pitchDF.loc[(pitchDF['frame']=='bottom')]
speed = df['speed']
print(sum(speed) / len(speed))
print(max(speed))
print(min(speed))
print(max(speed) - min(speed))
ax = df.plot(x='pitchIdx', y='speed', color='blue', figsize=[12,6])
ax.set_ylabel('speed')
ax.set_title('Changement de vitesse de balle dans les Rocheuses')
plt.savefig('pitch_rockies_speed.png')
plt.show()
>>>>>>>>>>>>>>>>>>>>>>>>>
#Vitesse moyenne de la balle: 89.13599999999998
#Le plus rapide: 96.3
#Le plus tardif: 71.8
#Différence lente / rapide: 24.5
Comparer les deux pichets, Clayton ** Vitesse moyenne de la balle: 140 km ** ** Plus rapide: 95 miles ** ** Tardif: 72 miles ** ** Différence de vitesse: 22 miles **
Rockies ** Vitesse moyenne de la balle: 89 miles ** ** Plus rapide: 96 miles ** ** Tardif: 71 milles ** ** Différence de vitesse: 24 miles ** J'ai découvert que.
Rockies emploie cinq pichets, il est donc naturel qu'il y ait une différence de tendances.
Ensuite, regardons le changement de vitesse de balle.
baseball_analysis.ipynb
df = pitchDF.loc[(pitchDF['frame']=='top')]
df.pitchtype.value_counts().plot(kind='pie', autopct="%.1f%%", pctdistance=0.8)
plt.axis('equal')
plt.axis('off')
plt.title('Ratio de type de boule')
plt.show()
baseball_analysis.ipynb
df = pitchDF.loc[(pitchDF['pitchtype']=='4-seam fb') & (pitchDF['frame']=='top')]
df.des.value_counts().plot(kind='pie', autopct="%.1f%%", pctdistance=0.8)
plt.axis('equal')
plt.axis('off')
plt.title('4-résultat de l'événement de couture')
plt.show()
baseball_analysis.ipynb
df = pitchDF.loc[(pitchDF['pitchtype']=='slider') & (pitchDF['frame']=='top')]
df.des.value_counts().plot(kind='pie', autopct="%.1f%%", pctdistance=0.8)
plt.axis('equal')
plt.axis('off')
plt.title('résultat de l'événement de curseur')
plt.show()
baseball_analysis.ipynb
df = pitchDF.loc[(pitchDF['pitchtype']=='curveball') & (pitchDF['frame']=='top')]
df.des.value_counts().plot(kind='pie', autopct="%.1f%%", pctdistance=0.8)
plt.axis('equal')
plt.axis('off')
plt.title('résultat de l'événement Curveball')
plt.show()
baseball_analysis.ipynb
df = pitchDF.loc[(pitchDF['pitchtype']=='changeup') & (pitchDF['frame']=='top')]
df.des.value_counts().plot(kind='pie', autopct="%.1f%%", pctdistance=0.8)
plt.axis('equal')
plt.axis('off')
plt.title('résultat de l'événement de modification')
plt.show()
Comparaison des taux de sortie pour chaque type de balle, ** 4 coutures: 35,7% ** ** Curseur: 18,8% ** ** Courbe: 22,3% ** ** Changement: 0% ** J'ai découvert que.
Les quatre coutures, qui représentent la moitié du nombre de lancers, sont plutôt bonnes.
Ensuite, regardons la distribution des balles par nombre.
baseball_analysis.ipynb
titleList = []
dataList = []
fig, axes = plt.subplots(4, 3, figsize=(12,16))
#Création de compte
for b in range(4):
for s in range(3):
df = pitchDF.loc[(pitchDF['ballCount']==b) & (pitchDF['strikeCount']==s) & (pitchDF['frame']=='top')]
title = "Count:" + str(b) + "-" + str(s) + " (" + str(len(df)) + ")"
titleList.append(title)
dataList.append(df)
for i, ax in enumerate(axes.flatten()):
x = dataList[i].pitchtype.value_counts()
l = dataList[i].pitchtype.unique()
ax.pie(x, autopct="%.1f%%", pctdistance=0.9, labels=l)
ax.set_title(titleList[i])
plt.show()
Eh bien, presque 4 coutures.
Ensuite, regardons les résultats par nombre.
baseball_analysis.ipynb
titleList = []
dataList = []
fig, axes = plt.subplots(4, 3, figsize=(12,16))
for b in range(4):
for s in range(3):
df = pitchDF.loc[(pitchDF['ballCount']==b) & (pitchDF['strikeCount']==s) & pitchDF['des'] & (pitchDF['frame']=='top')]
title = "Count:" + str(b) + "-" + str(s) + " (" + str(len(df)) + ")"
titleList.append(title)
dataList.append(df)
for i, ax in enumerate(axes.flatten()):
x = dataList[i].des.value_counts()
l = dataList[i].des.unique()
ax.pie(x, autopct="%.1f%%", pctdistance=0.9, labels=l)
ax.set_title(titleList[i])
plt.show()
Vous pouvez voir qu'il y a une forte probabilité d'un jugement de grève et en play-outs (à la suite de la balle volant sur le terrain) à tout compte.
――Il y a beaucoup de premières frappes de balle, et nous avons un décompte favorable (nous en avons pris pas mal avant que cela ne devienne avantageux)
--Méfiez-vous des curseurs inattendus (probablement des fissures verticales)
J'étais familier avec les caractéristiques du lanceur Clayton dans une certaine mesure, mais je ne pouvais pas comprendre la raison pour laquelle il a obtenu un no-run sans coup sûr sans comparer avec d'autres jeux. Vous aurez également besoin d'un enregistrement des batailles passées avec des adversaires. Le lanceur Clayton était bien contrôlé et seulement 107 lancers ont été lancés dans ce match. MLB a plus de matchs que NPB et lance toute la saison au milieu du 4e, donc même si le lanceur lance bien, il y a une tendance à tomber à environ 120 balles en raison de la limite de lancer. Par conséquent, un bon contrôle peut être le facteur le plus important pour obtenir une course sans coup sûr dans les ligues majeures. Cela fait longtemps, mais merci d'avoir lu jusqu'ici. Si vous constatez des erreurs, je vous serais très reconnaissant de bien vouloir les signaler dans les commentaires.
Recommended Posts