Skip to content

Commit d591bb9

Browse files
authored
Merge pull request vuejs#2 from American-Soccer-Analysis/python-package
Use dataframes instead of dictionaries to perform ID lookup
2 parents be02cd8 + e39193b commit d591bb9

File tree

11 files changed

+164
-105
lines changed

11 files changed

+164
-105
lines changed

.github/workflows/python-tests.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ jobs:
77
runs-on: ubuntu-latest
88
strategy:
99
matrix:
10-
python-version: [3.6, 3.7, 3.8, 3.9]
10+
python-version: [3.7, 3.8, 3.9]
1111

1212
steps:
1313
- uses: actions/checkout@v2
@@ -22,8 +22,8 @@ jobs:
2222
if [ -f requirements.dev.txt ]; then pip install -r requirements.dev.txt; fi
2323
- name: Test with pytest
2424
run: |
25-
pytest tests/python/
25+
pytest python-package/tests/
2626
- name: Behave tests
2727
run: |
28-
cd tests
28+
cd python-package/tests
2929
behave

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
# asa-package
1+
# itscalledsoccer
22

33
R and Python packages that wrap the ASA API
+152-95
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
from os import name
2-
import re
31
import requests
42
from typing import Dict, List, Any, Union
53
from cachecontrol import CacheControl
64
from fuzzywuzzy import fuzz, process
5+
import pandas as pd
6+
import json
77

88

99
class AmericanSoccerAnalysis:
@@ -20,34 +20,31 @@ def __init__(self) -> None:
2020

2121
self.session = CACHE_SESSION
2222
self.base_url = self.BASE_URL
23-
self.players = self._get_all_ids("player")
24-
self.teams = self._get_all_ids("team")
25-
self.stadia = self._get_all_ids("stadia")
26-
self.managers = self._get_all_ids("manager")
27-
self.referees = self._get_all_ids("referee")
28-
29-
def _get_all_ids(self, type: str) -> Dict[str, str]:
30-
"""Creates a dictionary where keys are names and values
31-
are corresponding ids.
32-
33-
:param type: type of ids to get
34-
:returns: dictionary
23+
self.players = self._get_all("player")
24+
self.teams = self._get_all("team")
25+
self.stadia = self._get_all("stadia")
26+
self.managers = self._get_all("manager")
27+
self.referees = self._get_all("referee")
28+
29+
def _get_all(self, type: str) -> pd.DataFrame:
30+
"""Gets all the data for a specific type and
31+
stores it in a dataframe.
32+
33+
:param type: type of data to get
34+
:returns: dataframe
3535
"""
36-
all_ids = {}
36+
df = pd.DataFrame([])
3737
for league in self.LEAGUES:
3838
if type == "stadia":
39-
url = f"{self.BASE_URL}{league}/{type}"
40-
type = "stadium"
39+
url = f"{self.BASE_URL}{league}/stadia"
4140
else:
4241
url = f"{self.BASE_URL}{league}/{type}s"
4342
response = self.session.get(url).json()
44-
for resp in response:
45-
name = resp.get(f"{type}_name", "None")
46-
name_id = resp.get(f"{type}_id", "None")
47-
all_ids.update({name: name_id})
48-
if type == "stadium":
49-
type = "stadia"
50-
return all_ids
43+
# Convert list of objects to JSON
44+
resp_df = pd.read_json(json.dumps(response, default=lambda x: x.__dict__))
45+
resp_df = resp_df.assign(competition=league)
46+
df = df.append(resp_df)
47+
return df
5148

5249
def _convert_name_to_id(self, type: str, name: str) -> Union[str, int]:
5350
"""Converts the name of a player, manager, stadium, referee or team
@@ -60,31 +57,31 @@ def _convert_name_to_id(self, type: str, name: str) -> Union[str, int]:
6057
min_score = 70
6158
if type == "player":
6259
lookup = self.players
63-
names = self.players.keys()
60+
names = self.players["player_name"].to_list()
6461
elif type == "manager":
6562
lookup = self.managers
66-
names = self.managers.keys()
63+
names = self.managers["manager_name"].to_list()
6764
elif type == "stadium":
6865
lookup = self.stadia
69-
names = self.stadia.keys()
66+
names = self.stadia["stadium_name"].to_list()
7067
elif type == "referee":
7168
lookup = self.referees
72-
names = self.referees.keys()
69+
names = self.referees["referee_name"].to_list()
7370
elif type == "team":
7471
lookup = self.teams
75-
names = self.teams.keys()
72+
names = self.teams["team_name"].to_list()
7673

