| import json |
| import os |
| import shutil |
| import sys |
| import unittest |
| from importlib import reload |
|
|
| import pytest |
| import socceraction.data.statsbomb as sb |
| from py.path import local |
| from pytest import fixture |
| from socceraction.data.base import ParseError |
| from socceraction.data.statsbomb import ( |
| StatsBombCompetitionSchema, |
| StatsBombEventSchema, |
| StatsBombGameSchema, |
| StatsBombPlayerSchema, |
| StatsBombTeamSchema, |
| ) |
|
|
|
|
| @fixture(scope="module", params=["local", "remote"]) |
| def SBL(request) -> sb.StatsBombLoader: |
| """Create a StatsBombLoader instance.""" |
| data_dir = os.path.join(os.path.dirname(__file__), os.pardir, "datasets", "statsbomb", "raw") |
| return sb.StatsBombLoader(getter=request.param, root=data_dir) |
|
|
|
|
| |
|
|
|
|
| def test_load_remote() -> None: |
| """It can load remote data.""" |
| SBL = sb.StatsBombLoader(getter="remote") |
| assert SBL._creds is not None |
|
|
|
|
| def test_load_local() -> None: |
| """It can load local data.""" |
| data_dir = os.path.join(os.path.dirname(__file__), os.pardir, "datasets", "statsbomb", "raw") |
| SBL = sb.StatsBombLoader(getter="local", root=str(data_dir)) |
| assert SBL._root is not None |
|
|
|
|
| def test_load_invalid_source() -> None: |
| """It raises an error if the source is not ``remote`` or ``local``.""" |
| with pytest.raises(ValueError): |
| sb.StatsBombLoader(getter="foo") |
|
|
|
|
| def test_load_local_missing_root() -> None: |
| """It raises an error if the root is not provided when loading local data.""" |
| with pytest.raises(ValueError): |
| sb.StatsBombLoader(getter="local") |
|
|
|
|
| class TestWithoutStatsBombPy(unittest.TestCase): |
| def setUp(self) -> None: |
| self._temp_sbpy = sys.modules.get("statsbombpy") |
| sys.modules["statsbombpy"] = None |
| reload(sys.modules["socceraction.data.statsbomb.loader"]) |
|
|
| def tearDown(self) -> None: |
| sys.modules["statsbombpy"] = self._temp_sbpy |
| reload(sys.modules["socceraction.data.statsbomb.loader"]) |
|
|
| def tests_load_without_statsbombpy(self) -> None: |
| """It raises an error upon initialization of a remote loader if statsbombpy is not installed.""" |
| with pytest.raises(ImportError): |
| sb.StatsBombLoader(getter="remote") |
|
|
|
|
| |
|
|
|
|
| def test_competitions(SBL: sb.StatsBombLoader) -> None: |
| """It loads a DataFrame with available competitions.""" |
| df_competitions = SBL.competitions() |
| assert len(df_competitions) > 0 |
| StatsBombCompetitionSchema.validate(df_competitions) |
|
|
|
|
| def test_no_competitions(tmpdir: local) -> None: |
| """It returns an empty DataFrame when no competitions are available.""" |
| p = tmpdir.join("competitions.json") |
| p.write(json.dumps([])) |
| SBL = sb.StatsBombLoader(root=str(tmpdir), getter="local") |
| df_competitions = SBL.competitions() |
| assert len(df_competitions) == 0 |
| StatsBombCompetitionSchema.validate(df_competitions) |
|
|
|
|
| def test_invalid_competitions(tmpdir: local) -> None: |
| """It raises an error if the competitions.json file is invalid.""" |
| p = tmpdir.join("competitions.json") |
| p.write(json.dumps({"this is wrong": 1})) |
| SBL = sb.StatsBombLoader(root=str(tmpdir), getter="local") |
| with pytest.raises(ParseError): |
| SBL.competitions() |
|
|
|
|
| |
|
|
|
|
| def test_games(SBL: sb.StatsBombLoader) -> None: |
| """It loads a DataFrame with available competitions.""" |
| df_games = SBL.games(43, 3) |
| assert len(df_games) == 64 |
| StatsBombGameSchema.validate(df_games) |
|
|
|
|
| def test_no_games(tmpdir: local) -> None: |
| """It returns an empty DataFrame when no games are available.""" |
| p = tmpdir.mkdir("matches").mkdir("11").join("1.json") |
| p.write(json.dumps([])) |
| SBL = sb.StatsBombLoader(root=str(tmpdir), getter="local") |
| df_games = SBL.games(11, 1) |
| assert len(df_games) == 0 |
| StatsBombGameSchema.validate(df_games) |
|
|
|
|
| def test_invalid_games(tmpdir: local) -> None: |
| """It raises an error if the json file is invalid.""" |
| p = tmpdir.mkdir("matches").mkdir("11").join("1.json") |
| p.write(json.dumps({"this is wrong": 1})) |
| SBL = sb.StatsBombLoader(root=str(tmpdir), getter="local") |
| with pytest.raises(ParseError): |
| SBL.games(11, 1) |
|
|
|
|
| |
|
|
|
|
| def test_teams(SBL: sb.StatsBombLoader) -> None: |
| """It loads a DataFrame with both teams that participated in a game.""" |
| df_teams = SBL.teams(7584) |
| assert len(df_teams) == 2 |
| StatsBombTeamSchema.validate(df_teams) |
|
|
|
|
| def test_no_teams(tmpdir: local) -> None: |
| """It raises an error when no lineups are available for each team.""" |
| p = tmpdir.mkdir("lineups").join("7584.json") |
| p.write(json.dumps([])) |
| SBL = sb.StatsBombLoader(root=str(tmpdir), getter="local") |
| with pytest.raises(ParseError): |
| SBL.teams(7584) |
|
|
|
|
| def test_invalid_teams(tmpdir: local) -> None: |
| """It raises an error if the json file is invalid.""" |
| p = tmpdir.mkdir("lineups").join("7584.json") |
| p.write(json.dumps({"this is wrong": 1})) |
| SBL = sb.StatsBombLoader(root=str(tmpdir), getter="local") |
| with pytest.raises(ParseError): |
| SBL.teams(7584) |
|
|
|
|
| |
|
|
|
|
| def test_players(SBL: sb.StatsBombLoader) -> None: |
| """It loads a DataFrame with all players that participated in a game.""" |
| df_players = SBL.players(7584) |
| assert len(df_players) == 26 |
| StatsBombPlayerSchema.validate(df_players) |
|
|
|
|
| def test_no_players(tmpdir: local) -> None: |
| """It raises an error when no lineups are available for both teams.""" |
| p = tmpdir.mkdir("lineups").join("7584.json") |
| p.write(json.dumps([])) |
| SBL = sb.StatsBombLoader(root=str(tmpdir), getter="local") |
| with pytest.raises(ParseError): |
| SBL.players(7584) |
|
|
|
|
| def test_invalid_players(tmpdir: local) -> None: |
| """It raises an error if the json file is invalid.""" |
| p = tmpdir.mkdir("lineups").join("7584.json") |
| p.write(json.dumps({"this is wrong": 1})) |
| SBL = sb.StatsBombLoader(root=str(tmpdir), getter="local") |
| with pytest.raises(ParseError): |
| SBL.players(7584) |
|
|
|
|
| |
|
|
|
|
| def test_events(SBL: sb.StatsBombLoader) -> None: |
| """It loads a DataFrame with all events during a game.""" |
| df_events = SBL.events(7584) |
| assert len(df_events) > 0 |
| StatsBombEventSchema.validate(df_events) |
|
|
|
|
| def test_no_events(tmpdir: local) -> None: |
| """It returns an empty DataFrame when no events are available.""" |
| p = tmpdir.mkdir("events").join("7584.json") |
| p.write(json.dumps([])) |
| SBL = sb.StatsBombLoader(root=str(tmpdir), getter="local") |
| df_events = SBL.events(7584) |
| assert len(df_events) == 0 |
| StatsBombEventSchema.validate(df_events) |
|
|
|
|
| def test_invalid_events(tmpdir: local) -> None: |
| """It raises an error if the json file is invalid.""" |
| p = tmpdir.mkdir("events").join("7584.json") |
| p.write(json.dumps({"this is wrong": 1})) |
| SBL = sb.StatsBombLoader(root=str(tmpdir), getter="local") |
| with pytest.raises(ParseError): |
| SBL.events(7584) |
|
|
|
|
| |
|
|
|
|
| def test_frames(SBL: sb.StatsBombLoader) -> None: |
| """It loads a DataFrame with all 360 frames recorded during a game.""" |
| df_frames = SBL.events(3788741, load_360=True) |
| assert len(df_frames) > 0 |
| StatsBombEventSchema.validate(df_frames) |
| assert "visible_area_360" in df_frames.columns |
| assert "freeze_frame_360" in df_frames.columns |
|
|
|
|
| def test_no_frames_empty(tmpdir: local) -> None: |
| """It just returns the events DataFrame when no 360 frames are available.""" |
| tmpdir.mkdir("events") |
| datadir = os.path.join(os.path.dirname(__file__), os.pardir, "datasets", "statsbomb", "raw") |
| shutil.copy( |
| os.path.join(datadir, "events/7584.json"), |
| os.path.join(tmpdir, "events/7584.json"), |
| ) |
| p = tmpdir.mkdir("three-sixty").join("7584.json") |
| p.write(json.dumps([])) |
| SBL = sb.StatsBombLoader(root=str(tmpdir), getter="local") |
| df_frames = SBL.events(7584, load_360=True) |
| assert len(df_frames) > 0 |
| assert "visible_area_360" in df_frames.columns |
| assert "freeze_frame_360" in df_frames.columns |
| StatsBombEventSchema.validate(df_frames) |
|
|
|
|
| def test_invalid_frames(tmpdir: local) -> None: |
| """It raises an error if the json file is invalid.""" |
| tmpdir.mkdir("events") |
| datadir = os.path.join(os.path.dirname(__file__), os.pardir, "datasets", "statsbomb", "raw") |
| shutil.copy( |
| os.path.join(datadir, "events/7584.json"), |
| os.path.join(tmpdir, "events/7584.json"), |
| ) |
| p = tmpdir.mkdir("three-sixty").join("7584.json") |
| p.write(json.dumps({"this is wrong": 1})) |
| SBL = sb.StatsBombLoader(root=str(tmpdir), getter="local") |
| with pytest.raises(ParseError): |
| SBL.events(7584, load_360=True) |
|
|
|
|
| |
|
|
|
|
| def test_extract_player_games(SBL: sb.StatsBombLoader) -> None: |
| df_events = SBL.events(7584) |
| df_player_games = sb.extract_player_games(df_events) |
| assert len(df_player_games) == 26 |
| assert len(df_player_games.player_name.unique()) == 26 |
| assert set(df_player_games.team_name) == {"Belgium", "Japan"} |
| assert df_player_games.minutes_played.sum() == 22 * 96 |
|
|
|
|
| def test_minutes_played(SBL: sb.StatsBombLoader) -> None: |
| |
| df_players = SBL.players(7584).set_index("player_id") |
| assert df_players.at[5630, "minutes_played"] == 64 + 1 |
| assert df_players.at[3296, "minutes_played"] == 96 - (64 + 1) |
| |
| df_players = SBL.players(7581).set_index("player_id") |
| assert df_players.minutes_played.sum() / 22 == 127 |
| |
| df_players = SBL.players(7541).set_index("player_id") |
| assert df_players.at[5685, "minutes_played"] == 2 |
| |
| df_players = SBL.players(7551).set_index("player_id") |
| assert df_players.at[5578, "minutes_played"] == 82 |
|
|