【问题标题】:Pandas: join on partial string match, like Excel VLOOKUPPandas:加入部分字符串匹配,如 Excel VLOOKUP
【发布时间】:2018-12-01 15:55:38
【问题描述】:

我正在尝试在 Python 中执行一个与 Excel 中的 VLOOKUP 非常相似的操作。 StackOverflow 上有很多与此相关的问题,但它们都与这个用例略有不同。希望任何人都可以指导我正确的方向。我有以下两个熊猫数据框:

df1 = pd.DataFrame({'Invoice': ['20561', '20562', '20563', '20564'],
                    'Currency': ['EUR', 'EUR', 'EUR', 'USD']})
df2 = pd.DataFrame({'Ref': ['20561', 'INV20562', 'INV20563BG', '20564'],
                    'Type': ['01', '03', '04', '02'],
                    'Amount': ['150', '175', '160', '180'],
                    'Comment': ['bla', 'bla', 'bla', 'bla']})

print(df1)
    Invoice Currency
0   20561   EUR
1   20562   EUR
2   20563   EUR
3   20564   USD

print(df2)
    Ref         Type    Amount  Comment
0   20561       01      150     bla
1   INV20562    03      175     bla
2   INV20563BG  04      160     bla
3   20564       02      180     bla

现在我想创建一个新的数据框 (df3),在其中根据发票编号将两者结合起来。问题是发票号码并不总是“完全匹配”,但有时在 df2['Ref'] 中是“部分匹配”。因此,加入“发票”并没有提供所需的输出,因为它没有复制发票 20562 和 20563 的数据,见下文:

df3 = df1.join(df2.set_index('Ref'), on='Invoice')

print(df3)
    Invoice Currency    Type    Amount  Comment
0   20561   EUR         01       150    bla
1   20562   EUR         NaN      NaN    NaN
2   20563   EUR         NaN      NaN    NaN
3   20564   USD         02       180    bla

有没有办法加入部分比赛?我知道如何用正则表达式“清理”df2['Ref'],但这不是我想要的解决方案。使用 for 循环,我有很长的路要走,但这不是很 Pythonic。

df4 = df1.copy()
for i, row in df1.iterrows():
    tmp = df2[df2['Ref'].str.contains(row['Invoice'])]
    df4.loc[i, 'Amount'] = tmp['Amount'].values[0]

print(df4)
Invoice     Currency    Amount
0   20561   EUR         150
1   20562   EUR         175
2   20563   EUR         160
3   20564   USD         180

str.contains() 能否以更优雅的方式使用?非常感谢您的帮助!

【问题讨论】:

    标签: python python-3.x pandas dataframe join


    【解决方案1】:

    这里有两个替代解决方案,都使用 Pandas 的merge

    # Solution 1 (checking directly if 'Invoice' string is in the 'Ref' string)
    df4 = df2.copy()
    df4['Invoice'] = [val for idx, val in enumerate(df1['Invoice']) if val in df2['Ref'][idx]]
    df_m4 = df1.merge(df4[['Amount', 'Invoice']], on='Invoice')
    
    # Solution 2 (regex)
    import re
    df5 = df2.copy()
    df5['Invoice'] = [re.findall(r'(\d{5})', s)[0] for s in df2['Ref']]
    df_m5 = df1.merge(df5[['Amount', 'Invoice']], on='Invoice')
    

    df_m4df_m5 都会打印出来

      Currency Invoice Amount
    0      EUR   20561    150
    1      EUR   20562    175
    2      EUR   20563    160
    3      USD   20564    180
    

    注意:提供的正则表达式解决方案假定发票号码始终为 5 位数字,并且只采用第一个此类事件。解决方案 1 更健壮,因为它直接比较字符串。 如果需要,可以改进正则表达式解决方案以使其更加健壮。

    【讨论】:

      【解决方案2】:

      这是使用pd.Series.apply 的一种方式,它只是一个隐蔽的循环。您正在寻找“部分字符串合并”,我不确定它是否以矢量化形式存在。

      df4 = df1.copy()
      
      def get_amount(x):
          return df2.loc[df2['Ref'].str.contains(x), 'Amount'].iloc[0]
      
      df4['Amount'] = df4['Invoice'].apply(get_amount)
      
      print(df4)
      
        Currency Invoice Amount
      0      EUR   20561    150
      1      EUR   20562    175
      2      EUR   20563    160
      3      USD   20564    180
      

      【讨论】:

        猜你喜欢
        • 2014-05-05
        • 2017-07-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-06-15
        相关资源
        最近更新 更多