5
\$\begingroup\$

I was curious which U.S. president had the lowest approval rating for each day in their presidency. For example, which president had the lowest approval rating on day 42, and what was the rating. I downloaded the data from here and built this code to visualize it.

I'm particularly interested in feedback regarding anything inefficient or clumsy that I'm doing. I want the code to be clean and professional looking. This might be out of the scope of this site but any thoughts on how to visualize the data more effectively would be welcome as well.

# Here are the imports that we'll use
import os
import pandas as pd
from datetime import datetime
from collections import Counter
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
from matplotlib import font_manager as fm

'''
Here's the path to all the data. The data were copied from http://www.presidency.ucsb.edu/data/popularity.php and saved as tsv
files.

'''
djt_path = os.getcwd() + '/data/djt.tsv'
bho_path = os.getcwd() + '/data/bho.tsv'
gwb_path = os.getcwd() + '/data/gwb.tsv'
wjc_path = os.getcwd() + '/data/wjc.tsv'
ghwb_path = os.getcwd() + '/data/ghwb.tsv'
rwr_path = os.getcwd() + '/data/rwr.tsv'
jec_path = os.getcwd() + '/data/jec.tsv'
grf_path = os.getcwd() + '/data/grf.tsv'
rmn_path = os.getcwd() + '/data/rmn.tsv'
lbj_path = os.getcwd() + '/data/lbj.tsv'
jfk_path = os.getcwd() + '/data/jfk.tsv'
dde_path = os.getcwd() + '/data/dde.tsv'
hst_path = os.getcwd() + '/data/hst.tsv'

# Now let's read in all the data
djt = pd.read_table(djt_path)
bho = pd.read_table(bho_path)
gwb = pd.read_table(gwb_path)
wjc = pd.read_table(wjc_path)
ghwb = pd.read_table(ghwb_path)
rwr = pd.read_table(rwr_path)
jec = pd.read_table(jec_path)
grf = pd.read_table(grf_path)
rmn = pd.read_table(rmn_path)
lbj = pd.read_table(lbj_path)
jfk = pd.read_table(jfk_path)
dde = pd.read_table(dde_path)
hst = pd.read_table(hst_path)
# The first Gallup poll for this question was on 07/22/1941, which was in
# FDR's third term, so FDR has not been included.

# Let's make a list of all the presidents
presidents = [djt, bho, gwb, wjc, ghwb, rwr, jec, grf, rmn, lbj, jfk, dde, hst]

# And we'll need a list of their names
president_names = ["Donald Trump", "Barack Obama", "George W. Bush", "Bill Clinton", "George H.W. Bush", "Ronald Reagan",
                   "Jimmy Carter", "Gerald Ford", "Richard Nixon", "Lyndon Johnson", "John F. Kennedy", "Dwight Eisenhower",
                   "Harry Truman"]

# There are extra columns in the dataframe that we won't use, so let's
# remove them
for x in range(len(presidents)):
    del presidents[x]['President']
    del presidents[x]['Unnamed: 3']

# We'll need their inauguration dates
inauguration_dates = ['01/20/2017', '01/20/2009', '01/20/2001', '01/20/1993', '01/20/1989', '01/20/1981',
                      '01/20/1977', '08/09/1974', '01/20/1969', '11/22/1963', '01/20/1961', '01/20/1953', '04/12/1945', ]

# Now let's add a column that contains the number of days into their
# administration
# We'll need a helper function to make the dates easier to work with


def conv(date):
    return datetime.strptime(date, '%m/%d/%Y')

# Let's find how many days into the administration each poll represents
for x in range(len(presidents)):
    presidents[x]['time_in_admin'] = presidents[x][
        'Start Date'].apply(conv) - conv(inauguration_dates[x])
    # Now let's extract the actual value
    presidents[x]['days_in_admin'] = presidents[x][
        'time_in_admin'].apply(lambda row: row.days)

# Polls are not conducted every day. Let's build a function to find the most recent poll numbers for a
# given day if there are no poll number for that day.


def find_closest_date(array, value):
    '''this function could be improved greatly'''
    for x in range(len(array)):
        if array[x] <= value:
            # We want to return x, but we want to make sure there are more
            # polls
            if x == 0:
                # Only return the value if it is exact
                if array[x] == value:
                    return x
                else:
                    break
            return x

# OK, build a function that extracts the minimum and maximum approval rating from a list of all presidents
# Note that I'm not actually using the max part here


def get_min_max(day):
    min_approval = 100  # Set values that will easily be beaten
    max_approval = 0
    for x in range(len(presidents)):
        closest = find_closest_date(presidents[x]['days_in_admin'], day)
        #print("{pres}'s approval rating on his {day}th day was {rating}".format(pres=president_names[x], day=day, rating=presidents[x]['Approving'][closest]))
        if not closest:
            continue
        if presidents[x]['Approving'][closest] < min_approval:
            min_approval = presidents[x]['Approving'][closest]
            min_president = president_names[x]
        if presidents[x]['Approving'][closest] > max_approval:
            max_approval = presidents[x]['Approving'][closest]
            max_president = president_names[x]
    return min_president, min_approval, max_president, max_approval

