Pandas 滚动回归:循环的替代方法
Pandas rolling regression: alternatives to looping
我在已弃用的 stats/ols
模块中很好地利用了 pandas' MovingOLS
class(来源 here)。不幸的是,它被 pandas 0.20.
完全破坏了
如何 运行 以有效的方式滚动 OLS 回归的问题已被问过好几次(例如 ),但措辞有点宽泛,没有很好的答案,在我看来。
这是我的问题:
如何才能最好地模仿pandas'MovingOLS
的基本框架?此 class 最吸引人的功能是能够将多个 methods/attributes 视为单独的时间序列——即系数、r 平方、t 统计量等,无需重新 运行 回归。例如,您可以创建类似 model = pd.MovingOLS(y, x)
的内容,然后调用 .t_stat
、.rmse
、.std_err
等。相反,在下面的示例中,我看不到被迫单独计算每个统计数据的方法。有没有一种方法不涉及创建 sliding/rolling "blocks" (步幅)和 运行ning regressions/using 线性代数来获取每个模型参数?
更广泛地说,pandas 中发生了什么导致 rolling.apply
无法执行更复杂的函数?* 当您创建 .rolling
对象,用外行的话来说,内部发生了什么——它与循环遍历每个 window 并创建一个高维数组有根本的不同吗?
*即func
传递给.apply
:
Must produce a single value from an ndarray input *args and **kwargs
are passed to the function
这是我目前使用的一些样本数据,回归贸易加权美元对利差和铜价的百分比变化。 (这没有多大意义;只是随机选择了这些。)我已经将它从基于 class 的实现中取出并试图将其简化为一个更简单的脚本。
from datetime import date
from pandas_datareader.data import DataReader
import statsmodels.formula.api as smf
syms = {'TWEXBMTH' : 'usd',
'T10Y2YM' : 'term_spread',
'PCOPPUSDM' : 'copper'
}
start = date(2000, 1, 1)
data = (DataReader(syms.keys(), 'fred', start)
.pct_change()
.dropna())
data = data.rename(columns = syms)
data = data.assign(intercept = 1.) # required by statsmodels OLS
def sliding_windows(x, window):
"""Create rolling/sliding windows of length ~window~.
Given an array of shape (y, z), it will return "blocks" of shape
(x - window + 1, window, z)."""
return np.array([x[i:i + window] for i
in range(0, x.shape[0] - window + 1)])
data.head(3)
Out[33]:
usd term_spread copper intercept
DATE
2000-02-01 0.012573 -1.409091 -0.019972 1.0
2000-03-01 -0.000079 2.000000 -0.037202 1.0
2000-04-01 0.005642 0.518519 -0.033275 1.0
window = 36
wins = sliding_windows(data.values, window=window)
y, x = wins[:, :, 0], wins[:, :, 1:]
coefs = []
for endog, exog in zip(y, x):
model = smf.OLS(endog, exog).fit()
# The full set of model attributes gets lost with each loop
coefs.append(model.params)
df = pd.DataFrame(coefs, columns=data.iloc[:, 1:].columns,
index=data.index[window - 1:])
df.head(3) # rolling 36m coefficients
Out[70]:
term_spread copper intercept
DATE
2003-01-01 -0.000122 -0.018426 0.001937
2003-02-01 0.000391 -0.015740 0.001597
2003-03-01 0.000655 -0.016811 0.001546
我创建了一个 ols
模块,旨在模仿 pandas' 已弃用 MovingOLS
;它是 here.
三核类:
OLS
:静态(单window)普通最小二乘回归。输出是 NumPy 数组
RollingOLS
:滚动(多window)普通最小二乘回归。输出是高维 NumPy 数组。
PandasRollingOLS
:将 RollingOLS
的结果包装在 pandas 系列和数据帧中。旨在模仿已弃用的 pandas 模块的外观。
请注意,该模块是 package 的一部分(我目前正在将其上传到 PyPi),它需要一个包间导入。
上面的前两个 类 完全在 NumPy 中实现,主要使用矩阵代数。 RollingOLS
也广泛利用广播。属性在很大程度上模仿了 statsmodels 的 OLS RegressionResultsWrapper
.
一个例子:
import urllib.parse
import pandas as pd
from pyfinance.ols import PandasRollingOLS
# You can also do this with pandas-datareader; here's the hard way
url = "https://fred.stlouisfed.org/graph/fredgraph.csv"
syms = {
"TWEXBMTH" : "usd",
"T10Y2YM" : "term_spread",
"GOLDAMGBD228NLBM" : "gold",
}
params = {
"fq": "Monthly,Monthly,Monthly",
"id": ",".join(syms.keys()),
"cosd": "2000-01-01",
"coed": "2019-02-01",
}
data = pd.read_csv(
url + "?" + urllib.parse.urlencode(params, safe=","),
na_values={"."},
parse_dates=["DATE"],
index_col=0
).pct_change().dropna().rename(columns=syms)
print(data.head())
# usd term_spread gold
# DATE
# 2000-02-01 0.012580 -1.409091 0.057152
# 2000-03-01 -0.000113 2.000000 -0.047034
# 2000-04-01 0.005634 0.518519 -0.023520
# 2000-05-01 0.022017 -0.097561 -0.016675
# 2000-06-01 -0.010116 0.027027 0.036599
y = data.usd
x = data.drop('usd', axis=1)
window = 12 # months
model = PandasRollingOLS(y=y, x=x, window=window)
print(model.beta.head()) # Coefficients excluding the intercept
# term_spread gold
# DATE
# 2001-01-01 0.000033 -0.054261
# 2001-02-01 0.000277 -0.188556
# 2001-03-01 0.002432 -0.294865
# 2001-04-01 0.002796 -0.334880
# 2001-05-01 0.002448 -0.241902
print(model.fstat.head())
# DATE
# 2001-01-01 0.136991
# 2001-02-01 1.233794
# 2001-03-01 3.053000
# 2001-04-01 3.997486
# 2001-05-01 3.855118
# Name: fstat, dtype: float64
print(model.rsq.head()) # R-squared
# DATE
# 2001-01-01 0.029543
# 2001-02-01 0.215179
# 2001-03-01 0.404210
# 2001-04-01 0.470432
# 2001-05-01 0.461408
# Name: rsq, dtype: float64
使用自定义滚动应用功能。
import numpy as np
df['slope'] = df.values.rolling(window=125).apply(lambda x: np.polyfit(np.array(range(0,125)), x, 1)[0], raw=True)
我在已弃用的 stats/ols
模块中很好地利用了 pandas' MovingOLS
class(来源 here)。不幸的是,它被 pandas 0.20.
如何 运行 以有效的方式滚动 OLS 回归的问题已被问过好几次(例如
这是我的问题:
如何才能最好地模仿pandas'
MovingOLS
的基本框架?此 class 最吸引人的功能是能够将多个 methods/attributes 视为单独的时间序列——即系数、r 平方、t 统计量等,无需重新 运行 回归。例如,您可以创建类似model = pd.MovingOLS(y, x)
的内容,然后调用.t_stat
、.rmse
、.std_err
等。相反,在下面的示例中,我看不到被迫单独计算每个统计数据的方法。有没有一种方法不涉及创建 sliding/rolling "blocks" (步幅)和 运行ning regressions/using 线性代数来获取每个模型参数?更广泛地说,pandas 中发生了什么导致
rolling.apply
无法执行更复杂的函数?* 当您创建.rolling
对象,用外行的话来说,内部发生了什么——它与循环遍历每个 window 并创建一个高维数组有根本的不同吗?
*即func
传递给.apply
:
Must produce a single value from an ndarray input *args and **kwargs are passed to the function
这是我目前使用的一些样本数据,回归贸易加权美元对利差和铜价的百分比变化。 (这没有多大意义;只是随机选择了这些。)我已经将它从基于 class 的实现中取出并试图将其简化为一个更简单的脚本。
from datetime import date
from pandas_datareader.data import DataReader
import statsmodels.formula.api as smf
syms = {'TWEXBMTH' : 'usd',
'T10Y2YM' : 'term_spread',
'PCOPPUSDM' : 'copper'
}
start = date(2000, 1, 1)
data = (DataReader(syms.keys(), 'fred', start)
.pct_change()
.dropna())
data = data.rename(columns = syms)
data = data.assign(intercept = 1.) # required by statsmodels OLS
def sliding_windows(x, window):
"""Create rolling/sliding windows of length ~window~.
Given an array of shape (y, z), it will return "blocks" of shape
(x - window + 1, window, z)."""
return np.array([x[i:i + window] for i
in range(0, x.shape[0] - window + 1)])
data.head(3)
Out[33]:
usd term_spread copper intercept
DATE
2000-02-01 0.012573 -1.409091 -0.019972 1.0
2000-03-01 -0.000079 2.000000 -0.037202 1.0
2000-04-01 0.005642 0.518519 -0.033275 1.0
window = 36
wins = sliding_windows(data.values, window=window)
y, x = wins[:, :, 0], wins[:, :, 1:]
coefs = []
for endog, exog in zip(y, x):
model = smf.OLS(endog, exog).fit()
# The full set of model attributes gets lost with each loop
coefs.append(model.params)
df = pd.DataFrame(coefs, columns=data.iloc[:, 1:].columns,
index=data.index[window - 1:])
df.head(3) # rolling 36m coefficients
Out[70]:
term_spread copper intercept
DATE
2003-01-01 -0.000122 -0.018426 0.001937
2003-02-01 0.000391 -0.015740 0.001597
2003-03-01 0.000655 -0.016811 0.001546
我创建了一个 ols
模块,旨在模仿 pandas' 已弃用 MovingOLS
;它是 here.
三核类:
OLS
:静态(单window)普通最小二乘回归。输出是 NumPy 数组RollingOLS
:滚动(多window)普通最小二乘回归。输出是高维 NumPy 数组。PandasRollingOLS
:将RollingOLS
的结果包装在 pandas 系列和数据帧中。旨在模仿已弃用的 pandas 模块的外观。
请注意,该模块是 package 的一部分(我目前正在将其上传到 PyPi),它需要一个包间导入。
上面的前两个 类 完全在 NumPy 中实现,主要使用矩阵代数。 RollingOLS
也广泛利用广播。属性在很大程度上模仿了 statsmodels 的 OLS RegressionResultsWrapper
.
一个例子:
import urllib.parse
import pandas as pd
from pyfinance.ols import PandasRollingOLS
# You can also do this with pandas-datareader; here's the hard way
url = "https://fred.stlouisfed.org/graph/fredgraph.csv"
syms = {
"TWEXBMTH" : "usd",
"T10Y2YM" : "term_spread",
"GOLDAMGBD228NLBM" : "gold",
}
params = {
"fq": "Monthly,Monthly,Monthly",
"id": ",".join(syms.keys()),
"cosd": "2000-01-01",
"coed": "2019-02-01",
}
data = pd.read_csv(
url + "?" + urllib.parse.urlencode(params, safe=","),
na_values={"."},
parse_dates=["DATE"],
index_col=0
).pct_change().dropna().rename(columns=syms)
print(data.head())
# usd term_spread gold
# DATE
# 2000-02-01 0.012580 -1.409091 0.057152
# 2000-03-01 -0.000113 2.000000 -0.047034
# 2000-04-01 0.005634 0.518519 -0.023520
# 2000-05-01 0.022017 -0.097561 -0.016675
# 2000-06-01 -0.010116 0.027027 0.036599
y = data.usd
x = data.drop('usd', axis=1)
window = 12 # months
model = PandasRollingOLS(y=y, x=x, window=window)
print(model.beta.head()) # Coefficients excluding the intercept
# term_spread gold
# DATE
# 2001-01-01 0.000033 -0.054261
# 2001-02-01 0.000277 -0.188556
# 2001-03-01 0.002432 -0.294865
# 2001-04-01 0.002796 -0.334880
# 2001-05-01 0.002448 -0.241902
print(model.fstat.head())
# DATE
# 2001-01-01 0.136991
# 2001-02-01 1.233794
# 2001-03-01 3.053000
# 2001-04-01 3.997486
# 2001-05-01 3.855118
# Name: fstat, dtype: float64
print(model.rsq.head()) # R-squared
# DATE
# 2001-01-01 0.029543
# 2001-02-01 0.215179
# 2001-03-01 0.404210
# 2001-04-01 0.470432
# 2001-05-01 0.461408
# Name: rsq, dtype: float64
使用自定义滚动应用功能。
import numpy as np
df['slope'] = df.values.rolling(window=125).apply(lambda x: np.polyfit(np.array(range(0,125)), x, 1)[0], raw=True)