python - 删除行内的Pandas重复值,替换为NaN,将NaN移至行尾

标签 python pandas dataframe duplicates

问题:
如何从Pandas数据框中单独考虑每一行(并可能用NaN代替)从每一行中删除重复的单元格值?
如果我们可以将所有新创建的NaN移到每一行的末尾,那就更好了。

引用文献:相关但不同的文章:

  • 上的帖子如何删除被视为重复的整行:
  • how do I remove rows with duplicate values of columns in pandas data frame?
  • Drop all duplicate rows across multiple columns in Python Pandas
  • Remove duplicate rows from Pandas dataframe where only some columns have the same value

  • 上发布如何从Pandas列的列表中删除重复项:
  • Remove duplicates from rows and columns (cell) in a dataframe, python
  • (该答案返回一系列字符串,而不是数据帧)



  • 例子:
    import pandas as pd
    df = pd.DataFrame({'a': ['A', 'A', 'C', 'B'],
                       'b': ['B', 'D', 'B', 'B'],
                       'c': ['C', 'C', 'C', 'A'],
                       'd': ['D', 'D', 'B', 'A']},
                       index=[0, 1, 2, 3])
    
    这将创建此df:



    一种
    b
    C
    d


    0
    一种

    C
    d

    1个
    一种
    d
    C
    d

    2
    C

    C


    3


    一种
    一种


    (使用this打印。)

    一种解决方案:
    从每行中删除重复项的一种方法,分别考虑每行:
    df = df.apply(lambda row: pd.Series(row).drop_duplicates(keep='first'),axis='columns')
    
    使用apply()lambda函数pd.Series()Series.drop_duplicates()
    使用Shift NaNs to the end of their respective rows将所有NaN推到每一行的末尾:
    df.apply(lambda x : pd.Series(x[x.notnull()].values.tolist()+x[x.isnull()].values.tolist()),axis='columns') 
    
    输出(根据需要):



    0
    1个
    2
    3


    0
    一种

    C
    d

    1个
    一种
    d
    C


    2
    C




    3

    一种




    问题:是否有更有效的方法来做到这一点?也许具有一些内置的Pandas功能?

    最佳答案

    您可以先stack,然后再drop_duplicates。然后,我们需要借助cumcount级别进行透视。 stack保留值在行中出现的顺序,并且cumcount确保NaN出现在最后。

    df1 = df.stack().reset_index().drop(columns='level_1').drop_duplicates()
    
    df1['col'] = df1.groupby('level_0').cumcount()
    df1 = (df1.pivot(index='level_0', columns='col', values=0)
              .rename_axis(index=None, columns=None))
    
       0  1    2    3
    0  A  B    C    D
    1  A  D    C  NaN
    2  C  B  NaN  NaN
    3  B  A  NaN  NaN
    

    时机
    假设有4列,让我们看看随着行数的增加,这些方法的比较情况。当事情变小的时候,mapapply解决方案具有很好的优势,但是随着DataFrame越来越长,它们变得比涉及更多的stack + drop_duplicates + pivot解决方案要慢一些。无论如何,它们对于大型DataFrame都需要花费一些时间。
    import perfplot
    import pandas as pd
    import numpy as np
    
    def stack(df):
        df1 = df.stack().reset_index().drop(columns='level_1').drop_duplicates()
    
        df1['col'] = df1.groupby('level_0').cumcount()
        df1 = (df1.pivot(index='level_0', columns='col', values=0)
                  .rename_axis(index=None, columns=None))
        return df1
    
    def apply_drop_dup(df):
        return pd.DataFrame.from_dict(df.apply(lambda x: x.drop_duplicates().tolist(),
                                               axis=1).to_dict(), orient='index')
    
    def apply_unique(df):
        return pd.DataFrame(df.apply(pd.Series.unique, axis=1).tolist())
    
    
    def list_map(df):
        return pd.DataFrame(list(map(pd.unique, df.values)))
    
    
    perfplot.show(
        setup=lambda n: pd.DataFrame(np.random.choice(list('ABCD'), (n, 4)),
                                     columns=list('abcd')), 
        kernels=[
            lambda df: stack(df),
            lambda df: apply_drop_dup(df),
            lambda df: apply_unique(df),
            lambda df: list_map(df),
        ],
        labels=['stack', 'apply_drop_dup', 'apply_unique', 'list_map'],
        n_range=[2 ** k for k in range(18)],
        equality_check=lambda x,y: x.compare(y).empty,  
        xlabel='~len(df)'
    )
    
    enter image description here

    最后,如果保留每个行中最初出现的值的顺序不重要,则可以使用numpy。要删除重复数据,请排序然后检查差异。然后创建一个输出数组,将值向右移动。因为此方法将始终返回4列,所以在每行少于4个唯一值的情况下,我们需要dropna与其他输出匹配。
    def with_numpy(df):
        arr = np.sort(df.to_numpy(), axis=1)
        r = np.roll(arr, 1, axis=1)
        r[:, 0] = np.NaN
        
        arr = np.where((arr != r), arr, np.NaN)
        
        # Move all NaN to the right. Credit @Divakar
        mask = pd.notnull(arr)
        justified_mask = np.flip(np.sort(mask, axis=1), 1)
        out = np.full(arr.shape, np.NaN, dtype=object) 
        out[justified_mask] = arr[mask]
        
        return pd.DataFrame(out, index=df.index).dropna(how='all', axis='columns')
    
    with_numpy(df)
    #   0  1    2    3
    #0  A  B    C    D
    #1  A  C    D  NaN
    #2  B  C  NaN  NaN     # B/c this method sorts, B before C
    #3  A  B  NaN  NaN
    
    perfplot.show(
        setup=lambda n: pd.DataFrame(np.random.choice(list('ABCD'), (n, 4)),
                                     columns=list('abcd')), 
        kernels=[
            lambda df: stack(df),
            lambda df: with_numpy(df),
        ],
        labels=['stack', 'with_numpy'],
        n_range=[2 ** k for k in range(3, 22)],
        # Lazy check to deal with string/NaN and irrespective of sort order. 
        equality_check=lambda x, y: (np.sort(x.fillna('ZZ').to_numpy(), 1) 
                                     == np.sort(y.fillna('ZZ').to_numpy(), 1)).all(),
        xlabel='len(df)'
    )
    
    enter image description here

    关于python - 删除行内的Pandas重复值,替换为NaN,将NaN移至行尾,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63583502/

    相关文章:

    python pandas 3个最小值和3个最大值

    python - 合并列不匹配的 pandas 数据框

    python - leetcode 爆破气球超时

    python - 如何使用 isalpha() 将非字母字符替换为空格?

    python - For循环确定加权平均python

    python - 将系列字典从数据帧列转换为同一数据帧中的单独列

    python - pandas groupby 并在不同类型之间使用数字

    python - 合并数据框中的值以在 excel 中写入

    python - 如何将列表中的所有元素从日期时间转换为日期?

    python - 在 2D numpy 数组的子矩阵上高效运行