Sports

Xavier Worthy’s record-breaking 4.21 40-yard dash

If you’re an NFL fan you probably heard the buzz from this weekend’s Scouting Combine: former Texas wide receiver Xavier Worthy set a new event record in the 40-yard dash (4.21 seconds). Whether that means he’s destined for the Hall of Fame—or absolutely nothing—is up for debate. Fans of Worthy’s first NFL team might want to overlook the Combine’s spotty record of predicting success in the league.

I’m not going to do a full analysis of Combine measurables and how they correlate with NFL performance (although that would be fun to read!). But I thought it would be fun to plot Worthy’s athleticism, measured in terms of 40-yard dash time and vertical leap, in the context of all the Combine’s previous wide receivers.

Or at least the wide receivers who have come through since 2000, because that’s as far back as Pro Football Reference goes. I’ll link the dataset at the bottom of this post.


1. Prepare the data.

Start by importing pandas, matplotlib, and numpy.arange(), which we’ll use for x-ticks.

The data requires minimal processing. We can simply:

  1. Read the CSV file into a pandas DataFrame.
  2. Create a view containing only wide receiver rows.
  3. Drop rows where either 40-yard dash or vertical leap data is missing.
import pandas as pd
import matplotlib.pyplot as plt
from numpy import arange

df = pd.read_csv("nfl_combine_2000-2023.csv")
df = df[df["position"] == "WR"]
df = df.dropna(subset=["forty", "vertical"])

This takes us from 7,999 rows down to 896, which we can turn into a scatter plot.


2. Plot the data.

I’m using a custom mplstyle meant to mimic R’s ggplot2. Matplotlib provides a similar style (and many others) when you install the library. It’s easy to tweak those files or to create your own from scratch. It’s often an easier way to achieve your desired appearance than to write extra lines of Python.

plt.style.use("wollen_ggplot.mplstyle")
fig, ax = plt.subplots(figsize=(9, 9))

The dataset contains Combine results from 2000 through 2023. We’ll separately add Xavier Worthy’s 2024 measurements, assign his dot a unique appearance, and identify it with a legend in the upper-left corner.

When plotting data like this with many overlapping points, it helps to lower the scatter’s alpha, i.e. increase transparency. A darker color indicates that dots are stacked on top of each other.

ax.scatter(df["forty"], df["vertical"], alpha=0.5)
ax.scatter([4.21], [41], marker="D", s=50, label="Xavier Worthy (2024)")

I like to define x-ticks and y-ticks so that the data is entirely within the tick range. In other words the topmost data point doesn’t poke above the top y-axis line.

And then I define the axis window in terms of those limits. Usually a 1% margin looks fine. Sometimes tick labels take up more space and it helps to have a larger margin, or to rotate the labels, but this plot doesn’t cause any issues.

x_ticks = arange(4.2, 5.0, 0.1)
ax.set_xticks(x_ticks)
x_tick_span = x_ticks[-1] - x_ticks[0]
x_left, x_right = x_ticks[0] - x_tick_span * 0.015, x_ticks[-1] + x_tick_span * 0.015
ax.set_xlim(x_left, x_right)

y_ticks = range(26, 50, 2)
ax.set_yticks(y_ticks)
y_tick_span = y_ticks[-1] - y_ticks[0]
y_bottom, y_top = y_ticks[0] - y_tick_span * 0.015, y_ticks[-1] + y_tick_span * 0.015
ax.set_ylim(y_bottom, y_top)

ax.set_xlabel("40-Yard Dash (s)")
ax.set_ylabel("Vertical  (inches)")
ax.set_title("NFL Combine  |  Wide Receivers  |  2000–2023")

We should add a citation to Pro Football Reference, again defining its position in terms of the data.

x_range = x_right - x_left
y_range = y_top - y_bottom
ax.text(x_right - x_range * 0.005, y_top - y_range * 0.007,
        "Data: www.pro-football-reference.com.",
        size=9, ha="right", va="top")

Finally, turn on a legend to annotate Worthy’s performance and save the figure to a file. I usually bump up the default dpi to give a cleaner presentation.

ax.legend(loc="upper left")

plt.savefig("WR_40_vertical.png", dpi=200)

3. The output.

You can see Xavier Worthy and his record-breaking 40-yard dash as the farthest-left dot. John Ross, to Worthy’s right, held the record previously but with a 4-inch lower vertical. Worthy has deservedly gotten attention for his speed but we’ve shown that he’s also one of the best overall athletes to come through the draft.


Download the data.

Full code:

import pandas as pd
import matplotlib.pyplot as plt
from numpy import arange


df = pd.read_csv("nfl_combine_2000-2023.csv")
df = df[df["position"] == "WR"]
df = df.dropna(subset=["forty", "vertical"])

plt.style.use("wollen_ggplot.mplstyle")
fig, ax = plt.subplots(figsize=(9, 9))

ax.scatter(df["forty"], df["vertical"], alpha=0.5)
ax.scatter([4.21], [41], marker="D", s=50, label="Xavier Worthy (2024)")

x_ticks = arange(4.2, 5.0, 0.1)
ax.set_xticks(x_ticks)
x_tick_span = x_ticks[-1] - x_ticks[0]
x_left, x_right = x_ticks[0] - x_tick_span * 0.015, x_ticks[-1] + x_tick_span * 0.015
ax.set_xlim(x_left, x_right)

y_ticks = range(26, 50, 2)
ax.set_yticks(y_ticks)
y_tick_span = y_ticks[-1] - y_ticks[0]
y_bottom, y_top = y_ticks[0] - y_tick_span * 0.015, y_ticks[-1] + y_tick_span * 0.015
ax.set_ylim(y_bottom, y_top)

ax.set_xlabel("40-Yard Dash (s)")
ax.set_ylabel("Vertical  (inches)")
ax.set_title("NFL Combine  |  Wide Receivers  |  2000–2023")

x_range = x_right - x_left
y_range = y_top - y_bottom
ax.text(x_right - x_range * 0.005, y_top - y_range * 0.007,
        "Data: www.pro-football-reference.com.",
        size=9, ha="right", va="top")

ax.legend(loc="upper left")

plt.savefig("WR_40_vertical.png", dpi=200)