在许多数据帧上进行高效的Python测试版计算

在许多数据帧上进行高效的Python测试版计算,python,algorithm,performance,pandas,dataframe,Python,Algorithm,Performance,Pandas,Dataframe,我有许多(4000+个)股票数据的CSV(日期、开盘、高位、低位、收盘),我将其导入到各个Pandas数据框中进行分析。我是python新手,希望为每种股票计算12个月的滚动beta,我找到了一篇文章来计算滚动beta(),但是在我下面的代码中使用它需要2.5个小时!考虑到我可以在3分钟内在SQL表中运行完全相同的计算,这太慢了 如何提高下面代码的性能以与SQL相匹配?我知道Pandas/python具有这种能力。我当前的方法在每一行上循环,我知道这会降低性能,但我不知道在数据帧上执行滚动窗口b

我有许多(4000+个)股票数据的CSV(日期、开盘、高位、低位、收盘),我将其导入到各个Pandas数据框中进行分析。我是python新手,希望为每种股票计算12个月的滚动beta,我找到了一篇文章来计算滚动beta(),但是在我下面的代码中使用它需要2.5个小时!考虑到我可以在3分钟内在SQL表中运行完全相同的计算,这太慢了

如何提高下面代码的性能以与SQL相匹配?我知道Pandas/python具有这种能力。我当前的方法在每一行上循环,我知道这会降低性能,但我不知道在数据帧上执行滚动窗口beta计算的任何聚合方法

注:将CSV加载到单个数据帧并计算每日回报的前两个步骤只需约20秒。我所有的CSV数据帧都存储在名为“filesload”的字典中,名字如“XAO”

非常感谢您的帮助! 谢谢:)

生成随机股票数据
20年4000只股票的月度数据

dates = pd.date_range('1995-12-31', periods=480, freq='M', name='Date')
stoks = pd.Index(['s{:04d}'.format(i) for i in range(4000)])
df = pd.DataFrame(np.random.rand(480, 4000), dates, stoks)


滚动功能
返回已准备好应用自定义函数的groupby对象


测试功能
使用OLS回归的闭式解
假设第0列为市场


演示


定时


验证
将计算结果与OP进行比较

def calc_beta(df):
    np_array = df.values
    m = np_array[:,0] # market returns are column zero from numpy array
    s = np_array[:,1] # stock returns are column one from numpy array
    covariance = np.cov(s,m) # Calculate covariance between stock and market
    beta = covariance[0,1]/covariance[1,1]
    return beta



注意第一个单元格
与上述验证计算值相同

betas = rdf.apply(beta)
betas.iloc[:5, :5]


评论回复
模拟多个数据帧的完整工作示例

num_sec_dfs = 4000

cols = ['Open', 'High', 'Low', 'Close']
dfs = {'s{:04d}'.format(i): pd.DataFrame(np.random.rand(480, 4), dates, cols) for i in range(num_sec_dfs)}

market = pd.Series(np.random.rand(480), dates, name='Market')

df = pd.concat([market] + [dfs[k].Close.rename(k) for k in dfs.keys()], axis=1).sort_index(1)

betas = roll(df.pct_change().dropna(), 12).apply(beta)

for c, col in betas.iteritems():
    dfs[c]['Beta'] = col

dfs['s0001'].head(20)
生成随机股票数据
20年4000只股票的月度数据

dates = pd.date_range('1995-12-31', periods=480, freq='M', name='Date')
stoks = pd.Index(['s{:04d}'.format(i) for i in range(4000)])
df = pd.DataFrame(np.random.rand(480, 4000), dates, stoks)


滚动功能
返回已准备好应用自定义函数的groupby对象


测试功能
使用OLS回归的闭式解
假设第0列为市场


演示


定时


验证
将计算结果与OP进行比较

def calc_beta(df):
    np_array = df.values
    m = np_array[:,0] # market returns are column zero from numpy array
    s = np_array[:,1] # stock returns are column one from numpy array
    covariance = np.cov(s,m) # Calculate covariance between stock and market
    beta = covariance[0,1]/covariance[1,1]
    return beta



注意第一个单元格
与上述验证计算值相同

betas = rdf.apply(beta)
betas.iloc[:5, :5]


评论回复
模拟多个数据帧的完整工作示例

num_sec_dfs = 4000

cols = ['Open', 'High', 'Low', 'Close']
dfs = {'s{:04d}'.format(i): pd.DataFrame(np.random.rand(480, 4), dates, cols) for i in range(num_sec_dfs)}

market = pd.Series(np.random.rand(480), dates, name='Market')

df = pd.concat([market] + [dfs[k].Close.rename(k) for k in dfs.keys()], axis=1).sort_index(1)

betas = roll(df.pct_change().dropna(), 12).apply(beta)

for c, col in betas.iteritems():
    dfs[c]['Beta'] = col

dfs['s0001'].head(20)

使用生成器提高内存效率

模拟数据

