两个数据帧之间的有效逐行比较

时间:2018-07-25 14:15:29

标签: arrays python-3.x numpy dataframe vectorization

我正在逐行比较两个数据框。

对于data中的每一行,我想检查reference中是否有匹配的行。

要使比赛被视为正确,必须满足某些条件:

  1. 我希望两行中具有相同数量的非null值(这样我就不会因数据行中的假阳性而最终只匹配参考中一行的一部分)
  2. 我要避免比较NaN值,因此仅比较包含实际值的行部分(因此,第一个条件必须为true的原因)
  3. 我想在进行比较时允许一些容忍度(我使用np.isclose来做到这一点)
  4. 我希望代码快

找到匹配项后,我会将两行的名称都添加到列表中。 如果没有匹配项,我将数据的行名附加在“与以上相同的列表中”和“未找到”。最后,我创建一个汇总表,以查看哪行对应(或不对应)什么。

让您了解我的数据框的结构:

    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
  • 我的数据框有〜130列
  • 大多数时候,它们将具有2到20个数值,其余为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)

我用25×130 data数据帧上的400×130 reference数据帧对其进行了测试,它的性能比第一个版本快6倍,但仍然需要1秒钟才能完成。但是也许没有太多的改进空间。

问题:

  • 我如何向量化处理条件1和2的操作,从而不执行NaN比较?

  • 是否有可能摆脱内部for循环?如果是,那是否可以提高速度?

奖金问题:

为什么我必须在代码的第一版和第二版之间将索引从read[1]更改为read[0]才能选择['name']列?似乎在一个版本中它是基于0的,而在另一个版本中却不是,或者类似的。但是对python陌生并自己学习,我真的不明白这里发生了什么。

1 个答案:

答案 0 :(得分:0)

可以通过使用io.on('connection', function(client) { // var address = io.handshake.address; console.log('Ein neuer Client hat sich zum Chat verbunden! IP: ') client.on('sendmsg', function(data) { console.log(data); }); }); 来避免循环。 df.apply很慢,仅在绝对必要时才应使用。

itertuples

如果您进入numpy层和用户# 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

,则可以进一步提高速度
np.apply_along_axis

时间:

在我的机器上,带有示例数据

  • numpy版本大约需要920微秒
  • 大熊猫适用版本需要约1.35毫秒
  • 您的优化版本需要约2.20毫秒