熊猫:加入部分字符串匹配,例如Excel VLOOKUP

时间:2018-06-22 08:03:14

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

我正在尝试在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()可以以某种更优雅的方式使用吗?提前非常感谢您的帮助!

2 个答案:

答案 0 :(得分:1)

这是使用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

答案 1 :(得分:1)

这里有两个替代解决方案,都使用了熊猫的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更健壮,因为它直接比较字符串。 如果需要,可以将regex解决方案改进为更强大。