我正以一种逐行的方式比较两个数据格式。
对于data
中的每一行,我希望检查reference
中是否有匹配的行。
要使匹配被认为是正确的,必须充分提出一些条件:
np.isclose
)当找到匹配时,我会在列表中追加两行的名称。如果没有匹配,我将数据中的行名附加到"not“,与上面的列表相同。最后,我创建了一个汇总表,以查看哪一行与什么对应(或不对应)。
为了让您了解我的数据流的结构:
name col1 col2 col3 col4 col5 col6 col7 col8
0 X 10 20 30 40 50 60 70 80
1 X 20 30 NaN NaN NaN NaN NaN NaN
2 X 10 25 30 50 NaN NaN NaN NaN
3 X 20 25 30 50 NaN NaN NaN NaN
我有一个使用2 for-循环的工作代码,但是在大数据文件上使用它是相当慢的(这里我在一些‘示例’数据格式上测试代码):
data = pd.DataFrame({'name':['read 1','read 2','read 3','read 4'],
'start 1':[100,102,100,103],
'end 1':[198,504,500,200],
'start 2':[np.NaN,600,650,601],
'end 2':[np.NaN,699, 700,702],
'start 3':[np.NaN,800,800,np.NaN],
'end 3':[np.NaN,901, 900,np.NaN]},
columns=['name', 'start 1', 'end 1', 'start 2', 'end 2', 'start 3', 'end 3'],
dtype='float64')
reference = pd.DataFrame({'name':['a-1','a-2','b-1','c-1'],
'start 1':[100,100,100,300],
'end 1':[200,200,500,400],
'start 2':[300,np.NaN,600,600],
'end 2':[400,np.NaN, 700,700],
'start 3':[np.NaN,np.NaN,800,np.NaN],
'end 3':[np.NaN,np.NaN, 900,np.NaN]},
columns=['name', 'start 1', 'end 1', 'start 2', 'end 2', 'start 3', 'end 3'],
dtype='float64')
match = []
checklist = set()
for read in data.itertuples():
ndata = np.count_nonzero(~np.isnan(read[2:]),axis=0)
end = ndata+1 if ndata>2 and read[1] not in checklist else 4
for ref in reference.itertuples():
nref = np.count_nonzero(~np.isnan(ref[2:]),axis=0)
if np.isclose(read[2:end],ref[2:end], atol=5).all() == True and ndata == nref:
match.append([read[1], ref[1]])
checklist.add(read[1])
break
if read[1] not in checklist:
match.append([read[1], "not found"])
checklist.add(read[1])
match_table = pd.DataFrame(match)
match_table:
read name reference
0 read 1 a-2
1 read 2 b-1
2 read 3 not found
3 read 4 not found
所以我决定尝试用矢量化来优化它。现在,我只使用了1 for -循环,并且能够用np.isclose将第三个条件矢量化,但没有对其他条件进行管理。
我可以通过允许equal_nan=True
来绕过它,但是由于我的大多数行都将满是NaN值,我认为如果不需要进行这些比较,我会得到一些时间。
到目前为止,我得到的是:
count = []
for read in data.itertuples(index=False):
idx = np.argwhere(np.isclose(read[1:], reference.iloc[:,1:], atol=5, equal_nan=True).all(axis=1) == True).flatten()
if idx.size == 0:
count.append([read[0], "not found"])
else:
idx = idx.item()
count.append([read[0], reference['name'][idx]])
match = pd.DataFrame(count)
我用一个400×130个data
数据帧在25×130个reference
数据框架上测试了它,它的执行速度是第一个版本的6倍,但仍然需要1s才能完成。但也许没有多少改进的余地。
问题:
奖金问题:
为什么我必须在代码的第1版和第2版之间将索引从read[1]
更改为read[0]
,才能选择['name']
列?在一个版本中,它是基于0的,而在另一个版本中,它不是,或者类似的。但作为刚接触蟒蛇和自学的人,我真的不明白这里发生了什么..
发布于 2018-07-26 05:31:09
可以通过使用df.apply
来避免循环。itertuples
是缓慢的,只有在绝对必要时才应该使用。
# index-setting not technically required, but makes the
# rest of the code simpler
data = data.set_index('name')
reference = reference.set_index('name')
# define a helper function to use with apply
# taking the same logic as you have used
def get_ref(x):
m = np.isclose(x, reference.values, atol=5, equal_nan=True).all(axis=1)
return reference.index[m].item() if m.any() else np.nan
out = data.apply(get_ref, axis=1).rename('reference').reset_index()
# Outputs:
name reference
0 read 1 a-2
1 read 2 b-1
2 read 3 NaN
3 read 4 NaN
如果深入到numpy层&用户np.apply_along_axis
,您可以获得额外的速度提升。
pd.DataFrame({'read name': data.index,
'reference': np.apply_along_axis(get_ref, 1, data.values)}
计时:
在我的机器上,带着样本数据
https://stackoverflow.com/questions/51521171
复制