Python 如何加速数组/矩阵的迭代?尝试了pandas和numpy阵列

Python 如何加速数组/矩阵的迭代?尝试了pandas和numpy阵列,python,pandas,numpy,tensorflow,Python,Pandas,Numpy,Tensorflow,我想通过一个大的二维数组(15100m)来丰富功能 在一个有10万条记录的样本集上工作表明,我需要更快地完成这项工作 编辑(数据模型信息) 为了简化,假设我们只有两个相关列: IP(标识符) Unix(自1970年以来以秒为单位的时间戳) 我想添加第三列,计算这个IP在过去12小时内出现了多少次 结束编辑 我的第一次尝试是使用pandas,因为它可以轻松地处理命名维度,但速度太慢: for index,row in tqdm_notebook(myData.iterrows(),desc='

我想通过一个大的二维数组(15100m)来丰富功能

在一个有10万条记录的样本集上工作表明,我需要更快地完成这项工作

编辑(数据模型信息)

为了简化,假设我们只有两个相关列:

  • IP(标识符)
  • Unix(自1970年以来以秒为单位的时间戳)
我想添加第三列,计算这个IP在过去12小时内出现了多少次

结束编辑

我的第一次尝试是使用pandas,因为它可以轻松地处理命名维度,但速度太慢:

for index,row in tqdm_notebook(myData.iterrows(),desc='iterrows'):
# how many times was the IP address (and specific device) around in the prior 5h?
    hours = 12
    seen = myData[(myData['ip']==row['ip'])
                 &(myData['device']==row['device'])
                 &(myData['os']==row['os'])
                 &(myData['unix']<row['unix'])
                 &(myData['unix']>(row['unix']-(60*60*hours)))].shape[0]
    ip_seen = myData[(myData['ip']==row['ip'])
                 &(myData['unix']<row['unix'])
                 &(myData['unix']>(row['unix']-(60*60*hours)))].shape[0]
    myData.loc[index,'seen'] = seen
    myData.loc[index,'ip_seen'] = ip_seen
tqdm_笔记本(myData.iterrows(),desc='iterrows')中的索引行的
:
#在之前的5小时内,IP地址(和特定设备)出现了多少次?
小时=12
SEED=myData[(myData['ip']==行['ip'])
&(myData['device']==行['device'])
&(myData['os']==行['os'])
&(myData['unix'](行['unix']-(60*60*h))))。形状[0]
ip_seen=myData[(myData['ip']==行['ip'])
&(myData['unix'](行['unix']-(60*60*h))))。形状[0]
myData.loc[索引,'seen']=seen
myData.loc[索引,'ip_seen']=ip_seen
然后我切换到numpy数组,希望得到更好的结果,但它仍然太慢,无法在完整的数据集上运行:

# speed test numpy arrays
for i in np.arange(myArray.shape[0]):
    hours = 12
    ip,device,os,ts = myArray[i,[0,3,4,12]]
    ip_seen = myArray[(np.where((myArray[:,0]==ip) 
                            & (myArray[:,12]<ts)
                            & (myArray[:,12]>(ts-60*60*hours) )))].shape[0]
    device_seen = myArray[(np.where((myArray[:,0]==ip) 
                            & (myArray[:,2] == device)
                            & (myArray[:,3] == os)
                            & (myArray[:,12]<ts)
                            & (myArray[:,12]>(ts-60*60*hours) )))].shape[0]
    myArray[i,13]=ip_seen
    myArray[i,14]=device_seen
#速度测试numpy阵列
对于np.arange中的i(myArray.shape[0]):
小时=12
ip,设备,操作系统,ts=myArray[i[0,3,4,12]]
ip_seen=myArray[(np.where((myArray[:,0]==ip)
&(myArray[:,12](ts-60*60*h)))。形状[0]
设备=myArray[(np.where((myArray[:,0]==ip)
&(myArray[:,2]==设备)
&(myArray[:,3]==os)
&(myArray[:,12](ts-60*60*h)))。形状[0]
myArray[i,13]=ip_
myArray[i,14]=设备
我的下一个想法是只迭代一次,并维护一个不断增长的当前计数字典,而不是在每次迭代中向后看

但这也会有一些其他的缺点(例如,如何跟踪何时减少从12小时窗口落下的观察次数)

你将如何处理这个问题

甚至可以选择使用低级别的Tensorflow函数来包含GPU吗


谢谢

加快速度的唯一方法是不要循环。在您的例子中,您可以尝试使用所需时间跨度的窗口,使用Unix时间戳作为日期时间索引(假设记录是按时间戳排序的,否则需要先排序)。这对于所看到的
ip\u应该可以正常工作

ip = myData['ip']
ip.index = pd.to_datetime(myData['unix'], unit='s')
myData['ip_seen'] = ip.rolling('5h')
    .agg(lambda w: np.count_nonzero(w[:-1] == w[-1]))
    .values.astype(np.int32)
但是,当聚合涉及多个列时,就像在
seen
列中一样,它会变得更加复杂。当前(请参见)滚动函数不支持跨二维的聚合。一种解决方法可以是将感兴趣的列合并到一个新的系列中,例如元组(如果值是数字,则效果更好)或字符串(如果值已经是字符串,则效果更好)。例如:

criteria = myData['ip'] + '|' + myData['device'] + '|' + myData['os']
criteria.index = pd.to_datetime(myData['unix'], unit='s')
myData['seen'] = criteria.rolling('5h')
    .agg(lambda w: np.count_nonzero(w[:-1] == w[-1]))
    .values.astype(np.int32)
编辑

显然只适用于数字类型,剩下两个选项:

  • 操作数据以使用数字类型。对于IP来说,这很容易,因为它实际上表示一个32位的数字(或者如果我猜是IPv6,则表示64位)。对于设备和操作系统,假设它们现在是字符串,这就更复杂了,您必须将每个可能的值映射为一个整数,并将其与IP合并为一个长值,例如,将它们放入更高的位或类似的位置(对于IPv6,甚至可能不可能,因为NumPy现在支持的最大整数是64位)
  • 滚动
    myData
    的索引(现在应该不是datetime,因为
    rolling
    也不能使用该索引),并使用索引窗口获取必要的数据并操作:

    # Use sequential integer index
    idx_orig = myData.index
    myData.reset_index(drop=True, inplace=True)
    # Index to roll
    idx = pd.Series(myData.index)
    idx.index = pd.to_datetime(myData['unix'], unit='s')
    # Roll aggregation function
    def agg_seen(w, data, fields):
        # Use slice for faster data frame slicing
        slc = slice(int(w[0]), int(w[-2])) if len(w) > 1 else []
        match = data.loc[slc, fields] == data.loc[int(w[-1]), fields]
        return np.count_nonzero(np.all(match, axis=1))
    # Do rolling
    myData['ip_seen'] = idx.rolling('5h') \
        .agg(lambda w: agg_seen(w, myData, ['ip'])) \
        .values.astype(np.int32)
    myData['ip'] = idx.rolling('5h') \
        .agg(lambda w: agg_seen(w, myData, ['ip', 'device', 'os'])) \
        .values.astype(np.int32)
    # Put index back
    myData.index = idx_orig
    
    不过,这并不是使用
    滚动
    的方式,我不确定这是否比循环提供了更好的性能


  • 正如在@jdehesa的评论中提到的,我采用了另一种方法,它允许我只对整个数据集迭代一次,并从索引中提取(衰减)权重

    decay_window = 60*60*12 # every 12
    decay = 0.5 # fall by 50% every window
    ip_idx = pd.DataFrame(myData.ip.unique())
    ip_idx['ts_seen'] = 0
    ip_idx['ip_seen'] = 0
    ip_idx.columns = ['ip','ts_seen','ip_seen']
    ip_idx.set_index('ip',inplace=True)
    
    for index, row in myData.iterrows(): # all
        # How often was this IP seen?
        prior_ip_seen = ip_idx.loc[(row['ip'],'ip_seen')]
        prior_ts_seen = ip_idx.loc[(row['ip'],'ts_seen')]
        delay_since_count = row['unix']-ip_idx.loc[(row['ip'],'ts_seen')]
        new_ip_seen = prior_ip_seen*decay**(delay_since_count/decay_window)+1
        ip_idx.loc[(row['ip'],'ip_seen')] = new_ip_seen
        ip_idx.loc[(row['ip'],'ts_seen')] = row['unix']
        myData.iloc[index,14] = new_ip_seen-1
    
    这样一来,结果就不是最初要求的固定时间窗口,而是先前的观察随着时间的推移而“淡出”,从而使频繁的最近观察具有更高的权重

    此功能比最初计划的简化(结果更昂贵)方法携带更多信息

    谢谢你的意见

    编辑


    同时,我切换到numpy阵列进行相同的操作,现在只需要一小部分时间(循环中的200m更新能否请您提供一个数据示例和示例的预期输出?在python中,无论使用哪个库,对行的迭代都会很慢。您的数据库是哪种格式的?如果您的数据库很大,pandas可能不是操作它的最佳选项。为什么不使用SQL?@jdehesa,请参阅更新的po以上st表示数据模型(简化以说明想法)@Imanol,DB是不相关的,现在它都发生在内存中,对于完整的数据集,如果太大,它也可以成批运行。SQL不是为频繁重新获取的“滚动”更新而设计的。我认为这会使它更慢,因为它会一直从磁盘重新读取。如果你将时间设置为索引,你可以使用
    DataFrame.rolling
    。请提供一些示例数据,以便我们可以测试一些可能的解决方案。很好,我不知道滚动可以处理可变的时间窗口。将尝试并还原。谢谢!@szeta我注意到,
    滚动
    与字符串不一样,它似乎会跳过所有非数值,我正在寻找另一条路径。@szeta我已经添加了一些使用
    滚动
    的东西,可以在
    
    %%time
    import sys
    ## temporary lookup
    ip_seen_ts = [0]*365000
    ip_seen_count = [0]*365000
    cnt = 0
    window = 60*60*12 # 12h
    decay = 0.5
    counter = 0
    chunksize = 10000000
    store = pd.HDFStore('store.h5')
    t = time.process_time()
    try:
        store.remove('myCount')
    except:
        print("myData not present.")
    for myHdfData in store.select_as_multiple(['myData','myFeatures'],columns=['ip','unix','ip_seen'],chunksize=chunksize):
        print(counter, time.process_time() - t)
        #display(myHdfData.head(5))
        counter+=chunksize
        t = time.process_time()
        sys.stdout.flush()
        keep_index = myHdfData.index.values
        myArray = myHdfData.as_matrix()
        for row in myArray[:,:]:
            #for row in myArray:
            i = (row[0].astype('uint32')) # IP as identifier
            u = (row[1].astype('uint32')) # timestamp
            try:
                delay = u - ip_seen_ts[i]
            except:
                delay = 0
            ip_seen_ts[i] = u
            try:
                ip_seen_count[i] = ip_seen_count[i]*decay**(delay/window)+1
            except:
                ip_seen_count[i] = 1
            row[3] = np.tanh(ip_seen_count[i]-1) # tanh to normalize between 0 and 1
        myArrayAsDF = pd.DataFrame(myArray,columns=['c_ip','c_unix','c_ip2','ip_seen'])
        myArrayAsDF.set_index(keep_index,inplace=True)
        store.append('myCount',myArrayAsDF)
    store.close()