7774
matches = process.extractOne(name, names, scorer=fuzz.partial_ratio)
7875
if matches:
79-
if matches[1] > min_score:
80-
lookup_id = matches[0]
76+
if matches[1] >= min_score:
77+
name = matches[0]
8178
else:
82-
print(f"No match found for {name}")
79+
print(f"No match found for {name} due to score")
8380
return ""
8481
else:
8582
print(f"No match found for {name}")
8683
return ""
87-
matched_id = lookup.get(lookup_id)
84+
matched_id = lookup.loc[lookup[f"{type}_name"] == name, f"{type}_id"].iloc[0]
8885
return matched_id
8986

9087
def _convert_names_to_ids(
@@ -106,101 +103,161 @@ def _convert_names_to_ids(
106103
ids.append(self._convert_name_to_id(type, n))
107104
return ids
108105

106+
def _check_leagues(self, leagues: Union[str, List[str]]):
107+
"""Validates the leagues parameter
108+
109+
:param leagues: league abbreviation or list of league abbreviations
110+
"""
111+
if isinstance(leagues, list):
112+
if not all(l in leagues for l in self.LEAGUES):
113+
print(
114+
f"Leagues are limited only to the following options: {self.LEAGUES.join(',')}."
115+
)
116+
exit()
117+
else:
118+
if leagues not in self.LEAGUES:
119+
print(
120+
f"Leagues are limited only to the following options: {self.LEAGUES.join(',')}."
121+
)
122+
exit()
123+
124+
def _check_ids_names(
125+
self, ids: Union[str, List[str]], names: Union[str, List[str]]
126+
):
127+
"""Makes sure only ids or names are passed to a function and verifies
128+
they are the right data type.
129+
130+
:param ids: a single id or list of ids
131+
:param names: a single name or list of names
132+
"""
133+
if ids and names:
134+
print("Please specify only IDs or names, not both.")
135+
exit()
136+
137+
if ids:
138+
if not isinstance(ids, str) and not isinstance(ids, list):
139+
print("IDs must be passed as a string or list of strings.")
140+
exit()
141+
142+
if names:
143+
if not isinstance(names, str) and not isinstance(names, list):
144+
print("Names must be passed as a string or list of names.")
145+
exit()
146+
147+
def _filter_entity(
148+
self,
149+
entity_all: pd.DataFrame,
150+
entity_type: str,
151+
leagues: Union[str, List[str]],
152+
ids: Union[str, List[str]] = None,
153+
names: Union[str, List[str]] = None,
154+
) -> List[Dict[str, Any]]:
155+
"""Filters a dataframe based on the arguments given.
156+
157+
:param entity_all: a dataframe containing the complete set of data
158+
:param entity_type: the type of data
159+
:param ids: a single id or list of ids
160+
:param names: a single name or list of names
161+
:returns: list of dictionaries, e.g. a dataframe converted to JSON
162+
"""
163+
self._check_leagues(leagues)
164+
self._check_ids_names(ids, names)
165+
166+
entity = entity_all
167+
168+
if names:
169+
converted_ids = self._convert_names_to_ids(entity_type, names)
170+
else:
171+
converted_ids = ids
172+
173+
if isinstance(leagues, str):
174+
leagues = [leagues]
175+
if isinstance(converted_ids, str):
176+
converted_ids = [converted_ids]
177+
178+
entity = entity[entity["competition"].isin(leagues)]
179+
180+
if converted_ids:
181+
entity = entity[entity[f"{entity_type}_id"].isin(converted_ids)]
182+
183+
return entity.to_json(orient="records")
184+
109185
def get_stadia(
110-
self, league: str, names: Union[str, List[str]] = None
186+
self,
187+
leagues: Union[str, List[str]],
188+
ids: Union[str, List[str]] = None,
189+
names: Union[str, List[str]] = None,
111190
) -> List[Dict[str, Any]]:
112191
"""Get information associated with stadia
113192
114-
:param league: league abbreviation
193+
:param leagues: league abbreviation or a list of league abbreviations
194+
:param ids: a single stadium id or a list of stadia ids (optional)
115195
:param names: a single stadium name or a list of stadia names (optional)
116196
:returns: list of dictionaries
117197
"""
118-
ids = self._convert_names_to_ids("stadium", names)
119-
if isinstance(ids, str):
120-
stadia_url = f"{self.base_url}{league}/stadia?stadium_id={ids}"
121-
elif isinstance(ids, list):
122-
stadia_url = f"{self.base_url}{league}/stadia?stadium_id={','.join(ids)}"
123-
else:
124-
stadia_url = f"{self.base_url}{league}/stadia"
125-
response = self.session.get(stadia_url)
126-
return response.json()
198+
stadia = self._filter_entity(self.stadia, "stadium", leagues, ids, names)
199+
return stadia
127200

128201
def get_referees(
129-
self, league: str, names: Union[str, List[str]] = None
202+
self,
203+
leagues: Union[str, List[str]],
204+
ids: Union[str, List[str]] = None,
205+
names: Union[str, List[str]] = None,
130206
) -> List[Dict[str, Any]]:
131207
"""Get information associated with referees
132208
133-
:param league: league abbreviation
209+
:param leagues: league abbreviation or a list of league abbreviations
210+
:param ids: a single referee id or a list of referee ids (optional)
134211
:param names: a single referee name or a list of referee names (optional)
135212
:returns: list of dictionaries
136213
"""
137-
ids = self._convert_names_to_ids("referee", names)
138-
if isinstance(ids, str):
139-
referees_url = f"{self.base_url}{league}/referees?referee_id={ids}"
140-
elif isinstance(ids, list):
141-
referees_url = (
142-
f"{self.base_url}{league}/referees?referee_id={','.join(ids)}"
143-
)
144-
else:
145-
referees_url = f"{self.base_url}{league}/referees"
146-
response = self.session.get(referees_url)
147-
return response.json()
214+
referees = self._filter_entity(self.referees, "referee", leagues, ids, names)
215+
return referees
148216

149217
def get_managers(
150-
self, league: str, names: Union[str, List[str]] = None
218+
self,
219+
leagues: Union[str, List[str]],
220+
ids: Union[str, List[str]] = None,
221+
names: Union[str, List[str]] = None,
151222
) -> List[Dict[str, Any]]:
152223
"""Get information associated with managers
153224
154-
:param league: league abbreviation
155-
:param names: a single manager name or list of manager names (optional)
225+
:param leagues: league abbreviation or a list of league abbreviations
226+
:param ids: a single referee id or a list of referee ids (optional)
227+
:param names: a single referee name or a list of referee names (optional)
156228
:returns: list of dictionaries
157229
"""
158-
ids = self._convert_names_to_ids("manager", names)
159-
if isinstance(ids, str):
160-
managers_url = f"{self.base_url}{league}/managers?manager_id={ids}"
161-
elif isinstance(ids, list):
162-
managers_url = (
163-
f"{self.base_url}{league}/managers?manager_id={','.join(ids)}"
164-
)
165-
else:
166-
managers_url = f"{self.base_url}{league}/managers"
167-
response = self.session.get(managers_url)
168-
return response.json()
230+
managers = self._filter_entity(self.managers, "manager", leagues, ids, names)
231+
return managers
169232

170233
def get_teams(
171-
self, league: str, names: Union[str, List[str]]
234+
self,
235+
leagues: Union[str, List[str]],
236+
ids: Union[str, List[str]] = None,
237+
names: Union[str, List[str]] = None,
172238
) -> List[Dict[str, Any]]:
173239
"""Get information associated with teams
174240
175-
:param league: league abbreviation
176-
:param names: a single team name or list of team names (optional)
241+
:param leagues: league abbreviation or a list of league abbreviations
242+
:param ids: a single team id or a list of team ids (optional)
243+
:param names: a single team name or a list of team names (optional)
177244
:returns: list of dictionaries
178245
"""
179-
ids = self._convert_names_to_ids("team", names)
180-
if isinstance(ids, str):
181-
teams_url = f"{self.base_url}{league}/teams?team_id={ids}"
182-
if isinstance(ids, list):
183-
teams_url = f"{self.base_url}{league}/teams?team_id={','.join(ids)}"
184-
else:
185-
teams_url = f"{self.base_url}{league}/teams"
186-
response = self.session.get(teams_url)
187-
return response.json()
246+
teams = self._filter_entity(self.teams, "team", leagues, ids, names)
247+
return teams
188248

189249
def get_players(
190-
self, league: str, names: Union[str, List[str]]
250+
self,
251+
leagues: Union[str, List[str]],
252+
ids: Union[str, List[str]] = None,
253+
names: Union[str, List[str]] = None,
191254
) -> List[Dict[str, Any]]:
192255
"""Get information associated with players
193256
194-
:param league: league abbreviation
195-
:param names: a single player name or list of player names (optional)
257+
:param league: league abbreviation or a list of league abbreviations
258+
:param ids: a single player id or a list of player ids (optional)
259+
:param names: a single player name or a list of player names (optional)
196260
:returns: list of dictionaries
197261
"""
198-
ids = self._convert_names_to_ids("player", names)
199-
if isinstance(ids, str):
200-
players_url = f"{self.base_url}{league}/players?player_id={ids}"
201-
if isinstance(ids, list):
202-
players_url = f"{self.base_url}{league}/players?player_id={','.join(ids)}"
203-
else:
204-
players_url = f"{self.base_url}{league}/players"
205-
response = self.session.get(players_url)
206-
return response.json()
262+
players = self._filter_entity(self.players, "player", leagues, ids, names)
263+
return players

python-package/requirements.dev.txt

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ CacheControl==0.12.6
33
fuzzywuzzy==0.18.0
44
python-Levenshtein==0.12.2
55
behave==1.2.6
6-
pytest==6.2.4
6+
pytest==6.2.4
7+
pandas==1.3.1

python-package/requirements.txt

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
requests==2.25.1
22
CacheControl==0.12.6
33
fuzzywuzzy==0.18.0
4-
python-Levenshtein==0.12.2
4+
python-Levenshtein==0.12.2
5+
pandas==1.3.1

python-package/setup.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@
1010
long_description=long_description,
1111
author="American Soccer Analysis",
1212
author_email="[email protected]",
13-
url="https://github.com/American-Soccer-Analysis/asa-package",
13+
url="https://github.com/American-Soccer-Analysis/itscalledsoccer",
1414
classifiers=[
1515
"Programming Language :: Python",
1616
"Programming Language :: Python :: 3",
17-
"Programming Language :: Python :: 3.6",
1817
"Programming Language :: Python :: 3.7",
1918
"Programming Language :: Python :: 3.8",
19+
"Programming Language :: Python :: 3.9",
2020
],
2121
python_requires=">=3.6",
2222
keywords="stats soccer api american machine learning football",
File renamed without changes.

0 commit comments

Comments
 (0)