Plotting Pitches in Python

Introduction

If you’ve spent any time looking at x,y data with Opta, Strata Bet, or Statsbomb, you’ll know that their coordinate system is not based on any specific unit of measurement.

Opta’s coordinates run from 0,100 for. x and for y, Strata Bet (now defunct) coordinates ran from 0,420 for x and -136,136 for y , and Statsbomb’s data runs from 0,120 for x and 0,80 for y. 

As Statsbomb’s data is 3:2 ratio you can start plotting straight away, but calculating distance and angle accurately gets tricky.

So for me, I like to plot in metres and at a range of x(0,104) and y(0,68). This is an average pitch size. If you have access to the data and know the exact dimensions for pitches in the leagues you are interested in, you could make this even more accurate, however, you’ll need to change a few of the numbers I’ll run through below.

x,y conversion and pitch measurements

 df['xM'] = df.x / 100 * 104
 df['yM'] = df.y / 100 * 68


The above takes our x and y coordinates from our DataFrame (df) and converts it to my preferred scale of 104×68. Now that that’s done, we can start plotting!

All we are doing here is drawing a series of boxes and circles, and layering them to get our desired result. Thankfully in python, we can do this quite easily with Matplotlib! Below I will show the code I use followed by an image for each stage of the process

Plotting a pitch

import matplotlib.pyplot as plt

fig,ax = plt.subplots(figsize=(10.4,6.8))
ax.axis('off') # this hides the x and y ticks

# side and goal lines #
ly1 = [0,0,68,68,0]
lx1 = [0,104,104,0,0]

plt.plot(lx1,ly1,color="black",zorder=5)

We give four coordinates for x and y. This will draw a line from and to the following points: (0,0),(0,104)(68,104),(68,0),(0,0).We set the line color to black, and zorder to 5. zorder controls which elements are above others in matplotlib. This will be important later.

 # boxes, 6 yard box and goals

    #outer boxes#
ly2 = [13.84,13.84,54.16,54.16] 
lx2 = [104,87.5,87.5,104]
plt.plot(lx2,ly2,color="black",zorder=5)

ly3 = [13.84,13.84,54.16,54.16] 
lx3 = [0,16.5,16.5,0]
plt.plot(lx3,ly3,color="black",zorder=5)
    #goals#
ly4 = [30.34,30.34,37.66,37.66]
lx4 = [104,104.2,104.2,104]
plt.plot(lx4,ly4,color="black",zorder=5)

ly5 = [30.34,30.34,37.66,37.66]
lx5 = [0,-0.2,-0.2,0]
plt.plot(lx5,ly5,color="black",zorder=5)


   #6 yard boxes#
ly6 = [24.84,24.84,43.16,43.16]
lx6 = [104,99.5,99.5,104]
plt.plot(lx6,ly6,color="black",zorder=5)

ly7 = [24.84,24.84,43.16,43.16]
lx7 = [0,4.5,4.5,0]
plt.plot(lx7,ly7,color="black",zorder=5)
#Halfway line, penalty spots, and kickoff spot
vcy5 = [0,68] 
vcx5 = [52,52]
plt.plot(vcx5,vcy5,color="black",zorder=5)


plt.scatter(93,34,color="black",zorder=5)
plt.scatter(11,34,color="black",zorder=5)
plt.scatter(52,34,color="black",zorder=5)


circle1 = plt.Circle((93.5,34), 9.15,ls='solid',lw=1.5,color="black", fill=False, zorder=1,alpha=1)
circle2 = plt.Circle((10.5,34), 9.15,ls='solid',lw=1.5,color="black", fill=False, zorder=1,alpha=1)
circle3 = plt.Circle((52, 34), 9.15,ls='solid',lw=1.5,color="black", fill=False, zorder=2,alpha=1)


Here we add circles to the viz. We set fill = False, as we are only interested in the outer lines of the circles. I’m probably cheating a bit here because I haven’t figured out how best to curve a line for the semi circles. You’ll notice I have the zorder here set to 1 for circles 1 and 2, and zorder=2 for circle three. This is to make sure that these are layered correctly with the rectangles we will plot. if we just left these as is, the pitch would look like this:

So lastly, we need to add some rectangles to the plot to hid those circles in the box:

circle1 = plt.Circle((93.5,34), 9.15,ls='solid',lw=1.5,color="black", fill=False, zorder=1,alpha=1)
circle2 = plt.Circle((10.5,34), 9.15,ls='solid',lw=1.5,color="black", fill=False, zorder=1,alpha=1)
circle3 = plt.Circle((52, 34), 9.15,ls='solid',lw=1.5,color="black", fill=False, zorder=2,alpha=1)
 
## Rectangles in boxes
rec1 = plt.Rectangle((87.5,20), 16,30,ls='-',color="white", zorder=1,alpha=1)
rec2 = plt.Rectangle((0, 20), 16.5,30,ls='-',color="white", zorder=1,alpha=1)
 
## Pitch rectangle
rec3 = plt.Rectangle((-1,-1), 106,70,color=pitch,zorder=1,alpha=1)
 
ax.add_artist(rec3)
ax.add_artist(circle1)
ax.add_artist(circle2)
ax.add_artist(rec1)
ax.add_artist(rec2)
ax.add_artist(circle3)
#we defined ax after we imported matplotlib.pyplot#
plt.show()

That’s basically it. Here’s how it looks in gif form:

Final notes

While the above code works fine, you’re playing a fools game by hardcoding the colours (I’d argue the coordinates too). A much more flexible solution is to declare two variables before you plot anything. One variable should be for pitch colour and one for line colour. This allows you to change shades as you wish with either hexcodes or literally typing the color you want. You can get some really nice results with hexcodes as it’s far more granular. How many shades of grey are there anyway? A fuck load.

With our pitch drawn we can get onto the really fun stuff, plotting player and team match data

Liked it? Take a second to support petermckeever on Patreon!
No Comments

Post A Comment