pitchmap

Football Heatmaps with Seaborn

Football heatmaps are used by in-club and media analysts to illustrate the area within which a player has been present. They might illustrate player location, or the events of a player or team and are effectively a smoothed out scatter plot of these points. While there may be some debate as to how much they are useful (they don’t tell you if actions/movement are a good or bad thing!), they can often be very aesthetically pleasing and engaging, hence their popularity. This article will take you through loading your dataset and plotting a heatmap around x & y coordinates in Python.

Let’s get our modules imported and our data ready to go!

In [1]:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Arc
import seaborn as sns

%matplotlib inline

data = pd.read_csv("Data/passes.csv")

data.head()
Out[1]:
Half Time Event Player Team Xstart Ystart Xend Yend
0 First Half 1 Pass Wombech USA 26 38 66 52
1 First Half 6 Pass Wombech USA 81 34 62 68
2 First Half 6 Pass Wombech USA 46 45 84 63
3 First Half 8 Pass Wombech USA 89 66 89 39
4 First Half 9 Pass Wombech USA 68 64 21 25

Plotting a heatmap

Today’s dataset showcases Wombech’s passes from her match. As you can see, we have time, player and location data. We will be looking to plot the starting X/Y coordinates of Wombech’s passes, but you would be able to do the same with any coordinates that you have – whether GPS/optical tracking coordinates, or other event data.

Python’s Seaborn module makes plotting a tidy dataset incredibly easy with ‘.kdeplot()’. Plotting it with simply the x and y coordinate columns as arguments will give you something like this:

In [2]:
fig, ax = plt.subplots()
fig.set_size_inches(7, 5)


sns.kdeplot(data["Xstart"],data["Ystart"])
plt.show()

Cool, we have a contour plot, which groups lines closer to eachother where we have more density in our data.

Let’s customise this with a couple of additional arguments:

  • shade: fills the gaps between the lines to give us more of the heatmap effect that we are looking for.
  • n_levels: draws more lines – adding lots of these will blur the lines into a heatmap

Take a look at the examples below to see the differences these two arguments produce:

In [3]:
fig, ax = plt.subplots()
fig.set_size_inches(14,4)

#Plot one - include shade
plt.subplot(121)
sns.kdeplot(data["Xstart"],data["Ystart"], shade="True")

#Plot two - no shade, lines only
plt.subplot(122)
sns.kdeplot(data["Xstart"],data["Ystart"])

plt.show()
In [4]:
fig, ax = plt.subplots()
fig.set_size_inches(14,4)

#Plot One - distinct areas with few lines
plt.subplot(121)
sns.kdeplot(data["Xstart"],data["Ystart"], shade="True", n_levels=5)

#Plot Two - fade lines with more of them
plt.subplot(122)
sns.kdeplot(data["Xstart"],data["Ystart"], shade="True", n_levels=40)

plt.show()

Now that we can customise our plot as we see fit, we just need to add our pitch map. Learn more about plotting pitches here, but feel free to use this pitch map below – although you may need to change the coordinates to fit your data!

Also take note of our xlim and ylim lines – we use these to set the size of the plot, so that the heatmap does not spill over the pitch.

In [5]:
#Create figure
fig=plt.figure()
fig.set_size_inches(7, 5)
ax=fig.add_subplot(1,1,1)

#Pitch Outline & Centre Line
plt.plot([0,0],[0,90], color="black")
plt.plot([0,130],[90,90], color="black")
plt.plot([130,130],[90,0], color="black")
plt.plot([130,0],[0,0], color="black")
plt.plot([65,65],[0,90], color="black")

#Left Penalty Area
plt.plot([16.5,16.5],[65,25],color="black")
plt.plot([0,16.5],[65,65],color="black")
plt.plot([16.5,0],[25,25],color="black")

#Right Penalty Area
plt.plot([130,113.5],[65,65],color="black")
plt.plot([113.5,113.5],[65,25],color="black")
plt.plot([113.5,130],[25,25],color="black")

#Left 6-yard Box
plt.plot([0,5.5],[54,54],color="black")
plt.plot([5.5,5.5],[54,36],color="black")
plt.plot([5.5,0.5],[36,36],color="black")

#Right 6-yard Box
plt.plot([130,124.5],[54,54],color="black")
plt.plot([124.5,124.5],[54,36],color="black")
plt.plot([124.5,130],[36,36],color="black")

#Prepare Circles
centreCircle = plt.Circle((65,45),9.15,color="black",fill=False)
centreSpot = plt.Circle((65,45),0.8,color="black")
leftPenSpot = plt.Circle((11,45),0.8,color="black")
rightPenSpot = plt.Circle((119,45),0.8,color="black")

