Python:Pandas - 按组删除第一行

29

我有一个类似于下面这样的大型数据框(df):

    ID     date        PRICE       
1   10001  19920103  14.500    
2   10001  19920106  14.500    
3   10001  19920107  14.500     
4   10002  19920108  15.125     
5   10002  19920109  14.500   
6   10002  19920110  14.500    
7   10003  19920113  14.500 
8   10003  19920114  14.500     
9   10003  19920115  15.000 

问题:如何最有效地删除每个ID的第一行?我想要这样:

        ID     date     PRICE       
    2   10001  19920106  14.500    
    3   10001  19920107  14.500     
    5   10002  19920109  14.500   
    6   10002  19920110  14.500    
    8   10003  19920114  14.500     
    9   10003  19920115  15.000 

我可以循环遍历每个唯一的ID并删除第一行,但我认为这不是很高效。

5个回答

43

另一种简洁的代码是df.groupby('ID').apply(lambda group: group.iloc[1:, 1:])

Out[100]: 
             date  PRICE
ID                      
10001 2  19920106   14.5
      3  19920107   14.5
10002 5  19920109   14.5
      6  19920110   14.5
10003 8  19920114   14.5
      9  19920115   15.0

16
为什么在 [1:, 1:] 中我们需要两个 1: - Jason Goal
4
第二个“1:”是不必要的。它的意思是从第二列开始取(因为第一列是0)直到结尾。你可以简单地做group.iloc[1:]。 - Algorithman

23

您可以使用groupby/transform来准备一个布尔掩码,该掩码对于您想要的行为True,对于您不想要的行为False。一旦您拥有这样的布尔掩码,您就可以使用df.loc[mask]选择子数据框:

import numpy as np
import pandas as pd

df = pd.DataFrame(
    {'ID': [10001, 10001, 10001, 10002, 10002, 10002, 10003, 10003, 10003],
     'PRICE': [14.5, 14.5, 14.5, 15.125, 14.5, 14.5, 14.5, 14.5, 15.0],
     'date': [19920103, 19920106, 19920107, 19920108, 19920109, 19920110,
              19920113, 19920114, 19920115]},
    index = range(1,10)) 

def mask_first(x):
    result = np.ones_like(x)
    result[0] = 0
    return result

mask = df.groupby(['ID'])['ID'].transform(mask_first).astype(bool)
print(df.loc[mask])

产量
      ID  PRICE      date
2  10001   14.5  19920106
3  10001   14.5  19920107
5  10002   14.5  19920109
6  10002   14.5  19920110
8  10003   14.5  19920114
9  10003   15.0  19920115

既然您关心效率,这里有一个基准测试:

import timeit
import operator
import numpy as np
import pandas as pd

N = 10000
df = pd.DataFrame(
    {'ID': np.random.randint(100, size=(N,)),
     'PRICE': np.random.random(N),
     'date': np.random.random(N)}) 

def using_mask(df):
    def mask_first(x):
        result = np.ones_like(x)
        result[0] = 0
        return result

    mask = df.groupby(['ID'])['ID'].transform(mask_first).astype(bool)
    return df.loc[mask]

def using_apply(df):
    return df.groupby('ID').apply(lambda group: group.iloc[1:, 1:])

def using_apply_alt(df):
    return df.groupby('ID', group_keys=False).apply(lambda x: x[1:])

timing = dict()
for func in (using_mask, using_apply, using_apply_alt):
    timing[func] = timeit.timeit(
        '{}(df)'.format(func.__name__), 
        'from __main__ import df, {}'.format(func.__name__), number=100)

for func, t in sorted(timing.items(), key=operator.itemgetter(1)):
    print('{:16}: {:.2f}'.format(func.__name__, t))

报告
using_mask      : 0.85
using_apply_alt : 2.04
using_apply     : 3.70

11

虽然旧但仍经常使用:更快的解决方案是将nth(0)与去重drop duplicates结合使用:

def using_nth(df):
    to_del = df.groupby('ID',as_index=False).nth(0)
    return pd.concat([df,to_del]).drop_duplicates(keep=False)

在我的系统中,unutbus设置的时间如下:

using_nth       : 0.43
using_apply_alt : 1.93
using_mask      : 2.11
using_apply     : 4.33

4
谢谢,.nth()很棒!虽然我通过以下代码使速度提高了两倍: return df.drop(df.groupby('ID',as_index=False).nth(0).index) (时间从 3.13 毫秒±11.8 微秒降至1.49 毫秒 ± 90.2 微秒)。 - Marius Wallraff

5
我找到的最快的解决方案是生成一个具有组 - 观察编号的列,然后删除所有值为 0 的观察。
df['num_in_group'] = df.groupby('ID').cumcount()
df = df[df['num_in_group'] > 0]

或者
df = df[df.groupby('ID').cumcount() != 0]

5

按列ID使用DataFrame.duplicated

df = df[df.duplicated('ID')]
print (df)
      ID      date  PRICE
2  10001  19920106   14.5
3  10001  19920107   14.5
5  10002  19920109   14.5
6  10002  19920110   14.5
8  10003  19920114   14.5
9  10003  19920115   15.0

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