Skip to content

Commit e29417e

Browse files
author
Kevin Krauss
committed
add box score to gather line score data
removed unused deps change this to kind so it does not shadow builtin change this to be usable in 3.9 as the library currently supports 3.9 removed unnecessary column changes remove dropping empty string key
1 parent b7ecf3c commit e29417e

4 files changed

Lines changed: 106 additions & 11 deletions

File tree

docs/boxes.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# boxes
2+
3+
`boxes(team: str, date: str)`
4+
5+
Get Baseball Reference's box score data for a particular game.
6+
7+
## Arguments
8+
`team` String. The team name abbrevation format.
9+
`date` String. The date of the game which we want to find the box score.
10+
`doubleheader` Integer. 0 for the first game of the day, and 1 for the second game. Default is 0.
11+
12+
## Examples of valid queries
13+
14+
```python
15+
from pybaseball import boxes
16+
17+
team = 'DET'
18+
date = '2010-07-19'
19+
20+
boxes(team, date)
21+
22+
```
23+

pybaseball/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from .playerid_lookup import chadwick_register
66
from .teamid_lookup import fangraphs_teams
77
from .teamid_lookup import team_ids
8+
from .boxes import boxes
89
from .statcast import statcast, statcast_single_game
910
from .statcast_pitcher import (
1011
statcast_pitcher,

pybaseball/boxes.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
from datetime import date
2+
3+
import pandas as pd
4+
from bs4 import BeautifulSoup
5+
6+
from .utils import sanitize_date_range
7+
from .datasources.bref import BRefSession
8+
9+
session = BRefSession()
10+
11+
12+
def get_soup(team: str, start_dt: date, doubleheader: int = 0) -> BeautifulSoup:
13+
# get most recent standings if date not specified
14+
# if((start_dt is None) or (end_dt is None)):
15+
# print('Error: a date range needs to be specified')
16+
# return None
17+
# https://www.baseball-reference.com/boxes/DET/DET201007190.shtml
18+
url = "http://www.baseball-reference.com/boxes/{}/{}{}{}.shtml".format(team, team, start_dt.strftime('%Y%m%d'), doubleheader)
19+
s = session.get(url).content
20+
# a workaround to avoid beautiful soup applying the wrong encoding
21+
s = s.decode('utf-8')
22+
return BeautifulSoup(s, features="lxml")
23+
24+
def extract_line_score(data):
25+
return {
26+
'team': data[1],
27+
'line_score': ''.join(data[2:-4]),
28+
'runs': data[-4],
29+
'hits': data[-3],
30+
'errors': data[-2],
31+
}
32+
33+
34+
def get_table(soup: BeautifulSoup) -> pd.DataFrame:
35+
table = soup.find_all('table')[0]
36+
data = []
37+
headings = [th.get_text() for th in table.find("tr").find_all("th")][1:]
38+
headings.append("mlbID")
39+
data.append(headings)
40+
table_body = table.find('tbody')
41+
rows = table_body.find_all('tr')
42+
for row in rows:
43+
cols = row.find_all('td')
44+
row_anchor = row.find("a")
45+
mlbid = row_anchor["href"].split("mlb_ID=")[-1] if row_anchor else pd.NA # ID str or nan
46+
cols = [ele.text.strip() for ele in cols]
47+
cols.append(mlbid)
48+
data.append([ele for ele in cols])
49+
data = [extract_line_score(d) for d in data]
50+
df = pd.DataFrame(data)
51+
df = df.reindex(df.index.drop(0))
52+
return df
53+
54+
55+
def boxes(team: str, date: str) -> pd.DataFrame:
56+
"""
57+
Get all batting stats for a set time range. This can be the past week, the
58+
month of August, anything. Just supply the start and end date in YYYY-MM-DD
59+
format.
60+
"""
61+
# make sure date inputs are valid
62+
game_date, end_dt_date = sanitize_date_range(date, date)
63+
if game_date.year < 2008:
64+
raise ValueError("Year must be 2008 or later")
65+
if end_dt_date.year < 2008:
66+
raise ValueError("Year must be 2008 or later")
67+
# retrieve html from baseball reference
68+
soup = get_soup(team, game_date)
69+
table = get_table(soup)
70+
table = table.dropna(how='all') # drop if all columns are NA
71+
return table

pybaseball/retrosheet.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@
3030
from github.GithubException import RateLimitExceededException
3131
import warnings
3232

33+
EVENT_TYPES = {
34+
'regular': ('.EVA','.EVN'),
35+
'post': ('CS.EVE','D1.EVE','D2.EVE','W1.EVE','W2.EVE','WS.EVE'),
36+
'asg': ('AS.EVE')
37+
}
38+
3339
gamelog_columns = [
3440
'date', 'game_num', 'day_of_week', 'visiting_team',
3541
'visiting_team_league', 'visiting_team_game_num', 'home_team',
@@ -107,9 +113,9 @@
107113
roster_url = 'https://raw.githubusercontent.com/chadwickbureau/retrosheet/master/seasons/{}/{}{}.ROS'
108114
event_url = 'https://raw.githubusercontent.com/chadwickbureau/retrosheet/master/seasons/{}/{}'
109115

110-
def events(season, type='regular', export_dir='.'):
116+
def events(season, kind='regular', export_dir='.'):
111117
"""
112-
Pulls retrosheet event files for an entire season. The `type` argument
118+
Pulls retrosheet event files for an entire season. The `kind` argument
113119
specifies whether to pull regular season, postseason or asg files. Valid
114120
arguments are 'regular', 'post', and 'asg'.
115121
@@ -119,14 +125,8 @@ def events(season, type='regular', export_dir='.'):
119125
GH_TOKEN=os.getenv('GH_TOKEN', '')
120126
if not os.path.exists(export_dir):
121127
os.mkdir(export_dir)
122-
123-
match type:
124-
case 'regular':
125-
file_extension = ('.EVA','.EVN')
126-
case 'post':
127-
file_extension = ('CS.EVE','D1.EVE','D2.EVE','W1.EVE','W2.EVE','WS.EVE')
128-
case 'asg':
129-
file_extension = ('AS.EVE')
128+
129+
file_extension = EVENT_TYPES.get(kind)
130130

131131
try:
132132
g = Github(GH_TOKEN)
@@ -215,7 +215,7 @@ def schedules(season):
215215
repo = g.get_repo('chadwickbureau/retrosheet')
216216
season_folder = [f.path[f.path.rfind('/')+1:] for f in repo.get_contents(f'seasons/{season}')]
217217
file_name = f'{season}schedule.csv'
218-
218+
219219
if file_name not in season_folder:
220220
raise ValueError(f'Schedule not available for {season}')
221221
s = get_text_file(schedule_url.format(season, season))

0 commit comments

Comments
 (0)