Visualizing daily cases of COVID-19 in the United States
In this notebook we will create a graphic of the 50 states (plus D.C.), each containing a chart displaying the daily case count over time for that state.
Click here to view the final map result in a new tab or scroll to the bottom.
import csv
import requests
import numpy as np
import pandas as pd
from io import StringIO
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
from datetime import datetime
import matplotlib.lines as mlines
import matplotlib.offsetbox as offsetbox
import formatting as form
plt.close('all')
First we download the raw data from The COVID Tracking Project
CSV_URL = 'https://covidtracking.com/api/v1/states/daily.csv'
with requests.Session() as s:
download = s.get(CSV_URL)
decoded_content = download.content.decode('utf-8')
cr = pd.read_csv(StringIO(decoded_content))
df = pd.DataFrame(cr)
Next, convert date integers to DateTime format
date = pd.to_datetime(df['date'].astype(str), format='%Y%m%d')
dataType = 'positiveIncrease'
df = pd.DataFrame({'date':date, 'state':df['state'], dataType:df[dataType]})
df.groupby('date')
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x00000186CD9DA9D0>
List containing every state abbreviation (includes DC)
stateNames = ['WA', 'ID', 'MT', 'ND', 'MN','WI','MI','NY','VT', 'NH','ME',
'OR','WY', 'SD', 'IA', 'IL', 'IN', 'OH', 'PA', 'MA', 'RI', 'CT', 'NJ',
'DE', 'MD', 'DC', 'CA', 'NV', 'UT', 'CO', 'NE', 'MO', 'KS', 'OK', 'NM',
'AZ', 'TX', 'LA', 'AR', 'TN', 'MS', 'AL', 'KY', 'WV', 'VA', 'NC',
'SC', 'GA', 'FL', 'AK', 'HI']
Create a new DataFrame with each state as a column and their positiveIncreases as rows, indexed by date.
I’m sure there’s a cleaner way to do this…
dfstates = []
for name in stateNames:
sel = df[(df['state']==name)]
sel.index=sel['date']
dfstates.insert(len(dfstates), sel)
dfgroup = pd.DataFrame()
for d in dfstates:
dfgroup[d['state'][0]] = d[dataType]
Compare weekly totals to the previous week for 7 weeks. Returns how many consecutive weeks saw a relative increase to the previous week.
def consecutiveWeeksIncreased(state):
weeks = 0
prev = 999999
for week in range(7):
thisWeekSum = sumRange(state, weeks * 7, weeks * 7 + 8)
if thisWeekSum <= prev:
prev = thisWeekSum
weeks += 1
else:
return weeks
return weeks - 1
Get the average number of new cases for all states
avgIncrease = 0
for state in dfgroup:
avgIncrease += dfgroup[state][0]
avgIncrease /= dfgroup.shape[1]
avgIncrease = int(avgIncrease)
avgIncrease
814
Create a subplot for the state by state abbreviation
def do_plot(ax, name):
#Get color pair based on how many weeks the state has seen an increase in total cases
weeksClimbing = consecutiveWeeksIncreased(dfgroup[name])
colors = form.getContrastingColors(weeksClimbing)
faceColor = colors[0]
infoColor = colors[1]
#Set the face color
ax.set_facecolor(faceColor)
#plot the data for this state
ax.plot(dfgroup[name], color=(infoColor), linewidth=1)
#remove the labels, grids & ticks for a cleaner look
ax.set_xlabel('')
ax.grid(False)
ax.set_xticklabels('')
ax.set_yticklabels('')
#Display the state abbreviation title
ax.set_title(name,horizontalalignment='left', color=(infoColor), fontweight="bold", fontsize=14, x=0.025, y=.775)
#Display a subtle border
form.formatBorder(ax)
#Display a '100 new cases' threshold for scale
ax.axhline(y=avgIncrease, color='r', linestyle='-', linewidth=.5)
#Latest increase
lastChange = form.getLastIncreaseChange(name, dfgroup)
changeNote = '(' + form.getIncreaseSign(lastChange) + str(lastChange) + ')'
box = dict(boxstyle='round,pad=0.1', fc=faceColor, ec=faceColor, alpha=0.7)
ax.annotate(s= '+'+str(int(dfgroup[name][0])), xy=(.025, 0.7), xycoords='axes fraction', fontsize=11, color=infoColor, bbox = box)
#Latest increase vs previous day
ax.annotate(s= changeNote, xy=(.025, 0.581), xycoords='axes fraction', fontsize=11, color=form.getIncreaseColor(lastChange), bbox = box)
Get the sum of contents within a list’s range
def sumRange(L,a,b):
sum = 0
for i in range(a,b+1,1):
sum += L[i]
return sum
def invLerp(a, b, t):
return (float(t)-float(a)) / (float(b)-float(a))
Add a state to the grid by abbreviation
def add_state(figure, stateName, grid):
state = figure.add_subplot(grid)
do_plot(state, stateName)
return state
Create a “Last updated on ___” string to use in the plot
now = datetime.now()
dt_string = now.strftime("%m/%d/%Y %H:%M:%S")
dt_string = "Last updated: " + dt_string + " PST"
Create a string to describe the range of time on the plot
lastDate = date[0]
lastYear = lastDate.year
lastDate = lastDate = lastDate.month_name() + ' ' + str(lastDate.day)
shape = date.shape
firstDate = date[shape[0]-1]
firstYear = firstDate.year
firstDate = firstDate.month_name() + ' ' + str(firstDate.day)
dateRangeString = 'From ' + firstDate + ', ' + str(firstYear) + ' to ' + lastDate + ', ' + str(lastYear)
Add groups of states in vertical columns using a list of abbreviation strings
def add_column(fig, names, column, grid):
row = 0
for name in names:
if(name == ""):
row+=1
continue
else:
add_state(fig, name, grid[row, column])
row+=1
Creating the Plot
Create the plot and a grid to fit each state into.
plt.style.use('fivethirtyeight')
g = gridspec.GridSpec(11, 11)
fig = plt.figure(figsize=(30,25), facecolor=form.light)
<Figure size 2160x1800 with 0 Axes>
Create the Plot Title
fig.suptitle('US Daily COVID-19 Case Increases', y=0.85, fontsize=24, color=form.titleColor)
fig.text(.5, .822, s=dateRangeString, color = form.titleColor, horizontalAlignment = 'center')
<ipython-input-52-db0fcf42275a>:2: MatplotlibDeprecationWarning: Case-insensitive properties were deprecated in 3.3 and support will be removed two minor releases later
fig.text(.5, .822, s=dateRangeString, color = form.titleColor, horizontalAlignment = 'center')
Text(0.5, 0.822, 'From January 13, 2020 to March 7, 2021')
Add the “Last updated on ___” label to the plot
fig.text(x=.05, y=.25, s = dt_string, color=form.labelColor)
x = .1
y =.75
Creating the legend
twoWeeksIncrease = mlines.Line2D([],[], color = form.medLight, marker='s',markersize=30, label='Rising for 2 weeks')
threeWeeksIncrease = mlines.Line2D([],[], color = form.medium, marker='s',markersize=30, label='Rising for 3 weeks')
fourWeeksIncrease = mlines.Line2D([],[], color = form.medDark, marker='s',markersize=30, label='Rising for 4 weeks')
fiveWeeksIncrease = mlines.Line2D([],[], color = form.medDark2, marker='s',markersize=30, label='Rising for 5+ weeks')
legend = fig.legend(handles=[twoWeeksIncrease, threeWeeksIncrease, fourWeeksIncrease, fiveWeeksIncrease], loc='upper left',borderaxespad=10, framealpha=0, labelspacing=2)
Set the color for each legend
for t in legend.get_texts():
t.set_color(form.labelColor)
Creating State Subplots
This is where we organize the order of how the states are displayed to look like the shape of the United States map.
Blank grid cells (''
) in the array are empty spaces in the grid.
add_column(fig, ['', '', 'WA', 'OR', 'CA', '', '', 'HI'], 0, g)
add_column(fig, ['', '', 'ID', 'NV', 'UT', 'AZ', '', 'AK'], 1, g)
add_column(fig, ['','', 'MT', 'WY', 'CO', 'NM'], 2, g)
add_column(fig, ['','','ND','SD', 'NE', 'KS', 'OK', 'TX'], 3, g)
add_column(fig, ['', '', 'MN', 'IA', 'MO', 'AR', 'LA'], 4, g)
add_column(fig, ['', 'WI', 'IL', 'IN', 'KY', 'TN', 'MS'], 5, g)
add_column(fig, ['', '','MI', 'OH', 'WV', 'NC', 'AL'], 6, g)
add_column(fig, ['', '', '', 'PA', 'VA', 'SC', 'GA'], 7, g)
add_column(fig, ['', '', 'NY', 'NJ', 'MD', '', '', 'FL'], 8, g)
add_column(fig, ['', 'VT', 'MA', 'CT', 'DE'], 9, g)
add_column(fig, ['ME', 'NH', 'RI', 'DC'], 10, g)
<ipython-input-44-510a91b7e274>:34: MatplotlibDeprecationWarning: The 's' parameter of annotate() has been renamed 'text' since Matplotlib 3.3; support for the old name will be dropped two minor releases later.
ax.annotate(s= '+'+str(int(dfgroup[name][0])), xy=(.025, 0.7), xycoords='axes fraction', fontsize=11, color=infoColor, bbox = box)
<ipython-input-44-510a91b7e274>:37: MatplotlibDeprecationWarning: The 's' parameter of annotate() has been renamed 'text' since Matplotlib 3.3; support for the old name will be dropped two minor releases later.
ax.annotate(s= changeNote, xy=(.025, 0.581), xycoords='axes fraction', fontsize=11, color=form.getIncreaseColor(lastChange), bbox = box)
#create guide info
#Get WI's ax so we can annotate the plot as an example
ax = fig.axes[24]
form.createLabelGuide(ax, 'State', (0, .85), 'axes fraction', .9)
form.createLabelGuide(ax, 'New cases reported on ' + lastDate, (0.01, .72), 'axes fraction', .7)
form.createLabelGuide(ax, 'Relative to prev. day', (.01, .61), 'axes fraction', .5)
form.createLabelGuide(ax, 'Countrywide avg new cases on '+ lastDate + ' (' + str(avgIncrease) + ')', (date[date.shape[0]-100], avgIncrease), 'data', .3)
c:\Users\User\Documents\GitHub\covid_tracking_us\formatting.py:63: MatplotlibDeprecationWarning: The 's' parameter of annotate() has been renamed 'text' since Matplotlib 3.3; support for the old name will be dropped two minor releases later.
ax.annotate(s=text,xy=xy, xytext=(-.3, textypos),
c:\Users\User\Documents\GitHub\covid_tracking_us\formatting.py:63: MatplotlibDeprecationWarning: Case-insensitive properties were deprecated in 3.3 and support will be removed two minor releases later
ax.annotate(s=text,xy=xy, xytext=(-.3, textypos),
Displaying the Plot
#Data source label
fig.text(x=.975, y=.25, s='Data collected via The COVID Tracking Project', color = form.labelColor, horizontalAlignment='right')
fig
<ipython-input-58-4623ef3e5cd4>:2: MatplotlibDeprecationWarning: Case-insensitive properties were deprecated in 3.3 and support will be removed two minor releases later
fig.text(x=.975, y=.25, s='Data collected via The COVID Tracking Project', color = form.labelColor, horizontalAlignment='right')