zab*_*bop 31 python duplicates dataframe pandas
问题:
如何在 Pandas 数据框中分别考虑每一行(并可能用 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])
Run Code Online (Sandbox Code Playgroud)
这创造了这个df
:
一种 | 乙 | C | d | |
---|---|---|---|---|
0 | 一种 | 乙 | C | D |
1 | 一种 | D | C | D |
2 | C | 乙 | C | 乙 |
3 | 乙 | 乙 | 一种 | 一种 |
(使用此打印。)
一种解决方案:
从每一行中删除重复项的一种方法,分别考虑每一行:
df = df.apply(lambda row: pd.Series(row).drop_duplicates(keep='first'),axis='columns')
Run Code Online (Sandbox Code Playgroud)
使用apply(),一个lambda函数,pd.Series()和Series.drop_duplicates()。
将所有 NaN 推到每一行的末尾,使用Shift NaN 到它们各自行的末尾:
df.apply(lambda x : pd.Series(x[x.notnull()].values.tolist()+x[x.isnull()].values.tolist()),axis='columns')
Run Code Online (Sandbox Code Playgroud)
输出(根据需要):
0 | 1 | 2 | 3 | |
---|---|---|---|---|
0 | 一种 | 乙 | C | D |
1 | 一种 | D | C | 南 |
2 | C | 乙 | 南 | 南 |
3 | 乙 | 一种 | 南 | 南 |
问题:有没有更有效的方法来做到这一点?也许有一些内置的 Pandas 函数?
ALo*_*llz 27
你可以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
Run Code Online (Sandbox Code Playgroud)
假设有 4 列,让我们看看随着行数的增加,这些方法是如何比较的。在map
和apply
解决方案具有良好的优势时,事情虽小,但他们变得有点比更多地参与较慢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)'
)
Run Code Online (Sandbox Code Playgroud)
最后,如果保留值最初出现在每行中的顺序并不重要,则可以使用numpy
. 要删除重复,您排序然后检查差异。然后创建一个将值向右移动的输出数组。由于此方法将始终返回 4 列,因此我们需要dropna
在每行少于 4 个唯一值的情况下匹配其他输出。
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
Run Code Online (Sandbox Code Playgroud)
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)'
)
Run Code Online (Sandbox Code Playgroud)
WeN*_*Ben 13
尝试新的
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
Run Code Online (Sandbox Code Playgroud)
使用apply
和构造一个新的数据框pd.DataFrame.from_dict
with 选项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
Run Code Online (Sandbox Code Playgroud)
注意:None
实际上类似于NaN
. 如果你想要准确的NaN
. 只需链接额外.fillna(np.nan)
归档时间: |
|
查看次数: |
6514 次 |
最近记录: |