【问题标题】:Pandas/Python equivalent of complex ifelse match in RPandas/Python 相当于 R 中的复杂 ifelse 匹配
【发布时间】:2017-08-16 06:54:06
【问题描述】:

我的目标是获得以下 R 代码的 pandas 等效项:

df1$String_1_check = ifelse(df1$String_1 == df2[match(df1$String_2, df2$String_2), 1], TRUE, FALSE)

如果 df1 的 String_1 列第 n 行的值等于 df1 的 String_2 列的第 n 行与 df2 的 String_2 匹配的 df2 的第一列,则在新列 String_1_check 中为 True,否则在 String_1_check 中为 False。

df1 在 String_1 和 String_2 中有许多相同值的实例,而 df2 在 String_1 中只有一个可能值的实例。 String_3 不是唯一的。使用这些示例数据框:

df1 = pd.DataFrame({'String_1': ['string 1', 'string 1', 'string 2', 'string 3', 'string 1'], 'String_2': ['string a', 'string a', 'string b', 'string a', 'string c']})
df2 = pd.DataFrame({'String_3': ['string 1', 'string 2', 'string 3'], 'String_2': ['string a', 'string b', 'string c']})

   String_1  String_2
0  string 1  string a
1  string 1  string a
2  string 2  string b
3  string 3  string a
4  string 1  string c

   String_3  String_2
0  string 1  string a
1  string 2  string b
2  string 3  string c

期望的输出是:

   String_1  String_2  String_1_check
0  string 1  string a  True
1  string 1  string a  True
2  string 2  string b  True
3  string 3  string a  False
4  string 1  string c  False

我尝试了np.whereisinpd.match(现已弃用),但没有找到解决方案。

【问题讨论】:

    标签: python pandas if-statement match


    【解决方案1】:

    将值分配回df1,就像原来的R一样,您可以这样做:

    In []:
    df1['String_1_check'] = df1.merge(df2, how='left')['String_3'] == df1['String_1']
    df1
    
    Out:
       String_1  String_2  String_1_check
    0  string 1  string a            True
    1  string 1  string a            True
    2  string 2  string b            True
    3  string 3  string a           False
    4  string 1  string c           False
    

    【讨论】:

      【解决方案2】:

      您可以使用map,而无需更改原来df的顺序

      df1['String_1_check']=list(zip(df1['String_1'],df1['String_2']))
      df2.index=list(zip(df2['String_3'],df2['String_2']))
      df2['Check']=True
      df1['String_1_check']=df1['String_1_check'].map(df2['Check']).fillna(False)
      
      Out[764]: 
         String_1  String_2  String_1_check
      0  string 1  string a            True
      1  string 1  string a            True
      2  string 2  string b            True
      3  string 3  string a           False
      4  string 1  string c           False
      

      【讨论】:

      • 这最终为我的真实数据情况提供了最佳效果。谢谢!
      【解决方案3】:

      合并两个数据框并检查字符串 1 和 3 是否匹配(经过编辑以纳入 AChampion 的建议):

      dfnew = df1.merge(df2, how='left')
      dfnew["String_1_check"] = (dfnew.String_1 == dfnew.String_3)
      del dfnew["String_3"]
      print(dfnew)
      #   String_1  String_2 String_1_check
      #0  string 1  string a           True
      #1  string 1  string a           True
      #2  string 3  string a          False
      #3  string 2  string b           True
      #4  string 1  string c          False
      

      【讨论】:

      • 我也在尝试合并,但请注意它丢失了 df1 的顺序。不知道这是否重要。
      • 如有必要,可以按任何顺序对行进行排序。
      • 如果你做df1.merge(df2, how='left'),你可以保持订单,所以df1['String_1_check'] == df1.merge(df2, how='left')['String_3'] == df1['String_1'],相当于原来的R代码将结果分配回df1
      【解决方案4】:

      假设df2.String_3 是唯一的,从df2 创建一个系列,并在map 中使用它来与df1.String_2 进行比较。考虑到与merge 相比,map 是恒定时间查找,这将很快。

      如果 df2.String_3 不是唯一的,请注意 OP 要求我们只关注从 df1.String_1 中找到第一个匹配项的行。这意味着我们可以使用drop_duplicates 使df2.String_3 独一无二

      df1.String_1.map(df2.set_index('String_3').String_2).eq(df1.String_2)
      
      0     True
      1     True
      2     True
      3    False
      4    False
      dtype: bool
      

      非唯一性的修改版本

      df1.String_1.map(
          df2.drop_duplicates('String_3').set_index('String_3').String_2
      ).eq(df1.String_2)
      

      使用pd.DataFrame.assign 创建包含新列的df1 的副本。

      df1.assign(
          String_1_check=df1.String_1.map(
              df2.drop_duplicates('String_3').set_index('String_3').String_2
          ).eq(df1.String_2)
      )
      
         String_1  String_2  String_1_check
      0  string 1  string a            True
      1  string 1  string a            True
      2  string 2  string b            True
      3  string 3  string a           False
      4  string 1  string c           False
      

      时机
      在这个模拟中,df2 的大小是静态的。我不想为独特的价值观建模。
      下面的代码

      pir = lambda df1, df2: df1.assign(String_1_check=df1.String_1.map(df2.drop_duplicates('String_3').set_index('String_3').String_2).eq(df1.String_2))
      achamp = lambda df1, df2: df1.assign(String_1_check=df1.merge(df2, how='left').eval('String_3 == String_1'))
      
      results = pd.DataFrame(
          index=pd.Index([10, 30, 100, 300, 1000, 3000, 10000, 30000]),
          columns='pir achamp'.split()
      )
      
      for i in results.index:
          d1 = pd.concat([df1] * i, ignore_index=True)
          for j in results.columns:
              stmt = '{}(d1, df2)'.format(j)
              setp = 'from __main__ import d1, df2, {}'.format(j)
              results.set_value(i, j, timeit(stmt, setp, number=20))
      
      results.plot(loglog=True)
      

      【讨论】:

      • 我知道 merge 不会很快 - 有趣的是我反向构建了我的 map ;) df1['String_2'].map(df2.set_index('String_2')['String_3']) == df1['String_1'] - 结果相同。 +1
      • 我也对你的功能不屑一顾,并使用了eval。这是对较小数据的性能障碍,而对较大数据则有利。但是用lambda 包裹起来会更漂亮。
      • 我真的很喜欢这个。不幸的是,String_3 中的值不是唯一的。我已经编辑了我的问题以反映这一点。很抱歉有任何混淆。你有比merge更快的解决方案吗?
      • 实际上,因为您只关心与df2 中构成您第一次从df1 找到匹配项的行进行比较...那么您可以删除重复项并使其唯一.我会更新帖子。
      • @AndrewRussell 还注意到,如果 String_3 不是唯一的,merge 也会有一些有趣的问题。
      猜你喜欢
      • 2022-01-18
      • 2011-05-05
      • 1970-01-01
      • 2017-04-16
      • 2014-03-17
      • 1970-01-01
      • 2019-09-08
      • 1970-01-01
      相关资源
      最近更新 更多