S&P 500 Earnings Growth Rates in Python

I recently read through a 2015 post on the Philsophical Economics blog about understanding S&P 500 earnings.  Essentially, less and less dividends are paid out in the US, and are instead being reinvested either in operations or in company stock.

This has a few implications. Earnings growth rate calculations are fodder for forecasts (note: I am not a fan of forecasts). Second, monitoring the change in earnings, say year-over-year, does have usefulness as an indicator of an oncoming recession.  When you adjust for earnings being reinvested (aka Total Return EPS) rather than paid out, it changes the discussion around earnings growth.

The source data comes from Prof. Robert Shiller’s website, here.

To understand the original post better and keep an eye on it, I coded up the calculation in Python:


import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib

def shiller():
    spdvds = pd.read_excel('http://www.econ.yale.edu/~shiller/data/ie_data.xls','Data',header=7)
    spdvds = spdvds.ix[:spdvds[spdvds['Date']==spdvds.Date.dropna().iloc[-1]].index[0],:]
    yr  = np.floor(spdvds.Date)
    mn  = (spdvds.Date - yr)*100
    spdvds['Date'] = pd.to_datetime(dict(year=yr,month=mn,day=1))
    spdvds = spdvds.set_index('Date')
    return spdvds

def total_return_eps():
    t  = shiller()
    cape = t['CAPE'].dropna().mean()
    sh = (1-1/(t['Earnings']*cape/t['Dividend']*12)) #shares of prior month remaining after theoretical buyback
    sh = sh.dropna().cumprod() #cumulative remaining shares
    return (t['Earnings']/sh).dropna(),cape

def treps_chart():
    plt.figure()
    ax = plt.gca()
    treps,cape = total_return_eps()
    treps.plot(ax=ax,logy=True)
    xmin,xmax = ax.get_xlim()
    ymin,ymax = ax.get_ylim()
    x = np.arange(len(treps))+ax.get_xlim()[0]-1
    y = treps.values
    fit = np.polyfit(x,np.log(y),1)
    fit_fn = np.poly1d(fit)
    yhat = np.exp(fit_fn(x))
    ax.plot(x,yhat,'--k')
    ticklbl = [ymin,.10*ymax,ymax]
    ticklbl = [np.round(t,-1) for t in ticklbl]
    ax.set_yticks(ticklbl)
    ax.get_yaxis().set_major_formatter(matplotlib.ticker.ScalarFormatter())
    txt = 'Total Return EPS\nShiller Real EPS if all Dividends Reinvested at CAPE={:.2f}'
    plt.title(txt.format(cape))
    rate = np.exp(np.log(yhat[-1]/yhat[0])/(x[-1]-x[0])*12.)-1
    txt = ' Total Return EPS growth rate: {:.2f}%\n (Uncorrected)'.format(rate*100)
    ax.text(xmin,ymax*0.25,txt)

def eps_chart():
    plt.figure()
    ax = plt.gca()
    rs = shiller()
    eps = rs['Earnings'].dropna()
    eps.plot(ax=ax,logy=True)
    xmin,xmax = ax.get_xlim()
    ymin,ymax = ax.get_ylim()
    x = np.arange(len(eps))+ax.get_xlim()[0]-1
    y = eps.values
    fit = np.polyfit(x,np.log(y),1)
    fit_fn = np.poly1d(fit)
    yhat = np.exp(fit_fn(x))
    ax.plot(x,yhat,'--k')
    ticklbl = [ymin,.10*ymax,ymax]
    ticklbl = [np.round(t,-1) for t in ticklbl]
    ax.set_yticks(ticklbl)
    ax.get_yaxis().set_major_formatter(matplotlib.ticker.ScalarFormatter())
    ax.set_ylim([ymin,ymax])
    txt = 'Shiller EPS (Real; corrected for CPI inflation)'
    plt.title(txt)
    rate = np.exp(np.log(yhat[-1]/yhat[0])/(x[-1]-x[0])*12.)-1
    txt = ' Real EPS growth rate: {:.2f}%\n(Uncorrected)'.format(rate*100)
    ax.text(xmin,ymax*0.25,txt)  

Breaking Away

The original post was an insightful look at flaws in a traditional macroeconomic measure. Coding it up in Python will be useful for future reference. Going forward, earnings per share is more likely to grow close to 5.5% than it is 1.5%. If trends continue and P/E ratios stay constant, stock prices will be at higher levels than economists expect (or have predicted). Good lesson: do not predict.

(Disclaimer: P/E ratios don’t stay constant! This is not a recommendation to buy stocks. Further, this is not professional investment advice. I am expressing an opinion and make no representations as to the accuracy, completeness, suitability, or validity of any of the information presented.)

 

You may also like...