计量经济学背景
Fama Macbeth回归指的是对面板数据进行回归的过程(其中有N个不同的个体,并且每个个体对应于多个周期T,例如白天,月,年)。所以总共有N x T obs。请注意,如果面板数据不平衡,则可以。Fama Macbeth回归是首先对每个时期进行横向分析,即在给定时间段内将N个人聚集在一起。为t = 1,...做这个。因此总共运行T回归。然后我们有每个自变量的时间序列系数。然后我们可以使用系数的时间序列进行假设检验。通常我们将平均值作为每个自变量的最终系数。我们使用t-stats来测试重要性。
我的问题
我的问题是在熊猫中实现这一点。从大熊猫的源代码中,我注意到有一个名为fama_macbeth
的过程。但我无法找到有关此问题的任何文件。
此操作也可以通过groupby
轻松完成。目前我这样做:
def fmreg(data,formula):
return smf.ols(formula,data=data).fit().params[1]
res=df.groupby('date').apply(fmreg,'ret~var1')
这是有效的,res
是一个由date
编制索引的系列,系列的值是params[1]
,它是var1
的系数。但现在我想拥有更多的自变量,我需要提取所有这些自变量的系数,但我无法弄清楚。我试过这个
def fmreg(data,formula):
return smf.ols(formula,data=data).fit().params
res=df.groupby('date').apply(fmreg,'ret~var1+var2+var3')
这不会奏效。期望的结果是res
是由date
索引的数据框,并且数据框的每列应包含每个变量的系数intercept
,var1
,{{1} }和var2
。
我还检查了var3
,他们也没有这样的内置程序。
是否有任何软件包可以生成出版品质的回归表?就像Stata中的statsmodels
和R中的outreg2
一样?
谢谢你的帮助!
答案 0 :(得分:8)
自2018年秋季起反映Fama-MacBeth图书馆情况的更新。fama_macbeth
功能已暂时从pandas
移除。那你有什么选择呢?
如果你正在使用python 3,那么你可以在LinearModels中使用Fama-MacBeth方法:https://github.com/bashtage/linearmodels/blob/master/linearmodels/panel/model.py
如果您正在使用python 2或者只是不想使用LinearModels,那么您最好的选择就是自己动手。
例如,假设您在以下面板中拥有Fama-French行业投资组合(您还计算了一些变量,如过去的beta或过去的回报用作x变量):
In [1]: import pandas as pd
import numpy as np
import statsmodels.formula.api as smf
In [4]: df = pd.read_csv('industry.csv',parse_dates=['caldt'])
df.query("caldt == '1995-07-01'")
In [5]: Out[5]:
industry caldt ret beta r12to2 r36to13
18432 Aero 1995-07-01 6.26 0.9696 0.2755 0.3466
18433 Agric 1995-07-01 3.37 1.0412 0.1260 0.0581
18434 Autos 1995-07-01 2.42 1.0274 0.0293 0.2902
18435 Banks 1995-07-01 4.82 1.4985 0.1659 0.2951
Fama-MacBeth主要涉及逐月计算相同的横截面回归模型,因此您可以使用groupby
来实现它。您可以创建一个dataframe
(它将来自groupby
)和patsy
公式的函数;然后它适合模型并返回参数估计值。这是你如何实现它的准系统版本(注意这是几年前原始提问者试图做的事情......不知道为什么它不起作用虽然它可能在那时statsmodels
结果对象方法params
未返回pandas
Series
,因此返回需要明确转换为Series
...它在当前版本的{{1 },0.23.4):
pandas
然后只计算均值,平均值的标准误差和t检验(或任何你想要的统计数据)。如下所示:
def ols_coef(x,formula):
return smf.ols(formula,data=x).fit().params
In [9]: gamma = (df.groupby('caldt')
.apply(ols_coef,'ret ~ 1 + beta + r12to2 + r36to13'))
gamma.head()
In [10]: Out[10]:
Intercept beta r12to2 r36to13
caldt
1963-07-01 -1.497012 -0.765721 4.379128 -1.918083
1963-08-01 11.144169 -6.506291 5.961584 -2.598048
1963-09-01 -2.330966 -0.741550 10.508617 -4.377293
1963-10-01 0.441941 1.127567 5.478114 -2.057173
1963-11-01 3.380485 -4.792643 3.660940 -1.210426
提高速度
使用def fm_summary(p):
s = p.describe().T
s['std_error'] = s['std']/np.sqrt(s['count'])
s['tstat'] = s['mean']/s['std_error']
return s[['mean','std_error','tstat']]
In [12]: fm_summary(gamma)
Out[12]:
mean std_error tstat
Intercept 0.754904 0.177291 4.258000
beta -0.012176 0.202629 -0.060092
r12to2 1.794548 0.356069 5.039896
r36to13 0.237873 0.186680 1.274230
进行回归会产生很大的开销(特别是考虑到您只需要估算的系数)。如果您想要更高的效率,那么您可以从statsmodels
切换到statsmodels
。编写一个执行ols估计的新函数......类似于以下内容(注意我没有做任何事情,比如检查这些矩阵的等级......):
numpy.linalg.lstsq
如果您仍在使用较旧版本的def ols_np(data,yvar,xvar):
gamma,_,_,_ = np.linalg.lstsq(data[xvar],data[yvar],rcond=None)
return pd.Series(gamma)
,则以下内容将起作用:
以下是使用pandas
中的fama_macbeth
函数的示例:
pandas
注意,结构。 >>> df
y x
date id
2012-01-01 1 0.1 0.4
2 0.3 0.6
3 0.4 0.2
4 0.0 1.2
2012-02-01 1 0.2 0.7
2 0.4 0.5
3 0.2 0.1
4 0.1 0.0
2012-03-01 1 0.4 0.8
2 0.6 0.1
3 0.7 0.6
4 0.4 -0.1
函数期望y-var和x-vars具有多索引,其中date作为第一个变量,stock / firm / entity id作为索引中的第二个变量:
fama_macbeth
请注意,只需打印>>> fm = pd.fama_macbeth(y=df['y'],x=df[['x']])
>>> fm
----------------------Summary of Fama-MacBeth Analysis-------------------------
Formula: Y ~ x + intercept
# betas : 3
----------------------Summary of Estimated Coefficients------------------------
Variable Beta Std Err t-stat CI 2.5% CI 97.5%
(x) -0.0227 0.1276 -0.18 -0.2728 0.2273
(intercept) 0.3531 0.0842 4.19 0.1881 0.5181
--------------------------------End of Summary---------------------------------
即可调用fm.summary
fm
另外,请注意>>> fm.summary
----------------------Summary of Fama-MacBeth Analysis-------------------------
Formula: Y ~ x + intercept
# betas : 3
----------------------Summary of Estimated Coefficients------------------------
Variable Beta Std Err t-stat CI 2.5% CI 97.5%
(x) -0.0227 0.1276 -0.18 -0.2728 0.2273
(intercept) 0.3531 0.0842 4.19 0.1881 0.5181
--------------------------------End of Summary---------------------------------
函数会自动添加拦截(与fama_macbeth
例程相对)。此外,x-var必须是statsmodels
,因此如果您只传递一列,则需要将其作为dataframe
传递。
如果你不想进行拦截,你必须这样做:
df[['x']]
答案 1 :(得分:2)
编辑:新库
存在一个更新的库,可以通过以下命令进行安装:
pip install finance-byu
此处的文档:https://fin-585-byu.readthedocs.io/en/latest/contents.html
新库包含速度更快的Fama Macbeth回归实现和更新的Regtable
类。新的库还包括GRS统计信息,这对于对Fama Macbeth回归感兴趣的人也可能有用。
作为上述Karl D.答案的更新,现在存在一个非常年轻的库,该库使用python进行回归表生成并包含Fama Macbeth回归函数。
文档:https://byu-finance-library-finance-byu.readthedocs.io/en/latest/contents.html
该库当前可以从测试PyPi服务器导入:
pip install -i https://test.pypi.org/simple/ finance-byu
文档中的此页面概述了Fama Macbeth函数:https://byu-finance-library-finance-byu.readthedocs.io/en/latest/fama_macbeth.html
有一个实现与上面的numpy
线性代数函数的Karl D.实现非常相似,当大量的joblib
用于并行化时,可以利用numba
进行并行化以提高性能。数据中的时间段,以及使用>>> from finance_byu.fama_macbeth import fama_macbeth, fama_macbeth_parallel, fm_summary, fama_macbeth_numba
>>> import pandas as pd
>>> import time
>>> import numpy as np
>>>
>>> n_jobs = 5
>>> n_firms = 1.0e2
>>> n_periods = 1.0e2
>>>
>>> def firm(fid):
>>> f = np.random.random((int(n_periods),4))
>>> f = pd.DataFrame(f)
>>> f['period'] = f.index
>>> f['firmid'] = fid
>>> return f
>>> df = [firm(i) for i in range(int(n_firms))]
>>> df = pd.concat(df).rename(columns={0:'ret',1:'exmkt',2:'smb',3:'hml'})
>>> df.head()
ret exmkt smb hml period firmid
0 0.766593 0.002390 0.496230 0.992345 0 0
1 0.346250 0.509880 0.083644 0.732374 1 0
2 0.787731 0.204211 0.705075 0.313182 2 0
3 0.904969 0.338722 0.437298 0.669285 3 0
4 0.121908 0.827623 0.319610 0.455530 4 0
>>> result = fama_macbeth(df,'period','ret',['exmkt','smb','hml'],intercept=True)
>>> result.head()
intercept exmkt smb hml
period
0 0.655784 -0.160938 -0.109336 0.028015
1 0.455177 0.033941 0.085344 0.013814
2 0.410705 -0.084130 0.218568 0.016897
3 0.410537 0.010719 0.208912 0.001029
4 0.439061 0.046104 -0.084381 0.199775
>>> fm_summary(result)
mean std_error tstat
intercept 0.506834 0.008793 57.643021
exmkt 0.004750 0.009828 0.483269
smb -0.012702 0.010842 -1.171530
hml 0.004276 0.010530 0.406119
>>> %timeit fama_macbeth(df,'period','ret',['exmkt','smb','hml'],intercept=True)
123 ms ± 117 µs per loop (mean ± std. dev. of 7 runs, 10 loops each
>>> %timeit fama_macbeth_parallel(df,'period','ret',['exmkt','smb','hml'],intercept=True,n_jobs=n_jobs,memmap=False)
146 ms ± 16.9 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
>>> %timeit fama_macbeth_numba(df,'period','ret',['exmkt','smb','hml'],intercept=True)
5.04 ms ± 5.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
进行优化的实现,可在小型数据集上节省一个数量级。
下面是一个带有少量模拟数据集的示例,如文档中所示:
>>> from finance_byu.regtables import Regtable
>>> import pandas as pd
>>> import statsmodels.formula.api as smf
>>> import numpy as np
>>>
>>>
>>> nobs = 1000
>>> df = pd.DataFrame(np.random.random((nobs,3))).rename(columns={0:'age',1:'bmi',2:'hincome'})
>>> df['age'] = df['age']*100
>>> df['bmi'] = df['bmi']*30
>>> df['hincome'] = df['hincome']*100000
>>> df['hincome'] = pd.qcut(df['hincome'],16,labels=False)
>>> df['rich'] = df['hincome'] > 13
>>> df['gender'] = np.random.choice(['M','F'],nobs)
>>> df['race'] = np.random.choice(['W','B','H','O'],nobs)
>>>
>>> regformulas = ['bmi ~ age',
>>> 'bmi ~ np.log(age)',
>>> 'bmi ~ C(gender) + np.log(age)',
>>> 'bmi ~ C(gender) + C(race) + np.log(age)',
>>> 'bmi ~ C(gender) + rich + C(gender)*rich + C(race) + np.log(age)',
>>> 'bmi ~ -1 + np.log(age)',
>>> 'bmi ~ -1 + C(race) + np.log(age)']
>>> reg = [smf.ols(f,df).fit() for f in regformulas]
>>> tbl = Regtable(reg)
>>> tbl.render()
注意:关闭内存映射可以进行公平的比较,而不必在每次运行时都生成新数据。使用memmap,并行实现将仅提取缓存的结果。
以下是使用模拟数据的表类的几个简单实现:
>>> df2 = pd.DataFrame(np.random.random((nobs,10)))
>>> df2.columns = ['t0_vw','t4_vw','et_vw','t0_ew','t4_ew','et_ew','mktrf','smb','hml','umd']
>>> regformulas2 = ['t0_vw ~ mktrf',
>>> 't0_vw ~ mktrf + smb + hml',
>>> 't0_vw ~ mktrf + smb + hml + umd',
>>> 't4_vw ~ mktrf',
>>> 't4_vw ~ mktrf + smb + hml',
>>> 't4_vw ~ mktrf + smb + hml + umd',
>>> 'et_vw ~ mktrf',
>>> 'et_vw ~ mktrf + smb + hml',
>>> 'et_vw ~ mktrf + smb + hml + umd',
>>> 't0_ew ~ mktrf',
>>> 't0_ew ~ mktrf + smb + hml',
>>> 't0_ew ~ mktrf + smb + hml + umd',
>>> 't4_ew ~ mktrf',
>>> 't4_ew ~ mktrf + smb + hml',
>>> 't4_ew ~ mktrf + smb + hml + umd',
>>> 'et_ew ~ mktrf',
>>> 'et_ew ~ mktrf + smb + hml',
>>> 'et_ew ~ mktrf + smb + hml + umd'
>>> ]
>>> regnames = ['Small VW','','',
>>> 'Large VW','','',
>>> 'Spread VW','','',
>>> 'Small EW','','',
>>> 'Large EW','','',
>>> 'Spread EW','',''
>>> ]
>>> reg2 = [smf.ols(f,df2).fit() for f in regformulas2]
>>>
>>> tbl2 = Regtable(reg2,orientation='horizontal',regnames=regnames,sig='coeff',intercept_name='alpha',nobs=False,rsq=False,stat='se')
>>> tbl2.render()
tbl.to_latex()
产生以下内容:
Regtable类的文档在这里:https://byu-finance-library-finance-byu.readthedocs.io/en/latest/regtables.html
这些表可以导出到LaTeX,以便轻松地写入文件中:
guard let url = URL(string: "https://rotogrinders.com/projected-stats/nba-player.csv?site=fanduel") else { return }
let config = URLSessionConfiguration.default
// I don't know what I'm doing here. Also, the user name and password is not correct
let credential = URLCredential(user: "joe", password: "12345", persistence: .forSession)
let protectionSpace = URLProtectionSpace(host: "rotogrinders.com", port: 443, protocol: "https", realm: "Restricted", authenticationMethod: NSURLAuthenticationMethodHTTPBasic)
// I don't know what I'm doing here either.
let credentialStorage = URLCredentialStorage()
credentialStorage.set(credential, for: protectionSpace)
config.urlCredentialStorage = credentialStorage
let task = URLSession(configuration: config).dataTask(with: url) { data, response, error in
guard data != nil else { return }
guard let rows = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)?.components(separatedBy: "\n") else { return }
print(rows)
}
task.resume()
答案 2 :(得分:0)
一种快速而肮脏的解决方案,可以解决问题并继续使用与您以前使用的相同的东西。
对我有用。
def fmreg(data,formula):
return smf.ols(formula,data=data).fit().params[:]
res = df.groupby('date').apply(fmreg,'ret~var1+var2+var3')