【问题标题】:Merge on multiple or conditions in python在python中合并多个或条件
【发布时间】:2020-11-09 00:58:57
【问题描述】:

我有两个数据框。一个具有产品 ID 信息,另一个是具有区域映射和映射 ID 的主数据框。

# this is dummy dataframe for example
product_df= pd.DataFrame([['abc','+1235']['cshdgs','+1352648'],['gdsfsn','+1232455']],columns='['roll','prod_id'])

master_df=pd.DataFrame([['AZ','32'],['WW','123'],['RT','12'],['PO','13'],['SZ','1352']], columns=['Zone','match_id']

我想在 product_df 记录旁边获取区域信息。其逻辑是(inSQL):

select product_df.* left join master_df.Zone where '+'||match_id=substr(prod_id,1,2) or  '+'||match_id=substr(prod_id,1,3) or  '+'||match_id=substr(prod_id,1,4) or  '+'||match_id=substr(prod_id,1,5)

基本上这将是条件合并。 我知道,由于这种加入逻辑,多个区域可能会映射到同一个卷。但这就是问题所在。

我正在尝试在 Python 中实现相同的功能。我正在使用以下代码:

master_df_dict=master_df.sert_index('match_id')['Zone'].to_dict
keys_list=['+' + key for key in master_df_dict.keys()]

def zone(pr_id):
    if pr_id[0:2] in keys_list:
         C=keys_list[pr_id[1:2]] # basically getting the zone information using the matched key, and as 
                                 #first character is always plus, starting the index at 1
    elif pr_id[0:3] in keys_list:
         C=keys_list[pr_id[1:3]]
    elif pr_id[0:4] in keys_list:
         C=keys_list[pr_id[1:4]]
    elif pr_id[0:5] in keys_list:
         C=keys_list[pr_id[1:5]]
    else:
         C=''
    return C

product_df['Zone_info']=product_df['prod_id].apply(zone)

这种方法有两个问题:

  1. 它只会给我第一个匹配的代码,即使后面的条件也匹配,它会在匹配到一个条件时立即退出循环。

  2. 使用这种方法,我需要大约 45 分钟来解析 1700 条记录。

我需要以下方面的帮助:

  1. 为什么上面的代码需要这么长时间才能工作?我怎样才能让它运行得更快

  2. 对于上面提到的基于多个条件加入的 sql 逻辑,我可以得到精确的 python 执行吗?据我搜索,python 没有像 sql 那样在条件下合并的功能。我们能有一个解决方法吗?

  3. 如果没有办法合并所有的 or 条件,有没有办法合并至少第一个匹配条件并使其运行得更快?

请帮忙!!

【问题讨论】:

    标签: python pandas merge conditional-statements


    【解决方案1】:

    您的匹配条件并不是真正的关键。在这些情况下,我形成一个笛卡尔积,然后建立一种方法来查找所需的唯一行。这与关系数据库连接的工作方式非常等效,尤其是在连接中是一种低效的表达式。

    1. Cartesian product idiom
    2. 派生 3 个附加列 a) expr - 将 match_id 与 prod_id 匹配的正则表达式 b) 仅在 sort_values() 上使用的长度 c) 如果 match_id 在您指定的条件下与 prod_id 对齐,则加入 true
    3. 排序并获取第一个有趣的记录
    4. 重置未找到连接的值
    5. 删除用于使其正常工作的列

    更好的解决方案是拥有可靠的连接密钥...

    import re
    product_df= pd.DataFrame(
        [['abc','+1235'],['cshdgs','+1352648'],['gdsfsn','+1232455']],columns=['roll','prod_id'])
    
    master_df=pd.DataFrame([['AZ','32'],['WW','123'],['RT','12'],['PO','13'],['SZ','1352']], columns=['Zone','match_id'])
    
    cp = product_df.assign(foo=1).merge(master_df.assign(foo=1)).drop("foo",1)
    
    cp["len"] = cp.match_id.str.len()
    cp["expr"] = cp.apply(lambda r: "^[+]" + "".join([f"[{c}]{'' if i<2 else '?'}" for i, c in enumerate(r.match_id)]), axis=1)
    cp["join"] = cp.apply(lambda r: re.search(r.expr, r.prod_id) is not None, axis=1)
    cp = cp.sort_values(["Zone", "join", "len"], ascending=[True, False, False]).reset_index()\
        .groupby(["Zone"]).first().reset_index()
    cp.loc[~cp["join"],("roll","prod_id")] = ""
    cp.drop(["len","expr","join","index"], axis=1)
    

    输出

    Zone    roll    prod_id match_id
    0   AZ          32
    1   PO  cshdgs  +1352648    13
    2   RT  abc +1235   12
    3   SZ  cshdgs  +1352648    1352
    4   WW  abc +1235   123
    
    

    【讨论】:

    • 感谢您的回答。输出不包括 roll 'gdsfsn' 的记录。在连接列创建之后,如果我们过滤掉连接值为 true 的位置,那么我们会得到正确的输出。后续步骤的任何原因?
    • 根据您的规范,不清楚您是否想要没有加入的记录。剩下的就是清理——如果没有加入,它不应该显示rollprod_id。加上为解决方案合成的三列不在您的数据集中,我清理了
    • 我在expr 列中添加了一些短暂的透明度...您的规范在前两个字符后注明,如果它们匹配,则它是可选的,因此 reg expr 是为了兑现这一点而构建的
    猜你喜欢
    • 2017-12-30
    • 2019-01-20
    • 2016-01-19
    • 1970-01-01
    • 2019-05-07
    • 1970-01-01
    • 1970-01-01
    • 2021-06-11
    • 1970-01-01
    相关资源
    最近更新 更多