【问题标题】:Filtering for and replacing values in one Pandas DataFrame based on common columns of another DataFrame根据另一个 DataFrame 的公共列过滤和替换一个 Pandas DataFrame 中的值
【发布时间】:2021-11-10 08:12:45
【问题描述】:

我有一个关于 Pandas 以及正确索引和替换值的问题。

我有 2 个 DataFrame,df1 和 df2,具有相同的列(Col1、Col2、Col3 和 Col4)。

df1 = pd.DataFrame([['A','b','x',1], ['A','b','y',2], ['A','c','z',3], ['B','b','x',4]], columns=['Col1', 'Col2', 'Col3', 'Col4'])
df2 = pd.DataFrame([['A','b','y',0], ['B','b','x',0]], columns=['Col1','Col2','Col3','Col4'])
    
df1
  Col1 Col2 Col3 Col4
0    A    b    x    1
1    A    b    y    2
2    A    c    z    3
3    B    b    x    4
    
df2
  Col1 Col2 Col3 Col4
0    A    b    y    0
1    B    b    x    0

在 df1 中,我想替换 Col4 中与 其他列 的值匹配的行中的值(Col1, df2 中的 Col2 和 Col3) 具有另一个值(比如说 100)。

生成的 df1 如下所示:

df1
  Col1 Col2 Col3 Col4
0    A    b    x    1
1    A    b    y  100
2    A    c    z    3
3    B    b    x  100

我尝试过这样的事情:

columns = list(df1.columns)    
columns.remove('Col4')
df1.loc[(df1[cols] == df2[cols].values).all(axis=1)]['Col4']=100

但是我遇到了错误,我不确定这是否达到了我想要的效果。

【问题讨论】:

    标签: python pandas indexing


    【解决方案1】:

    您可以使用索引执行isin,并通过布尔掩码分配值:

    
    cols = ['Col1', 'Col2', 'Col3']
    
    temp1 = df1.set_index(cols)
    
    temp2 = df2.set_index(cols)
    
    # get the booleans here 
    booleans = temp1.index.isin(temp2.index)
    
    
    # this assigns 100 to only rows in Col4
    # that are True
    df1.loc[booleans, 'Col4'] = 100
    
    df1
    
      Col1 Col2 Col3  Col4
    0    A    b    x     1
    1    A    b    y   100
    2    A    c    z     3
    3    B    b    x   100
    

    或者,您可以使用pd.merge 和indicator 参数来解决它:

    (df1.merge(df2, 
               on = cols, 
               how = 'left', 
               indicator=True, 
               suffixes = (None, '_y'))
        .assign(Col4 = lambda df: np.where(df._merge == 'both', 
                                           100, 
                                           df.Col4))
       .loc[:, df1.columns]
    )
    
      Col1 Col2 Col3  Col4
    0    A    b    x     1
    1    A    b    y   100
    2    A    c    z     3
    3    B    b    x   100
    

    【讨论】:

    • 非常感谢!第一个解决方案似乎对我有用。应用解决方案时,Col4 中与布尔值中的 True 值不对应的值被 NaN 替换。因此,虽然第 1 行和第 3 行中的值被正确地替换为 100,但第 0 行和第 2 行中的值被错误地替换为 NaN。否则,这似乎工作得很好,而且非常简约。有没有办法纠正这最后一个问题?
    • 列名有变化吗?从Col4 到col4?小案例?大写?您介意分享一个引入 NaN 的更大示例吗?
    • 我的错!这正是问题所在,你的最后一行有'col4'而不是'Col4'。将编辑
    • 对此我深表歉意
    【解决方案2】:

    让我们使用pandas.DataFrame.update 方法:

    df1 = df1.set_index(['Col1', 'Col2', 'Col3'])
    _df2 = df2.set_index(['Col1', 'Col2', 'Col3'])
    
    # set arbitrary value for Col4
    _df2['Col4'] = 100
    
    # update values in df1
    df1.update(_df2)
    
    # reset index
    df1 = df1.reset_index()
    
    

    【讨论】:

    • 这似乎工作得很好(尽管 df1 和 df2 在示例中似乎被交换了;即想用 df2 的值更新 df1,而不是相反)。但是,我没有在我的问题中提到这一点,此解决方案更改了更新后的 df 中的行顺序,以便更改的行位于顶部。我想保留行的顺序。尽管如此,一个优雅的解决方案!
    • 我对 df2 mixup 不好,编辑了答案。如果需要,我们可以在完成对行重新排序后执行.sort_values(),但否则我更喜欢@sammywemmy 的解决方案布尔值.loc 赋值,我认为它会更快(性能方面)
    【解决方案3】:

    你可以试试这个:

    new_df =df1.merge(df2, on=['Col1', 'Col2', 'Col3'], how='left', suffixes=(None, '_y'))
    new_df.loc[new_df.Col4_y.notna(), 'Col4'] = 100
    
    new_df.loc[:, df1.columns]
    
    
    
       Col1 Col2    Col3    Col4
    0   A    b        x       1
    1   A    b        y      100
    2   A    c        z       3
    3   B    b        x      100
    

    我还想注意 merge 和 isin 方法的性能

    %%timeit
    # best answer
    cols = ['Col1', 'Col2', 'Col3']
    
    temp1 = df1.set_index(cols)
    
    temp2 = df2.set_index(cols)
    
    # get the booleans here 
    booleans = temp1.index.isin(temp2.index)
    
    
    # this assigns 100 to only rows in Col4
    # that are True
    df1.loc[booleans, 'Col4'] = 100
    
    
    3.97 ms ± 765 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    
    %%timeit
    cols = ['Col1', 'Col2', 'Col3']
    (df1.merge(df2, 
               on = cols, 
               how = 'left', 
               indicator=True, 
               suffixes = (None, '_y'))
        .assign(Col4 = lambda df: np.where(df._merge == 'both', 
                                           100, 
                                           df.Col4))
       .loc[:, df1.columns]
    )
    
    5.78 ms ± 660 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    
    %%time
    # by kelvt
    df1 = df1.set_index(['Col1', 'Col2', 'Col3'])
    _df2 = df2.set_index(['Col1', 'Col2', 'Col3'])
    
    # set arbitrary value for Col4
    _df2['Col4'] = 100
    
    # update values in df1
    df1.update(_df2)
    
    # reset index
    df1 = df1.reset_index()
    
    21.46 ms ± 609 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
    

    和

    %%timeit
    # my answer
    new_df =df1.merge(df2, on=['Col1', 'Col2', 'Col3'], how='left', suffixes=(None, '_y'))
    new_df.loc[new_df.Col4_y.notna(), 'Col4'] = 100
    
    1.49 ms ± 109 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
    

    如果您的 df1 和 df2 足够大,这很重要

    【讨论】:

      【解决方案4】:

      您始终可以循环遍历数据框并在匹配时替换值

      for index_df1, row_df1 in df1.iterrows():
          for index_df2, row_df2 in df2.iterrows():
              if (row_df2[:-1] == row_df1[:-1]).all():
                  df1.loc[index_df1, 'Col4'] = 100
      

      【讨论】:

      • 始终尽量避免在 for 循环中迭代 pandas 数据框
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-02-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-15
      • 2019-07-16
      相关资源
      最近更新 更多