从另一个数据框架中的一个数据框架中搜索值 On ^ 2个操作用合并替换循环

问题描述

警告:您将看到一个非常。非常糟糕的一段代码。我知道,我只是不知道如何解决。我尝试了几种替代方法,但是我缺乏在Pandas的经验(或numpy-也许这是更好的替代方法)。您已被警告!

我有两个数据框,我需要从数据框2上存在的数据框1中找到匹配的信息。让我告诉你:

# DataFrame 1
d1 = {'name': ['John Doe','Jane Doe'],'email': ['john@example.com','jane@example.com'],'phone': ['15181111111','15182222222']}

df1 = pd.DataFrame(data=d1)
###
# DataFrame 2
d2 = {'name': ['Fred Flinstone','Barney Rubble','Betty Rubble'],'barney@example.com','betty@example.com'],'Mobile': ['15183333333','15182222222','15184444444'],'LandLine': ['15181111111','15185555555']}

df2 = pd.DataFrame(data=d2)

所以我的目标是找到df2中的哪些行与df1(电子邮件,电话)中的每条可用数据(但名称)匹配。找到匹配项后,我需要记录两个数据帧中的所有数据。

现在,开始咬指甲,深吸一口气,看看我在做的耻辱。它确实有效,但是您会很快意识到问题所在:

# Empty dataframe to store matches
df_found = pd.DataFrame(columns=['df1 Name','df1 email','df1 phone','df2 name','df2 email','df2 mobile','df2 landline'])

# Search for matches
for row_df1 in df1.itertuples():
    tmp_df = df2[df2['email'].str.contains(row_df1.email,na=False,case=False)]
    if(len(tmp_df) > 0):
        for row_df2 in tmp_df.itertuples():
            df_found.loc[len(df_found)] = [row_df1.name,row_df1.email,row_df1.phone,row_df2.name,row_df2.email,row_df2.Mobile,row_df2.LandLine]
    
    tmp_df = df2[df2['Mobile'].str.contains(row_df1.phone,row_df2.LandLine]
    
    tmp_df = df2[df2['LandLine'].str.contains(row_df1.phone,row_df2.LandLine]

#Drop duplicates - Yes of course there are many
df_found.drop_duplicates(keep='first',inplace=True)

您去了,我在一个循环中有一系列循环,每个循环遍历相同的数据并添加 temporary 数据框和 matchholder 数据框。

最后我得到了结果:

enter image description here

但是速度太恐怖了。我的真实数据帧的第一列为29列,第二列为55列。第一类大约有10万条记录,第二类大约有50万条记录。现在,在没有GPU和16GB RAM的i7中,该过程大约需要四个小时。

如果您已经可以呼吸并且不再用力撞墙,那么我将对如何正确使用此方法提出一些想法。

非常感谢您!

解决方法

O(n ^ 2)个操作

向数据帧添加一行需要复制整个数据帧-因此一次建立一个数据帧是O(n ^ 2)操作,而且非常慢。另外,Series.str.contains要求检查每个字符串值是否包含。由于您正在将每一行与其他每一行进行比较,因此这也是O(n ^ 2)操作。

通常,熊猫的单行操作表示代码很慢。

用合并替换循环

您可以执行SQL样式的连接来执行您在此处尝试执行的操作。

email_merge = df1.merge(df2,on=["email"],suffixes=("","_right"))
mobile_merge = df1.merge(df2,left_on=["phone"],right_on=["Mobile"],"_right"))
landline_merge = df1.merge(df2,right_on=["LandLine"],"_right"))

第一行在电子邮件字段之间进行联接。第二个联接针对第一种电话。第三次加入针对第二种电话。顺便说一句,您将要完成很多重复操作。

然后可以将每个数据帧连接在一起:

print(pd.concat([email_merge,landline_merge,mobile_merge],sort=True))

这给了我以下结果:

      LandLine       Mobile             email         email_right      name      name_right        phone
0  15181111111  15183333333  john@example.com                 NaN  John Doe  Fred Flinstone  15181111111
0  15181111111  15183333333  john@example.com    john@example.com  John Doe  Fred Flinstone  15181111111
1  15182222222  15182222222  jane@example.com  barney@example.com  Jane Doe   Barney Rubble  15182222222
0  15182222222  15182222222  jane@example.com  barney@example.com  Jane Doe   Barney Rubble  15182222222

,

尝试合并数据框:

df_found = pd.merge(df1.loc[:,df1.columns != 'name'],df2.loc[:,df2.columns != 'name'],how='inner')
df_found = df_found.merge(df1)