How to Save Break Points in Tennis Using Data
By: Derek Austin
Tags: Data Visualization, Seaborn, Pandas, Tennis

One of the main keys to any tennis match is the ability to save break points. Take a look at the ATP leaderboard and you will be reminded of that fact through seeing the top pros in the game consistently at the very top of break point save percentage. But what makes a player excel at saving break points? Courtesy of Jeff Sackman’s point-by-point dataset for each Grand Slam over the past 10 years, we are able to dive in and look at how pros are saving break points on the biggest stages in tennis.

First we start by importing the relevant library’s:

import pandas as pd

import seaborn as sn

import matplotlib.pyplot as plt

import numpy as np

Then we need to import the data. Data from the Australian Open and French Open from 2018 on are packaged differently so we drop those few tournaments for now.

years = [i for i in range(2011, 2021)]

slams = ['ausopen', 'frenchopen', 'usopen', 'wimbledon']

data = pd.DataFrame() for year in years:

for slam in slams:

if year >= 2018 and slam in ['ausopen', 'frenchopen']: #these slams did not have the same data collected

continue

try:

new_data = pd.read_csv('./tennis_slam_pointbypoint-master/' + str(year) + '-' + slam + '-points.csv')

if year == 2011 and slam == 'ausopen':

data = new_data

else:

data = pd.concat([new_data, data])

except FileNotFoundError:

print(year, slam)

continue

Next I dropped the rows that did not have rally data (in this case the ones that did not have a serve speed reading) and collected relevant features such as aces, net points, unforced errors, winners etc. I would refer anybody interested to the Github file as posting all into the article would be overwhelming.

Finally we can start analyzing. The first decision made on a tennis court is whether or not to serve first. As shown below it seems to be a huge help serving first, as there are drastically less break points that occur in the first game as opposed to game 2 (about a 25% decrease). The best way to be good at saving break points is to not face them in the first place and serving first can minimize the break points a player faces throughout a match.

#imported library's include (Seaborn, Matplotlib.pyplot, pandas)

#sets the x axis to be 12 games and then uses a pandas groupby statement a_plot = sn.lineplot(x = [i for i in range(0,12)], y = data.groupby('GamesPlayed').mean()['BreakPoint'][0:11]) a_plot.set(ylim = (0, .19))

plt.title("Break Points Faced per Game")

plt.grid()

plt.xlabel("Games into Set")

The first game shows a 25% decrease in break points faced as compared to others

The next thing we are going to investigate is how to serve during break points, after all it is the one thing on a tennis court that you have complete control over. While we are somewhat limited in that the dataset does not give us a radar reading if the player missed the first serve (ie. how hard they were trying to hit), we can still examine how serve speed affects a number of key factors of break point save percentage.

I grouped serve speeds into buckets every 5 MPH going from 80–140 MPH. All metrics tie back to this loop and each metric name should be fairly intuitive

for i in range(80, 145, 5): #bucketing by fives and then appending to lists corresponding to different metrics bucket_data = data[(data.ServeSpeed >= i) & (data.ServeSpeed <= i + 5)]

trimmed_data = bucket_data[bucket_data.BreakPoint]



x = bucket_data.groupby('BreakPoint').mean()#all data

y = trimmed_data.groupby('BreakPointWon').mean()#just bp data





save_percentage.append(1 - x.loc[True, 'BreakPointWon'])#doing the one because has two entries True and False

rally_length.append(y.loc[True, 'Rally'])



buckets.append(i)



winner_percentage_loss.append(y.loc[False, 'Winner'])

winner_percentage_save.append(y.loc[True, 'Winner'])



unforced_error_percentage_loss.append(y.loc[True, 'UnforcedError'])

unforced_error_percentage_save.append(y.loc[False, 'UnforcedError'])



net_point.append(x.loc[True, 'ServerNetPoint'])

net_point_won.append(x.loc[True, 'ServerNetPointWon'] / x.loc[True, 'ServerNetPoint'])