#Draw Circles
ax.add_patch(centreCircle)
ax.add_patch(centreSpot)
ax.add_patch(leftPenSpot)
ax.add_patch(rightPenSpot)

#Prepare Arcs
leftArc = Arc((11,45),height=18.3,width=18.3,angle=0,theta1=310,theta2=50,color="black")
rightArc = Arc((119,45),height=18.3,width=18.3,angle=0,theta1=130,theta2=230,color="black")

#Draw Arcs
ax.add_patch(leftArc)
ax.add_patch(rightArc)

#Tidy Axes
plt.axis('off')

sns.kdeplot(data["Xstart"],data["Ystart"], shade=True,n_levels=50)
plt.ylim(0, 90)
plt.xlim(0, 130)


#Display Pitch
plt.show()

Great work, now we can see Wombech’s pass locations as a heatmap!

Summary

Seaborn makes heatmaps a breeze – we simply use the contour plots with ‘kdeplot()’ and blur our lines to give a heatmap effect.

If using these to communicate rather than analyse, always take care. There is nothing telling you if the actions in the plot are good or bad, but we may make these inferences when discussing them. As always, be sure that what you think is being communicated is actually being communicated!

As for next steps, why not take a look at pass maps, or other parts of our visualisation series?

Posted by FCPythonADMIN in Visualisation

Drawing a Pitchmap – Adding Lines & Circles in Matplotlib

There are lots of reasons why we might want to draw a line or circle on our charts. We could look to add an average line, highlight a key data point or even draw a picture. This article will show how to add lines, circles and arcs with the example of a football pitch map that could then be used to show heatmaps, passes or anything else that happens during a match.

This example works with FIFA’s offical pitch sizes, but you might want to change them according to your data/sport/needs. Let’s import matplotlib as normal, in addition to its Arc functionality.

In [1]:
import matplotlib.pyplot as plt
from matplotlib.patches import Arc

Drawing Lines

It is easiest for us to start with our lines around the outside of the pitch. Once we create our plot with the first two lines of our code, drawing a line is pretty easy with ‘.plot’. You have probably already seen ‘.plot’ used to display scatter points, but to draw a line, we just need to provide two lists as arguments and matplotlib will do the thinking for us:

  • List one: starting and ending X locations
  • List two: starting and ending Y locations

Take a look at the code and plot below to understand our outlines. Use the colour guides to see how they are plotted with start and end point lists.

In [2]:
fig=plt.figure()
ax=fig.add_subplot(1,1,1)

plt.plot([0,0],[0,90], color="blue")
plt.plot([0,130],[90,90], color="orange")
plt.plot([130,130],[90,0], color="green")
plt.plot([130,0],[0,0], color="red")
plt.plot([65,65],[0,90], color="pink")

plt.show()

Great job! Matplotlib makes drawing lines very easy, it just takes some clear thinking with start and end locations to get them plotted.

Drawing Circles

Next up, we’re going to draw some circles on the pitch. Primarily, we need a centre circle, but we also need markers for the centre and penalty spots.

Adding circles is slightly different to lines. Firstly, we need to assign our circles to a variable. We use ‘.circle’ to do this, passing it two essential arguments:

  • X/Y coordinates of the middle of the circle
  • Radius of the circle

For our circles, we’ll also assign colour and fill, but these are optional.

With these circles assigned then use ‘.patch’ to draw the circle to our plot.

Take a look at our code below:

In [3]:
#Create figure
fig=plt.figure()
ax=fig.add_subplot(1,1,1)

#Pitch Outline & Centre Line
plt.plot([0,0],[0,90], color="black")
plt.plot([0,130],[90,90], color="black")
plt.plot([130,130],[90,0], color="black")
plt.plot([130,0],[0,0], color="black")
plt.plot([65,65],[0,90], color="black")

#Assign circles to variables - do not fill the centre circle!
centreCircle = plt.Circle((65,45),9.15,color="red",fill=False)
centreSpot = plt.Circle((65,45),0.8,color="blue")

#Draw the circles to our plot
ax.add_patch(centreCircle)
ax.add_patch(centreSpot)


plt.show()

Drawing Arcs

Now that you can create circles, arcs will be just as easy – we’ll need them for the lines outside the penalty area. While they take a few more arguments, they follow the same pattern as before. Let’s go through the arguments:

  • X/Y coordinates of the centrepoint of the arc, assuming the arc was a complete shape.
  • Width – we must pass width and height as the arc might not be a circle, it might instead be from an oval shape
  • Height – as above
  • Angle – degree rotation of the shape (anti-clockwise)
  • Theta1 – start location of the arc, in degrees
  • Theta2 – end location of the arc, in degrees

