Fama Macbeth回归Python(熊猫或Statsmodels)

时间:2014-06-06 05:01:09

标签: python r pandas statsmodels

计量经济学背景

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索引的数据框,并且数据框的每列应包含每个变量的系数interceptvar1,{{1} }和var2

我还检查了var3,他们也没有这样的内置程序。

是否有任何软件包可以生成出版品质的回归表?就像Stata中的statsmodels和R中的outreg2一样? 谢谢你的帮助!

3 个答案:

答案 0 :(得分:8)

自2018年秋季起反映Fama-MacBeth图书馆情况的更新。fama_macbeth功能已暂时从pandas移除。那你有什么选择呢?

  1. 如果你正在使用python 3,那么你可以在LinearModels中使用Fama-MacBeth方法:https://github.com/bashtage/linearmodels/blob/master/linearmodels/panel/model.py

  2. 如果您正在使用python 2或者只是不想使用LinearModels,那么您最好的选择就是自己动手。

  3. 例如,假设您在以下面板中拥有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()

产生以下内容: enter image description here

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')