# Fama-French regression¶

The purpose of this application is to estimate a Fama-French 3-factor model for a specific corporation. We will study IBM. First, we probably need the usual tools.

In [7]:
import pandas as pd # pandas is excellent for creating and manipulating dataframes, R-style
import numpy as np # great for array manipulations and simulations
import matplotlib.pyplot as plt #graphing module with matlab-like properties
%matplotlib inline
import requests # to make requests to webpages (like mine)
import statsmodels.api as sm # module full of standard statistical and econometric models, including OLS and time-series stuff
from IPython.display import Latex # to be able to display latex in python command cells
import getFamaFrenchFactors as gff # a library that downloads FF data nicely, could use datareader for that too
import datetime


Next we build the data we need to run our regressions using various scraping libraries.

In [8]:
# Get five years worth of data for the Fama French 3-factor model (monthly data)

FF3data = gff.famaFrench3Factor(frequency='m') #Datareader could do this too
FF3data=FF3data.tail(60) # keep 5 years of data
FF3data['Month']=FF3data['date_ff_factors'].dt.month
FF3data['Year']=FF3data['date_ff_factors'].dt.year #creates month and year variables for merging below

#import yfinance as yf
#IBM = yf.Ticker("IBM")
#IBMdata=IBM.history(period="5y")

# get 5 years of adjusted close price data for IBM

end=datetime.datetime.today()
IBMdata=DataReader('IBM', 'yahoo', '2015-01-01', end)['Adj Close'] # (at least) five years worth of IBM data
IBMdataF=IBMdata.resample('M').last() #only keep the last observation of every month
IBMdataF=IBMdataF.to_frame() # converts the series format to a frame format which makes the upcomning merge easier

IBMdataF['Year']=IBMdataF.index.year
IBMdataF['Month']=IBMdataF.index.month

# now we merge our two datasets into one, using year and month as the merging variables

datanow=pd.merge(
IBMdataF,
FF3data,
left_on=['Year','Month'],
right_on=['Year','Month'])

# finally we create IBM's monthly return data from the adjusted price series
# Note the use of panda's shift operator to create a lag variable

datanow.sort_values(['date_ff_factors'],axis=0, ascending=False, inplace=True)

datanow['rIBM']=(datanow['IBM']/datanow.IBM.shift(-1)-1) #computes monthly return based on adjusted series for IBM
datanow['Mkt']=datanow['Mkt-RF']+datanow['RF'] # this the market return, we will use that below
datanow.dropna(subset=['rIBM'], inplace=True) # drop the entries with missing returns (missing due to lag operator)


Now let's look at our data a bit to make sure it all looks good.

In [9]:
datanow[0:4]

Out[9]:
IBM Year Month date_ff_factors Mkt-RF SMB HML RF rIBM Mkt
59 123.519997 2020 11 2020-11-30 0.1247 0.0546 0.0219 0.0001 0.122256 0.1248
58 110.064018 2020 10 2020-10-31 -0.0210 0.0444 0.0388 0.0001 -0.082272 -0.0209
57 119.930939 2020 9 2020-09-30 -0.0363 0.0006 -0.0256 0.0001 -0.013300 -0.0362
56 121.547493 2020 8 2020-08-31 0.0763 -0.0026 -0.0295 0.0001 0.016142 0.0764

Next we plot IBM's monthly return vs the market and fit a capm line. Here we estimate IBM's CAPM beta to be around $1.2$ (this may changes as I re-run this book and data get updated.)

In [10]:
datanow.sort_values(['date_ff_factors'],axis=0, ascending=False, inplace=True)

x=datanow['Mkt']
y=datanow['rIBM']

fig, ax = plt.subplots()
plt.plot(x, y, 'o') # each dot is a given month

m, b = np.polyfit(x, y, 1)  # fit the best possible line, beta is the slope of that line
plt.plot(x, m*x + b)  # draw the line on our chart

print('Our estimate of IBMs (CAPM) beta is %.3f' %m)

ax.set_ylabel('Return on IBM')
ax.set_xlabel('Market return')

plt.show() # show our work

Our estimate of IBMs (CAPM) beta is 1.189


Instead of taking the quick route above, we can also run a proper CAPM regression. For that we need the excess return on IBM and then we need to estimate the following model: $$r^{IBM}_t-r^F_t= \alpha + \beta \left(r^{S\&P}_t-r^F_t\right)+ \epsilon_t.$$ If CAPM holds, $\alpha$ should estimate to a number that is not statistically different from zero. We will need to create a few variables first.

As shown below, we get about the same estimate of $\beta$ as with the quick route (that's because $r^F$ shows little variability so it is as if it were not in the regression at all) and we do get a statistically insignificant $\alpha.$

In [11]:
datanow['rIBM-rF']=datanow['rIBM']-datanow['RF']

y=datanow['rIBM-rF']
x=datanow['Mkt-RF']
x=sm.add_constant(x) # we run a standard OLS with constant
mod=sm.OLS(y,x)
res=mod.fit()
res.summary()

Out[11]:
Dep. Variable: R-squared: rIBM-rF 0.596 OLS 0.589 Least Squares 84.11 Sun, 24 Jan 2021 8.12e-13 15:44:37 98.453 59 -192.9 57 -188.8 1 nonrobust
coef std err t P>|t| [0.025 0.975] -0.0110 0.006 -1.766 0.083 -0.024 0.001 1.1866 0.129 9.171 0.000 0.927 1.446
 Omnibus: Durbin-Watson: 1.646 2.155 0.439 0.942 -0.256 0.624 3.348 21.4

Warnings:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.

Now we want to estimate a 3-factor Fama-French model for IBM: $$\left(r^{IBM}_t-r^F_t\right)= \alpha + \beta_{Market} \left(r^{Market}_t-r^F_t\right) + \beta_{HML} r^{HML}_t + \beta_{SMB} r^{SMB}_t + \epsilon_t$$ where $\epsilon$ is an error term with all the standard properties. The python for this and the results follow. Only the CAPM beta is significant. Its size changes ever so slightly (though not significantly) which owes to the different specification.

In [12]:
X=datanow[['Mkt-RF','SMB','HML']]
X=sm.add_constant(X) # here we're building our right-hand side variables
y=datanow['rIBM']

mod=sm.OLS(y,X)
res=mod.fit()
res.summary()

Out[12]:
Dep. Variable: R-squared: rIBM 0.595 OLS 0.573 Least Squares 26.90 Sun, 24 Jan 2021 7.67e-11 15:44:37 98.406 59 -188.8 55 -180.5 3 nonrobust
coef std err t P>|t| [0.025 0.975] -0.0106 0.007 -1.601 0.115 -0.024 0.003 1.2066 0.149 8.122 0.000 0.909 1.504 -0.0691 0.277 -0.250 0.804 -0.624 0.486 -0.0397 0.196 -0.203 0.840 -0.432 0.352
 Omnibus: Durbin-Watson: 1.57 2.135 0.456 0.894 -0.256 0.639 3.317 46.8

Warnings:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.