-
-
Notifications
You must be signed in to change notification settings - Fork 3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #565 from plotly/add-soccer-analytics
Add soccer analytics demo Former-commit-id: 6ec68e5
- Loading branch information
Showing
20 changed files
with
3,785 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
web: gunicorn app:server |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# Soccer Match Analytics | ||
|
||
## Overview | ||
|
||
* `Soccer Match Analytics` helps coaches and analysts to analyze the events and view a digital recreation of action in a single match | ||
|
||
## Usage | ||
The app is actually comprised of two parts: a visual app and a game animation preparation script. This pre-preparation of animated game activity is necessary in order to speed up the graphical rendering process and minimize the amount of data processing downloading required to view a match. It is recommended to process no more than 25 minutes of a match at at time. Beyond this threshold it may be too difficult to create and render graphs. | ||
|
||
Pre-processing of animated match data can be accomplished by doing the following: | ||
- Executing the motion-graph.py script and selecting the time period of a match that you would like to pre-process (again in max 25 minute windows). You will need to select a .csv tracking file when executing the script. | ||
- Save the resulting file in the data directory and name it using a .json file extension | ||
- The file will now be visible and selectable within the app | ||
- The submit button must be selected to view the match | ||
|
||
The event viewer is fairly self-explanatory and the user can select the team that they wish to see using the menus at the very top of the app. | ||
|
||
## Acknowledgements | ||
With great gratitude I would like to thank Bruno Dadnino and his team at Metrica Sports (https://metrica-sports.com/about-us/) for the sample tracking and event data used in this application. | ||
The original files are here: https://github.com/metrica-sports/sample-data. These files are part of Metrica's Elite Data product. So if you are subscribers to the Elite Data package you will be able to use those files with this application with very minimal changes required (they have been very lightly modified for the purposes of this app). Just copy the format used in the demo files (the changes/differences are very small). Better yet, do it programmatically and it's even easier! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import pandas as pd | ||
import numpy as np | ||
|
||
# raw_df = pd.read_csv(cv_file, names=header_list, error_bad_lines=False, dtype=str) | ||
raw_df1 = pd.read_csv("data/Sample_Game_2_RawTrackingData_Away_Team.csv") | ||
# sample every 5th row | ||
raw_df1 = raw_df1.iloc[::7, :] | ||
|
||
raw_df2 = pd.read_csv("data/Sample_Game_2_RawTrackingData_Home_Team.csv") | ||
raw_df2 = raw_df2.iloc[::7, :] | ||
|
||
column = 1 | ||
df = pd.DataFrame(columns=["half", "frame", "time", "x", "y"]) | ||
# x range needs adjusted depending on how many columns there are. Should really calculate this not eyeball items | ||
# and do it manually. But hey it's just for a demo not ongoing | ||
for x in range(0, 13): | ||
column = column + 2 | ||
df_temp = raw_df1.iloc[:, [0, 1, 2, column, column + 1]].copy() | ||
df_temp.columns = ["half", "frame", "time", "x", "y"] | ||
df_temp["jersey_number"] = raw_df1.columns[column] | ||
df = pd.concat([df, df_temp]).reset_index(drop=True) | ||
df["team"] = "Away" | ||
df.loc[df["jersey_number"] == "0", "team"] = "Ball" | ||
df.loc[df["x"].isna(), "x"] = None | ||
df.loc[df["y"].isna(), "y"] = None | ||
df = df[df["x"].notna()] | ||
df.drop(df.loc[df["half"] == "Period"].index, inplace=True) | ||
|
||
column = 1 | ||
df2 = pd.DataFrame(columns=["half", "frame", "time", "x", "y"]) | ||
for x in range(0, 12): | ||
column = column + 2 | ||
df_temp2 = raw_df2.iloc[:, [0, 1, 2, column, column + 1]].copy() | ||
df_temp2.columns = ["half", "frame", "time", "x", "y"] | ||
df_temp2["jersey_number"] = raw_df2.columns[column] | ||
df2 = pd.concat([df2, df_temp2]).reset_index(drop=True) | ||
df2["team"] = "Home" | ||
df2.loc[df2["x"].isna(), "x"] = 0.5 | ||
df2.loc[df2["y"].isna(), "y"] = 0.5 | ||
df2 = df2[df2["x"].notna()] | ||
df2.drop(df2.loc[df2["half"] == "Period"].index, inplace=True) | ||
|
||
df = df.iloc[1:] | ||
df["frame"] = df["frame"].apply(pd.to_numeric, errors="coerce") | ||
df = df.sort_values(by=["frame"]) | ||
|
||
df2 = df2.iloc[1:] | ||
df2["frame"] = df2["frame"].apply(pd.to_numeric, errors="coerce") | ||
df2 = df2.sort_values(by=["frame"]) | ||
|
||
df_export = pd.concat([df, df2]).reset_index(drop=True) | ||
df_export = df_export.sort_values(by=["frame"]) | ||
df_export["time"] = df_export["time"].apply(pd.to_numeric, errors="coerce") | ||
df_export["time"] = df_export["time"].div(60).round(4) | ||
export_file_name = input( | ||
"Please enter a name for the file to be exported (ending with .csv): " | ||
) | ||
export_file_name = "data/" + export_file_name | ||
df_export.to_csv(export_file_name, index=False) |
Oops, something went wrong.