按列A去重,保留列B中最高值所在的行。

315
我有一个包含重复值的A列数据框。我想删除重复项,保留B列中具有最高值的行。 所以原始数据如下:
A B
1 10
1 20
2 30
2 40
3 10

应该变成这样:

A B
1 20
2 40
3 10

我猜可能有一种简单的方法来做到这一点——也许只需在删除重复项之前对DataFrame进行排序,但我不太了解groupby的内部逻辑,不能想出来。您有什么建议吗?


1
请注意,问题中的URL已经过时。 - DaveL17
为了达到惯用且高效的方式,请参考下面的解决方案:点击此处 - Ted Petrou
时间已经过去...就我目前的了解,我相信这个下面的解决方案更快(至少在存在大量重复项的情况下),而且更简单。 - Pierre D
15个回答

375

这会获取最后一个,但不是最大值:

In [10]: df.drop_duplicates(subset='A', keep="last")
Out[10]: 
   A   B
1  1  20
3  2  40
4  3  10

你也可以这样做:

In [12]: df.groupby('A', group_keys=False).apply(lambda x: x.loc[x.B.idxmax()])
Out[12]: 
   A   B
A       
1  1  20
2  2  40
3  3  10

14
注:colstake_last参数已被弃用,现已被subsetkeep参数所取代。http://pandas.pydata.org/pandas-docs/version/0.17.1/generated/pandas.DataFrame.drop_duplicates.html - Jezzamon
正如 @Jezzamon 所说,FutureWarning: take_last=True 关键字已被弃用,请使用 keep='last' - tumultous_rooster
2
有没有不使用 df.sort_values(by=['B']).drop_duplicates(subset=['A'], keep='last') 的原因?我的意思是,这个 sort_values 对我来说似乎很安全,但我不知道它是否真的安全。 - Little Bobby Tables
7
这个答案现在已经过时了。请参考下面 @Ted Petrou 的回答。 - cxrodgers
如果您想使用此代码,但是在group_by中有多列的情况下,可以添加.reset_index(drop=True)df.groupby(['A','C'], group_keys=False).apply(lambda x: x.ix[x.B.idxmax()]).reset_index(drop=True) 这将重置索引,因为其默认值将是由'A''C'组成的Multindex。 - Hamri Said
显示剩余4条评论

156

顶部答案正在做过多的工作,对于较大的数据集来说速度非常慢。apply很慢,如果可能的话应该避免使用。ix已被弃用,也应该避免使用。

df.sort_values('B', ascending=False).drop_duplicates('A').sort_index()

   A   B
1  1  20
3  2  40
4  3  10

或者只需按所有其他列分组,并获取您需要的列的最大值。 df.groupby('A', as_index=False).max()


3
这实际上是一个聪明的方法。我在想是否可以通过在删除时使用一些 lambda函数来进行概括。例如,我如何仅删除小于重复值平均值的值。 - Dexter
这比groupby慢(因为它需要进行初始的sort_values(),其时间复杂度为O[n log n],而groupby则避免了这一步骤)。请参见2021年的一个回答 - Pierre D

58

最简单的解决方案:

根据一个列删除重复项:

df = df.drop_duplicates('column_name', keep='last')

根据多个列删除重复项:

df = df.drop_duplicates(['col_name1','col_name2','col_name3'], keep='last')

1
最佳解决方案。谢谢。 - Flavio
1
我的数据框有10列,我使用了这段代码来删除三列中的重复项。然而,它也删除了其余列中的行。有没有办法只删除最后4列中的重复项? - Sofia
9
但是 OP 希望保留 B 列中的最高值。如果您先进行排序,这可能有效。但那基本上就是 Ted Petrou 的答案了。 - Teepeemm
3
本回答假设列已排序,这一点在问题中并未说明。 - Denziloe
1
非常好的解决方案,非常感谢。 - Hùng Nguyễn
显示剩余3条评论

33

我会先按照 B 列降序排列数据框,然后根据 A 列去重并保留第一个。

df = df.sort_values(by='B', ascending=False)
df = df.drop_duplicates(subset='A', keep="first")

没有任何分组


12

试试这个:

df.groupby(['A']).max()

1
你知道最好的习语来重新索引它,使其看起来像原始的DataFrame吗?当你快速解决问题时,我正在尝试弄清楚这个问题。 :^) - DSM
5
整齐。如果数据框包含更多列(例如C、D、E),该怎么办?在这种情况下,最大值似乎不起作用,因为我们需要指定只有B列需要最大化。 - Abe
1
@DSM 请检查原始问题中的链接。有一些代码可以重新索引分组数据帧。 - Abe

8

我被一篇重复的问题的链接带到这里。

对于只有两列,做以下操作是否更简单:

df.groupby('A')['B'].max().reset_index()

如果要保留整个行(即使有多个列),可以这样做:

df.loc[df.groupby(...)[column].idxmax()]

例如,为了保留每个['A', 'B']组的'C'取最大值的完整行,我们可以执行以下操作:
out = df.loc[df.groupby(['A', 'B')['C'].idxmax()]

当群组较少(即有很多重复项)时,此方法比使用drop_duplicates() 解决方案更快(排序量更小):

设置:

n = 1_000_000
df = pd.DataFrame({
    'A': np.random.randint(0, 20, n),
    'B': np.random.randint(0, 20, n),
    'C': np.random.uniform(size=n),
    'D': np.random.choice(list('abcdefghijklmnopqrstuvwxyz'), size=n),
})

(为确保相同的解决方案,添加 sort_index()):
%timeit df.loc[df.groupby(['A', 'B'])['C'].idxmax()].sort_index()
# 101 ms ± 98.7 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

%timeit df.sort_values(['C', 'A', 'B'], ascending=False).drop_duplicates(['A', 'B']).sort_index()
# 667 ms ± 784 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)

5

最简单的方法:

# First you need to sort this DF as Column A as ascending and column B as descending 
# Then you can drop the duplicate values in A column 
# Optional - you can reset the index and get the nice data frame again
# I'm going to show you all in one step. 

d = {'A': [1,1,2,3,1,2,3,1], 'B': [30, 40,50,42,38,30,25,32]}
df = pd.DataFrame(data=d)
df

    A   B
0   1   30
1   1   40
2   2   50
3   3   42
4   1   38
5   2   30
6   3   25
7   1   32


df = df.sort_values(['A','B'], ascending =[True,False]).drop_duplicates(['A']).reset_index(drop=True)

df

    A   B
0   1   40
1   2   50
2   3   42

4

我认为在你的情况下,你并不真正需要使用groupby。我建议按照B列的降序排序,然后在A列上去重,如果需要,还可以给它们一个新的漂亮、干净的索引:

df.sort_values('B', ascending=False).drop_duplicates('A').sort_index().reset_index(drop=True)

这有什么不同于其他帖子呢? - DJK

2

这里有一个我必须解决的变化,值得分享:对于columnA中的每个唯一字符串,我想找到与之关联的最常见字符串在columnB中。

df.groupby('columnA').agg({'columnB': lambda x: x.mode().any()}).reset_index()

.any()如果模式相同,则选择其中一个。(请注意,在int序列上使用.any()会返回布尔值而不是其中一个。)

对于原始问题,相应的方法简化为

df.groupby('columnA').columnB.agg('max').reset_index()


2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接