Python可以缩短大型数据集的计算时间,目前运行大约需要400分钟

Python可以缩短大型数据集的计算时间,目前运行大约需要400分钟,python,pandas,large-data,Python,Pandas,Large Data,我正在努力提高我每天需要构建的数据帧的性能,我想知道是否有人有一些想法。我在下面创建了一个简单的示例: 首先,我有一个数据帧的记录,就像这样。这是时间序列数据,因此每天都会更新 import pandas as pd import numpy as np import datetime as dt from scipy import stats dates = [dt.datetime.today().date() - dt.timedelta(days=x) for x in range(2

我正在努力提高我每天需要构建的数据帧的性能,我想知道是否有人有一些想法。我在下面创建了一个简单的示例:

首先,我有一个数据帧的记录,就像这样。这是时间序列数据,因此每天都会更新

import pandas as pd
import numpy as np
import datetime as dt
from scipy import stats

dates = [dt.datetime.today().date() - dt.timedelta(days=x) for x in range(2000)]

m_list = [str(i) + 'm' for i in range(0, 15)]
names = [i + j  for i in m_list for j in m_list]

keys = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J']
values = [pd.DataFrame([np.random.rand(225) for x in range(0, 2000)], index=dates, columns=names) for i in range(0, 10)]

df_dict = dict(zip(keys, values))    #this is my time series data
接下来我有三个列表:

#I will build a dict of DataFrames calc attributes for these combos for each df in dict_df above
combos = ['{}/{}'.format(*np.random.choice(names, 2)) for i in range(750)] + ['{}/{}/{}'.format(*np.random.choice(names, 3)) for i in range(1500)]

periods = [20, 60, 100, 200, 500, 1000, 2000]   #num of datapoints to use from time series
benchmarks = np.random.choice(combos, 25)       #benchmarks to compare combos to
这里是我构建所需数据帧的地方:

def calc_beta (a_series, b_series) :

    covariance = np.cov (a_series, b_series)     
    beta = covariance[0, 1] / covariance[1, 1]    
    
    return beta

data_dict = {}

for i in list(df_dict.keys()) :
    
    attr_list = []
    
    df = df_dict[i]
    
    for c in combos :
        
        c_split = c.split('/')
        combo_list = []
        for cs in c_split :
            _list = [int(x) for x in list(filter(None, cs.split('m')))]
            combo_list.append(_list)
        if len(combo_list) == 2 :
            combo_list.append([np.nan, np.nan])
        
        c1a, c1b, c2a, c2b, c3a, c3b = [item for subl in combo_list for item in subl]
        
        if len(c_split) == 2 :
            l1, l2 = c_split
            _series = df[l1] - df[l2]
            
        if len(c_split) == 3 :
            l1, l2, l3 = c_split
            _series = df[l1] - df[l2] - df[l3]
        
        attr = {
            
            'name' : c,
            'a' : c1a,
            'b' : c1b,
            'c' : c2a,
            'd' : c2b,
            'e' : c3a,
            'f' : c3b,
            'series' : _series,
            'last' : _series[-1]
        }
        
        for p in periods :
            _str = str(p)
            p_series = _series[-p:]
            
            attr['quantile' + _str] = stats.percentileofscore(p_series, attr['last'])
            attr['z_score' + _str] = stats.zscore(p_series)[-1]
            attr['std' + _str] = np.std(p_series)            
            attr['range' + _str] = max(p_series) - min(p_series)
            attr['last_range' + _str] = attr['last'] / attr['range' + _str]
            attr['last_std' + _str] = attr['last'] / attr['std' + _str]        
            
            if p > 100 :
                attr['5d_autocorr' + _str] = p_series.autocorr(-5)
            else :
                attr['5d_autocorr' + _str] = np.nan
                
            for b in benchmarks :
                b_split = b.split('/')
                
                if len(b_split) == 1 :
                    b_series = df[b_split[0]]
                    
                elif len(b_split) == 2 :                    
                    b_series = df[b_split[0]] - df[b_split[1]]  
                
                elif len(b_split) == 3 :                    
                    b_series = df[b_split[0]] - df[b_split[1]] - df[b_split[2]]  
                
                b_series = b_series[-p:]
                
                corr_value = p_series.corr(b_series)
                
                beta_value = calc_beta (p_series, b_series)
                
                corr_ticker = '{}_corr{}'.format(b, _str)
                beta_ticker = '{}_beta{}'.format(b, _str)
                
                attr[corr_ticker] = corr_value    
                attr[beta_ticker] = corr_value    
        
                if p > 500 :
                    attr[b + '_20rolling_corr_mean' + _str] = p_series.rolling(20).corr(b_series).mean()

                    df1 = pd.DataFrame({c : p_series, b : b_series})

                    attr[b + '_20d_rolling_beta_mean' + _str] =  df1.rolling(20) \
                                                                    .cov(df1 , pairwise=True) \
                                                                    .drop([c], axis=1) \
                                                                    .unstack(1) \
                                                                    .droplevel(0, axis=1) \
                                                                    .apply(lambda row: row[c] / row[b], axis=1) \
                                                                    .mean()
        
        attr_list.append(attr)
    
    data_dict[i] = pd.DataFrame(attr_list)
这是实际数据的一般示例,但它几乎完全复制了我要做的,每种类型的计算,尽管我减少了数字以使其更简单

最后一部分在Dict中每个数据帧大约需要40分钟,即此数据集总共需要400分钟

我过去没有处理过大型数据集,据我所知,我需要最小化For循环和Apply函数,我已经做到了,但是我还应该做什么呢?感谢您的任何意见


谢谢

所以,我去了一个黑暗的地方想办法帮你:

长的,短的是脚本末尾的两个函数,在这里p>500会让你丧命。当p500时,你会看到一些节省

最后,不管是你的代码还是我的代码,真正的问题是最后两个函数。其他一切都是小的,但真实的,节省和加起来,但重新思考最后一块可以节省你的一天


另一种选择是多处理或将其分解到多台机器上

因此,我去了一个黑暗的地方,想找出一些方法来提供帮助:

长的,短的是脚本末尾的两个函数,在这里p>500会让你丧命。当p500时,你会看到一些节省

最后,不管是你的代码还是我的代码,真正的问题是最后两个函数。其他一切都是小的,但真实的,节省和加起来,但重新思考最后一块可以节省你的一天


另一种选择是多处理或将其分解到多台机器上

我在最内部的循环中更改了一行,这为2000行的数据帧提供了1.6倍的加速

这并不能解决所有问题,但可能会有所帮助

for b in benchmarks:
    ...
    
    if p > 500:
        attr[b + '_20d_rolling_beta_mean' + _str] =  (
            df1
            .rolling(5)  
            .cov(df1, pairwise=True)
            .drop([c], axis=1)
            .unstack(1)
            .droplevel(0, axis=1)
            # .apply(lambda row: row[c] / row[b], axis=1)                 # <-- removed
            .assign(result = lambda x: x[c] / x[b]).iloc[:, -1].squeeze() # <-- added
            .mean()
        )
原始计时信息经过的时间,对于A中的前100个组合:

应用语句:142.6秒 assign语句:90.1秒
我改变了最内层循环中的一行,对于2000行的数据帧,它的加速比为1.6倍

这并不能解决所有问题,但可能会有所帮助

for b in benchmarks:
    ...
    
    if p > 500:
        attr[b + '_20d_rolling_beta_mean' + _str] =  (
            df1
            .rolling(5)  
            .cov(df1, pairwise=True)
            .drop([c], axis=1)
            .unstack(1)
            .droplevel(0, axis=1)
            # .apply(lambda row: row[c] / row[b], axis=1)                 # <-- removed
            .assign(result = lambda x: x[c] / x[b]).iloc[:, -1].squeeze() # <-- added
            .mean()
        )
原始计时信息经过的时间,对于A中的前100个组合:

应用语句:142.6秒 assign语句:90.1秒
你可以缩小范围,或者将问题分成明确的部分,描述你在每个部分中所做的工作。目前所说的问题需要很多时间来理解,不确定你是否能找到专门帮助你的人……明白了,我试图平衡现实问题和简化问题,但可能我的平衡错了。您可以将第一个for循环所在的代码移除,只需移除每个循环中除一个之外的所有计算,这样您就可以看到代码的结构,但不一定看到每个计算?calc_beta函数在哪里?beta_value=calc_beta p_series,b_series如果我只是剪切/粘贴你的代码就会抛出一个错误。@JonathanLeon捕捉得好,很抱歉。刚刚加的,你考虑过达斯克吗?它会让你的CPU的所有核心都做出贡献。你可以缩小它的范围,或者把问题分成明确的部分来描述你在每个部分中所做的事情。目前所说的问题需要很多时间来理解,不确定你是否能找到专门帮助你的人……明白了,我试图平衡现实问题和简化问题,但可能我的平衡错了。您可以将第一个for循环所在的代码移除,只需移除每个循环中除一个之外的所有计算,这样您就可以看到代码的结构,但不一定看到每个计算?calc_beta函数在哪里?beta_value=calc_beta p_series,b_series如果我只是剪切/粘贴你的代码就会抛出一个错误。@JonathanLeon捕捉得好,很抱歉。刚刚加的,你考虑过达斯克吗?这会让你的CPU的所有核心都贡献出来。哇,这是一个巨大的改进,可以转换为分配。谢谢,您看过Python内置的多处理功能了吗?我认为A,B,…,J键是独立的。如果可以将最后一个for循环作为函数写入,则可以通过使用多个核来缩短总的计算时间。你也需要这个函数来使用Dask。谢谢。我已经阅读了关于Dask的可能解决方案。最初看它,我不确定我是否有能力实现它,但我会对它做更多的阅读,因为你是对的,A-J键是独立的,所以异步解决它们可能是解决方案中非常方便的一部分。哇,这是一个巨大的改进切换到分配。谢谢,您看过Python内置的多处理功能了吗?我认为A,B,…,J键是独立的。如果你能为我写最后一封信
循环作为一个函数,您可以通过使用多个核来减少总的计算时间。你也需要这个函数来使用Dask。谢谢。我已经阅读了关于Dask的可能解决方案。最初看了一下,我不确定我是否有能力实现它,但我会对它做更多的阅读,因为你是对的,A-J键是独立的,所以异步解决它们可能是解决方案中非常方便的一部分。非常感谢。这么多真正有见地的小变化,今天仍在实施其中的一些,但帮助很大。谢谢你,谢谢你。这么多真正有见地的小变化,今天仍在实施其中的一些,但帮助很大。谢谢
def get_series(a):
    if len(a) == 2 :
        l1, l2 = a
        s = df[l1] - df[l2]
        return s.tolist()
    else:
        l1, l2, l3 = a
        s = df[l1] - df[l2] - df[l3]
        return s.tolist()

mydf['series'] = mydf['name'].apply(lambda x: get_series(x.split('/')))
mydf['quantile' + _str] =  mydf.apply(lambda x: stats.percentileofscore(x['series'][-p:], x['last']), axis=1)
for b in benchmarks:
    ...
    
    if p > 500:
        attr[b + '_20d_rolling_beta_mean' + _str] =  (
            df1
            .rolling(5)  
            .cov(df1, pairwise=True)
            .drop([c], axis=1)
            .unstack(1)
            .droplevel(0, axis=1)
            # .apply(lambda row: row[c] / row[b], axis=1)                 # <-- removed
            .assign(result = lambda x: x[c] / x[b]).iloc[:, -1].squeeze() # <-- added
            .mean()
        )