m, n = 480, 10000
dates = pd.date_range('1995-12-31', periods=m, freq='M', name='Date')
stocks = pd.Index(['s{:04d}'.format(i) for i in range(n)])
df = pd.DataFrame(np.random.rand(m, n), dates, stocks)
market = pd.Series(np.random.rand(m), dates, name='Market')
df = pd.concat([df, market], axis=1)
def beta(df, market=None):
    # If the market values are not passed,
    # I'll assume they are located in a column
    # named 'Market'.  If not, this will fail.
    if market is None:
        market = df['Market']
        df = df.drop('Market', axis=1)
    X = market.values.reshape(-1, 1)
    X = np.concatenate([np.ones_like(X), X], axis=1)
    b = np.linalg.pinv(X.T.dot(X)).dot(X.T).dot(df.values)
    return pd.Series(b[1], df.columns, name=df.index[-1])
betas = pd.concat([beta(sdf) for sdf in roll(df.pct_change().dropna(), 12)], axis=1).T
贝塔计算

m, n = 480, 10000
dates = pd.date_range('1995-12-31', periods=m, freq='M', name='Date')
stocks = pd.Index(['s{:04d}'.format(i) for i in range(n)])
df = pd.DataFrame(np.random.rand(m, n), dates, stocks)
market = pd.Series(np.random.rand(m), dates, name='Market')
df = pd.concat([df, market], axis=1)
def beta(df, market=None):
    # If the market values are not passed,
    # I'll assume they are located in a column
    # named 'Market'.  If not, this will fail.
    if market is None:
        market = df['Market']
        df = df.drop('Market', axis=1)
    X = market.values.reshape(-1, 1)
    X = np.concatenate([np.ones_like(X), X], axis=1)
    b = np.linalg.pinv(X.T.dot(X)).dot(X.T).dot(df.values)
    return pd.Series(b[1], df.columns, name=df.index[-1])
betas = pd.concat([beta(sdf) for sdf in roll(df.pct_change().dropna(), 12)], axis=1).T
滚动功能
这将返回一个生成器,并将大大提高内存效率

def roll(df, w):
    for i in range(df.shape[0] - w + 1):
        yield pd.DataFrame(df.values[i:i+w, :], df.index[i:i+w], df.columns)
将所有内容放在一起

m, n = 480, 10000
dates = pd.date_range('1995-12-31', periods=m, freq='M', name='Date')
stocks = pd.Index(['s{:04d}'.format(i) for i in range(n)])
df = pd.DataFrame(np.random.rand(m, n), dates, stocks)
market = pd.Series(np.random.rand(m), dates, name='Market')
df = pd.concat([df, market], axis=1)
def beta(df, market=None):
    # If the market values are not passed,
    # I'll assume they are located in a column
    # named 'Market'.  If not, this will fail.
    if market is None:
        market = df['Market']
        df = df.drop('Market', axis=1)
    X = market.values.reshape(-1, 1)
    X = np.concatenate([np.ones_like(X), X], axis=1)
    b = np.linalg.pinv(X.T.dot(X)).dot(X.T).dot(df.values)
    return pd.Series(b[1], df.columns, name=df.index[-1])
betas = pd.concat([beta(sdf) for sdf in roll(df.pct_change().dropna(), 12)], axis=1).T

验证 OP beta calc

实验设置

注意:

使用生成器来提高内存效率的计算结果是相同的

模拟数据

m, n = 480, 10000
dates = pd.date_range('1995-12-31', periods=m, freq='M', name='Date')
stocks = pd.Index(['s{:04d}'.format(i) for i in range(n)])
df = pd.DataFrame(np.random.rand(m, n), dates, stocks)
market = pd.Series(np.random.rand(m), dates, name='Market')
df = pd.concat([df, market], axis=1)
def beta(df, market=None):
    # If the market values are not passed,
    # I'll assume they are located in a column
    # named 'Market'.  If not, this will fail.
    if market is None:
        market = df['Market']
        df = df.drop('Market', axis=1)
    X = market.values.reshape(-1, 1)
    X = np.concatenate([np.ones_like(X), X], axis=1)
    b = np.linalg.pinv(X.T.dot(X)).dot(X.T).dot(df.values)
    return pd.Series(b[1], df.columns, name=df.index[-1])
betas = pd.concat([beta(sdf) for sdf in roll(df.pct_change().dropna(), 12)], axis=1).T
贝塔计算

m, n = 480, 10000
dates = pd.date_range('1995-12-31', periods=m, freq='M', name='Date')
stocks = pd.Index(['s{:04d}'.format(i) for i in range(n)])
df = pd.DataFrame(np.random.rand(m, n), dates, stocks)
market = pd.Series(np.random.rand(m), dates, name='Market')
df = pd.concat([df, market], axis=1)
def beta(df, market=None):
    # If the market values are not passed,
    # I'll assume they are located in a column
    # named 'Market'.  If not, this will fail.
    if market is None:
        market = df['Market']
        df = df.drop('Market', axis=1)
    X = market.values.reshape(-1, 1)
    X = np.concatenate([np.ones_like(X), X], axis=1)
    b = np.linalg.pinv(X.T.dot(X)).dot(X.T).dot(df.values)
    return pd.Series(b[1], df.columns, name=df.index[-1])
