Skip to content

Commit

Permalink
Merge pull request #584 from plotly/update-soccer-analytics
Browse files Browse the repository at this point in the history
Update soccer analytics app
  • Loading branch information
Xing Han Lu authored Mar 24, 2021
2 parents 4f2d9c3 + 149e84d commit 7ea90b8
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 31 deletions.
6 changes: 4 additions & 2 deletions apps/dash-soccer-analytics/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
app.layout = dbc.Container(
fluid=True,
children=[
html.Header([html.H3("Soccer Match Analysis")]),
html.Header([html.H3("Match Analysis Tool")]),
dbc.Card(
dbc.Row([dbc.Col(c) for c in static_graph_controls], form=True), body=True
),
Expand Down Expand Up @@ -349,7 +349,7 @@ def event_graph(event_file, team):
fig_crosses = plotEvents("Crosses", event_file, team, "Home")
fig_set_plays = plotEvents("Set Plays", event_file, team, "Home")
fig_progressive_passes = plotEvents(
"Progressive Passes Into Final 3rd", event_file, team, "Home"
"Progressive Passes", event_file, team, "Home"
)
for x in [
fig_shots,
Expand Down Expand Up @@ -433,6 +433,8 @@ def game_simulation_graph(n_clicks, speed, filename):
plot_bgcolor="rgba(0, 0, 0, 0)",
paper_bgcolor="rgba(0, 0, 0, 0)",
)
fig["layout"]["template"]["data"]["scatter"][0]["marker"]["line"]["color"] = "white"
fig["layout"]["template"]["data"]["scatter"][0]["marker"]["opacity"] = 0.9
return fig


Expand Down
100 changes: 87 additions & 13 deletions apps/dash-soccer-analytics/event_plotter.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def find_assists(df):


# Locate and build a dataframe of all set plays, ignoring kick-offs and throw-ins
def find_set_plays(df):
def find_set_plays(df, mode):
sp_df = pd.DataFrame()

count = 0
Expand Down Expand Up @@ -100,9 +100,14 @@ def find_set_plays(df):
),
ignore_index=True,
)
if mode == "progressive":
df = df.drop(index + 1)
except Exception as e:
print(e)

if mode == "progressive":
sp_df = df

sp_df.loc[sp_df.To.isnull(), "Type"] = "Incomplete"
return sp_df

Expand Down Expand Up @@ -130,7 +135,7 @@ def left_justify_events(df, team_on_left):
return df


