Jeg har bygget videre på scripts fra de seneste versioner, og har nu samlet funktionerne “Shot Plot” og “Xg-Table” i 1 script, der leverer 2 seperate PNGs, som jeg begge finder visuelt indbydende.
Mangler pt.:
1) At klublogoerne stadig hentes lokalt i stedet via cloud/api
2) At det ikke er lykkedes at få indbygget rundenummeret på hver PNG.
3) At jeg endnu ikke ved hvad jeg skal bruge det 🙂
Eksempel på produkt!


Jeg er tilfreds med de visuelle udtryk og den visuelle opsætning.
En anden mangel er at scriptet åbner alle plots på en gang, hvis man f.eks. beder det om at forholde sig til 32 events….
import os
import requests
import json
import pandas as pd
import matplotlib.pyplot as plt
from PIL import Image
from mplsoccer import VerticalPitch, FontManager
from matplotlib.lines import Line2D
# List of teams and their IDs with short names
teams = [
{"teamId": 8071, "teamName": "AGF"},
{"teamId": 8470, "teamName": "AaB"},
{"teamId": 8595, "teamName": "BIF"}, # Short name for Brøndby IF
{"teamId": 8391, "teamName": "FCK"}, # Short name for F.C. København
{"teamId": 8113, "teamName": "FCM"}, # Short name for FC Midtjylland
{"teamId": 10202, "teamName": "FCN"}, # Short name for FC Nordsjælland
{"teamId": 9907, "teamName": "LBK"}, # Short name for Lyngby Boldklub
{"teamId": 8410, "teamName": "RFC"}, # Short name for Randers FC
{"teamId": 8415, "teamName": "SIF"}, # Short name for Silkeborg IF
{"teamId": 8487, "teamName": "SJE"}, # Short name for Sønderjyske Fodbold
{"teamId": 8231, "teamName": "VB"}, # Short name for Vejle Boldklub
{"teamId": 9939, "teamName": "VFF"}, # Short name for Viborg FF
{"teamId": 9814, "teamName": "ACH"}, # Short name for AC Horsens
{"teamId": 10240, "teamName": "Hvidovre"}, # Short name for Hvidovre IF
{"teamId": 8414, "teamName": "OB"},
]
# Function to display the list of teams
def display_teams(teams):
print("List of teams and their IDs with short names:")
for team in teams:
print(f"Team ID: {team['teamId']}, Team Name: {team['teamName']}")
display_teams(teams)
# Function to get the short name of the team based on teamId
def get_team_short_name(team_id):
for team in teams:
if team["teamId"] == team_id:
return team["teamName"]
return "Unknown"
# Function to parse rounds input and handle ranges
def parse_rounds(rounds_input):
rounds = []
for part in rounds_input.split(','):
if '-' in part:
start, end = part.split('-')
rounds.extend(range(int(start), int(end) + 1))
else:
rounds.append(int(part))
return [str(round) for round in rounds] # Convert rounds to strings for comparison
# Prompt user to input the rounds they want to fetch event IDs for
rounds_input = input("Enter the rounds you want to fetch event IDs for (comma separated, e.g., 1,2,5 or 1-5): ")
selected_rounds = parse_rounds(rounds_input)
# Prompt user to input the team ID if they want to filter by a specific team (leave blank if not)
team_id = input("Enter the team ID to filter by (leave blank if not applicable): ").strip()
# Step 1: Fetch events
events_url = 'https://api.superliga.dk/events-v2?appName=dk.releaze.livecenter.spdk&access_token=5b6ab6f5eb84c60031bbbd24&env=production&locale=da&seasonId=20962'
response = requests.get(events_url)
events_data = response.json()
# Step 2: Filter finished events from selected rounds and optional team ID
finished_events = [
event for event in events_data['events']
if event['statusType'] == 'finished' and event['round'] in selected_rounds
and (not team_id or event['homeId'] == int(team_id) or event['awayId'] == int(team_id))
]
# Print the list of available events with short team names
print("Available Events:")
for event in finished_events:
home_short_name = get_team_short_name(event['homeId'])
away_short_name = get_team_short_name(event['awayId'])
print(f"{event['eventId']} {home_short_name} vs {away_short_name}")
# Prompt user to select whether to generate for all events or a single event
choice = input("Do you want to generate xG tables for all events or a single event? (all/single): ").strip().lower()
def generate_xg_table(event_id, show_table=True, plot_shots=False, single_event=False):
try:
details_url = f'https://api.superliga.dk/opta-stats/event/{event_id}/detail-expected-goals?appName=superligadk&access_token=5b6ab6f5eb84c60031bbbd24&env=production&locale=da'
response = requests.get(details_url)
data = response.json()
home_team_id = data.get('homeId')
away_team_id = data.get('awayId')
home_team_name = data.get('homeName')
away_team_name = data.get('awayName')
home_score = data['score']['home']
away_score = data['score']['away']
# Paths to team logos
logo_directory = '/home/xxxxxxx/python_scripts/TeamPngs'
home_team_logo_path = os.path.join(logo_directory, f'{home_team_id}.png')
away_team_logo_path = os.path.join(logo_directory, f'{away_team_id}.png')
# Load team logos
home_team_logo = Image.open(home_team_logo_path)
away_team_logo = Image.open(away_team_logo_path)
# Extract home and away expected goals data
home_data = pd.DataFrame(data['expectedGoalsData']['home'])
away_data = pd.DataFrame(data['expectedGoalsData']['away'])
# Adjusting the coordinates assuming the pitch is 120x80
home_data['x_adjusted'] = home_data['x'] * 120 / 100
home_data['y_adjusted'] = home_data['y'] * 80 / 100
away_data['x_adjusted'] = away_data['x'] * 120 / 100
away_data['y_adjusted'] = away_data['y'] * 80 / 100
# Format shot data for table
home_shots = home_data[['min', 'sec', 'firstName', 'lastName', 'expectedGoalsValue', 'type', 'x_adjusted', 'y_adjusted']].copy()
home_shots['team'] = home_team_name
away_shots = away_data[['min', 'sec', 'firstName', 'lastName', 'expectedGoalsValue', 'type', 'x_adjusted', 'y_adjusted']].copy()
away_shots['team'] = away_team_name
# Combine home and away shots
shots = pd.concat([home_shots, away_shots])
# Sort by minute and second
shots = shots.sort_values(by=['min', 'sec'])
# Format xG values to 4 decimal places
shots['expectedGoalsValue'] = shots['expectedGoalsValue'].map(lambda x: f"{x:.4f}")
# Create the table plot
fig, ax = plt.subplots(figsize=(12, 8))
ax.axis('tight')
ax.axis('off')
table_data = shots[['team', 'min', 'sec', 'firstName', 'lastName', 'expectedGoalsValue', 'type']].values
column_labels = ['Team', 'Min', 'Sec', 'First Name', 'Last Name', 'xG Value', 'Type']
table = ax.table(cellText=table_data, colLabels=column_labels, cellLoc='center', loc='center')
# Set table styles
table.auto_set_font_size(False)
table.set_fontsize(11)
table.scale(1.3, 1.3)
# Adjust column widths
table.auto_set_column_width([0, 1, 2, 3, 4, 5, 6])
# Add title with team logos
fig.suptitle(f" xG Table for {home_team_name} vs {away_team_name} ", fontsize=16, fontweight='bold', y=0.98)
# Add final score
fig.text(0.5, 0.92, f"Final Score: {home_score} - {away_score}", ha='center', fontsize=18, fontweight='bold')
# Reduce space between title and table
plt.subplots_adjust(top=0.85)
# Add team logos to the title
ax_logo_home = fig.add_axes([0.15, 0.88, 0.1, 0.1], anchor='NE', zorder=1)
ax_logo_home.imshow(home_team_logo)
ax_logo_home.axis('off')
ax_logo_away = fig.add_axes([0.75, 0.88, 0.1, 0.1], anchor='NE', zorder=1)
ax_logo_away.imshow(away_team_logo)
ax_logo_away.axis('off')
# Save the table plot as PNG
output_directory = '/home/xxxxxxx/python_scripts/xg_tables' # Specify your directory here
os.makedirs(output_directory, exist_ok=True) # Create directory if it doesn't exist
table_filename = f'{home_team_name}_vs_{away_team_name}_xg_table.png'
table_filepath = os.path.join(output_directory, table_filename)
plt.savefig(table_filepath, bbox_inches='tight')
if plot_shots:
FIGWIDTH = 16
FIGHEIGHT = 9
NROWS = 1
NCOLS = 2
SPACE = 0.05
MAX_GRID = 0.95
pitch = VerticalPitch(pad_top=3, pad_bottom=-15,
pad_left=-15, pad_right=-15, linewidth=1, half=True,
pitch_color='grass', stripe=True, line_color='white')
GRID_WIDTH, GRID_HEIGHT = pitch.grid_dimensions(figwidth=FIGWIDTH, figheight=FIGHEIGHT,
nrows=NROWS, ncols=NCOLS,
max_grid=MAX_GRID, space=SPACE)
TITLE_HEIGHT = 0.08
ENDNOTE_HEIGHT = 0.04
fig, axs = pitch.grid(figheight=FIGHEIGHT, grid_width=GRID_WIDTH, grid_height=GRID_HEIGHT,
space=SPACE, ncols=NCOLS, nrows=NROWS, title_height=TITLE_HEIGHT,
endnote_height=ENDNOTE_HEIGHT, axis=False)
# Plot non-goal shots
pitch.scatter(home_data[home_data['type'] != 'goal'].x_adjusted,
home_data[home_data['type'] != 'goal'].y_adjusted,
s=(home_data[home_data['type'] != 'goal'].expectedGoalsValue.astype(float) * 1900) + 100,
c='blue', edgecolors='black', marker='o', ax=axs['pitch'][0], label=f'{home_team_name} Shots (Non-Goals)')
pitch.scatter(away_data[away_data['type'] != 'goal'].x_adjusted,
away_data[away_data['type'] != 'goal'].y_adjusted,
s=(away_data[away_data['type'] != 'goal'].expectedGoalsValue.astype(float) * 1900) + 100,
c='red', edgecolors='black', marker='o', ax=axs['pitch'][1], label=f'{away_team_name} Shots (Non-Goals)')
# Plot goal shots with different marker
pitch.scatter(home_data[home_data['type'] == 'goal'].x_adjusted,
home_data[home_data['type'] == 'goal'].y_adjusted,
s=(home_data[home_data['type'] == 'goal'].expectedGoalsValue.astype(float) * 1900) + 100,
c='orange', edgecolors='black', marker='*', ax=axs['pitch'][0], label=f'{home_team_name} Shots (Goals)')
pitch.scatter(away_data[away_data['type'] == 'goal'].x_adjusted,
away_data[away_data['type'] == 'goal'].y_adjusted,
s=(away_data[away_data['type'] == 'goal'].expectedGoalsValue.astype(float) * 1900) + 100,
c='grey', edgecolors='black', marker='*', ax=axs['pitch'][1], label=f'{away_team_name} Shots (Goals)')
fig.suptitle(f'Xg-Shot-Plot: {home_team_name} vs {away_team_name}', fontsize=24)
# Add team logos to the title
ax_logo_home = fig.add_axes([0.15, 0.88, 0.1, 0.1], anchor='NE', zorder=1)
ax_logo_home.imshow(home_team_logo)
ax_logo_home.axis('off')
ax_logo_away = fig.add_axes([0.75, 0.88, 0.1, 0.1], anchor='NE', zorder=1)
ax_logo_away.imshow(away_team_logo)
ax_logo_away.axis('off')
# Custom legend
legend_elements = [Line2D([0], [0], marker='o', color='w', label=f'{home_team_name} Shots (Non-Goals)',
markerfacecolor='blue', markersize=10, markeredgecolor='black'),
Line2D([0], [0], marker='*', color='w', label=f'{home_team_name} Shots (Goals)',
markerfacecolor='orange', markersize=10, markeredgecolor='black'),
Line2D([0], [0], marker='o', color='w', label=f'{away_team_name} Shots (Non-Goals)',
markerfacecolor='red', markersize=10, markeredgecolor='black'),
Line2D([0], [0], marker='*', color='w', label=f'{away_team_name} Shots (Goals)',
markerfacecolor='grey', markersize=10, markeredgecolor='black')]
fig.legend(handles=legend_elements, loc='upper center', fontsize=12, bbox_to_anchor=(0.5, 0.95), ncol=2)
# Save the shot plot as PNG
shotplot_directory = '/home/xxxxxx/python_scripts/xg_shotplots'
os.makedirs(shotplot_directory, exist_ok=True) # Create directory if it doesn't exist
shotplot_filename = f'{home_team_name}_vs_{away_team_name}_xg_shotplot.png'
shotplot_filepath = os.path.join(shotplot_directory, shotplot_filename)
plt.savefig(shotplot_filepath, bbox_inches='tight')
if single_event:
plt.show()
if show_table:
plt.show()
# Trim the table image to the content
with Image.open(table_filepath) as img:
img = img.crop(img.getbbox())
img.save(table_filepath)
except Exception as e:
print(f"An error occurred while processing event ID {event_id}: {e}")
# Generate the xG table and shot plot for the selected event(s)
if choice == 'all':
for event in finished_events:
print(f"Generating xG table and shot plot for event ID: {event['eventId']}")
generate_xg_table(event['eventId'], show_table=False, plot_shots=True, single_event=False)
else:
selected_event_id = input("Enter the event ID you want to generate the xG table and shot plot for: ").strip()
generate_xg_table(selected_event_id, show_table=True, plot_shots=True, single_event=True)