betas = pd.concat([beta(sdf) for sdf in roll(df.pct_change().dropna(), 12)], axis=1).T
滚动功能
这将返回一个生成器,并将大大提高内存效率

def roll(df, w):
    for i in range(df.shape[0] - w + 1):
        yield pd.DataFrame(df.values[i:i+w, :], df.index[i:i+w], df.columns)
将所有内容放在一起

m, n = 480, 10000
dates = pd.date_range('1995-12-31', periods=m, freq='M', name='Date')
stocks = pd.Index(['s{:04d}'.format(i) for i in range(n)])
df = pd.DataFrame(np.random.rand(m, n), dates, stocks)
market = pd.Series(np.random.rand(m), dates, name='Market')
df = pd.concat([df, market], axis=1)
def beta(df, market=None):
    # If the market values are not passed,
    # I'll assume they are located in a column
    # named 'Market'.  If not, this will fail.
    if market is None:
        market = df['Market']
        df = df.drop('Market', axis=1)
    X = market.values.reshape(-1, 1)
    X = np.concatenate([np.ones_like(X), X], axis=1)
    b = np.linalg.pinv(X.T.dot(X)).dot(X.T).dot(df.values)
    return pd.Series(b[1], df.columns, name=df.index[-1])
betas = pd.concat([beta(sdf) for sdf in roll(df.pct_change().dropna(), 12)], axis=1).T

验证 OP beta calc

实验设置

注意:

计算结果是相同的,但如果您要求对多只股票(n)进行跨日期(m)的贝塔系数计算,则计算结果将是块状的(m x n)

通过在多个内核上运行每个日期或库存可以减轻一些负担,但这样您将拥有巨大的硬件

可用解决方案的主要时间要求是找出方差和协方差,并且应避免在(指数和股票)数据中使用NaN,以便根据pandas==0.23.0进行正确计算

因此,除非缓存计算,否则再次运行将导致愚蠢的移动

如果未删除NaN,则numpy方差和协方差版本也会碰巧未计算β


对于庞大的数据集,Cython实现是必须的。

但是,当您需要对多个股票(n)进行跨日期(m)的beta计算,从而产生(m x n)个计算数时,这将是一个障碍

通过在多个内核上运行每个日期或库存可以减轻一些负担,但这样您将拥有巨大的硬件

可用解决方案的主要时间要求是找出方差和协方差,并且应避免在(指数和股票)数据中使用NaN,以便根据pandas==0.23.0进行正确计算

因此,除非缓存计算,否则再次运行将导致愚蠢的移动

如果未删除NaN,则numpy方差和协方差版本也会碰巧未计算β


对于庞大的数据集,必须使用Cython实现。

虽然将输入数据集有效细分为滚动窗口对于优化整体计算非常重要,但beta计算本身的性能也可以显著提高

以下仅将数据集细分为滚动窗口进行优化:

def numpy_betas(x_name, window, returns_data, intercept=True):
    if intercept:
        ones = numpy.ones(window)

    def lstsq_beta(window_data):
        x_data = numpy.vstack([window_data[x_name], ones]).T if intercept else window_data[[x_name]]
        beta_arr, residuals, rank, s = numpy.linalg.lstsq(x_data, window_data)
        return beta_arr[0]

    indices = [int(x) for x in numpy.arange(0, returns_data.shape[0] - window + 1, 1)]
    return DataFrame(
        data=[lstsq_beta(returns_data.iloc[i:(i + window)]) for i in indices]
        , columns=list(returns_data.columns)
        , index=returns_data.index[window - 1::1]
    )
以下内容还优化了beta计算本身:

def custom_betas(x_name, window, returns_data):
    window_inv = 1.0 / window
    x_sum = returns_data[x_name].rolling(window, min_periods=window).sum()
    y_sum = returns_data.rolling(window, min_periods=window).sum()
    xy_sum = returns_data.mul(returns_data[x_name], axis=0).rolling(window, min_periods=window).sum()
    xx_sum = numpy.square(returns_data[x_name]).rolling(window, min_periods=window).sum()
    xy_cov = xy_sum - window_inv * y_sum.mul(x_sum, axis=0)
    x_var = xx_sum - window_inv * numpy.square(x_sum)
    betas = xy_cov.divide(x_var, axis=0)[window - 1:]
    betas.columns.name = None
    return betas
比较两种不同计算的性能,您可以看到,随着beta计算中使用的窗口的增加,第二种方法的性能明显优于第一种方法:


将性能与@piRSquared的实现进行比较,自定义方法的评估时间约为350毫秒,而不是2秒。

而将输入数据集有效细分为滚动窗口对于优化整体计算非常重要,beta计算本身的性能也可以显著提高