# Once number of clusters is auto-calculated, graph the cluseters
# Once number of clusters is auto-calculated, graph the clusters
def create_cluster_graph(df, num_clusters):
# creates a new trace for each set of data
fig = make_subplots(
Expand Down Expand Up @@ -206,6 +211,67 @@ def drawAnnotations(df):
return annotations_list


def find_progressive_passes(df):
# df = df.loc[(df['End_X'] - df['location_x']) > 1000] # limit passes to those greater than 10M forward
df_own_half = df.loc[
(df["End_X"] < 0.5) & (df["Start_X"] < 0.5)
] # passes in own half
df_diff_half = df.loc[
(df["End_X"] > 0.5) & (df["Start_X"] < 0.5)
] # passes in different half
df_opp_half = df.loc[
(df["End_X"] > 0.5) & (df["Start_X"] > 0.5)
] # passes in opponent's half
goal_x = float(1)
goal_y = float(0.5)

# Passes in own half
if len(df_own_half) > 0:
# dist = math.hypot(x2 - x1, y2 - y1)
df_own_half["orig_distance_to_goal"] = df_own_half.apply(
lambda x: math.hypot(x["Start_X"] - goal_x, x["Start_Y"] - goal_y), axis=1
)
df_own_half["end_distance_to_goal"] = df_own_half.apply(
lambda x: math.hypot(x["End_X"] - goal_x, x["End_Y"] - goal_y), axis=1
)
df_own_half["distance"] = (
df_own_half["orig_distance_to_goal"] - df_own_half["end_distance_to_goal"]
)
df_own_half = df_own_half.loc[(df_own_half["distance"]) >= 0.30]

# Passes in both halves
if len(df_diff_half) > 0:
df_diff_half["orig_distance_to_goal"] = df_diff_half.apply(
lambda x: math.hypot(x["Start_X"] - goal_x, x["Start_Y"] - goal_y), axis=1
)
df_diff_half["end_distance_to_goal"] = df_diff_half.apply(
lambda x: math.hypot(x["End_X"] - goal_x, x["End_Y"] - goal_y), axis=1
)

df_diff_half["distance"] = (
df_diff_half["orig_distance_to_goal"] - df_diff_half["end_distance_to_goal"]
)
df_diff_half = df_diff_half.loc[(df_diff_half["distance"]) >= 0.15]

# Passes in opposition half
if len(df_opp_half) > 0:
df_opp_half["orig_distance_to_goal"] = df_opp_half.apply(
lambda x: math.hypot(x["Start_X"] - goal_x, x["Start_Y"] - goal_y), axis=1
)
df_opp_half["end_distance_to_goal"] = df_opp_half.apply(
lambda x: math.hypot(x["End_X"] - goal_x, x["End_Y"] - goal_y), axis=1
)
df_opp_half["distance"] = (
df_opp_half["orig_distance_to_goal"] - df_opp_half["end_distance_to_goal"]
)
df_opp_half = df_opp_half.loc[(df_opp_half["distance"]) >= 0.12]

df_list = [df_own_half, df_diff_half, df_opp_half] # List of your dataframes
df_combo = pd.concat(df_list)

return df_combo


# Main function - graph all football events which occur in a match
def plotEvents(eventType, filename, team, team_on_left):
# Read in event csv data file
Expand All @@ -225,7 +291,7 @@ def plotEvents(eventType, filename, team, team_on_left):

# For events involving the graphing of movement of the ball from one location to another
if (
(eventType == "Progressive Passes Into Final 3rd")
(eventType == "Progressive Passes")
or (eventType == "Crosses")
or (eventType == "Set Plays")
or (eventType == "Assists to Shots")
Expand All @@ -235,14 +301,15 @@ def plotEvents(eventType, filename, team, team_on_left):
if eventType == "Assists to Shots":
df = find_assists(events_df)
elif eventType == "Set Plays":
df = find_set_plays(events_df)
elif eventType == "Progressive Passes Into Final 3rd":
df = events_df.loc[events_df["Type"] == "PASS"]
df = find_set_plays(events_df, "normal")
elif eventType == "Progressive Passes":
df = find_set_plays(
events_df, "progressive"
) # take out set plays as they include corners and throw-ins
df = df[(df["Start_Y"] > 0) & (df["Start_Y"] < 1)]
df = df.loc[events_df["Type"] == "PASS"]
df.reset_index(drop=True, inplace=True)
df = df.loc[
(df["End_X"] - df["Start_X"]) > 0.1
] # limit passes to those greater than 10M forward
df = df.loc[df["End_X"] > 0.7]
df = find_progressive_passes(df)
elif eventType == "Crosses":
df = events_df.loc[events_df["Subtype"].str.contains("CROSS", na=False)]
df.reset_index(drop=True, inplace=True)
Expand Down Expand Up @@ -279,7 +346,7 @@ def plotEvents(eventType, filename, team, team_on_left):
"Crosses",
"Set Plays",
"Assists to Shots",
"Progressive Passes Into Final 3rd",
"Progressive Passes",
]:
colorfactor = df["Type"]
fig = px.scatter(
Expand All @@ -299,7 +366,6 @@ def plotEvents(eventType, filename, team, team_on_left):
"Start_X": False,
"Start_Y": False,
"size": False,
"Type": False,
"From": True,
"To": True,
},
Expand Down Expand Up @@ -377,6 +443,14 @@ def plotEvents(eventType, filename, team, team_on_left):
# Metrica data starts 0, 0 at top left corner. Need to account for that or markers will be wrong
fig.update_yaxes(autorange="reversed")

# Add corner flags to prevent zoom and pitch distortion
fig.add_scatter(
x=[0, 0, 1, 1],
y=[0, 1, 0, 1],
mode="markers",
marker=dict(size=2, color="grey"),
)

# Remove side color scale and hide zero and gridlines
fig.update_layout(
coloraxis_showscale=False,
Expand Down Expand Up @@ -423,7 +497,7 @@ def plotEvents(eventType, filename, team, team_on_left):
fig.update_xaxes(title_text="")
fig.update_yaxes(title_text="")
image_file = "assets/Pitch.png"
fig.update_yaxes(scaleanchor="x", scaleratio=0.65)
fig.update_yaxes(scaleanchor="x", scaleratio=0.70)

from PIL import Image

Expand Down
33 changes: 17 additions & 16 deletions apps/dash-soccer-analytics/initial_figures.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@

# Create initial placeholder figure for game simulator
def initial_figure_simulator():
fig = px.scatter(x=[0, 0, 105, 105], y=[69, -2, 69, -2])
fig.update_layout(xaxis=dict(range=[-4, 110]))
fig.update_layout(yaxis=dict(range=[-4, 72]))
# fig = px.scatter(x=[0, 0, 105, 105], y=[69, -2, 69, -2])
fig = px.scatter(x=[0, 0, 1, 1], y=[0, 1, 0, 1])
fig.update_layout(xaxis=dict(range=[0, 1]))
fig.update_layout(yaxis=dict(range=[0, 1]))
fig.update_traces(marker=dict(color="white", size=6))

# Remove side color scale and hide zero and gridlines
Expand Down Expand Up @@ -40,16 +41,16 @@ def initial_figure_simulator():
xref="x",
yref="y",
x=0,
y=69,
sizex=105,
sizey=71,
y=1,
sizex=1,
sizey=1,
sizing="stretch",
opacity=0.7,
layer="below",
)
)

