Python 删除行中的重复值,替换为NaN,将NaN移到行的末尾

Python 删除行中的重复值,替换为NaN,将NaN移到行的末尾,python,pandas,dataframe,duplicates,Python,Pandas,Dataframe,Duplicates,问题: 如何从每一行中删除重复的单元格值,在一个数据帧中分别考虑每一行(也许用NAN替换它们) 如果我们能将所有新创建的NAN移到每行的末尾,那就更好了 参考:相关但不同的帖子: 在上发布如何删除被视为重复的整行: 在上发布如何从列中的列表中删除重复项: (该答案返回一系列字符串,而不是数据帧) 例如: import pandas as pd df = pd.DataFrame({'a': ['A', 'A', 'C', 'B'],

问题

如何从每一行中删除重复的单元格值,在一个数据帧中分别考虑每一行(也许用NAN替换它们)

如果我们能将所有新创建的NAN移到每行的末尾,那就更好了


参考:相关但不同的帖子:

  • 上发布如何删除被视为重复的整行
  • 上发布如何从列中的列表中删除重复项
      • (该答案返回一系列字符串,而不是数据帧)

例如:

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

A. B C D 0 A. B C D 1. A. D C D 2. C B C B 3. B B A. A.
您可以
堆叠
,然后
以这种方式删除重复的
。然后,我们需要借助于
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列,让我们看看随着行数的增加,这些方法之间的比较情况。当事情很小时,
map
apply
解决方案有很好的优势,但是随着数据帧变长,它们比更复杂的
堆栈
+
drop\u duplicates
+
pivot
解决方案慢一些。不管怎样,它们都开始需要一段时间才能获得一个大数据帧

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)'
)


最后,如果保留值最初在每行中出现的顺序并不重要,则可以使用
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


您可以在
轴上搜索重复项,然后通过使用特定键对结果进行排序,将结果“推”到行末尾的
Nan

duplicates = df.apply(pd.Series.duplicated, axis=1)
df.where(~duplicates, np.nan).apply(lambda x: pd.Series(sorted(x, key=pd.isnull)), axis=1)
输出

| 0   | 1   | 2   | 3   |
|:----|:----|:----|:----|
| A   | B   | C   | D   |
| A   | D   | C   | NaN |
| C   | B   | NaN | NaN |
| B   | A   | NaN | NaN |
尝试新事物

df = pd.DataFrame(list(map(pd.unique, df.values)))
Out[447]: 
   0  1     2     3
0  A  B     C     D
1  A  D     C  None
2  C  B  None  None
3  B  A  None  None

使用
apply
并通过
pd.dataframe.from\u dict
选项
orient='index'

df_final = pd.DataFrame.from_dict(df.apply(lambda x: x.drop_duplicates().tolist(),
                                               axis=1).to_dict(), orient='index')

Out[268]:
   0  1     2     3
0  A  B     C     D
1  A  D     C  None
2  C  B  None  None
3  B  A  None  None

注:
None
实际上类似于
NaN
。如果您想要精确的
NaN
。只需在每行上链接附加的
.fillna(np.nan)
应用
pd.Series.unique
,提取结果并重新构建数据框:

print (pd.DataFrame(df.apply(pd.Series.unique, axis=1).tolist()))

   0  1     2     3
0  A  B     C     D
1  A  D     C  None
2  C  B  None  None
3  B  A  None  None

我试图找到一个问题,这篇文章是重复的,但令我惊讶的是,我没有找到任何一个是一个好的适合重复。我欢迎任何关于重复项的建议。你从未定义过“重复项”的含义,特别是因为你使用的是与正常意义不同的含义(“多列中的不同值,按行考虑”),并且你的标题过于宽泛,人们会被谷歌错误地发送到这里,因此搜索。举例来说,在第1行,为什么不从列“C”中删除第二个“C”,但从列“D”中删除了第二个“D”?这毫无意义。此外,“用NaN替换单元格”并不是真正的“删除”,因此这是一个两个问题,代码解决方案将不同(
fillna
duplicated
drop\u duplicates
,等等)。在您的用例中,您有多个列,所有列都被认为是等效的,相同的数据类型,不关心名称。因此,“FirstName=Murphy,LastName=Brown”将被视为“Brown,Murphy”的“复制品”;或者zipcode为77024、收入为60001或客户id为45678都将被视为“等同于”列中相同值的其他排列。这绝对不是“重复”的标准定义。您的数据实际上只是一个数组,而不是一个真正的数据帧,“将所有新创建的NaN移到每行的末尾”这一部分就证明了这一点。我不理解这个问题:“在第1行,为什么您没有从列“C”中删除第二个“C”,但您确实从列“D”中删除了第二个“D”第一行只有一个C。关于其他的:我希望你的编辑能够解决这些问题&这篇文章现在可以了。谢谢!这确实有效。如果有人提出了一个更简单的解决方案,那么这个问题就留一点余地吧。如果没有,我们肯定会接受这个问题。@Alolz:谢谢
perfplot
。我真的很好奇每个解决方案的性能,但我不擅长设置它。我已经投了你的票。否则,我会在
perfplot
:)上再次向上投票
print (pd.DataFrame(df.apply(pd.Series.unique, axis=1).tolist()))

   0  1     2     3
0  A  B     C     D
1  A  D     C  None
2  C  B  None  None
3  B  A  None  None