return_net_point.append(x.loc[True, 'NetPoint'] - x.loc[True, 'ServerNetPoint'])

return_net_point_won.append((x.loc[True, 'NetPointWon'] - x.loc[True, 'ServerNetPointWon']) / (x.loc[True, 'NetPoint'] - x.loc[True, 'ServerNetPoint']))

As you would expect, serve speed increase correlates with break point save percentage. However, once you hit 130 the affect seems to flatten. Ultimately serve speed could be formulated as a decision equation. If a player could quantify how much more likely they would be to miss their first serve for each MPH increase and what their percentage likelihood of winning the point on their second serve they would be able to calculate the optimal speed at which they should serve.

import matplotlib.ticker as mtick #imports to enable percentage reading #graphs save percentage by serve speed

ax = sn.lineplot(x = buckets, y = [i * 100 for i in save_percentage])

ax.yaxis.set_major_formatter(mtick.PercentFormatter())#sets y axis as a percent plt.grid()

plt.title("Save Percentage by Serve Speed") plt.ylabel("Save Percentage")

plt.xlabel("Serve Speed")

Steadily increasing chart with limited affects after 130 MPH

Next we consider winners and unforced errors. As we can see from below, winner percentage climbs steadily as your serve speed goes up, where as the percentage chance your opponent hits a winner stays relatively constant. This can lead to important tactical decisions as a server. For instance, if you were playing a big hitter who hits lots of winners, hitting a slower serve may be alright as serve speed does not seem to impact their chance of hitting a winner. However, if you are having to hit winners to win the majority of your points you may take the risk to hit a little harder on both your first and second serves in order to increase the likelihood of hitting a winner.

#uses two lines on one plot to visualize Winner percentage

ax1 = sn.lineplot(x = buckets, y = [i *100 for i in winner_percentage_loss], label = 'Server')

ax2 = sn.lineplot(x = buckets, y = [i * 100 for i in winner_percentage_save], label = 'Returner')

ax1.yaxis.set_major_formatter(mtick.PercentFormatter())

ax2.yaxis.set_major_formatter(mtick.PercentFormatter()) plt.grid()

plt.title("Winner Percentage Server vs Returner by Serve Speed") plt.ylabel("Winner Percentage")

plt.xlabel("Serve Speed MPH")

Winner percentage stays somewhat constant for returners

We also have to consider the likelihood of hitting an unforced error. As evidenced by the graph below your opponent is essentially no more likely to commit an unforced error if you hit a serve 90 MPH vs 135 MPH — with the percentage at 140 MPH likely being caused by low sample size. However, as the server you steadily decrease your percentage chance of hitting an unforced error as your serve speed increases. Again this can lead to important tactical changes. If your opponent is sporadic and commits a lot of unforced errors, you may want to hit a softer first serve as serve speed would not affect their propensity to make an error. However, if as a server you have been inconsistent, decreasing the probability of hitting an unforced error by hitting a harder serve would likely be the right move.

#uses two lines on one plot to visualize unforced errors

ax1 = sn.lineplot(x = buckets, y = [i * 100 for i in unforced_error_percentage_save], label = 'Server')

ax2 = sn.lineplot(x = buckets, y = [i * 100 for i in unforced_error_percentage_loss], label = 'Returner')

ax1.yaxis.set_major_formatter(mtick.PercentFormatter())

ax2.yaxis.set_major_formatter(mtick.PercentFormatter()) plt.grid()

plt.title("Unforced Error Percentage Server vs Returner by Serve Speed") plt.ylabel("Unforced Error Percentage")

plt.xlabel("Serve Speed MPH")

Unforced error percentage for the server decreases as speed increases

Finally, we find that when a player comes to the net they have a 68% chance of winning the point. Getting to the net is a successful strategy especially on break points when you force your opponent to make a tough shot in a high pressure situation. As we can see win percentage at the net stays somewhat constant as you serve speed increases, with both of the ends likely due to low sample sizes but your ability to get to the net increases dramatically — going from 10% at 80 MPH to around 20% at 120–130 MPH.