import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import matplotlib.patheffects as path_effects
import matplotlib.font_manager as fm
import matplotlib.colors as mcolors
from matplotlib import cm
from highlight_text import fig_text, ax_text
from mplsoccer import Pitch
from PIL import Image
import urllib
import os
font_path = "../assets/fonts"
for x in os.listdir(font_path):
for y in os.listdir(f"{font_path}/{x}"):
if y.split(".")[-1] == "otf":
fm.fontManager.addfont(f"{font_path}/{x}/{y}")
try:
fm.FontProperties(weight=y.split("-")[-1].split(".")[0].lower(), fname=y.split("-")[0])
except Exception:
continue
plt.style.use("../assets/stylesheets/soc_base.mplstyle")
plt.rcParams['font.family'] = 'Nippo'
data = pd.read_csv('data/10242022_seriea_touches.csv', index_col=0)
fig = plt.figure(figsize = (4,4), dpi = 200)
ax = plt.subplot(111)
pitch = Pitch(
pitch_type='uefa',
half=False,
axis=True,
label=True,
tick=True,
goal_type='box',
positional=True
)
pitch.draw(ax = ax)
# Here we can get the positional dimensions
pos_x = pitch.dim.positional_x
pos_y = pitch.dim.positional_y
# We test the dimensions with the labels we got from the Analyst
counter = 1
for index_y, y in enumerate(pos_y):
for index_x, x in enumerate(pos_x):
try:
label_y = (pos_y[index_y] + pos_y[index_y + 1])/2
label_x = (pos_x[index_x] + pos_x[index_x + 1])/2
except:
continue
ax.annotate(
xy=(label_x, label_y),
text=counter,
ha='center',
va='center'
)
counter += 1
test_df = data[data['team'] == 'Napoli']
test_df.head()
team_id | team | zone | perc_for | perc_against | classed | self_rank | opp_rank | biv_rank | season | |
---|---|---|---|---|---|---|---|---|---|---|
360 | 459 | Napoli | 1 | 0.698413 | 0.301587 | team | 0.225806 | 0.161290 | 1 | 2021/2022 |
361 | 459 | Napoli | 2 | 0.551155 | 0.448845 | team | 0.451613 | 0.532258 | 5 | 2021/2022 |
362 | 459 | Napoli | 3 | 0.554622 | 0.445378 | team | 0.548387 | 0.677419 | 6 | 2021/2022 |
363 | 459 | Napoli | 4 | 0.594737 | 0.405263 | team | 0.645161 | 0.629032 | 9 | 2021/2022 |
364 | 459 | Napoli | 5 | 0.517857 | 0.482143 | contested | 0.516129 | 0.790323 | 6 | 2021/2022 |
def plot_zone_dominance(ax, team_id):
filtered_df = data[data['team_id'] == team_id].copy()
pitch = Pitch(
pitch_type='uefa',
goal_type='box',
linewidth=1.1,
line_color='black',
pad_top=10,
corner_arcs=True
)
pitch.draw(ax = ax)
# Here we can get the positional dimensions
pos_x = pitch.dim.positional_x
pos_y = pitch.dim.positional_y
# We test the dimensions with the labels we got from the Analyst
counter = 1
filtered_df['zone'] = filtered_df['zone'].astype(int)
for index_y, y in enumerate(pos_y):
for index_x, x in enumerate(pos_x):
try:
label_y = (pos_y[index_y] + pos_y[index_y + 1])/2
label_x = (pos_x[index_x] + pos_x[index_x + 1])/2
lower_y = pos_y[index_y]
lower_x = pos_x[index_x]
upper_y = pos_y[index_y + 1]
upper_x = pos_x[index_x + 1]
except:
continue
data_point = filtered_df[filtered_df['zone'] == counter]['perc_for'].iloc[0]
if data_point > .55:
color = '#3E5641'
elif data_point < .45:
color = '#461220'
else:
color = '#FCB9B2'
ax.fill_between(
x=[lower_x, upper_x],
y1=lower_y,
y2=upper_y,
color=color,
zorder=0,
alpha=0.75,
ec='None'
)
counter += 1
ax.annotate(
xy=(pos_x[0]*1.15, pos_y[-1]*1.1),
text=f"{filtered_df['team'].iloc[0].upper()}",
size=9,
color='black',
ha='left',
va='center',
weight='bold',
)
for x in pos_x[1:-1]:
ax.plot([x,x], [pos_y[0], pos_y[-1]], color='#000000', ls='dashed', zorder=0, lw=0.3)
for y in pos_y[1:-1]:
ax.plot([pos_x[0], pos_x[-1]], [y,y], color='#000000', ls='dashed', zorder=0, lw=0.3)
return ax
fig = plt.figure(figsize = (4,4), dpi = 200)
ax = plt.subplot(111)
plot_zone_dominance(ax, 459)
<AxesSubplot:>
team_order = data[['team']].drop_duplicates().sort_values(by='team')
team_order = team_order['team'].to_list()
# Had to do this manually :(
team_logos = [
8524, 9857, 7801, 8534,
8535, 8636, 9885, 8543,
9888, 8564, 6504, 9875,
8686, 6480, 9882, 7943,
9881, 9804, 8600, 9876
]
layout = '''
ABCDE
AFGHI
AJKLM
ANOPQ
ARSTU
'''
fig = plt.figure(figsize=(15,8), dpi=300)
ax_dict = fig.subplot_mosaic(layout, gridspec_kw={'wspace':-0.45})
counter = 0
for k, ax in ax_dict.items():
if k == 'A':
continue
team_id = data[data['team'] == team_order[counter]]['team_id'].iloc[0]
plot_zone_dominance(ax, team_id)
counter += 1
# We add the logos once we've reshaped the figure
counter = 0
for k, ax in ax_dict.items():
if k == 'A':
continue
DC_to_FC = ax.transData.transform
FC_to_NFC = fig.transFigure.inverted().transform
# -- Take data coordinates and transform them to normalized figure coordinates
DC_to_NFC = lambda x: FC_to_NFC(DC_to_FC(x))
ax_coords = DC_to_NFC((65,71))
ax_size = 0.023
image_ax = fig.add_axes(
[ax_coords[0], ax_coords[1], ax_size, ax_size],
fc='None'
)
fotmob_url = 'https://images.fotmob.com/image_resources/logo/teamlogo/'
club_icon = Image.open(urllib.request.urlopen(f'{fotmob_url}{team_logos[counter]:.0f}.png'))
image_ax.imshow(club_icon)
image_ax.axis('off')
counter += 1
# Chart Legend and Title
ax_dict['A'].set_xlim(0,1)
ax_dict['A'].set_ylim(0,1)
ax_dict['A'].annotate(
xy=(.01,.95),
text='SERIE A\nZONES OF CONTROL',
ha='left',
va='center',
weight='bold',
size=20
)
ax_dict['A'].plot(
[.01,.7],
[.88, .88],
color='grey'
)
string_label = '''
<Where on the pitch are teams touching the>
<ball more than their opponents in open play?>
Zones labelled <IN GREEN> means that the
<the team> gain more than 55% of total touches.
Zones labelled <IN VIOLET> show where <the>
<opposing team> gain more than 55% of
total touches, while the others
are considered <CONTESTED>.
Viz by @sonofacorner.
Inspired by @petermckeever.
Data is from The Analyst.
'''
bbox_pad = 1.5
bboxprops = {'linewidth': 0, 'pad': bbox_pad}
ax_text(
x=.01, y=.84,
s=string_label,
ax=ax_dict['A'],
family='Karla',
highlight_textprops=[
{'weight':'bold'},
{'weight':'bold'},
{'color': 'w', 'weight': 'bold', 'bbox': {'facecolor':'#3E5641', **bboxprops}},
{'weight':'bold'},
{'color': 'w', 'weight': 'bold', 'bbox': {'facecolor':'#461220', **bboxprops}},
{'weight':'bold'},
{'weight':'bold'},
{'color': 'black', 'weight': 'bold', 'bbox': {'facecolor':'#FCB9B2', **bboxprops}},
]
)
ax_dict['A'].set_axis_off()
plt.savefig(
"figures/10242022_seriea_zones.png",
dpi = 600,
facecolor = "#EFE9E6",
bbox_inches="tight",
edgecolor="none",
transparent = False
)
plt.savefig(
"figures/10242022_seriea_zones_tr.png",
dpi = 600,
facecolor = "none",
bbox_inches="tight",
edgecolor="none",
transparent = True
)