That’s a few more arguments than for the circle and lines, but don’t let that make you think that this is too much more complicated. Our code will look like this for one arc:

leftArc = Arc((11,45),height=18.3,width=18.3,angle=0,theta1=310,theta2=50)

All that we need to do after this is draw the arc to our plot, just like with the circles:

ax.add_patch(leftArc)

You can see this in action below:

In [4]:
#Demo Arcs
 
#Create figure
fig=plt.figure()
ax=fig.add_subplot(1,1,1)

#Pitch Outline & Centre Line
plt.plot([0,0],[0,90], color="black")
plt.plot([0,130],[90,90], color="black")
plt.plot([130,130],[90,0], color="black")
plt.plot([130,0],[0,0], color="black")
plt.plot([65,65],[0,90], color="black")

#Left Penalty Area
plt.plot([16.5,16.5],[65,25],color="black")
plt.plot([0,16.5],[65,65],color="black")
plt.plot([16.5,0],[25,25],color="black")

#Centre Circle/Spot
centreCircle = plt.Circle((65,45),9.15,fill=False)
centreSpot = plt.Circle((65,45),0.8)
ax.add_patch(centreCircle)
ax.add_patch(centreSpot)

#Create Arc and add it to our plot
leftArc = Arc((11,45),height=18.3,width=18.3,angle=0,theta1=310,theta2=50,color="red")

ax.add_patch(leftArc)

plt.show()

Bringing everything together

The code below applies the above lines, cricles and arcs to a function for quick and easy use. The only new line removes our axes:

plt.axis(‘off’)

Take a look through our function belong and follow what we are doing. Feel free to take this and use it as the base for your own plots!

In [5]:
def createPitch():
    
    #Create figure
    fig=plt.figure()
    ax=fig.add_subplot(1,1,1)

    #Pitch Outline & Centre Line
    plt.plot([0,0],[0,90], color="black")
    plt.plot([0,130],[90,90], color="black")
    plt.plot([130,130],[90,0], color="black")
    plt.plot([130,0],[0,0], color="black")
    plt.plot([65,65],[0,90], color="black")
    
    #Left Penalty Area
    plt.plot([16.5,16.5],[65,25],color="black")
    plt.plot([0,16.5],[65,65],color="black")
    plt.plot([16.5,0],[25,25],color="black")
    
    #Right Penalty Area
    plt.plot([130,113.5],[65,65],color="black")
    plt.plot([113.5,113.5],[65,25],color="black")
    plt.plot([113.5,130],[25,25],color="black")
    
    #Left 6-yard Box
    plt.plot([0,5.5],[54,54],color="black")
    plt.plot([5.5,5.5],[54,36],color="black")
    plt.plot([5.5,0.5],[36,36],color="black")
    
    #Right 6-yard Box
    plt.plot([130,124.5],[54,54],color="black")
    plt.plot([124.5,124.5],[54,36],color="black")
    plt.plot([124.5,130],[36,36],color="black")
    
    #Prepare Circles
    centreCircle = plt.Circle((65,45),9.15,color="black",fill=False)
    centreSpot = plt.Circle((65,45),0.8,color="black")
    leftPenSpot = plt.Circle((11,45),0.8,color="black")
    rightPenSpot = plt.Circle((119,45),0.8,color="black")
    
    #Draw Circles
    ax.add_patch(centreCircle)
    ax.add_patch(centreSpot)
    ax.add_patch(leftPenSpot)
    ax.add_patch(rightPenSpot)
    
    #Prepare Arcs
    leftArc = Arc((11,45),height=18.3,width=18.3,angle=0,theta1=310,theta2=50,color="black")
    rightArc = Arc((119,45),height=18.3,width=18.3,angle=0,theta1=130,theta2=230,color="black")

    #Draw Arcs
    ax.add_patch(leftArc)
    ax.add_patch(rightArc)
    
    #Tidy Axes
    plt.axis('off')
    
    #Display Pitch
    plt.show()
    
createPitch()

Summary

In our article, we’ve seen how to draw lines, arcs and circles in Matplotlib. You’ll find this useful when trying to add the finishing touches with annotations to any plot. These tools are equally important when drawing a map on which we will plot our data – like our pitchmap example here.

Take a look at our other visualisation articles here and be sure to get in touch with us on Twitter!

Posted by FCPythonADMIN in Visualisation