【问题标题】:Pandas merging on multi columns while ignoring NaN熊猫在多列上合并而忽略 NaN
【发布时间】:2021-10-25 06:40:56
【问题描述】:

在这里Pandas merge on multiple columns ignoring NaN 提出了类似的问题,但没有答案,所以我会问也许有人可以提供帮助。

我需要将df2 中的值合并到df1,但是df2 中的行之间的合并键不同,因为df2 中的行在不同的列中具有NaNs,并且在在这种情况下,我想忽略这些列,并且只对每一行使用具有值的列。

df1 = pd.DataFrame([[0, 1, 3], [0, 2, 4], [1, 2, 5]], columns=['level1', 'level2', 'level3'])
df1    
    level1 level2 level3
0        0      1      3
1        0      2      4 
2        1      2      5 

df2 = pd.DataFrame([[0, None, None, 10], [0, 1, None, 12], [None, 2, 5, 13]], columns=['level1', 'level2', 'level3', 'value'])
df2
    level1 level2 level3 value
0      0.0    NaN    NaN    10 
1      0.0    1.0    NaN    12 
2      NaN    2.0    5.0    13 

当我执行df1.merge(df2, how='left') 时,我在value 列中得到df1NaN,因为在所有level 列上都没有匹配项,因为pandas 正在尝试匹配Nan价值观。

我想要的是匹配df2中的任何行而不尝试匹配NaNs:

    level1 level2 level3 value
0        0      1      3    10 
1        0      1      3    12 
2        0      2      4    10 
3        1      2      5    13 

说明: df1 中的第 0 行与df2 中第 0 行和第 1 行的非NaN 列匹配,因此它从那里获取值 10 和 12。 df1 中的第 1 行与 df2 中第 0 行的非NaN 列匹配,因此它从那里获得值 12。 df1 中的第 2 行与 df2 中第 2 行的非NaN 列匹配,因此它从那里获得值 13。

在实际数据中,我实际上有 6 个level 列,df2 中每一行的非NaN 列可以是任意组合,也可以是单列。

我现在要做的是iterrowsdf2 中的行,为每个行创建一个仅非NaN 列的迷你dataframe,并将df1 与它合并。但正如我们所知,它的效率并不高,我想知道是否有更好的方法可以做。

【问题讨论】:

  • 这可能是NetworkX 问题。话虽如此,我不知道如何快速解决它。

标签: python pandas dataframe merge nan


【解决方案1】:

我想我想出了一个矢量化的解决方案。

  1. 基本上,这个想法是您将 df1 与 df2 合并三次以获得级别数,然后将数据帧合并为一个。
  2. 从那里,您计算有多少列是空的,并从级别数中减去。这会告诉您需要多少个重复项(或匹配项),以防止以后删除数据。
  3. 然后,您计算其中有多少匹配或重复实际上。如果实际与要求的相同,则表示该行是匹配的,它会保存在数据框中。

这不是很漂亮,但为了改进我的答案,您可以创建一个合并函数来剪切一些代码。 最重要的是,与遍历每一行相比,它应该具有高性能。最后一点,对于 duplicates_required 辅助列,您需要将 3 更改为 6,因为您的实际数据集中有 6 列,您显然需要重复我的一些合并代码:

df1 = pd.DataFrame([[0, 1, 3], [0, 2, 4], [1, 2, 5]], columns=['level1', 'level2', 'level3'])
df2 = pd.DataFrame([[0, None, None, 10], [0, 1, None, 12], [None, 2, 5, 13]], columns=['level1', 'level2', 'level3', 'value'])
df2 = df2.assign(duplicates_required = 3 - df2.isnull().sum(axis=1))
df = pd.concat([
df1.merge(df2[['level1','value', 'duplicates_required']], on='level1'),
df1.merge(df2[['level2','value', 'duplicates_required']], on='level2'),
df1.merge(df2[['level3','value', 'duplicates_required']], on='level3')
    ])