fig.update_yaxes(scaleanchor="x", scaleratio=1)
fig.update_yaxes(scaleanchor="x", scaleratio=0.70)

fig.update_layout(autosize=True)

Expand All @@ -64,11 +65,11 @@ def initial_figure_simulator():

# Create initial placeholder figure for event plot
def initial_figure_events():
fig = px.scatter(x=[0], y=[0])

# fig = px.scatter(x=[0], y=[0])
fig = px.scatter(x=[0, 0, 1, 1], y=[0, 1, 0, 1])
fig.update_traces(marker=dict(color="white", size=6))
fig.update_layout(yaxis=dict(range=[-3650, 3650]))
fig.update_layout(xaxis=dict(range=[-5300, 5300]))
fig.update_layout(yaxis=dict(range=[0, 1]))
fig.update_layout(xaxis=dict(range=[0, 1]))
fig.update_layout(margin=dict(l=10, r=100, b=10, t=45))

fig.update_layout(modebar=dict(bgcolor="rgba(0, 0, 0, 0)"))
Expand All @@ -93,7 +94,7 @@ def initial_figure_events():
fig.update_xaxes(title_text="")
fig.update_yaxes(title_text="")
fig.update_xaxes(fixedrange=True)
fig.update_yaxes(scaleanchor="x", scaleratio=1)
fig.update_yaxes(scaleanchor="x", scaleratio=0.70)
fig.update_layout(margin=dict(l=10, r=30, b=30, t=30), autosize=True)
image_file = "assets/Pitch.png"

Expand All @@ -105,10 +106,10 @@ def initial_figure_events():
source=img,
xref="x",
yref="y",
x=-5150,
y=3685,
sizex=10450,
sizey=7340,
x=0,
y=1,
sizex=1,
sizey=1,
sizing="stretch",
opacity=0.8,
layer="below",
Expand Down
4 changes: 4 additions & 0 deletions apps/dash-soccer-analytics/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
--extra-index-url=https://dash-playground.plotly.host/Docs/packages
dash==1.19.0
gunicorn==20.0.4
plotly~=4.14.3
Expand All @@ -9,3 +10,6 @@ dash-daq==0.5.0
dash-bootstrap-components==0.10.7
dash-html-components==1.1.2
dash-core-components==1.15.0



0 comments on commit 7ea90b8

Please sign in to comment.