def turnovermap(Df,hometeam,homeid,awayteam,awayid): df = Df.copy() pitch = Pitch(pitch_type='statsbomb', figsize=(10,6), line_zorder=2, layout=(1,2), line_color='k', orientation='horizontal',constrained_layout=True, tight_layout=False) fig, ax = pitch.draw() homedef = df.query("(teamId==@homeid)&\ (type_displayName in ['Clearance','Tackle','Interception','Aerial','BlockedPass'])&\ (outcomeType_displayName=='Successful')") awaydef = df.query("(teamId==@awayid)&\ (type_displayName in ['Clearance','Tackle','Interception','Aerial','BlockedPass'])&\ (outcomeType_displayName=='Successful')") for i in homedef.index.tolist(): if(i<len(df)-1): if((df.teamId[i+1]==homeid)&(df.type_displayName[i+1] in ['BallRecovery','Pass','TakeOn'])& (df.outcomeType_displayName[i+1]=='Successful')): pitch.scatter(homedef.x[i],80-homedef.y[i],marker='o',s=50,zorder=5,facecolors='#082630', edgecolors='#fee090',linewidth=3,ax=ax[0]) for i in awaydef.index.tolist(): if(i<len(df)-1): if((df.teamId[i+1]==homeid)&(df.type_displayName[i+1] in ['BallRecovery','Pass','TakeOn'])& (df.outcomeType_displayName[i+1]=='Successful')): pitch.scatter(awaydef.x[i],80-awaydef.y[i],marker='o',s=50,zorder=5,facecolors='#082630', edgecolors='#fee090',linewidth=3,ax=ax[1]) ax[0].set_title(hometeam+'\n'+'Turnover creating'+'\n'+'Defensive activities',fontsize=30) ax[1].set_title(awayteam+'\n'+'Turnover creating'+'\n'+'Defensive activities',fontsize=30) fig.text(0.0, 0.0, "Created by Soumyajit Bose / @Soumyaj15209314",fontstyle="italic",fontsize=15,color='#edece9') fig.text(0.0, 0.05, "Turnover creating defensive activities include tackles, interceptions, aerials, clearances," +'\n'+" and blocked passes leading to winning possession",fontstyle="italic",fontsize=15,color='#edece9') # add_image(bu_alt, fig, left=0.9, bottom=0.01, width=0.07) return fig
def badpasses(Df,hometeam,homeid): df = Df.copy() pitch = Pitch(pitch_type='statsbomb', figsize=(10,4.5), line_zorder=2, layout=(1,2), line_color='k', orientation='horizontal',constrained_layout=True, tight_layout=False) fig, ax = pitch.draw() def3rd = df.query("(teamId==@homeid)&(type_displayName == 'Pass')&(x<40)") mid3rd = df.query("(teamId==@homeid)&(type_displayName == 'Pass')&(x>=40)&(x<80)") defmask = def3rd.outcomeType_displayName == 'Successful' midmask = mid3rd.outcomeType_displayName == 'Successful' # awaymask = awaydef.outcomeType_displayName == 'Successful' pitch.arrows(def3rd[~defmask].x,80-def3rd[~defmask].y,def3rd[~defmask].endX,80-def3rd[~defmask].endY, headwidth=5, headlength=5,zorder=5,color='tab:red',width=1,ax=ax[0]) # pitch.lines(homedef[~homemask].x,80-homedef[~homemask].y,homedef[~homemask].endX,80-homedef[~homemask].endY, # zorder=5,color='tab:red',linewidth=2,comet=True,ax=ax[0]) pitch.arrows(mid3rd[~midmask].x,80-mid3rd[~midmask].y,mid3rd[~midmask].endX,80-mid3rd[~midmask].endY, headwidth=5, headlength=5,zorder=5,color='tab:red',width=1,ax=ax[1]) # pitch.lines(awaydef[~awaymask].x,80-awaydef[~awaymask].y,awaydef[~awaymask].endX,80-awaydef[~awaymask].endY, # zorder=5,color='tab:red',linewidth=2,comet=True,ax=ax[1]) ax[0].set_title(hometeam+' Unsuccessful Passes'+'\n'+'from defensive 3rd',fontsize=20) ax[1].set_title(hometeam+' Unsuccessful Passes'+'\n'+'from middle 3rd',fontsize=20) # fig.text(0.0, 0.05, "All unsuccessful passes originating from defensive third of the pitch",fontstyle="italic",fontsize=15,color='#edece9') fig.text(0.0, 0.0, "Created by Soumyajit Bose / @Soumyaj15209314",fontstyle="italic",fontsize=15,color='#edece9') # add_image(bu_alt, fig, left=0.9, bottom=0.0, width=0.07) return fig
def switches(Df,hometeam,homeid,awayteam,awayid): df = Df.copy() pitch = Pitch(pitch_type='statsbomb', figsize=(10,5), line_zorder=2, layout=(1,2), line_color='k', orientation='horizontal',constrained_layout=True, tight_layout=False) fig, ax = pitch.draw() homedef = df.query("(teamId==@homeid)&(type_displayName == 'Pass')&((endY-y)**2>=1600.0)") awaydef = df.query("(teamId==@awayid)&(type_displayName == 'Pass')&((endY-y)**2>=1600.0)") homemask = homedef.outcomeType_displayName == 'Successful' awaymask = awaydef.outcomeType_displayName == 'Successful' pitch.lines(homedef[homemask].x,80-homedef[homemask].y,homedef[homemask].endX,80-homedef[homemask].endY, zorder=5,color='#fee090',linewidth=2,comet=True,ax=ax[0]) pitch.lines(homedef[~homemask].x,80-homedef[~homemask].y,homedef[~homemask].endX,80-homedef[~homemask].endY, zorder=5,color='tab:red',linewidth=2,comet=True,ax=ax[0]) pitch.lines(awaydef[awaymask].x,80-awaydef[awaymask].y,awaydef[awaymask].endX,80-awaydef[awaymask].endY, zorder=5,color='#fee090',linewidth=2,comet=True,ax=ax[1]) pitch.lines(awaydef[~awaymask].x,80-awaydef[~awaymask].y,awaydef[~awaymask].endX,80-awaydef[~awaymask].endY, zorder=5,color='tab:red',linewidth=2,comet=True,ax=ax[1]) ax[0].set_title(hometeam+'\n'+'Switches',fontsize=30) ax[1].set_title(awayteam+'\n'+'Switches',fontsize=30) fig.text(0.0, 0.0, "Created by Soumyajit Bose / @Soumyaj15209314",fontstyle="italic",fontsize=15,color='#edece9') fig.text(0.0, 0.05, "Red lines for unsuccessful switches, yellow for successful ones", fontstyle="italic",fontsize=15,color='#edece9') # add_image(bu_alt, fig, left=0.9, bottom=0.01, width=0.07) return fig
def keypasses(Df,hometeam,homeid,awayteam,awayid): df = Df.copy() pitch = Pitch(pitch_type='statsbomb', figsize=(10,5), line_zorder=2, layout=(1,2), line_color='k', orientation='horizontal',constrained_layout=True, tight_layout=False) fig, ax = pitch.draw() homedef = df.query("(teamId==@homeid)&(KP == 1)&(endX!=0)&(endY!=0)") awaydef = df.query("(teamId==@awayid)&(KP == 1)&(endX!=0)&(endY!=0)") # homemask = homedef.outcomeType_displayName == 'Successful' # awaymask = awaydef.outcomeType_displayName == 'Successful' pitch.arrows(homedef.x,80-homedef.y,homedef.endX,80-homedef.endY,headwidth=5, headlength=5, zorder=5,color='#fee090',width=1,ax=ax[0]) # pitch.lines(homedef[~homemask].x,80-homedef[~homemask].y,homedef[~homemask].endX,80-homedef[~homemask].endY, # zorder=5,color='tab:red',linewidth=2,comet=True,ax=ax[0]) pitch.arrows(awaydef.x,80-awaydef.y,awaydef.endX,80-awaydef.endY,headwidth=5, headlength=5, zorder=5,color='#fee090',width=1,ax=ax[1]) # pitch.lines(awaydef[~awaymask].x,80-awaydef[~awaymask].y,awaydef[~awaymask].endX,80-awaydef[~awaymask].endY, # zorder=5,color='tab:red',linewidth=2,comet=True,ax=ax[1]) ax[0].set_title(hometeam+'\n'+'Key Passes',fontsize=30) ax[1].set_title(awayteam+'\n'+'Key Passes',fontsize=30) fig.text(0.0, 0.0, "Created by Soumyajit Bose / @Soumyaj15209314",fontstyle="italic",fontsize=15,color='#edece9') # add_image(bu_alt, fig, left=0.9, bottom=0.01, width=0.07) return fig
def takeonmap(Df,hometeam,homeid,awayteam,awayid): df = Df.copy() pitch = Pitch(pitch_type='statsbomb', figsize=(10,5), line_zorder=2, layout=(1,2), line_color='k', orientation='horizontal',constrained_layout=True, tight_layout=False) fig, ax = pitch.draw() homedef = df.query("(teamId==@homeid)&(type_displayName == 'TakeOn')") awaydef = df.query("(teamId==@awayid)&(type_displayName == 'TakeOn')") homemask = homedef.outcomeType_displayName == 'Successful' awaymask = awaydef.outcomeType_displayName == 'Successful' pitch.scatter(homedef[homemask].x,80-homedef[homemask].y,marker='o',s=50,zorder=5,facecolors='#082630', edgecolors='#fee090',linewidth=3,ax=ax[0]) pitch.scatter(homedef[~homemask].x,80-homedef[~homemask].y,marker='o',s=50,zorder=5,facecolors='#082630', edgecolors='tab:red',linewidth=3,ax=ax[0]) pitch.scatter(awaydef[awaymask].x,80-awaydef[awaymask].y,marker='o',s=50,zorder=5,facecolors='#082630', edgecolors='#fee090',linewidth=3,ax=ax[1]) pitch.scatter(awaydef[~awaymask].x,80-awaydef[~awaymask].y,marker='o',s=50,zorder=5,facecolors='#082630', edgecolors='tab:red',linewidth=3,ax=ax[1]) ax[0].set_title(hometeam+'\n'+'TakeOns',fontsize=30) ax[1].set_title(awayteam+'\n'+'TakeOns',fontsize=30) fig.text(0.0, 0.0, "Created by Soumyajit Bose / @Soumyaj15209314",fontstyle="italic",fontsize=15,color='#edece9') fig.text(0.0, 0.05, "Red markers for unsuccessful takeons, yellow for successful ones", fontstyle="italic",fontsize=15,color='#edece9') # add_image(bu_alt, fig, left=0.9, bottom=0.01, width=0.07) return fig
def ind_heatmap(Df, player): from matplotlib.colors import LinearSegmentedColormap import cmasher as cmr #from mplsoccer import VerticalPitch df = Df.copy() pitch = Pitch(pitch_type='uefa', figsize=(10.5, 6.8), line_zorder=2, line_color='#636363', orientation='horizontal', constrained_layout=True, tight_layout=False, pitch_color='black') fig, ax = pitch.draw() df = df[df.name == player] touchdf = df[(df.isTouch == True) & (df.name == player)].reset_index() #pitch.kdeplot(touchdf.x, touchdf.y, ax=ax, cmap=cmap, #linewidths=0.3,fill=True,levels=1000) pitch.kdeplot(touchdf.x, touchdf.y, ax=ax, cmap=cmr.voltage, shade=True, levels=1000) ax.set_title(player + ' Touch-based heatmap', fontsize=25, color='white') fig.text(0.2, 0.0, "Created by Nikhil Rajesh / @nikhilrajesh231", fontstyle="italic", fontsize=15, color='black') fig.set_facecolor('black') ax.set_facecolor('black') return fig
def init_pitch(): pitch = Pitch(pitch_color=None, line_color='whitesmoke', stripe=False) fig, ax = pitch.draw() # SB coordinates start on the left side, so we should invert the axes ax.invert_yaxis() return fig, ax
def alineacion_442(valor_total, edad): try: valor_jugador = valor_total / 11 porteros = goalkeeper(1, valor_jugador, edad).reset_index() defensas = defenders(4, valor_jugador, edad).reset_index() medios = midfielders(4, valor_jugador, edad).reset_index() delanteros = forwards(2, valor_jugador, edad).reset_index() pitch = Pitch(pitch_color='grass', line_color='white', stripe=True) fig, ax = pitch.draw(figsize=(20, 20)) annotation = ax.annotate(porteros['short_name'][0], (5, 40), fontsize=40, ha='center') annotation = ax.annotate(defensas['short_name'][0], (24, 30), fontsize=40, ha='center') annotation = ax.annotate(defensas['short_name'][1], (24, 52), fontsize=40, ha='center') annotation = ax.annotate(defensas['short_name'][2], (32, 12), fontsize=40, ha='center') annotation = ax.annotate(defensas['short_name'][3], (32, 68), fontsize=40, ha='center') annotation = ax.annotate(medios['short_name'][0], (60, 30), fontsize=40, ha='center') annotation = ax.annotate(medios['short_name'][1], (60, 52), fontsize=40, ha='center') annotation = ax.annotate(medios['short_name'][2], (60, 12), fontsize=40, ha='center') annotation = ax.annotate(medios['short_name'][3], (60, 68), fontsize=40, ha='center') annotation = ax.annotate(delanteros['short_name'][0], (89, 24), fontsize=40, ha='center') annotation = ax.annotate(delanteros['short_name'][1], (89, 60), fontsize=40, ha='center') plt.savefig('output/images/442.jpg') result = pd.concat([porteros, defensas, medios, delanteros]).reset_index() result = result[[ 'short_name', 'nationality', 'position', 'age', 'wage_eur', 'value_eur', 'value_pred' ]] return result except: plt.savefig('output/images/442.jpg') return "No se han encontrado jugadores con esos parametros"
def show_buts(club_name): identifiant_club = df_2.loc[df_2['name'] == club_name, ['wyId']] identifiant_club = identifiant_club.iat[0,0] df_goals = df_4.loc[(df_4['teamId'] == identifiant_club) & (df_4['subEventName'] == 'Goal kick')] x, y = list(), list() for i in df_goals['positions']: x.append(i[1]['x']) y.append(i[1]['y']) pitch = Pitch(pitch_color='grass', line_color='white', stripe=True) fig, ax = pitch.draw() sc = pitch.scatter(y, x, s=100, ax=ax)
def createShotmap(match_data, events_df, team, pitchcolor, shotcolor, goalcolor, titlecolor, legendcolor, marker_size): # getting team id and venue if match_data['home']['name'] == team: teamId = match_data['home']['teamId'] venue = 'home' else: teamId = match_data['away']['teamId'] venue = 'away' # getting opponent if venue == 'home': opponent = match_data['away']['name'] else: opponent = match_data['home']['name'] total_shots = events_df.loc[[9 in row for row in list(events_df['satisfiedEventsTypes'])]] team_shots = total_shots.loc[total_shots['teamId'] == teamId].reset_index(drop=True) mask_goal = team_shots.isGoal == True # Setup the pitch pitch = Pitch(pitch_type='statsbomb', orientation='vertical', pitch_color=pitchcolor, line_color='#c7d5cc', figsize=(16, 11), view='half', pad_top=2, tight_layout=True) fig, ax = pitch.draw() # Plot the goals pitch.scatter(team_shots[mask_goal].x/100*120, 80-team_shots[mask_goal].y/100*80, s=marker_size, edgecolors='black', c=goalcolor, zorder=2, label='goal', ax=ax) pitch.scatter(team_shots[~mask_goal].x/100*120, 80-team_shots[~mask_goal].y/100*80, edgecolors='white', c=shotcolor, s=marker_size, zorder=2, label='shot', ax=ax) # Set the title ax.set_title(f'{team} shotmap \n vs {opponent}', fontsize=30, color=titlecolor) # set legend leg = ax.legend(facecolor=pitchcolor, edgecolor='None', fontsize=20, loc='lower center', handlelength=4) leg_texts = leg.get_texts() # list of matplotlib Text instances. leg_texts[0].set_color(legendcolor) leg_texts[1].set_color(legendcolor) # Set the figure facecolor fig.set_facecolor(pitchcolor)
def passtypes(Df,team,teamid): df = Df.copy() # matchdict = Dict pitch = Pitch(pitch_type='statsbomb', figsize=(11,6), line_zorder=2, layout=(2,3), view='half', line_color='#c7d5cc', orientation='vertical',constrained_layout=True, tight_layout=False) fig, ax = pitch.draw() passdf = df.query("(type_displayName == 'Pass')&(outcomeType_displayName=='Successful')\ &(teamId==@teamid)").reset_index() passdf['y'] = 80 - passdf['y'] passdf['endY'] = 80 - passdf['endY'] progtocentre = passdf.query("(x<80)&(endX>80)&(endY>18)&(endY<62)")[['x','y','endX','endY']] wingplay = passdf.query("(x<80)&(endX>80)&((endY<18)or(endY>62))")[['x','y','endX','endY']] allbox = passdf.query("(endX>102)&(endY>18)&(endY<62)")[['x','y','endX','endY']] wingtobox = passdf.query("(x>80)&((y<18)or(y>62))&(endX>102)&(endY>18)&(endY<62)")[['x','y','endX','endY']] halfspaceplay = passdf.query("(x>80)&(((y>18)&(y<30))or((y>50)&(y<62)))&(endX>x)")[['x','y','endX','endY']] zone14play = passdf.query("(x>80)&(x<102)&(y>30)&(y<50)&(endX>x)")[['x','y','endX','endY']] pitch.lines(progtocentre.x, progtocentre.y, progtocentre.endX, progtocentre.endY, transparent=True, lw=2, comet=True, color='#a1d76a',ax=ax[0,0]) ax[0,0].set_title('Middle 3rd'+'\n'+'to Centre of final 3rd',fontsize=15) pitch.lines(wingplay.x, wingplay.y, wingplay.endX, wingplay.endY, transparent=True, lw=2, comet=True, color='#a1d76a',ax=ax[0,1]) ax[0,1].set_title('Middle 3rd'+'\n'+'to wing of final 3rd',fontsize=15) pitch.lines(allbox.x, allbox.y, allbox.endX, allbox.endY, transparent=True, lw=2, comet=True, color='#a1d76a',ax=ax[0,2]) ax[0,2].set_title('All passes to box',fontsize=15) pitch.lines(wingtobox.x, wingtobox.y, wingtobox.endX, wingtobox.endY, transparent=True, lw=2, comet=True, color='#a1d76a',ax=ax[1,0]) ax[1,0].set_title('Wing to box',fontsize=15) pitch.lines(halfspaceplay.x, halfspaceplay.y, halfspaceplay.endX, halfspaceplay.endY, transparent=True, lw=2, comet=True, color='#a1d76a',ax=ax[1,1]) ax[1,1].set_title('Attacking Passes'+'\n'+'From halfspace',fontsize=15) pitch.lines(zone14play.x, zone14play.y, zone14play.endX, zone14play.endY, transparent=True, lw=2, comet=True, color='#a1d76a',ax=ax[1,2]) ax[1,2].set_title('Attacking Passes'+'\n'+'From Zone 14',fontsize=15) fig.suptitle(team+' Progressive/Attacking Passes',fontsize=20) fig.text(0.05, -0.05, "Created by Soumyajit Bose / @Soumyaj15209314",fontstyle="italic",fontsize=15,color='#edece9') # add_image(bu_alt, fig, left=0.9, bottom=0.9, width=0.07) return fig
def defensesmap(Df,team,teamid): df = Df.copy() pitch = Pitch(pitch_type='statsbomb', figsize=(10,6), line_zorder=2, layout=(1,2), line_color='k', orientation='horizontal',constrained_layout=True, tight_layout=False) fig, ax = pitch.draw() defensedf = df.query("(type_displayName in ['Aerial','Clearance','Interception','Tackle','BlockedPass','Challenge'])\ &(teamId==@teamid)").reset_index() defensedf['defensive'] = [1 for i in range(len(defensedf))] for i in range(len(defensedf)): if(defensedf.loc[i,['type_displayName']][0]=='Aerial'): quals = defensedf.quals[i] if(286 in quals): defensedf.loc[i,['defensive']] = 0 if(defensedf.loc[i,['type_displayName']][0]=='Challenge'): quals = defensedf.quals[i] if(286 in quals): defensedf.loc[i,['defensive']] = 0 defensedf = defensedf[defensedf.defensive==1] defensedf['y'] = 80 - defensedf['y'] ppda = defensedf[defensedf.x>=48] ppda = ppda.query("type_displayName in ['Interception','Tackle','Challenge']") deepdf = defensedf[defensedf.x<48] bin_statistic = pitch.bin_statistic(ppda.x, ppda.y, statistic='count', bins=(25, 25)) bin_statistic['statistic'] = gaussian_filter(bin_statistic['statistic'], 1) pcm = pitch.heatmap(bin_statistic, ax=ax[0], cmap='Reds', edgecolors=None) ax[0].set_title(team+'\n'+' Pressurizing'+'\n'+ 'Defensive activities',fontsize=30) bin_statistic = pitch.bin_statistic(deepdf.x, deepdf.y, statistic='count', bins=(25, 25)) bin_statistic['statistic'] = gaussian_filter(bin_statistic['statistic'], 1) pcm = pitch.heatmap(bin_statistic, ax=ax[1], cmap='Reds', edgecolors=None) # cbar = fig.colorbar(pcm, ax=ax[1]) ax[1].set_title(team+'\n'+' Goal Protecting'+'\n'+'Defensive activities',fontsize=30) fig.text(0.0, 0.0, "Created by Soumyajit Bose / @Soumyaj15209314",fontstyle="italic",fontsize=15,color='#edece9') fig.text(0.0, 0.15, "Pressurizing defensive activities include tackles, interceptions and challenges"+'\n'+ "made high up in opposition territory",fontstyle="italic",fontsize=15,color='#edece9') fig.text(0.0, 0.05, "Goal protecting defensive activities include tackles, interceptions, defensive aerials, challenges," +'\n'+"clearances and blocked passes deep in own territory",fontstyle="italic",fontsize=15,color='#edece9') # add_image(bu_alt, fig, left=0.9, bottom=0.01, width=0.07) return fig
def touchmap(Df,team,teamid): df = Df.copy() pitch = Pitch(pitch_type='statsbomb', figsize=(12,7), line_zorder=2, layout=(4,4), line_color='k', orientation='horizontal',constrained_layout=True, tight_layout=False) fig, ax = pitch.draw() touchdf = df[(df.isTouch==True)&(df.teamId==teamid)&(df.name!='')].reset_index() touchdf['y'] = 80 - touchdf['y'] players = touchdf.name.unique().tolist() Nplayers = len(players) for i in range(Nplayers): pdf = touchdf[touchdf.name==players[i]] bin_statistic = pitch.bin_statistic(pdf.x, pdf.y, statistic='count', bins=(25, 25)) bin_statistic['statistic'] = gaussian_filter(bin_statistic['statistic'], 1) pcm = pitch.heatmap(bin_statistic, ax=ax[i//4,i%4], cmap='Reds', edgecolors=None) ax[i//4,i%4].set_title(players[i],fontsize=15) for i in range(Nplayers,16): ax[i//4,i%4].remove() fig.text(0.0, 0.0, "Created by Soumyajit Bose / @Soumyaj15209314",fontstyle="italic",fontsize=15,color='#edece9') fig.text(0.3, 1.01, team+" Touch-based heatmaps",fontsize=25,color='#edece9') # add_image(bu_alt, fig, left=0.95, bottom=0.97, width=0.07) return fig
def progpassplotter(Df,team,teamid): pitch = Pitch(pitch_type='statsbomb', figsize=(12,10), line_zorder=2, layout=(4,4), view='full', line_color='#c7d5cc', orientation='horizontal',constrained_layout=True, tight_layout=False) fig, ax = pitch.draw() df = Df[(Df.teamId==teamid)&(Df.name!='')].reset_index() players = df.name.unique().tolist() Nplayers = len(players) for i in range(Nplayers): p = players[i] Passp = playerpasses(df,p) pitch.lines(Passp.x, Passp.y, Passp.endX, Passp.endY, transparent=True, lw=2, comet=True, color='#a1d76a',ax=ax[i//4,i%4]) ax[i//4,i%4].set_title(p,fontsize=15) for i in range(Nplayers,16): ax[i//4,i%4].remove() fig.text(0.05, -0.04, "Created by Soumyajit Bose / @Soumyaj15209314",fontstyle="italic",fontsize=15,color='#edece9') # add_image(bu_alt, fig, left=0.85, bottom=-0.07, width=0.05) fig_htext(s = "<Completed Progressive Passes> by all players of "+team, x = 0.1, y = 1, highlight_colors = ['#a1d76a'], highlight_weights=['bold'], string_weight='bold',fontsize=20,color='#edece9') return fig
Basics ====== First we import the Pitch class and set the matplotlib style sheet. """ import matplotlib.pyplot as plt from mplsoccer.pitch import Pitch plt.style.use('ggplot') ############################################################################## # Draw a pitch on a new axis # -------------------------- # Let's plot on a new axis first. pitch = Pitch(figsize=(8, 4)) # specifying figure size is optional (width, height) fig, ax = pitch.draw() ############################################################################## # Draw on an existing axis # ------------------------ # mplsoccer also plays nicely with other matplotlib figures. To draw a pitch on an # existing matplotlib axis specify an ``ax`` in the ``draw`` method. fig, ax = plt.subplots(nrows=1, ncols=2) pitch = Pitch() pitch.draw(ax=ax[1]) ############################################################################## # Supported data providers # ------------------------ # mplsoccer supports 7 pitch types by specifying the ``pitch_type`` argument:
def shotMapMatch(shots_home, shots_away, mask_goal_h, mask_goal_a, home_name, away_name, home_score, away_score): """ Creates a combined shot map (similar to caley style) for a both teams on a full, horizontal pitch Parameters ---------- shots_XXXX : A pandas dataframe - Subset of team dataframe for home and away - contains ['id', 'minute', 'X', 'Y', 'player', 'shotType', 'situation', 'result', 'xG', 'pos_x', 'pos_y'] - pos_x and pos_y are scaled to the 120x80 statsbomb sized pitch mask_goal_X : Pandas index mask - Mask for all goals separated home and away - Lets us separate points where the shot is scored team_name : string - Name of home/away team score : int - Actual team score - used for display options Returns ------- shotmap : figure - Pitch shot map for a both teams """ max_marker_size = 500 home_xg = round(shots_home['xG'].sum(),1) away_xg = round(shots_away['xG'].sum(),1) fig, ax = plt.subplots(figsize=(10,6)) fig.set_facecolor('#3d4849') ax.patch.set_facecolor('#3d4849') pitch = Pitch(pitch_type='statsbomb', orientation='horizontal', pitch_color='#22312b', line_color='#c7d5cc', figsize=(10,6), constrained_layout=False, tight_layout=True, view='full', pad_top=2) # Home pitch.scatter(120-shots_home[~mask_goal_h].pos_x, 80-shots_home[~mask_goal_h].pos_y, s=shots_home[~mask_goal_h].xG*max_marker_size, edgecolors='white', c='#1f77b4', marker='s', zorder=1, label=home_name, ax=ax, alpha=0.8) pitch.scatter(120-shots_home[mask_goal_h].pos_x, 80-shots_home[mask_goal_h].pos_y, edgecolors='black', c='white', s=shots_home[mask_goal_h].xG*max_marker_size, zorder=2, marker='s', alpha=0.95, ax=ax) # Away pitch.scatter(shots_away[~mask_goal_a].pos_x, shots_away[~mask_goal_a].pos_y, s=shots_away[~mask_goal_a].xG*max_marker_size, edgecolors='white', c='#ff7f0e', marker='s', zorder=1, label=away_name, ax=ax, alpha=0.8) pitch.scatter(shots_away[mask_goal_a].pos_x, shots_away[mask_goal_a].pos_y, edgecolors='black', c='white', s=shots_away[mask_goal_a].xG*max_marker_size, zorder=2, marker='s', alpha=0.95, label='Goal', ax=ax) pitch.draw(ax=ax) # Flip so goal is on bottom plt.gca().invert_yaxis() # Set the title ax.set_title(f'{home_name} (H) vs {away_name} (A) Shot Map', fontsize=20, color='w') legend = plt.legend(loc='center', markerscale=1, framealpha=1.0, bbox_to_anchor=[0.5, 0.88]) # make the markers the same size in the legend for handle in legend.legendHandles: handle.set_sizes([100]) # these are matplotlib.patch.Patch properties props = dict(boxstyle='round', facecolor='white', alpha=1.0) textstr = f'Score: (H) {home_score} - {away_score} (A)\nxG: (H) {home_xg} - {away_xg} (A)' ax.text(0.5, 0.125, textstr, transform=ax.transAxes, fontsize=14, verticalalignment='top', bbox=props, horizontalalignment='center') plt.show()
def shotMapTeam(shots, mask_goal, home_name, away_name, home=True): """ Creates a team shot map (similar to caley style) for a single team on half pitch Parameters ---------- shots : A pandas dataframe - Subset of team dataframe - contains ['id', 'minute', 'X', 'Y', 'player', 'shotType', 'situation', 'result', 'xG', 'pos_x', 'pos_y'] - pos_x and pos_y are scaled to the 120x80 statsbomb sized pitch mask_goal : Pandas index mask - Mask for all goals - Lets us separate points where the shot is scored team_name : string - Name of home/away team home : boolean - Defaults to true - True if looking at home team's shots. - Responsible for changing draw color options and title text Returns ------- shotmap : figure - Pitch shot map for a single team """ max_marker_size = 500 ## Plotting options for single team or both teams orientation = 'vertical' view = 'half' if home: color = '#1f77b4' team_name = home_name opp_name = away_name else: color = '#ff7f0e' team_name = away_name opp_name = home_name fig, ax = plt.subplots(figsize=(10,6)) fig.set_facecolor('#3d4849') ax.patch.set_facecolor('#3d4849') pitch = Pitch(pitch_type='statsbomb', orientation=orientation, pitch_color='#22312b', line_color='#c7d5cc', figsize=(8,5.5), constrained_layout=False, tight_layout=True, view=view, pad_top=2) pitch.scatter(shots[~mask_goal].pos_x, shots[~mask_goal].pos_y, s=shots[~mask_goal].xG*max_marker_size, edgecolors='white', c=color, marker='s', zorder=1, label='Shot', ax=ax, alpha=0.8) pitch.scatter(shots[mask_goal].pos_x, shots[mask_goal].pos_y, edgecolors='black', c='white', s=shots[mask_goal].xG*max_marker_size, zorder=2, marker='s', alpha=0.95, label='Goal', ax=ax) pitch.draw(ax=ax) # Flip so goal is on bottom plt.gca().invert_yaxis() # Set the title ax.set_title(f'{team_name} Shots \n vs {opp_name}', fontsize=20, color='w')
def getTeamSuccessfulBoxPasses(events_df, teamId, team, pitch_color, cmap): """ Parameters ---------- events_df : DataFrame of all events. teamId : ID of the team, the passes of which are required. team : Name of the team, the passes of which are required. pitch_color : color of the pitch. cmap : color design of the pass lines. You can select more cmaps here: https://matplotlib.org/3.1.0/tutorials/colors/colormaps.html Returns ------- Pitch Plot. """ # Get Total Passes passes_df = events_df.loc[[row['displayName'] == 'Pass' for row in list(events_df['type'])]].reset_index(drop=True) # Get Team Passes team_passes = passes_df.loc[passes_df['teamId'] == teamId] # Extracting Box Passes from Total Passes box_passes = team_passes.copy() for i,pas in box_passes.iterrows(): X = pas["x"]/100*120 Xend = pas["endX"]/100*120 Y = pas["y"]/100*80 Yend = pas["endY"]/100*80 if Xend >= 102 and Yend >= 18 and Yend <= 62: if X >=102 and Y >= 18 and Y <= 62: box_passes = box_passes.drop([i]) else: pass else: box_passes = box_passes.drop([i]) successful_box_passes = box_passes.copy() unsuccessful_box_passes = box_passes.copy() for i,pas in box_passes.iterrows(): if pas['outcomeType']['value'] == 0: successful_box_passes = successful_box_passes.drop([i]) else: unsuccessful_box_passes = unsuccessful_box_passes.drop([i]) pitch = Pitch(pitch_type='statsbomb', orientation='vertical', pitch_color=pitch_color, line_color='#c7d5cc', figsize=(16, 11), view='half', pad_top=2, tight_layout=True) fig, ax = pitch.draw() # Plot the completed passes pitch.lines(successful_box_passes.x/100*120, 80-successful_box_passes.y/100*80, successful_box_passes.endX/100*120, 80-successful_box_passes.endY/100*80, lw=5, opp_transparent=True, opp_comet=True, cmap=cmap, label='Successful Passes', ax=ax) pitch.scatter(successful_box_passes.x/100*120, 80-successful_box_passes.y/100*80, edgecolors='white', c='white', s=50, zorder=2, ax=ax) # Set the title fig.suptitle(f'Completed Box Passes - {team}', fontsize=15) # Set the subtitle ax.set_title('Data : Whoscored', fontsize=8, loc='right', fontstyle='italic', fontweight='bold') # set legend #ax.legend(facecolor='#22312b', edgecolor='None', fontsize=8, loc='lower center', handlelength=4) # Set the figure facecolor fig.set_facecolor(pitch_color)
def createAttPassNetworks(match_data, matches_df, events_df, team, pitch_color, max_lw, marker_size, marker_color, marker_label='kit_no', marker_label_size=20): """ Parameters ---------- match_data : Data containing everything about the match matches_df : DataFrame containing match data. events_df : DataFrame containing event data for that match. team : Name of the required team. pitch_color : color of the pitch and figure. max_lw : maximum line width of network lines. marker_size : size of the circle markers. marker_color : color of the circle markers. Returns ------- Pitch Plot. """ # getting match_id matchId = match_data['matchId'] # getting team id and venue if match_data['home']['name'] == team: teamId = match_data['home']['teamId'] venue = 'home' else: teamId = match_data['away']['teamId'] venue = 'away' # getting opponent if venue == 'home': opponent = match_data['away']['name'] else: opponent = match_data['home']['name'] # getting player dictionary team_players_dict = {} for player in match_data[venue]['players']: team_players_dict[player['playerId']] = player['name'] # getting player dictionary with kit no team_playerskitno_dict = {} for player in matches_df[venue][matchId]['players']: team_playerskitno_dict[player['shirtNo']] = player['name'] # getting minute of first substitution for i,row in events_df.iterrows(): if row['type']['displayName'] == 'SubstitutionOn' and row['teamId'] == teamId: sub_minute = str(row['minute']) break # getting players dataframe match_players_df = pd.DataFrame() player_names = [] player_ids = [] player_pos = [] player_kit_number = [] for player in match_data[venue]['players']: player_names.append(player['name']) player_ids.append(player['playerId']) player_pos.append(player['position']) player_kit_number.append(player['shirtNo']) match_players_df['playerId'] = player_ids match_players_df['playerName'] = player_names match_players_df['playerPos'] = player_pos match_players_df['playerKitNumber'] = player_kit_number # extracting passes passes_df = events_df.loc[events_df['teamId'] == teamId].reset_index().drop('index', axis=1) passes_df.dropna(subset=["playerId"], inplace=True) passes_df.insert(27, column='playerName', value=[team_players_dict[i] for i in list(passes_df['playerId'])]) passes_df.insert(28, column='passRecipientId', value=passes_df['playerId'].shift(-1)) passes_df.insert(29, column='passRecipientName', value=passes_df['playerName'].shift(-1)) passes_df.dropna(subset=["passRecipientName"], inplace=True) passes_df = passes_df.loc[[row['displayName'] == 'Pass' for row in list(passes_df['type'])]].reset_index(drop=True) passes_df = passes_df.loc[[row['displayName'] == 'Successful' for row in list(passes_df['outcomeType'])]].reset_index(drop=True) index_names = passes_df.loc[passes_df['playerName']==passes_df['passRecipientName']].index passes_df.drop(index_names, inplace=True) passes_df = passes_df.merge(match_players_df, on=['playerId', 'playerName'], how='left', validate='m:1') passes_df = passes_df.merge(match_players_df.rename({'playerId': 'passRecipientId', 'playerName':'passRecipientName'}, axis='columns'), on=['passRecipientId', 'passRecipientName'], how='left', validate='m:1', suffixes=['', 'Receipt']) passes_df = passes_df[passes_df['playerPos'] != 'Sub'] # getting team formation formation = match_data[venue]['formations'][0]['formationName'] formation = '-'.join(formation) # getting player average locations location_formation = passes_df[['playerKitNumber', 'x', 'y']] average_locs_and_count = location_formation.groupby('playerKitNumber').agg({'x': ['mean'], 'y': ['mean', 'count']}) average_locs_and_count.columns = ['x', 'y', 'count'] # filtering progressive passes passes_df = passes_df.loc[passes_df['EPV_difference'] > 0] # getting separate dataframe for selected columns passes_formation = passes_df[['id', 'playerKitNumber', 'playerKitNumberReceipt']].copy() passes_formation['EPV'] = passes_df['EPV_difference'] # getting dataframe for passes between players passes_between = passes_formation.groupby(['playerKitNumber', 'playerKitNumberReceipt']).agg({ 'id' : 'count', 'EPV' : 'sum'}).reset_index() passes_between.rename({'id': 'pass_count'}, axis='columns', inplace=True) passes_between = passes_between.merge(average_locs_and_count, left_on='playerKitNumberReceipt', right_index=True) passes_between = passes_between.merge(average_locs_and_count, left_on='playerKitNumber', right_index=True, suffixes=['', '_end']) # filtering passes pass_filter = int(passes_between['pass_count'].mean()) passes_between = passes_between.loc[passes_between['pass_count'] > pass_filter*2] # calculating the line width max_line_width = max_lw passes_between['width'] = passes_between.pass_count / passes_between.pass_count.max() * max_line_width passes_between = passes_between.reset_index(drop=True) # setting color to make the lines more transparent when fewer passes are made min_transparency = 0.3 color = np.array(to_rgba('white')) color = np.tile(color, (len(passes_between), 1)) c_transparency = passes_between.EPV / passes_between.EPV.max() c_transparency = (c_transparency * (1 - min_transparency)) + min_transparency color[:, 3] = c_transparency passes_between['alpha'] = color.tolist() # Plotting name on markers if marker_label == 'name': average_locs_and_count.index = average_locs_and_count.index.map(team_playerskitno_dict) # plotting pitch = Pitch(pitch_type='statsbomb', orientation='horizontal', pitch_color=pitch_color, line_color='#c7d5cc', figsize=(16, 11), constrained_layout=True, tight_layout=False) fig, ax = pitch.draw() average_locs_and_count['zorder'] = list(np.linspace(2,6,11, dtype='int')) pitch.lines(passes_between.x/100*120, 80-passes_between.y/100*80, passes_between.x_end/100*120, 80-passes_between.y_end/100*80, lw=passes_between.width, color=color, zorder=1, ax=ax) for index, row in average_locs_and_count.iterrows(): pitch.scatter(row.x/100*120, 80-row.y/100*80, s=marker_size, color=marker_color, edgecolors='black', linewidth=1, alpha=1, zorder=row.zorder, ax=ax) for index, row in average_locs_and_count.iterrows(): pitch.annotate(row.name, xy=(row.x/100*120, 80-row.y/100*80), family='DejaVu Sans', c='white', va='center', ha='center', zorder=row.zorder, size=marker_label_size, weight='bold', ax=ax) ax.set_title("{} Progressive Pass Network vs {}".format(team, opponent), size=15, y=0.97, color='#c7d5cc') fig.set_facecolor(pitch_color)
def getTeamTotalPasses(events_df, teamId, team, opponent, pitch_color): """ Parameters ---------- events_df : DataFrame of all events. teamId : ID of the team, the passes of which are required. team : Name of the team, the passes of which are required. opponent : Name of opponent team. pitch_color : color of the pitch. Returns ------- Pitch Plot. """ # Get Total Passes passes_df = events_df.loc[events_df['type'] == 'Pass'].reset_index( drop=True) # Get Team Passes team_passes = passes_df.loc[passes_df['teamId'] == teamId] successful_passes = team_passes.loc[team_passes['outcomeType'] == 'Successful'].reset_index(drop=True) unsuccessful_passes = team_passes.loc[ team_passes['outcomeType'] == 'Unsuccessful'].reset_index(drop=True) # Setup the pitch pitch = Pitch(pitch_type='statsbomb', pitch_color=pitch_color, line_color='#c7d5cc', figsize=(16, 11)) fig, ax = pitch.draw(constrained_layout=True, tight_layout=False) # Plot the completed passes pitch.arrows(successful_passes.x / 100 * 120, 80 - successful_passes.y / 100 * 80, successful_passes.endX / 100 * 120, 80 - successful_passes.endY / 100 * 80, width=1, headwidth=10, headlength=10, color='#ad993c', ax=ax, label='Completed') # Plot the other passes pitch.arrows(unsuccessful_passes.x / 100 * 120, 80 - unsuccessful_passes.y / 100 * 80, unsuccessful_passes.endX / 100 * 120, 80 - unsuccessful_passes.endY / 100 * 80, width=1, headwidth=6, headlength=5, headaxislength=12, color='#ba4f45', ax=ax, label='Blocked') # setup the legend ax.legend(facecolor=pitch_color, handlelength=5, edgecolor='None', fontsize=8, loc='upper left', shadow=True) # Set the title fig.suptitle(f'{team} Passes vs {opponent}', y=1, fontsize=15) # Set the subtitle ax.set_title('Data : Whoscored/Opta', fontsize=8, loc='right', fontstyle='italic', fontweight='bold') # Set the figure facecolor fig.set_facecolor(pitch_color)
def createPassNetworks(match_data, matches_df, events_df, team, pitch_color, max_lw, marker_size, marker_color, marker_label='kit_no', marker_label_size=20): """ Parameters ---------- match_data : Data containing everything about the match matches_df : DataFrame containing match data. events_df : DataFrame containing event data for that match. team : Name of the required team. pitch_color : color of the pitch and figure. max_lw : maximum line width of network lines. marker_size : size of the circle markers. marker_color : color of the circle markers. Returns ------- Pitch Plot. """ matchId = match_data['matchId'] # getting team id and venue if match_data['home']['name'] == team: teamId = match_data['home']['teamId'] venue = 'home' else: teamId = match_data['away']['teamId'] venue = 'away' # getting opponent if venue == 'home': opponent = match_data['away']['name'] else: opponent = match_data['home']['name'] team_players_dict = {} for player in matches_df[venue][matchId]['players']: team_players_dict[player['playerId']] = player['name'] team_playerskitno_dict = {} for player in matches_df[venue][matchId]['players']: team_playerskitno_dict[player['shirtNo']] = player['name'] passes_df = events_df.loc[[row['displayName'] == 'Pass' for row in list(events_df['type'])]].reset_index(drop=True) passes_df = passes_df.loc[passes_df['teamId'] == teamId].reset_index().drop('index', axis=1) passes_df = passes_df.loc[[row['displayName'] == 'Successful' for row in list(passes_df['outcomeType'])]].reset_index(drop=True) passes_df.insert(27, column='playerName', value=[team_players_dict[i] for i in list(passes_df['playerId'])]) passes_df.insert(28, column='passRecipientId', value=passes_df['playerId'].shift(-1)) passes_df.insert(29, column='passRecipientName', value=passes_df['playerName'].shift(-1)) passes_df.dropna(subset=["passRecipientName"], inplace=True) match_players_df = pd.DataFrame() player_names = [] player_ids = [] player_pos = [] player_kit_number = [] for player in matches_df[venue][matchId]['players']: player_names.append(player['name']) player_ids.append(player['playerId']) player_pos.append(player['position']) player_kit_number.append(player['shirtNo']) match_players_df['playerId'] = player_ids match_players_df['playerName'] = player_names match_players_df['playerPos'] = player_pos match_players_df['playerKitNumber'] = player_kit_number passes_df = passes_df.merge(match_players_df, on=['playerId', 'playerName'], how='left', validate='m:1') passes_df = passes_df.merge(match_players_df.rename({'playerId': 'passRecipientId', 'playerName':'passRecipientName'}, axis='columns'), on=['passRecipientId', 'passRecipientName'], how='left', validate='m:1', suffixes=['', 'Receipt']) passes_df = passes_df[passes_df['playerPos'] != 'Sub'] passes_formation = passes_df[['id', 'playerKitNumber', 'playerKitNumberReceipt']].copy() location_formation = passes_df[['playerKitNumber', 'x', 'y']] average_locs_and_count = location_formation.groupby('playerKitNumber').agg({'x': ['mean'], 'y': ['mean', 'count']}) average_locs_and_count.columns = ['x', 'y', 'count'] passes_formation['kitNo_max'] = passes_formation[['playerKitNumber', 'playerKitNumberReceipt']].max(axis='columns') passes_formation['kitNo_min'] = passes_formation[['playerKitNumber', 'playerKitNumberReceipt']].min(axis='columns') passes_between = passes_formation.groupby(['kitNo_max', 'kitNo_min']).id.count().reset_index() passes_between.rename({'id': 'pass_count'}, axis='columns', inplace=True) # add on the location of each player so we have the start and end positions of the lines passes_between = passes_between.merge(average_locs_and_count, left_on='kitNo_min', right_index=True) passes_between = passes_between.merge(average_locs_and_count, left_on='kitNo_max', right_index=True, suffixes=['', '_end']) ############################################################################## # Calculate the line width and marker sizes relative to the largest counts max_line_width = max_lw passes_between['width'] = passes_between.pass_count / passes_between.pass_count.max() * max_line_width #average_locs_and_count['marker_size'] = (average_locs_and_count['count'] # / average_locs_and_count['count'].max() * max_marker_size) ############################################################################## # Set color to make the lines more transparent when fewer passes are made min_transparency = 0.3 color = np.array(to_rgba('white')) color = np.tile(color, (len(passes_between), 1)) c_transparency = passes_between.pass_count / passes_between.pass_count.max() c_transparency = (c_transparency * (1 - min_transparency)) + min_transparency color[:, 3] = c_transparency # Plotting name on markers if marker_label == 'name': average_locs_and_count.index = average_locs_and_count.index.map(team_playerskitno_dict) ############################################################################## # Plotting pitch = Pitch(pitch_type='statsbomb', orientation='horizontal', pitch_color=pitch_color, line_color='#c7d5cc', figsize=(16, 11), constrained_layout=True, tight_layout=False) fig, ax = pitch.draw() pitch.lines(passes_between.x/100*120, 80-passes_between.y/100*80, passes_between.x_end/100*120, 80-passes_between.y_end/100*80, lw=passes_between.width, color=color, zorder=1, ax=ax) pitch.scatter(average_locs_and_count.x/100*120, 80-average_locs_and_count.y/100*80, s=marker_size, color=marker_color, edgecolors='black', linewidth=1, alpha=1, ax=ax) for index, row in average_locs_and_count.iterrows(): pitch.annotate(row.name, xy=(row.x/100*120, 80-row.y/100*80), c='white', va='center', ha='center', size=marker_label_size, weight='bold', ax=ax) ax.set_title("{} Pass Network vs {}".format(team, opponent), size=15, y=0.97, color='#c7d5cc') fig.set_facecolor(pitch_color)
def createPVFormationMap(match_data, events_df, team, color_palette, markerstyle, markersize, markeredgewidth, labelsize, labelcolor, ax): # getting team id and venue if match_data['home']['name'] == team: teamId = match_data['home']['teamId'] venue = 'home' else: teamId = match_data['away']['teamId'] venue = 'away' # getting opponent if venue == 'home': opponent = match_data['away']['name'] else: opponent = match_data['home']['name'] # getting player dictionary team_players_dict = {} for player in match_data[venue]['players']: team_players_dict[player['playerId']] = player['name'] # getting minute of first substitution for i, row in events_df.iterrows(): if row['type'] == 'SubstitutionOn' and row['teamId'] == teamId: sub_minute = str(row['minute']) break # getting players dataframe match_players_df = pd.DataFrame() player_names = [] player_ids = [] player_pos = [] player_kit_number = [] for player in match_data[venue]['players']: player_names.append(player['name']) player_ids.append(player['playerId']) player_pos.append(player['position']) player_kit_number.append(player['shirtNo']) match_players_df['playerId'] = player_ids match_players_df['playerName'] = player_names match_players_df['playerPos'] = player_pos match_players_df['playerKitNumber'] = player_kit_number # extracting passes passes_df = events_df.loc[events_df['teamId'] == teamId].reset_index().drop('index', axis=1) passes_df['playerId'] = passes_df['playerId'].astype('float').astype( 'Int64') if 'playerName' in passes_df.columns: passes_df = passes_df.drop(columns='playerName') passes_df.dropna(subset=["playerId"], inplace=True) passes_df.insert( 27, column='playerName', value=[team_players_dict[i] for i in list(passes_df['playerId'])]) if 'passRecipientId' in passes_df.columns: passes_df = passes_df.drop(columns='passRecipientId') passes_df = passes_df.drop(columns='passRecipientName') passes_df.insert(28, column='passRecipientId', value=passes_df['playerId'].shift(-1)) passes_df.insert(29, column='passRecipientName', value=passes_df['playerName'].shift(-1)) passes_df.dropna(subset=["passRecipientName"], inplace=True) passes_df = passes_df.loc[events_df['type'] == 'Pass', :].reset_index( drop=True) passes_df = passes_df.loc[events_df['outcomeType'] == 'Successful', :].reset_index(drop=True) index_names = passes_df.loc[passes_df['playerName'] == passes_df['passRecipientName']].index passes_df.drop(index_names, inplace=True) passes_df = passes_df.merge(match_players_df, on=['playerId', 'playerName'], how='left', validate='m:1') passes_df = passes_df.merge(match_players_df.rename( { 'playerId': 'passRecipientId', 'playerName': 'passRecipientName' }, axis='columns'), on=['passRecipientId', 'passRecipientName'], how='left', validate='m:1', suffixes=['', 'Receipt']) # passes_df = passes_df[passes_df['playerPos'] != 'Sub'] # Getting net possesion value for passes netPVPassed = passes_df.groupby(['playerId', 'playerName'])['EPV'].sum().reset_index() netPVReceived = passes_df.groupby(['passRecipientId', 'passRecipientName' ])['EPV'].sum().reset_index() # Getting formation and player ids for first 11 formation = match_data[venue]['formations'][0]['formationName'] formation_positions = match_data[venue]['formations'][0][ 'formationPositions'] playerIds = match_data[venue]['formations'][0]['playerIds'][:11] # Getting all data in a dataframe formation_data = [] for playerId, pos in zip(playerIds, formation_positions): pl_dict = {'playerId': playerId} pl_dict.update(pos) formation_data.append(pl_dict) formation_data = pd.DataFrame(formation_data) formation_data['vertical'] = normalize( formation_data['vertical'], { 'actual': { 'lower': 0, 'upper': 10 }, 'desired': { 'lower': 10, 'upper': 110 } }) formation_data['horizontal'] = normalize(formation_data['horizontal'], { 'actual': { 'lower': 0, 'upper': 10 }, 'desired': { 'lower': 80, 'upper': 0 } }) formation_data = netPVPassed.join(formation_data.set_index('playerId'), on='playerId', how='inner').reset_index(drop=True) formation_data = formation_data.rename(columns={"EPV": "PV"}) # Plotting pitch = Pitch(pitch_type='statsbomb', pitch_color='#171717', line_color='#5c5c5c', goal_type='box') pitch.draw(ax=ax, constrained_layout=True, tight_layout=True) sns.scatterplot(x='vertical', y='horizontal', data=formation_data, hue='PV', s=markersize, marker=markerstyle, legend=False, palette=color_palette, linewidth=markeredgewidth, ax=ax) ax.text(2, 78, '{}'.format('-'.join(formation)), size=20, c='grey') for index, row in formation_data.iterrows(): pitch.annotate(str(round(row.PV * 100, 2)) + '%', xy=(row.vertical, row.horizontal), c=labelcolor, va='center', ha='center', size=labelsize, zorder=2, weight='bold', ax=ax) pitch.annotate(row.playerName, xy=(row.vertical, row.horizontal + 5), c=labelcolor, va='center', ha='center', size=labelsize, zorder=2, weight='bold', ax=ax)
axes = axes.ravel() pitch_kwargs = {'line_color': '#94A7AE', 'axis': True, 'label': True, 'pad_left': 0, 'pad_right': 0, 'pad_top': 0, 'pad_bottom': 0, 'linewidth': 1} pitch_types = ['statsbomb', 'statsperform', 'tracab', 'wyscout', 'metricasports', 'opta', 'stats'] fontcolor = '#b6b9ea' arrowprops = {'arrowstyle': '->', 'lw': 4, 'connectionstyle': 'angle3,angleA=0,angleB=-90', 'color': fontcolor} font_kwargs = {'fontsize': 14, 'ha': 'center', 'va': 'bottom', 'fontweight': 'bold', 'fontstyle': 'italic', 'c': fontcolor} for idx, pt in enumerate(pitch_types): if pt in ['tracab', 'metricasports']: pitch = Pitch(pitch_type=pt, pitch_length=105, pitch_width=68, **pitch_kwargs) else: pitch = Pitch(pitch_type=pt, **pitch_kwargs) pitch.draw(axes[idx]) xmin, xmax, ymin, ymax = pitch.extent if pitch.aspect != 1: text = 'data coordinates \n are square (1:1) \n scale up to a real-pitch size' axes[idx].annotate(text, xy=(xmin, ymin), xytext=(0 + (xmax - xmin)/2, ymin), **font_kwargs) axes[idx].xaxis.set_ticks([xmin, xmax]) axes[idx].yaxis.set_ticks([ymin, ymax]) axes[idx].tick_params(labelsize=15) axes[idx].set_title(pt, fontsize=30, c='#9749b9', pad=15) if pitch.invert_y: text = 'inverted y axis' if pt == 'stats': xytext = (0 + (xmax - xmin)/2, 50) else: xytext = (0 + (xmax - xmin)/2, ymin + (ymax - ymin)/2)
def createPassNetworks(match_data, events_df, matchId, team, max_line_width, marker_size, edgewidth, dh_arrow_width, marker_color, marker_edge_color, shrink, ax, kit_no_size=20): # getting team id and venue if match_data['home']['name'] == team: teamId = match_data['home']['teamId'] venue = 'home' else: teamId = match_data['away']['teamId'] venue = 'away' # getting opponent if venue == 'home': opponent = match_data['away']['name'] else: opponent = match_data['home']['name'] # getting player dictionary team_players_dict = {} for player in match_data[venue]['players']: team_players_dict[player['playerId']] = player['name'] # getting minute of first substitution for i in events_df.index: if events_df.loc[i, 'type'] == 'SubstitutionOn' and events_df.loc[ i, 'teamId'] == teamId: sub_minute = str(events_df.loc[i, 'minute']) break # getting players dataframe match_players_df = pd.DataFrame() player_names = [] player_ids = [] player_pos = [] player_kit_number = [] for player in match_data[venue]['players']: player_names.append(player['name']) player_ids.append(player['playerId']) player_pos.append(player['position']) player_kit_number.append(player['shirtNo']) match_players_df['playerId'] = player_ids match_players_df['playerName'] = player_names match_players_df['playerPos'] = player_pos match_players_df['playerKitNumber'] = player_kit_number # extracting passes passes_df = events_df.loc[events_df['teamId'] == teamId].reset_index().drop('index', axis=1) passes_df['playerId'] = passes_df['playerId'].astype('float').astype( 'Int64') if 'playerName' in passes_df.columns: passes_df = passes_df.drop(columns='playerName') passes_df.dropna(subset=["playerId"], inplace=True) passes_df.insert( 27, column='playerName', value=[team_players_dict[i] for i in list(passes_df['playerId'])]) if 'passRecipientId' in passes_df.columns: passes_df = passes_df.drop(columns='passRecipientId') passes_df = passes_df.drop(columns='passRecipientName') passes_df.insert(28, column='passRecipientId', value=passes_df['playerId'].shift(-1)) passes_df.insert(29, column='passRecipientName', value=passes_df['playerName'].shift(-1)) passes_df.dropna(subset=["passRecipientName"], inplace=True) passes_df = passes_df.loc[events_df['type'] == 'Pass', :].reset_index( drop=True) passes_df = passes_df.loc[events_df['outcomeType'] == 'Successful', :].reset_index(drop=True) index_names = passes_df.loc[passes_df['playerName'] == passes_df['passRecipientName']].index passes_df.drop(index_names, inplace=True) passes_df = passes_df.merge(match_players_df, on=['playerId', 'playerName'], how='left', validate='m:1') passes_df = passes_df.merge(match_players_df.rename( { 'playerId': 'passRecipientId', 'playerName': 'passRecipientName' }, axis='columns'), on=['passRecipientId', 'passRecipientName'], how='left', validate='m:1', suffixes=['', 'Receipt']) passes_df = passes_df[passes_df['playerPos'] != 'Sub'] # getting team formation formation = match_data[venue]['formations'][0]['formationName'] formation = '-'.join(formation) # getting player average locations location_formation = passes_df[['playerKitNumber', 'x', 'y']] average_locs_and_count = location_formation.groupby('playerKitNumber').agg( { 'x': ['mean'], 'y': ['mean', 'count'] }) average_locs_and_count.columns = ['x', 'y', 'count'] # getting separate dataframe for selected columns passes_formation = passes_df[[ 'id', 'playerKitNumber', 'playerKitNumberReceipt' ]].copy() passes_formation['EPV'] = passes_df['EPV'] # getting dataframe for passes between players passes_between = passes_formation.groupby( ['playerKitNumber', 'playerKitNumberReceipt']).agg({ 'id': 'count', 'EPV': 'sum' }).reset_index() passes_between.rename({'id': 'pass_count'}, axis='columns', inplace=True) passes_between = passes_between.merge(average_locs_and_count, left_on='playerKitNumberReceipt', right_index=True) passes_between = passes_between.merge(average_locs_and_count, left_on='playerKitNumber', right_index=True, suffixes=['', '_end']) # filtering passes pass_filter = int(passes_between['pass_count'].mean()) passes_between = passes_between.loc[ passes_between['pass_count'] > pass_filter] # calculating the line width passes_between[ 'width'] = passes_between.pass_count / passes_between.pass_count.max( ) * max_line_width passes_between = passes_between.reset_index(drop=True) # setting color to make the lines more transparent when fewer passes are made min_transparency = 0.3 color = np.array(to_rgba('white')) color = np.tile(color, (len(passes_between), 1)) c_transparency = passes_between.pass_count / passes_between.pass_count.max( ) c_transparency = (c_transparency * (1 - min_transparency)) + min_transparency color[:, 3] = c_transparency passes_between['alpha'] = color.tolist() # separating paired passes from normal passes passes_between_threshold = 15 filtered_pair_df = [] pair_list = [ comb for comb in combinations(passes_between['playerKitNumber'].unique(), 2) ] for pair in pair_list: df = passes_between[( (passes_between['playerKitNumber'] == pair[0]) & (passes_between['playerKitNumberReceipt'] == pair[1])) | ( (passes_between['playerKitNumber'] == pair[1]) & (passes_between['playerKitNumberReceipt'] == pair[0]))] if df.shape[0] == 2: if (np.array(df.pass_count)[0] >= passes_between_threshold) and ( np.array(df.pass_count)[1] >= passes_between_threshold): filtered_pair_df.append(df) passes_between.drop(df.index, inplace=True) if len(filtered_pair_df) > 0: filtered_pair_df = pd.concat(filtered_pair_df).reset_index(drop=True) passes_between = passes_between.reset_index(drop=True) # plotting pitch = Pitch(pitch_type='opta', pitch_color='#171717', line_color='#5c5c5c', goal_type='box') pitch.draw(ax=ax, constrained_layout=True, tight_layout=True) average_locs_and_count['zorder'] = list(np.linspace(1, 5, 11)) for i in average_locs_and_count.index: pitch.scatter(average_locs_and_count.loc[i, 'x'], average_locs_and_count.loc[i, 'y'], s=marker_size, color=marker_color, edgecolors=marker_edge_color, linewidth=edgewidth, alpha=1, zorder=average_locs_and_count.loc[i, 'zorder'], ax=ax) for i in passes_between.index: x = passes_between.loc[i, 'x'] y = passes_between.loc[i, 'y'] endX = passes_between.loc[i, 'x_end'] endY = passes_between.loc[i, 'y_end'] coordsA = "data" coordsB = "data" con = ConnectionPatch([endX, endY], [x, y], coordsA, coordsB, arrowstyle="simple", shrinkA=shrink, shrinkB=shrink, mutation_scale=passes_between.loc[i, 'width'] * max_line_width, color=passes_between.loc[i, 'alpha']) ax.add_artist(con) if len(filtered_pair_df) > 0: for i in filtered_pair_df.index: x = filtered_pair_df.loc[i, 'x'] y = filtered_pair_df.loc[i, 'y'] endX = filtered_pair_df.loc[i, 'x_end'] endY = filtered_pair_df.loc[i, 'y_end'] coordsA = "data" coordsB = "data" con = ConnectionPatch([endX, endY], [x, y], coordsA, coordsB, arrowstyle="<|-|>", shrinkA=shrink, shrinkB=shrink, mutation_scale=dh_arrow_width, lw=filtered_pair_df.loc[i, 'width'] * max_line_width / 5, color=filtered_pair_df.loc[i, 'alpha']) ax.add_artist(con) for i in average_locs_and_count.index: pitch.annotate(i, xy=(average_locs_and_count.loc[i, 'x'], average_locs_and_count.loc[i, 'y']), family='DejaVu Sans', c='white', va='center', ha='center', zorder=average_locs_and_count.loc[i, 'zorder'], size=kit_no_size, weight='bold', ax=ax) ax.text(50, 104, "{} (Mins 1-{})".format(team, sub_minute).upper(), size=10, fontweight='bold', ha='center', va='center') ax.text(2, 3, '{}'.format(formation), size=9, c='grey')
passes_between['width'] = passes_between.pass_count / passes_between.pass_count.max() * max_line_width average_locs_and_count['marker_size'] = (average_locs_and_count['count'] / average_locs_and_count['count'].max() * max_marker_size) ############################################################################## # Set color to make the lines more transparent when fewer passes are made min_transparency = 0.3 color = np.array(to_rgba('white')) color = np.tile(color, (len(passes_between), 1)) c_transparency = passes_between.pass_count / passes_between.pass_count.max() c_transparency = (c_transparency * (1 - min_transparency)) + min_transparency color[:, 3] = c_transparency ############################################################################## # Plotting pitch = Pitch(pitch_type='statsbomb', orientation='horizontal', pitch_color='#22312b', line_color='#c7d5cc', figsize=(16, 11), constrained_layout=True, tight_layout=False) fig, ax = pitch.draw() pass_lines = pitch.lines(passes_between.x, passes_between.y, passes_between.x_end, passes_between.y_end, lw=passes_between.width, color=color, zorder=1, ax=ax) pass_nodes = pitch.scatter(average_locs_and_count.x, average_locs_and_count.y, s=average_locs_and_count.marker_size, color='red', edgecolors='black', linewidth=1, alpha=1, ax=ax) for index, row in average_locs_and_count.iterrows(): pitch.annotate(row.name, xy=(row.x, row.y), c='white', va='center', ha='center', size=16, weight='bold', ax=ax) title = ax.set_title("{} {} Formation vs {}".format(team, formation, opponent), size=28, y=0.97, color='#c7d5cc') fig.set_facecolor("#22312b")
def passmap(Df,teamname,teamid,min1,max1): pitch = Pitch(pitch_type='statsbomb', figsize=(20,13), line_zorder=2, line_color='#c7d5cc', orientation='horizontal',constrained_layout=True, tight_layout=False) fig, ax = pitch.draw() df = Df.copy() df = df[(df.expandedMinute>=min1)&(df.expandedMinute<=max1)] allplayers = df[(df.teamId==teamid)&(df.name!='')].name.tolist() playersubbedoff = df[(df.type_displayName == 'SubstitutionOff')&(df.teamId==teamid)]['name'].tolist() timeoff = df[(df.type_displayName == 'SubstitutionOff')&(df.teamId==teamid)]['expandedMinute'].tolist() playersubbedon = df[(df.type_displayName == 'SubstitutionOn')&(df.teamId==teamid)]['name'].tolist() timeon = df[(df.type_displayName == 'SubstitutionOn')&(df.teamId==teamid)]['expandedMinute'].tolist() majoritylist = [] minoritylist = [] for i in range(len(timeon)): if((timeon[i]>=min1)&(timeon[i]<=max1)): player1min = timeon[i] - min1 player2min = max1 - timeon[i] if(player1min >= player2min): majoritylist.append(playersubbedoff[i]) minoritylist.append(playersubbedon[i]) else: majoritylist.append(playersubbedon[i]) minoritylist.append(playersubbedoff[i]) players = list(set(allplayers) - set(minoritylist)) # return players shirtNo = [] for p in players: shirtNo.append(int(df[df.name==p]['shirtNo'].values[0])) passdf = df.query("(type_displayName=='Pass')&(name in @players)&(receiver in @players)&\ (outcomeType_displayName == 'Successful')&(teamId==@teamid)") passdf['x'] = passdf['x'] passdf['y'] = 80 - passdf['y'] passdf['endX'] = passdf['endX'] passdf['endY'] = 80 - passdf['endY'] cols = ['name','receiver'] gamedf = passdf[cols] totalpassdf = gamedf.groupby(cols).size().reset_index(name="count") totalpassdf[cols] = np.sort(totalpassdf[cols],axis=1) totalpassdf = totalpassdf.groupby(cols)['count'].agg(['sum']).reset_index() avg_x = [] avg_y = [] blank=np.zeros(len(totalpassdf)) totalpassdf['passer_x'] = blank totalpassdf['passer_y'] = blank totalpassdf['receiver_x'] = blank totalpassdf['receiver_y'] = blank uniquenames = np.array(players) uniquejerseys = np.array(shirtNo) totalpasses = [] for name in uniquenames: player_pass_df = passdf.query("(name == @name)") x = np.mean(player_pass_df['x']) y = np.mean(player_pass_df['y']) totalpasses.append(len(player_pass_df)) avg_x.append(x) avg_y.append(y) for i in range(len(totalpassdf)): passername = totalpassdf.iloc[i,0] receivername = totalpassdf.iloc[i,1] totalpassdf.iloc[i,3] = avg_x[np.where(uniquenames==passername)[0][0]] totalpassdf.iloc[i,4] = avg_y[np.where(uniquenames==passername)[0][0]] totalpassdf.iloc[i,5] = avg_x[np.where(uniquenames==receivername)[0][0]] totalpassdf.iloc[i,6] = avg_y[np.where(uniquenames==receivername)[0][0]] link = totalpassdf.iloc[i,2] passerx = totalpassdf.iloc[i,3] receiverx = totalpassdf.iloc[i,5] passery = totalpassdf.iloc[i,4] receivery = totalpassdf.iloc[i,6] if(link>=1): ax.plot([passerx, receiverx],[passery, receivery],linewidth=link/2.5, color ='lightgrey') for indx in range(len(uniquenames)): name = uniquenames[indx] size = 50*totalpasses[np.where(uniquenames==name)[0][0]] avgx = avg_x[np.where(uniquenames==name)[0][0]] avgy = avg_y[np.where(uniquenames==name)[0][0]] jersey = shirtNo[np.where(uniquenames==name)[0][0]] ax.scatter(avgx,avgy,color='#d7191c',s=1000,zorder=3) ax.annotate(jersey, (avgx, avgy),alpha=1,fontsize=25,color='w', horizontalalignment='center', verticalalignment='center').set_path_effects([path_effects.Stroke(linewidth=2, foreground='black'), path_effects.Normal()]) ax.text(125,3,'# Player (Acc passes)',fontsize=25) for i in range(12): ax.text(127,3+4*i,'|',fontsize=50) for i in range(30): ax.text(125+i,3.5,'_',fontsize=50) # ax.plot([126,3],[126,50]) for indx in range(len(players)): ax.text(125,7+4*indx,str(shirtNo[indx]),fontsize=25) for indx in range(len(players)): ax.text(130,7+4*indx,players[indx]+'('+str(totalpasses[indx])+')',fontsize=25) ax.set_title(teamname+' Passmap, Exp. Minutes '+str(min1)+ " to "+str(max1),fontsize=50) fig.text(0.02, 0.05, "Created by Soumyajit Bose / @Soumyaj15209314",fontstyle="italic",fontsize=20,color='#edece9') fig.text(0.02, -0.05, "Acc passes mentioned in brackets next to player names "+"\n" "Width of line is proportional to number of passes exchanged between two players"+"\n"+ "A minimum of 1 pass need to be exchanged to show up as a line", fontstyle="italic",fontsize=25,color='#edece9') # add_image(bu_alt, fig, left=0.9, bottom=-0.05, width=0.07) return fig
df = pd.read_csv('C:/Users/dev/Desktop/messibetis.csv') df['x'] = df['x'] * 1.2 df['y'] = df['y'] * 0.8 df['endX'] = df['endX'] * 1.2 df['endY'] = df['endY'] * 0.8 fig, ax = plt.subplots(figsize=(13, 8.5)) fig.set_facecolor('#22312b') ax.patch.set_facecolor('#22312b') pitch = Pitch(pitch_type='statsbomb', pitch_color='#22312b', line_color='#c7d5cc', figsize=(16, 11), constrained_layout=True, tight_layout=False) pitch.draw(ax=ax) plt.gca().invert_yaxis() kde = sns.kdeplot(df['x'], df['y'], shade=True, shade_lowest=False, alpha=0.5, n_levels=11, cmap='magma') for x in range(len(df['x'])): if df['outcome'][x] == 'Successful': plt.plot((df['x'][x], df['endX'][x]), (df['y'][x], df['endY'][x]), color='green') plt.scatter(df['x'][x], df['y'][x], color='Black') plt.xlim(0, 120) plt.ylim(0, 80)
""" from urllib.request import urlopen from PIL import Image import matplotlib.pyplot as plt from mplsoccer.pitch import Pitch from mplsoccer.utils import add_image # opening the background image # pic by webtreats: https://www.flickr.com/photos/webtreatsetc/ # available at: https://www.flickr.com/photos/webtreatsetc/5756834840 IMAGE_URL = 'https://live.staticflickr.com/5065/5756834840_e31c559b26_c_d.jpg' image = Image.open(urlopen(IMAGE_URL)) pitch = Pitch(pitch_color='None') fig, ax = pitch.draw(tight_layout=False) # adding the image and the image credits ax_image = add_image(image, fig, left=0, bottom=0, width=1, height=1) ax.text(70, 75, 'cloud pic by\nwebtreats (flickr)', color='white') # set the pitch to be plotted after the image # note these numbers can be anything like 0.5, 0.2 as long # as the image zorder is behind the pitch zorder ax.set_zorder(1) ax_image.set_zorder(0) # save with 'tight' and no padding to avoid borders # fig.savefig('cloud.png', bbox_inches='tight', pad_inches=0) plt.show() # If you are using a Jupyter notebook you do not need this line
tight_layout=False, constrained_layout=True) # mplsoccer calculates the binned statistics usually from raw locations, such as pressure events # for this example we will create a binned statistic dividing the pitch into thirds for one point (0, 0) # we will fill this in a loop later with each team's statistics from the dataframe bin_statistic = pitch.bin_statistic([0], [0], statistic='count', bins=(3, 1)) # load the StatsBomb logo sb_logo = Image.open( urlopen(( 'https://github.com/statsbomb/open-data/blob/fb54bd7fe20dbd5299fafc64f1f6f0d919a5e40d/' 'stats-bomb-logo.png?raw=true'))) # Plot fig, axes = pitch.draw() axes = axes.ravel() teams = df['Squad'].values vmin = df[pressure_cols].min().min( ) # we normalise the heatmaps with the min / max values vmax = df[pressure_cols].max().max() for i, ax in enumerate(axes): ax.set_title(teams[i], fontsize=20) # fill in the bin statistics from df bin_statistic['statistic'] = df.loc[df.Squad == teams[i], pressure_cols].values heatmap = pitch.heatmap(bin_statistic, ax=ax, cmap='coolwarm', vmin=vmin, vmax=vmax) # plot the heatmap
def passmap(Df, teamid, teamname, min1, max1): pitch = Pitch(pitch_type='statsbomb', orientation='vertical', pitch_color='#000000', line_color='#a9a9a9', constrained_layout=True, tight_layout=False, linewidth=0.5) fig, ax = pitch.draw() df = Df.copy() df = df[(df.expandedMinute >= min1) & (df.expandedMinute <= max1)] allplayers = df[(df.teamId == teamid) & (df.name.notna())].name.tolist() playersubbedoff = df[(df.type_displayName == 'SubstitutionOff') & (df.teamId == teamid)]['name'].tolist() timeoff = df[(df.type_displayName == 'SubstitutionOff') & (df.teamId == teamid)]['expandedMinute'].tolist() playersubbedon = df[(df.type_displayName == 'SubstitutionOn') & (df.teamId == teamid)]['name'].tolist() timeon = df[(df.type_displayName == 'SubstitutionOn') & (df.teamId == teamid)]['expandedMinute'].tolist() majoritylist = [] minoritylist = [] for i in range(len(timeon)): if ((timeon[i] >= min1) & (timeon[i] <= max1)): player1min = timeon[i] - min1 player2min = max1 - timeon[i] if (player1min >= player2min): majoritylist.append(playersubbedoff[i]) minoritylist.append(playersubbedon[i]) else: majoritylist.append(playersubbedon[i]) minoritylist.append(playersubbedoff[i]) players = list(set(allplayers) - set(minoritylist)) #return players shirtNo = [] for p in players: shirtNo.append(int(df[df.name == p]['shirtNo'].values[0])) passes_df = df.query( "(type_displayName=='Pass')&(name in @players)&(receiver in @players)&\ (outcomeType_displayName == 'Successful')&(teamId==@teamid)") #passes_df.insert(29, column='passRecipientName', value=passes_df['name'].shift(-1)) passes_df.dropna(subset=["receiver"], inplace=True) #passes_df['passer'] = passes_df['playerId'] #passes_df['recipient'] = passes_df['passer'].shift(-1) passes_df = passes_df[passes_df.columns[~passes_df.isnull().all()]] passes_df['playerKitNumber'] = passes_df['shirtNo'].fillna(0).astype( np.int) passes_df['playerKitNumberReceipt'] = passes_df['shirtNo'].shift( -1).fillna(0).astype(np.int) passer_avg = passes_df.groupby('playerKitNumber').agg({ 'x': ['median'], 'y': ['median', 'count'], 'epv_value': ['sum'] }) passer_avg.columns = ['x', 'y', 'count', 'epv'] passer_avg.index = passer_avg.index.astype(int) passes_formation = passes_df[[ 'id', 'playerKitNumber', 'playerKitNumberReceipt' ]].copy() passes_formation['kitNo_max'] = passes_formation[[ 'playerKitNumber', 'playerKitNumberReceipt' ]].max(axis='columns') passes_formation['kitNo_min'] = passes_formation[[ 'playerKitNumber', 'playerKitNumberReceipt' ]].min(axis='columns') passes_between = passes_formation.groupby(['kitNo_max', 'kitNo_min' ]).id.count().reset_index() passes_between.rename({'id': 'pass_count'}, axis='columns', inplace=True) # add on the location of each player so we have the start and end positions of the lines passes_between = passes_between.merge(passer_avg, left_on='kitNo_min', right_index=True) passes_between = passes_between.merge(passer_avg, left_on='kitNo_max', right_index=True, suffixes=['', '_end']) ''' #Between Passer and Recipient passes_between = passes_df.groupby(['passer', 'recipient']).id.count().reset_index() passes_between.rename({'id': 'pass_count'}, axis='columns', inplace=True) passes_between = passes_between.merge(passer_avg, left_on='passer', right_index=True) passes_between = passes_between.merge(passer_avg, left_on='recipient', right_index=True, suffixes=['', '_end']) ''' #Minimum No. of Passes passes_between = passes_between.loc[(passes_between['pass_count'] >= 3)] #Scaling for StatsBomb passes_between['x'] = passes_between['x'] * 1.2 passes_between['y'] = passes_between['y'] * 0.8 passer_avg['x'] = passer_avg['x'] * 1.2 passer_avg['y'] = passer_avg['y'] * 0.8 passes_between['x_end'] = passes_between['x_end'] * 1.2 passes_between['y_end'] = passes_between['y_end'] * 0.8 #Width Variable yo = passes_between.pass_count / passes_between.pass_count.max() b = 1 min_transparency = 0.3 color = np.array(to_rgba('#00bfff')) color = np.tile(color, (len(passes_between), 1)) c_transparency = passes_between.pass_count / passes_between.pass_count.max( ) c_transparency = (c_transparency * (1 - min_transparency)) + min_transparency color[:, 3] = c_transparency a = plt.scatter(passer_avg.y, passer_avg.x, s=100, c="#111111", facecolor='none', lw=1, cmap="winter", alpha=1, zorder=2, vmin=0, vmax=0.6, marker='h') c = plt.scatter(passer_avg.y, passer_avg.x, s=60, c='#FF0000', alpha=1, zorder=3, marker='h') pitch.arrows(passes_between.x, passes_between.y, passes_between.x_end, passes_between.y_end, color=color, ax=ax, zorder=1, width=1.5) cbar = plt.colorbar(a, orientation="horizontal", shrink=0.3, pad=0, ticks=[0, 0.2, 0.4, 0.6]) cbar.set_label('Expected Possession Value (EPV)', color='#a9a9a9', size=6) cbar.outline.set_edgecolor('#a9a9a9') cbar.ax.xaxis.set_tick_params(color='#a9a9a9') cbar.ax.xaxis.set_tick_params(labelcolor='#a9a9a9') cbar.ax.tick_params(labelsize=5) plt.gca().invert_xaxis() for index, row in passer_avg.iterrows(): pitch.annotate(row.name, xy=(row.x, row.y), c='#a9a9a9', va='center', ha='center', size=5, ax=ax) plt.text( 79, 2, "Positions = Median Location of Successful Passes\nArrows = Pass Direction\nTransparency = Frequency of Combination\nMinimum of 3 Passes ", color='#a9a9a9', fontsize=5, alpha=0.5, zorder=1) plt.text(80, 122, "Minutes 45-90", color='#a9a9a9', fontsize=5) plt.text(18, 122, "@nikhilrajesh231", color='#a9a9a9', fontsize=5) ax.set_title("Barcelona PV Pass Network\n5-2 vs Getafe (H)", fontsize=8, color="#a9a9a9", fontweight='bold', y=1.01) fig = plt.savefig('pn2.png', bbox_inches="tight", facecolor="#000000", dpi=600) return fig