# A full eight years is around 2920 days (excluding leap years)
# I'm just going to focus on the first 100 days
num_days = 100
# Now let's find which president had the lowest approval rating for each day
min_pres = []
min_value = []
max_pres = []
max_value = []
all_lists = [min_pres, min_value, max_pres, max_value]
for x in range(num_days):
    values = get_min_max(x)
    for x, lst in zip(values, all_lists):
        lst.append(x)

# Let's make a list of a color for each president from Trump to Truman
all_colors = ['#FF0000', '#0000FF', '#FF9F00', '#00DFFF', '#FF00CF', '#8700FF', '#FFFF00', '#B3FF00',
              '#A70053', '#01A129', '#003EB5', '#00EC00', '#000000']

# Now let's combine the names with the colors in a dictionary
colordict = {}
for p, c in zip(president_names, all_colors):
    colordict[p] = c

# We're going to need a list of colors by day. Let's make that now
min_colors = [colordict[min_pres] for min_pres in min_pres]

# To reduce clutter, we're only going to label presidents who are on the graph
# Let's grab those colors and patches
graph_patches = []
graph_colors = []
for pres in Counter(min_pres).keys():
    graph_colors.append(colordict[pres])
    graph_patches.append(mpatches.Patch(color=colordict[pres], label=pres))


# OK, now let's graph this
a = range(num_days)

plt.scatter(a, min_value, color=min_colors, s=100)  # s is the size
plt.legend(handles=graph_patches, prop={'size': 16})

# Let's make the plot bigger
# Get current size
fig_size = plt.rcParams["figure.figsize"]
font = {'family': 'serif',
        'color':  'k',
        'weight': 'normal',
        'size': 16,
        }
# Set figure width to 12 and height to 9
fig_size[0] = 12
fig_size[1] = 9
plt.rcParams["figure.figsize"] = fig_size
plt.title("Lowest approval rating of any president in first 100 days", fontdict=font)
plt.xlabel("Days into presidency", fontdict=font)
plt.ylabel("Approval rating", fontdict=font)
plt.annotate('Source: Gallup', xy=(1, 0), xycoords='axes fraction', fontsize=16,
             horizontalalignment='right', verticalalignment='bottom')
plt.show()

First graph

Then I wanted to see what it looked like in a pie chart. Here's the code for that:

# OK, now let's make a pie chart of what presidents are in the most

fig = plt.figure(1, figsize=(12, 12))
ax = fig.add_axes([0.1, 0.1, 0.8, 0.8])
plt.title("Number of days each president had the lowest approval ratings of any president in the first hundred days", fontdict=font)

labels = list(Counter(min_pres).keys())
values = list(Counter(min_pres).values())

patches, texts, autotexts = ax.pie(
    values, labels=labels, autopct="%1.f", colors=graph_colors)

proptease = fm.FontProperties()
proptease.set_size('16')
plt.setp(autotexts, fontproperties=proptease)
plt.setp(texts, fontproperties=proptease)

plt.show()

Second graph

\$\endgroup\$
2
\$\begingroup\$

I would change the setup of the dataframes. You could make your list of president dataframes into a dictionary with the name of the president as key. This way, you can greatly reduce the amount of code duplication:

president_names = ["Donald Trump", "Barack Obama", "George W. Bush",
                   "Bill Clinton", "George H.W. Bush", "Ronald Reagan",
                   "Jimmy Carter", "Gerald Ford", "Richard Nixon",
                   "Lyndon Johnson", "John F. Kennedy", "Dwight Eisenhower",
                   "Harry Truman"]
file_names = ['djt.tsv', 'bho.tsv', 'gwb.tsv', 'wjc.tsv', 'ghwb.tsv',
              'rwr.tsv', 'jec.tsv', 'grf.tsv', 'rmn.tsv', 'lbj.tsv', 'jfk.tsv',
              'dde.tsv', 'hst.tsv']

presidents = {name: pd.read_table(os.path.join(os.getcwd(), "data", file_name))
              for name, file_name in zip(president_names, file_names)}

inauguration_dates = ['01/20/2017', '01/20/2009', '01/20/2001', '01/20/1993',
                      '01/20/1989', '01/20/1981', '01/20/1977', '08/09/1974',
                      '01/20/1969', '11/22/1963', '01/20/1961', '01/20/1953',
                      '04/12/1945']

After this, you can iterate over this without always having to use

for x in range(len(presidents)):
    print presidents[x]

and can just do

for name, president_df in presidents.items():
    print president_df

pandas.read_table has a switch parse_dates, which, if enabled, will try to parse all columns as dates (and not do anything if they don't parse as dates). You can also tell it to parse only specific columns as dates by passing a list of column indices. By default it parses dates in the standard US format, so this should work. If not, there is also a switch to parse them as DD/MM/YYYY or you can even pass a custom parser function with date_parser=func.

So, I would use

presidents = {name: pd.read_table(os.path.join(os.getcwd(), "data", file_name), parse_dates=True)
              for name, file_name in zip(president_names, file_names)}
inauguration_dates = {name: conv(inauguration) for name, inauguration in zip(president_names, inauguration_dates)}

And for the time in the office you can use:

for name, president in presidents.items():
    inauguration = inauguration_dates[name]
    president['days_in_admin'] = (president['Start Date'] - inauguration).days
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.