작성일자 : 2023-12-28
Ver 0.1.1
슈팅 데이터 불러오기¶
In [1]:
import os
import numpy as np
import pandas as pd
import plotly.graph_objects as go
new_dir = '/Users/limjongjun/Desktop/JayJay/Growth/Python/soccer-analytics'
os.chdir(new_dir)
from src.plot_utils import get_pitch_layout
In [2]:
shots = pd.read_pickle('data/shots.pkl')
shots
Out[2]:
competition_name | match_id | event_id | period | time | team_id | team_name | player_id | player_name | event_type | sub_event_type | tags | x | y | distance | angle | freekick | header | goal | xg | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | England | 2499719 | 177959212 | 1H | 94.596 | 1609 | Arsenal | 25413 | A. Lacazette | Shot | Shot | [Goal, Right foot, Opportunity, Position: Goal... | 12.48 | 6.12 | 13.899813 | 26.933236 | 0 | 0 | 1 | 0.143848 |
1 | England | 2499719 | 177959247 | 1H | 179.855 | 1631 | Leicester City | 26150 | R. Mahrez | Shot | Shot | [Left foot, Opportunity, Position: Out center ... | 15.60 | -1.36 | 15.659170 | 26.224941 | 0 | 0 | 0 | 0.120612 |
2 | England | 2499719 | 177959280 | 1H | 254.745 | 1631 | Leicester City | 14763 | S. Okazaki | Shot | Shot | [Goal, Head/body, Opportunity, Position: Goal ... | 4.16 | -1.36 | 4.376665 | 79.289489 | 0 | 1 | 1 | 0.412967 |
3 | England | 2499719 | 177959289 | 1H | 425.824 | 1609 | Arsenal | 7868 | A. Oxlade-Chamberlain | Shot | Shot | [Left foot, Opportunity, Position: Out high le... | 19.76 | 11.56 | 22.893038 | 15.813597 | 0 | 0 | 0 | 0.042434 |
4 | England | 2499719 | 177959429 | 1H | 815.462 | 1609 | Arsenal | 7868 | A. Oxlade-Chamberlain | Shot | Shot | [Right foot, Opportunity, Position: Goal low l... | 26.00 | 13.60 | 29.342120 | 12.655803 | 0 | 0 | 0 | 0.019414 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
45940 | European_Championship | 1694440 | 90588583 | 2H | 2776.504 | 4418 | France | 25575 | A. Gignac | Shot | Shot | [Right foot, Opportunity, Position: Post cente... | 7.28 | 5.44 | 9.088014 | 37.600623 | 0 | 0 | 0 | 0.273871 |
45941 | European_Championship | 1694440 | 90589205 | E1 | 807.318 | 9905 | Portugal | 70410 | Éder | Shot | Shot | [Head/body, Opportunity, Position: Goal center... | 10.40 | 8.16 | 13.219138 | 25.258830 | 0 | 1 | 0 | 0.056536 |
45942 | European_Championship | 1694440 | 90589242 | E2 | 144.487 | 9905 | Portugal | 28907 | Raphaël Guerreiro | Free kick | Free kick shot | [Left foot, Direct, Position: Post high left, ... | 24.96 | 12.92 | 28.105658 | 13.240079 | 1 | 0 | 0 | 0.098744 |
45943 | European_Championship | 1694440 | 90589254 | E2 | 204.428 | 9905 | Portugal | 70410 | Éder | Shot | Shot | [Goal, Right foot, Opportunity, Position: Goal... | 23.92 | 2.72 | 24.074152 | 17.184831 | 0 | 0 | 1 | 0.040764 |
45944 | European_Championship | 1694440 | 90589034 | E2 | 981.239 | 4418 | France | 134513 | A. Martial | Shot | Shot | [Right foot, Blocked, Not accurate] | 13.52 | 6.80 | 15.133750 | 24.652934 | 0 | 0 | 0 | 0.120724 |
45945 rows × 20 columns
단일 경기 xG 시각화¶
(1) 슈팅 레이블 생성¶
In [3]:
match_id = 2057987
match_df = pd.read_csv('data/refined_events/World_Cup/matches.csv', index_col=0, header=0, encoding='utf-8-sig')
team1_name = match_df.at[match_id, 'team1_name']
team2_name = match_df.at[match_id, 'team2_name']
match_shots = shots[shots['match_id'] == match_id]
# mouse over 시 보일 text
match_shots['display_name'] = match_shots.apply(
lambda x: f"{x['player_name']}, " +
f"{x['period']} {int(x['time'] // 60):02d}:{int(x['time'] % 60):02d}, " +
f"xG: {round(x['xg'], 3)}", axis=1
)
team2_x = match_shots.loc[match_shots['team_name'] == team2_name, 'x']
team1_y = match_shots.loc[match_shots['team_name'] == team1_name, 'y']
team2_y = match_shots.loc[match_shots['team_name'] == team2_name, 'y']
match_shots.loc[match_shots['team_name'] == team2_name, 'x'] = 104 - team2_x
match_shots.loc[match_shots['team_name'] == team1_name, 'y'] = 34 - team1_y
match_shots.loc[match_shots['team_name'] == team2_name, 'y'] = 34 + team2_y
match_shots.head()
/var/folders/_b/znjp14gd02d8lg63thqc7bm40000gn/T/ipykernel_2682/2786941602.py:10: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy match_shots['display_name'] = match_shots.apply(
Out[3]:
competition_name | match_id | event_id | period | time | team_id | team_name | player_id | player_name | event_type | ... | tags | x | y | distance | angle | freekick | header | goal | xg | display_name | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
34329 | World_Cup | 2057987 | 259956999 | 1H | 698.173 | 15473 | Mexico | 7941 | J. Hernández | Shot | ... | [Head/body, Opportunity, Position: Out center ... | 91.52 | 27.88 | 13.899813 | 26.933236 | 0 | 1 | 0 | 0.054390 | J. Hernández, 1H 11:38, xG: 0.054 |
34330 | World_Cup | 2057987 | 259957180 | 1H | 1275.442 | 14855 | Korea Republic | 14911 | Son Heung-Min | Shot | ... | [Counter attack, Left foot, Blocked, Opportuni... | 13.52 | 45.56 | 17.788311 | 18.086581 | 0 | 0 | 0 | 0.069484 | Son Heung-Min, 1H 21:15, xG: 0.069 |
34331 | World_Cup | 2057987 | 259957182 | 1H | 1277.147 | 14855 | Korea Republic | 14911 | Son Heung-Min | Shot | ... | [Right foot, Blocked, Opportunity, Not accurate] | 12.48 | 45.56 | 17.011290 | 18.314813 | 0 | 0 | 0 | 0.075236 | Son Heung-Min, 1H 21:17, xG: 0.075 |
34332 | World_Cup | 2057987 | 259965138 | 1H | 1281.101 | 14855 | Korea Republic | 14911 | Son Heung-Min | Shot | ... | [Left foot, Blocked, Not accurate] | 21.84 | 41.48 | 23.085407 | 17.103758 | 0 | 0 | 0 | 0.042485 | Son Heung-Min, 1H 21:21, xG: 0.042 |
34333 | World_Cup | 2057987 | 259957220 | 1H | 1339.263 | 14855 | Korea Republic | 61962 | Sung-Yeung Ki | Shot | ... | [Head/body, Opportunity, Position: Goal high c... | 11.44 | 37.40 | 11.934555 | 32.982052 | 0 | 1 | 0 | 0.080193 | Sung-Yeung Ki, 1H 22:19, xG: 0.08 |
5 rows × 21 columns
(2) 슈팅 위치 및 xG 시각화¶
In [4]:
match_shots_failed = match_shots[match_shots['tags'].apply(lambda x: 'Goal' not in x)]
team1_shots = match_shots[match_shots['team_name'] == team1_name]
team2_shots = match_shots[match_shots['team_name'] == team2_name]
team1_goals = team1_shots[team1_shots['tags'].apply(lambda x: 'Goal' in x)]
team2_goals = team2_shots[team2_shots['tags'].apply(lambda x: 'Goal' in x)]
team1_goal_trace = go.Scatter(
x=team1_goals['x'],
y=team1_goals['y'],
name=team1_name,
text=team1_goals['display_name'],
mode='markers',
marker=dict(
color='red', size=np.sqrt(team1_goals['xg']) * 50,
symbol=team1_goals['freekick'].apply(lambda x: 'square' if x == 1 else 'circle')
)
)
team2_goal_trace = go.Scatter(
x=team2_goals['x'],
y=team2_goals['y'],
name=team2_name,
text=team2_goals['display_name'],
mode='markers',
marker=dict(
color='blue', size=np.sqrt(team2_goals['xg']) * 50,
symbol=team2_goals['freekick'].apply(lambda x: 'square' if x == 1 else 'circle')
)
)
shot_trace = go.Scatter(
x=match_shots_failed['x'],
y=match_shots_failed['y'],
name='Failed shot',
text=match_shots_failed['display_name'],
mode='markers',
marker=dict(
color='darkgrey', size=np.sqrt(match_shots_failed['xg']) * 50,
symbol=match_shots_failed['freekick'].apply(lambda x: 'square' if x == 1 else 'circle')
)
)
team1_xg = team1_shots['xg'].sum().round(2)
team2_xg = team2_shots['xg'].sum().round(2)
title = f"{team1_name} - {team2_name} (xG: {team1_xg} - {team2_xg})"
fig = go.Figure(data=[shot_trace, team1_goal_trace, team2_goal_trace], layout=get_pitch_layout(title))
fig.show()
선수별 시즌 전체 xG 및 부가 지표 집계¶
(1) 선수별 슈팅/유효슈팅/득점 횟수 집계¶
In [5]:
shots = shots[shots['competition_name'] == 'England']
shots_on_target = shots[shots['tags'].apply(lambda x: 'Accurate' in x)]
goals = shots[shots['tags'].apply(lambda x: 'Goal' in x)]
player_shot_counts = shots.groupby(['team_id', 'team_name', 'player_id', 'player_name'])['event_id'].count()
player_sot_counts = shots_on_target.groupby(['team_id', 'team_name', 'player_id', 'player_name'])['event_id'].count()
player_goal_counts = goals.groupby(['team_id', 'team_name', 'player_id', 'player_name'])['event_id'].count()
player_stats = pd.concat([player_shot_counts, player_sot_counts, player_goal_counts], axis=1).fillna(0).astype(int)
player_stats.columns = ['Shots', 'SoT', 'Goals']
player_stats
Out[5]:
Shots | SoT | Goals | ||||
---|---|---|---|---|---|---|
team_id | team_name | player_id | player_name | |||
1609 | Arsenal | 3319 | M. Özil | 39 | 14 | 4 |
3361 | A. Sánchez | 69 | 28 | 7 | ||
3560 | Nacho Monreal | 21 | 8 | 5 | ||
7855 | L. Koscielny | 8 | 2 | 2 | ||
7856 | P. Mertesacker | 2 | 2 | 1 | ||
... | ... | ... | ... | ... | ... | ... |
10531 | Swansea City | 77557 | W. Routledge | 5 | 2 | 0 |
246866 | A. Mawson | 12 | 4 | 2 | ||
258162 | Renato Sanches | 12 | 0 | 0 | ||
288865 | O. McBurnie | 3 | 1 | 0 | ||
343951 | T. Abraham | 39 | 16 | 5 |
437 rows × 3 columns
In [6]:
player_stats.sort_values('Goals', ascending=False)[:20].reset_index()
Out[6]:
team_id | team_name | player_id | player_name | Shots | SoT | Goals | |
---|---|---|---|---|---|---|---|
0 | 1612 | Liverpool | 120353 | Mohamed Salah | 142 | 68 | 32 |
1 | 1624 | Tottenham Hotspur | 8717 | H. Kane | 175 | 74 | 29 |
2 | 1625 | Manchester City | 8325 | S. Agüero | 91 | 42 | 21 |
3 | 1631 | Leicester City | 12829 | J. Vardy | 66 | 34 | 20 |
4 | 1625 | Manchester City | 11066 | R. Sterling | 80 | 35 | 18 |
5 | 1611 | Manchester United | 7905 | R. Lukaku | 80 | 41 | 16 |
6 | 1612 | Liverpool | 15808 | Roberto Firmino | 80 | 37 | 15 |
7 | 1609 | Arsenal | 25413 | A. Lacazette | 65 | 36 | 14 |
8 | 1625 | Manchester City | 340386 | Gabriel Jesus | 55 | 31 | 13 |
9 | 1651 | Brighton & Hove Albion | 8416 | G. Murray | 50 | 24 | 12 |
10 | 1610 | Chelsea | 25707 | E. Hazard | 65 | 34 | 12 |
11 | 1624 | Tottenham Hotspur | 14911 | Son Heung-Min | 70 | 31 | 12 |
12 | 1631 | Leicester City | 26150 | R. Mahrez | 71 | 36 | 12 |
13 | 1633 | West Ham United | 14703 | M. Arnautović | 67 | 31 | 11 |
14 | 1624 | Tottenham Hotspur | 54 | C. Eriksen | 97 | 40 | 11 |
15 | 1610 | Chelsea | 3324 | Álvaro Morata | 74 | 33 | 11 |
16 | 1628 | Crystal Palace | 127537 | L. Milivojević | 42 | 16 | 10 |
17 | 1623 | Everton | 7944 | W. Rooney | 44 | 14 | 10 |
18 | 1625 | Manchester City | 245364 | L. Sané | 54 | 21 | 10 |
19 | 1612 | Liverpool | 25747 | S. Mané | 67 | 26 | 10 |
(2) 선수별 xG 집계 및 정렬¶
In [7]:
player_stats['xG'] = shots.groupby(['team_id', 'team_name', 'player_id', 'player_name'])['xg'].sum()
player_stats.sort_values('xG', ascending=False)[:20].reset_index()
Out[7]:
team_id | team_name | player_id | player_name | Shots | SoT | Goals | xG | |
---|---|---|---|---|---|---|---|---|
0 | 1624 | Tottenham Hotspur | 8717 | H. Kane | 175 | 74 | 29 | 25.498832 |
1 | 1612 | Liverpool | 120353 | Mohamed Salah | 142 | 68 | 32 | 18.787223 |
2 | 1625 | Manchester City | 11066 | R. Sterling | 80 | 35 | 18 | 15.184169 |
3 | 1625 | Manchester City | 8325 | S. Agüero | 91 | 42 | 21 | 15.125795 |
4 | 1611 | Manchester United | 7905 | R. Lukaku | 80 | 41 | 16 | 14.873459 |
5 | 1631 | Leicester City | 12829 | J. Vardy | 66 | 34 | 20 | 13.074317 |
6 | 1625 | Manchester City | 340386 | Gabriel Jesus | 55 | 31 | 13 | 11.803429 |
7 | 1609 | Arsenal | 25413 | A. Lacazette | 65 | 36 | 14 | 11.477789 |
8 | 1651 | Brighton & Hove Albion | 8416 | G. Murray | 50 | 24 | 12 | 10.960509 |
9 | 1633 | West Ham United | 14703 | M. Arnautović | 67 | 31 | 11 | 10.647402 |
10 | 1610 | Chelsea | 3324 | Álvaro Morata | 74 | 33 | 11 | 10.504605 |
11 | 1644 | Watford | 377071 | Richarlison | 92 | 23 | 5 | 10.461122 |
12 | 1628 | Crystal Palace | 38031 | C. Benteke | 55 | 20 | 3 | 10.098130 |
13 | 1612 | Liverpool | 25747 | S. Mané | 67 | 26 | 10 | 9.218263 |
14 | 1613 | Newcastle United | 12536 | D. Gayle | 53 | 20 | 6 | 9.031792 |
15 | 1624 | Tottenham Hotspur | 13484 | D. Alli | 70 | 18 | 9 | 8.882627 |
16 | 1624 | Tottenham Hotspur | 54 | C. Eriksen | 97 | 40 | 11 | 8.570227 |
17 | 1612 | Liverpool | 15808 | Roberto Firmino | 80 | 37 | 15 | 8.434821 |
18 | 1627 | West Bromwich Albion | 8958 | J. Rodriguez | 67 | 25 | 7 | 8.334103 |
19 | 1628 | Crystal Palace | 8422 | W. Zaha | 67 | 28 | 9 | 8.232400 |
(3) 선수별 득점-xG 차이(dG) 집계 및 정렬¶
- 높을 수록 골 결정력이 좋은 선수
In [8]:
player_stats['dG'] = player_stats['Goals'] - player_stats['xG']
player_stats.sort_values('dG', ascending=False)[:20].reset_index()
Out[8]:
team_id | team_name | player_id | player_name | Shots | SoT | Goals | xG | dG | |
---|---|---|---|---|---|---|---|---|---|
0 | 1612 | Liverpool | 120353 | Mohamed Salah | 142 | 68 | 32 | 18.787223 | 13.212777 |
1 | 1631 | Leicester City | 12829 | J. Vardy | 66 | 34 | 20 | 13.074317 | 6.925683 |
2 | 1612 | Liverpool | 15808 | Roberto Firmino | 80 | 37 | 15 | 8.434821 | 6.565179 |
3 | 1631 | Leicester City | 26150 | R. Mahrez | 71 | 36 | 12 | 5.961230 | 6.038770 |
4 | 1625 | Manchester City | 8325 | S. Agüero | 91 | 42 | 21 | 15.125795 | 5.874205 |
5 | 1624 | Tottenham Hotspur | 14911 | Son Heung-Min | 70 | 31 | 12 | 6.654574 | 5.345426 |
6 | 1625 | Manchester City | 245364 | L. Sané | 54 | 21 | 10 | 5.104557 | 4.895443 |
7 | 1610 | Chelsea | 25707 | E. Hazard | 65 | 34 | 12 | 7.367824 | 4.632176 |
8 | 1624 | Tottenham Hotspur | 8717 | H. Kane | 175 | 74 | 29 | 25.498832 | 3.501168 |
9 | 1612 | Liverpool | 3802 | Philippe Coutinho | 52 | 18 | 7 | 3.566245 | 3.433755 |
10 | 1633 | West Ham United | 7941 | J. Hernández | 28 | 16 | 8 | 4.733130 | 3.266870 |
11 | 1644 | Watford | 28292 | A. Doucouré | 44 | 17 | 7 | 3.770038 | 3.229962 |
12 | 1651 | Brighton & Hove Albion | 15526 | P. Groß | 41 | 19 | 7 | 3.878759 | 3.121241 |
13 | 1673 | Huddersfield Town | 38377 | L. Depoitre | 20 | 10 | 6 | 2.942375 | 3.057625 |
14 | 1646 | Burnley | 9206 | C. Wood | 38 | 25 | 10 | 7.040081 | 2.959919 |
15 | 1609 | Arsenal | 25867 | P. Aubameyang | 29 | 18 | 10 | 7.094684 | 2.905316 |
16 | 1625 | Manchester City | 11066 | R. Sterling | 80 | 35 | 18 | 15.184169 | 2.815831 |
17 | 1609 | Arsenal | 3560 | Nacho Monreal | 21 | 8 | 5 | 2.201240 | 2.798760 |
18 | 1659 | AFC Bournemouth | 62224 | R. Fraser | 31 | 13 | 5 | 2.216259 | 2.783741 |
19 | 1625 | Manchester City | 265673 | Bernardo Silva | 23 | 14 | 6 | 3.228071 | 2.771929 |
(4) 선수별 득점당 평균 xG 집계 및 정렬¶
In [9]:
player_stats['xG for goals'] = goals.groupby(['team_id', 'team_name', 'player_id', 'player_name'])['xg'].sum()
player_stats['xG per goal'] = player_stats['xG for goals'] / player_stats['Goals']
player_stats[player_stats['Goals'] >= 5].sort_values('xG per goal')[:20].reset_index()
Out[9]:
team_id | team_name | player_id | player_name | Shots | SoT | Goals | xG | dG | xG for goals | xG per goal | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1625 | Manchester City | 38021 | K. De Bruyne | 91 | 39 | 7 | 5.650185 | 1.349815 | 0.463997 | 0.066285 |
1 | 1625 | Manchester City | 105339 | Fernandinho | 48 | 20 | 5 | 3.116935 | 1.883065 | 0.421568 | 0.084314 |
2 | 1639 | Stoke City | 49872 | X. Shaqiri | 67 | 29 | 8 | 5.382163 | 2.617837 | 0.741266 | 0.092658 |
3 | 1631 | Leicester City | 26150 | R. Mahrez | 71 | 36 | 12 | 5.961230 | 6.038770 | 1.184440 | 0.098703 |
4 | 1659 | AFC Bournemouth | 62224 | R. Fraser | 31 | 13 | 5 | 2.216259 | 2.783741 | 0.535540 | 0.107108 |
5 | 1651 | Brighton & Hove Albion | 91381 | J. Izquierdo | 53 | 16 | 5 | 4.706987 | 0.293013 | 0.607775 | 0.121555 |
6 | 1644 | Watford | 28292 | A. Doucouré | 44 | 17 | 7 | 3.770038 | 3.229962 | 0.941342 | 0.134477 |
7 | 1612 | Liverpool | 3802 | Philippe Coutinho | 52 | 18 | 7 | 3.566245 | 3.433755 | 1.005339 | 0.143620 |
8 | 1611 | Manchester United | 397178 | M. Rashford | 59 | 21 | 7 | 5.264043 | 1.735957 | 1.008644 | 0.144092 |
9 | 1673 | Huddersfield Town | 38377 | L. Depoitre | 20 | 10 | 6 | 2.942375 | 3.057625 | 0.874010 | 0.145668 |
10 | 1644 | Watford | 20593 | R. Pereyra | 34 | 13 | 5 | 4.443552 | 0.556448 | 0.736922 | 0.147384 |
11 | 1625 | Manchester City | 245364 | L. Sané | 54 | 21 | 10 | 5.104557 | 4.895443 | 1.508979 | 0.150898 |
12 | 1624 | Tottenham Hotspur | 54 | C. Eriksen | 97 | 40 | 11 | 8.570227 | 2.429773 | 1.662022 | 0.151093 |
13 | 1610 | Chelsea | 8032 | Marcos Alonso | 59 | 21 | 7 | 5.528816 | 1.471184 | 1.082327 | 0.154618 |
14 | 1624 | Tottenham Hotspur | 14911 | Son Heung-Min | 70 | 31 | 12 | 6.654574 | 5.345426 | 1.922408 | 0.160201 |
15 | 1627 | West Bromwich Albion | 3577 | S. Rondón | 79 | 28 | 7 | 7.596607 | -0.596607 | 1.127666 | 0.161095 |
16 | 1609 | Arsenal | 7870 | A. Ramsey | 52 | 22 | 7 | 6.215151 | 0.784849 | 1.142881 | 0.163269 |
17 | 1673 | Huddersfield Town | 214654 | S. Mounié | 49 | 21 | 8 | 6.587816 | 1.412184 | 1.307039 | 0.163380 |
18 | 1610 | Chelsea | 105333 | Willian | 53 | 25 | 6 | 5.184140 | 0.815860 | 1.023569 | 0.170595 |
19 | 1633 | West Ham United | 41174 | M. Lanzini | 43 | 16 | 5 | 4.712147 | 0.287853 | 0.854151 | 0.170830 |
선수별 시즌 전체 xG 시각화¶
(1) 슈팅 레이블 생성¶
In [10]:
match_df = pd.read_csv('data/refined_events/England/matches.csv', header=0, encoding='utf-8-sig')
shots = pd.merge(shots, match_df[['match_id', 'gameweek', 'team1_name', 'team2_name']])
# mouse over 시 보일 텍스트
shots['display_name'] = shots.apply(
lambda x: f"[{x['gameweek']}R] {x['team1_name']} vs {x['team2_name']}, " +
f"{x['period']} {int(x['time'] // 60):02d}:{int(x['time'] % 60):02d}, xG: {round(x['xg'], 3)}", axis=1
)
shots
Out[10]:
competition_name | match_id | event_id | period | time | team_id | team_name | player_id | player_name | event_type | ... | distance | angle | freekick | header | goal | xg | gameweek | team1_name | team2_name | display_name | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | England | 2499719 | 177959212 | 1H | 94.596 | 1609 | Arsenal | 25413 | A. Lacazette | Shot | ... | 13.899813 | 26.933236 | 0 | 0 | 1 | 0.143848 | 1 | Arsenal | Leicester City | [1R] Arsenal vs Leicester City, 1H 01:34, xG: ... |
1 | England | 2499719 | 177959247 | 1H | 179.855 | 1631 | Leicester City | 26150 | R. Mahrez | Shot | ... | 15.659170 | 26.224941 | 0 | 0 | 0 | 0.120612 | 1 | Arsenal | Leicester City | [1R] Arsenal vs Leicester City, 1H 02:59, xG: ... |
2 | England | 2499719 | 177959280 | 1H | 254.745 | 1631 | Leicester City | 14763 | S. Okazaki | Shot | ... | 4.376665 | 79.289489 | 0 | 1 | 1 | 0.412967 | 1 | Arsenal | Leicester City | [1R] Arsenal vs Leicester City, 1H 04:14, xG: ... |
3 | England | 2499719 | 177959289 | 1H | 425.824 | 1609 | Arsenal | 7868 | A. Oxlade-Chamberlain | Shot | ... | 22.893038 | 15.813597 | 0 | 0 | 0 | 0.042434 | 1 | Arsenal | Leicester City | [1R] Arsenal vs Leicester City, 1H 07:05, xG: ... |
4 | England | 2499719 | 177959429 | 1H | 815.462 | 1609 | Arsenal | 7868 | A. Oxlade-Chamberlain | Shot | ... | 29.342120 | 12.655803 | 0 | 0 | 0 | 0.019414 | 1 | Arsenal | Leicester City | [1R] Arsenal vs Leicester City, 1H 13:35, xG: ... |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
8876 | England | 2500089 | 251701666 | 2H | 1682.852 | 1659 | AFC Bournemouth | 9637 | J. King | Shot | ... | 15.367134 | 15.291389 | 0 | 0 | 1 | 0.083744 | 38 | Burnley | AFC Bournemouth | [38R] Burnley vs AFC Bournemouth, 2H 28:02, xG... |
8877 | England | 2500089 | 251701767 | 2H | 2267.378 | 1659 | AFC Bournemouth | 9739 | J. Ibe | Shot | ... | 26.432132 | 15.521739 | 0 | 0 | 0 | 0.029310 | 38 | Burnley | AFC Bournemouth | [38R] Burnley vs AFC Bournemouth, 2H 37:47, xG... |
8878 | England | 2500089 | 251701808 | 2H | 2593.305 | 1659 | AFC Bournemouth | 11669 | C. Wilson | Shot | ... | 32.824990 | 12.113593 | 0 | 0 | 0 | 0.013519 | 38 | Burnley | AFC Bournemouth | [38R] Burnley vs AFC Bournemouth, 2H 43:13, xG... |
8879 | England | 2500089 | 251701582 | 2H | 2644.663 | 1646 | Burnley | 9179 | N. Wells | Shot | ... | 18.176028 | 18.584769 | 0 | 0 | 0 | 0.073772 | 38 | Burnley | AFC Bournemouth | [38R] Burnley vs AFC Bournemouth, 2H 44:04, xG... |
8880 | England | 2500089 | 251701853 | 2H | 2839.356 | 1659 | AFC Bournemouth | 11669 | C. Wilson | Shot | ... | 11.736848 | 31.473639 | 0 | 0 | 1 | 0.188783 | 38 | Burnley | AFC Bournemouth | [38R] Burnley vs AFC Bournemouth, 2H 47:19, xG... |
8881 rows × 24 columns
(2) 슈팅 위치 및 xG 시각화¶
In [11]:
# 손흥민 선수의 xG 시각화
player_name = 'Son Heung-Min'
# player_name = 'Mohamed Salah'
# player_name = 'K. De Bruyne'
player_shots = shots[shots['player_name'] == player_name]
player_goals = player_shots[player_shots['tags'].apply(lambda x: 'Goal' in x)]
player_shots_failed = player_shots[player_shots['tags'].apply(lambda x: 'Goal' not in x)]
goal_trace = go.Scatter(
x=104 - player_goals['x'],
y=34 + player_goals['y'],
name='Goal',
text=player_goals['display_name'],
mode='markers',
marker=dict(
color='red', size=np.sqrt(player_goals['xg']) * 50,
symbol=player_goals['freekick'].apply(lambda x: 'square' if x == 1 else 'circle')
)
)
shot_trace = go.Scatter(
x=104 - player_shots_failed['x'],
y=34 + player_shots_failed['y'],
name='Failed shot',
text=player_shots_failed['display_name'],
mode='markers',
marker=dict(
color='darkgrey', size=np.sqrt(player_shots_failed['xg']) * 50,
symbol=player_shots_failed['freekick'].apply(lambda x: 'square' if x == 1 else 'circle')
)
)
title = f"{player_name} - Goals: {player_shots['goal'].sum()}, xG: {round(player_shots['xg'].sum(), 3)} "
fig = go.Figure(data=[shot_trace, goal_trace], layout=get_pitch_layout(title))
fig.show()