Plotting baseball team progress

I noticed today that the Cubs and Yankees have the same record. I knew that the Cubs have had some extreme ups and downs this year, but I wasn’t sure if the Yankees had, so I decided to plot their progress over the course of the season so far.

A plot of how far above or below .500 they were seemed like the best way to compare their seasons, so I got their records from baseball-reference.com (Cubs results, Yankees results) and cleaned up the data through a combination of Numbers and BBEdit. The result was a pair of CSV files that looked like this:

Date,Home,Opponent,Win
Mar 26 2026,TRUE,WSN,-1
Mar 28 2026,TRUE,WSN,1
Mar 29 2026,TRUE,WSN,-1
Mar 30 2026,TRUE,LAA,1
Mar 31 2026,TRUE,LAA,-1
Apr 1 2026,TRUE,LAA,1
Apr 3 2026,FALSE,CLE,-1
Apr 5 2026,FALSE,CLE,1
Apr 5 2026,FALSE,CLE,-1
Apr 6 2026,FALSE,TBR,-1
[etc.]

These are the first ten games for the Cubs. The Win field contains a +1 for wins and a -1 for losses. The plot of their progress looks like this:

Cubs and Yankees 2026

The teams were running in parallel from mid-April through early May, before the Cubs’ disastrous slide. The Yankees had their own smaller slide starting in mid-June but seem to have recovered. History has taught Cub fans not to trust the team’s recent success; 60 more games is plenty of time for another slide (or two).

The plot was made with Python, Pandas, NumPy, and Matplotlib. Here’s the code:

python:
 1:  #!/usr/bin/env python3
 2:  
 3:  import pandas as pd
 4:  import numpy as np
 5:  from datetime import datetime
 6:  import matplotlib.pyplot as plt
 7:  from matplotlib.ticker import MultipleLocator, AutoMinorLocator
 8:  from matplotlib.dates import DateFormatter, YearLocator, MonthLocator
 9:  
10:  # Read the files into dataframes
11:  dfCubs = pd.read_csv('cubs-2026.csv', parse_dates=[0])
12:  dfYankees = pd.read_csv('yankees-2026.csv', parse_dates=[0])
13:  
14:  # Calculate the games above .500
15:  dfCubs['Above'] = np.cumsum(dfCubs.Win)
16:  dfYankees['Above'] = np.cumsum(dfYankees.Win)
17:  
18:  # Create the plot with a given size in inches
19:  fig, ax = plt.subplots(figsize=(6, 4))
20:  
21:  # Add lines for each team. Team colors from teamcolorcodes.com.
22:  ax.plot(dfCubs.Date, dfCubs.Above, '-', color='#0E3386', lw=1.5, label='Cubs')
23:  ax.plot(dfYankees.Date, dfYankees.Above, '.', color='#0C2340',ms=5, label='Yankees')
24:  
25:  # Set the limits
26:  plt.ylim(ymin=-5, ymax=20)
27:  
28:  # Set the ticks and add a grid
29:  ax.xaxis.set_major_locator(MonthLocator())
30:  ax.xaxis.set_major_formatter(DateFormatter('%-m/%-d/%y'))
31:  ax.yaxis.set_major_locator(MultipleLocator(5))
32:  ax.grid(linewidth=.5, axis='x', which='major', color='#dddddd', linestyle='-')
33:  ax.grid(linewidth=.5, axis='y', which='major', color='#dddddd', linestyle='-')
34:  
35:  # Title and axis labels
36:  plt.title('Cubs and Yankees 2026')
37:  plt.ylabel('Games above .500')
38:  
39:  # Make the border and tick marks 0.5 points wide
40:  [ i.set_linewidth(0.5) for i in ax.spines.values() ]
41:  ax.tick_params(which='both', width=.5)
42:  
43:  # Add the legend
44:  ax.legend(loc='lower right')
45:  
46:  # Save as PDF
47:  plt.savefig('20260723-Cubs and Yankees 2026.png', format='png', dpi=200)

I wanted to use lines for both teams and let the team colors distinguish them, but the team colors are too close, so I used markers for the Yankees and added a legend. I did still use the team colors, which I got from the Team Color Codes website. I fiddled with the marker size and line width until the two looked to be of roughly equal importance.

It may seem like this is a lot of code to write for a simple plot, but I use a Typinator abbreviation to insert a code template and just tweak the template to get things looking the way I want. The Pandas and NumPy part of the code, Lines 10–16, was trivial.