cols = ['level1', 'level2', 'level3', 'value']
df['actual_duplicates'] = df.groupby(cols)['value'].transform('size')
df = (df[df['duplicates_required'].eq(df['actual_duplicates'])]
     .drop_duplicates(subset=cols)
     .drop(['duplicates_required', 'actual_duplicates'], axis=1)
     .reset_index(drop=True))
df
Out[1]: 
   level1  level2  level3  value
0       0       1       3     10
1       0       1       3     12
2       0       2       4     10
3       1       2       5     13

【讨论】:

  • 它似乎有效。我稍后会深入研究它,以确保它涵盖了我所有的真实数据选项。有问题再评论。
【解决方案2】:

我认为这比我之前使用正则表达式的答案更好。类似的过程,但更容易理解。

  1. 对两个数据框进行完全合并
  2. 比较不同级别并计算不匹配的数量
  3. 过滤到不匹配计数 == 0 的行
import pandas as pd

df1 = pd.DataFrame([[0, 1, 3], [0, 2, 4], [1, 2, 5]], columns=['level1', 'level2', 'level3'])
df2 = pd.DataFrame([[0, None, None, 10], [0, 1, None, 12], [None, 2, 5, 13]], columns=['level1', 'level2', 'level3', 'value'])

levels_to_match = ['level1','level2','level3']
levels_to_match_df2 = [level + '_df2' for level in levels_to_match]

for df in [df1,df2]:
    df['temp'] = 1
 
df1 = df1.merge(df2, on='temp', suffixes=[None,'_df2']).drop(columns=['temp'])

df1['mismatch'] = df1.apply(lambda x: 
    sum([(1 - (y == z or pd.isna(z))) for y, z in zip(list(x[levels_to_match]), list(x[levels_to_match_df2]))]), 
    axis=1)
    
df1 = df1.loc[df1['mismatch'] == 0, :].drop(columns=['mismatch'] + levels_to_match_df2)

print(df1)

   level1  level2  level3  value
0       0       1       3     10
1       0       1       3     12
3       0       2       4     10
8       1       2       5     13

正则表达式的旧答案

可能不理想,但也许尝试将您的关卡转换为字符串和正则表达式,然后对所有可能的组合进行完全合并,最后使用正则表达式搜索/匹配两个帮助列(一个来自 df1,另一个来自df2)。

假设您要匹配的数据是intNaN,那么这似乎可以正常工作。如果您的真实数据中有其他数据类型,则需要相应地调整字符串/正则表达式转换。

import pandas as pd
import re

df1 = pd.DataFrame([[0, 1, 3], [0, 2, 4], [1, 2, 5]], columns=['level1', 'level2', 'level3'])
df2 = pd.DataFrame([[0, None, None, 10], [0, 1, None, 12], [None, 2, 5, 13]], columns=['level1', 'level2', 'level3', 'value'])

levels_to_match = ['level1','level2','level3']

for df in [df1,df2]:
    df['helper'] = df[levels_to_match].apply(list, axis=1)
    df['helper'] = df['helper'].apply(lambda x: ','.join([str(int(item)) if pd.notna(item) else '.*' for item in x]))
    df['temp'] = 1

df1 = df1.merge(df2.drop(columns=levels_to_match), on='temp', suffixes=[None,'_df2']).drop(columns=['temp'])
df1['match'] = df1.apply(lambda x: re.search(x['helper_df2'], x['helper']) is not None, axis=1)

df1 = df1.loc[df1['match'], :].drop(columns=['helper','helper_df2','match'])

print(df1)

   level1  level2  level3  value
0       0       1       3     10
1       0       1       3     12
3       0       2       4     10
8       1       2       5     13

【讨论】:

    猜你喜欢
    • 2019-08-30
    • 2016-08-27
    • 2018-12-15
    • 1970-01-01
    • 2019-05-10
    • 2018-03-27
    • 1970-01-01
    • 2023-02-20
    • 2021-10-03
    相关资源
    最近更新 更多