Python中的Fama Macbeth回归(Pandas或Statsmodels)

Python中的Fama Macbeth回归(Pandas或Statsmodels),python,r,pandas,statsmodels,Python,R,Pandas,Statsmodels,计量经济学背景 Fama-Macbeth回归指的是对面板数据进行回归的过程(其中有N个不同的个体,每个个体对应于多个时段T,例如天、月、年)。所以总共有N x T OB。请注意,如果面板数据不平衡,则没有问题 Fama-Macbeth回归是对每个时期进行跨部门的首次回归,即在给定时期t内将N个个体汇集在一起。对于t=1,…t,这样做。因此,总的来说,T回归是可行的。然后我们有一个时间序列的系数为每个独立变量。然后,我们可以使用系数的时间序列进行假设检验。通常我们将平均值作为每个自变量的最终系数。

计量经济学背景

Fama-Macbeth回归指的是对面板数据进行回归的过程(其中有N个不同的个体,每个个体对应于多个时段T,例如天、月、年)。所以总共有N x T OB。请注意,如果面板数据不平衡,则没有问题

Fama-Macbeth回归是对每个时期进行跨部门的首次回归,即在给定时期t内将N个个体汇集在一起。对于t=1,…t,这样做。因此,总的来说,T回归是可行的。然后我们有一个时间序列的系数为每个独立变量。然后,我们可以使用系数的时间序列进行假设检验。通常我们将平均值作为每个自变量的最终系数。我们用t-stats检验显著性

我的问题

我的问题是在熊猫身上实现这一点。从pandas的源代码中,我注意到有一个过程叫做
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
索引的数据帧,数据帧的每一列都应该包含每个变量
截距
var1
var2
var3
的系数

我还检查了
statsmodels
,它们也没有这样的内置过程

是否有任何软件包可以生成出版物质量回归表?像Stata中的
outreg2
和R中的
texreg

谢谢你的帮助

反映Fama MacBeth截至2018年秋季图书馆状况的更新。
fama_macbeth
功能已从
pandas
中删除一段时间。那么你有什么选择

  • 如果您使用的是python 3,那么可以在LinearModels中使用Fama MacBeth方法:

  • 如果您正在使用Python2,或者只是不想使用LinearModels,那么最好的选择可能是使用自己的

  • 例如,假设您将Fama French行业投资组合放在如下面板中(您还计算了一些变量,如过去的beta或过去的回报率,用作您的x变量):

    Fama MacBeth主要涉及按月计算相同的横截面回归模型,因此您可以使用
    groupby
    实现它。您可以创建一个函数,该函数采用
    数据帧
    (它将来自
    groupby
    )和
    patsy
    公式;然后拟合模型并返回参数估计值。下面是如何实现它的基本版本(注意,这是最初的提问者几年前试图做的……不确定为什么它不起作用,尽管当时可能是
    statsmodels
    结果对象方法
    params
    没有返回
    pandas
    Series
    ,因此需要将返回显式转换为
    Series
    。)k在当前版本的
    pandas
    ,0.23.4)中的罚款:

    然后计算平均值、平均值的标准误差和t检验(或任何你想要的统计数据)。如下所示:

    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
    切换到
    numpy.linalg.lstsq
    。编写一个新的函数来进行ols估计…如下所示(请注意,我没有做任何类似于检查这些矩阵的秩的事情…):

    如果您仍在使用较旧版本的
    pandas
    ,则以下操作将起作用:

    下面是一个使用
    pandas
    中的
    fama_macbeth
    函数的示例:

    >>> 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
    
    请注意,结构。
    fama_macbeth
    函数期望y-var和x-var具有多个指数,其中日期为第一个变量,股票/公司/实体id为指数中的第二个变量:

    >>> 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
    即可调用fm.summary

    >>> 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
    函数会自动添加一个截距(与
    statsmodels
    例程相反)。此外,x-var必须是一个
    dataframe
    ,因此如果只传递一列,则需要将其作为
    df['x']
    传递

    如果您不想拦截,则必须执行以下操作:

    >>> fm  = pd.fama_macbeth(y=df['y'],x=df[['x']],intercept=False)
    

    编辑:新库

    已存在更新的库,可通过以下命令安装该库:

    pip install finance-byu
    
    此处的文档:

    新库包括Fama Macbeth回归实现和一个有助于报告结果的
    Regtable

    文档中的本页概述了Fama Macbeth的功能:

    有一个实现非常类似于Karl D.的上面的实现,使用
    numpy
    的线性代数函数,一个利用
    joblib
    进行并行化的实现,当数据中存在大量时间段时,可以提高性能,另一个利用
    numba
    进行优化的实现at在小型数据集上削减了一个数量级

    以下是一个小的模拟数据集示例,如文档所示:

    >>> 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)
    
    注意:关闭memmap有助于公平比较,而不会在每次运行时生成新数据
    >>> fm  = pd.fama_macbeth(y=df['y'],x=df[['x']],intercept=False)
    
    pip install finance-byu
    
    >>> 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()
    
    >>> 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()