Python 如何加速数组/矩阵的迭代?尝试了pandas和numpy阵列
我想通过一个大的二维数组(15100m)来丰富功能 在一个有10万条记录的样本集上工作表明,我需要更快地完成这项工作 编辑(数据模型信息) 为了简化,假设我们只有两个相关列: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='
- IP(标识符)
- Unix(自1970年以来以秒为单位的时间戳)
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)
编辑
显然只适用于数字类型,剩下两个选项:
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()