使用 groupby 后如何从 Pandas 数据帧中提取 select 行?

How to select rows from Pandas dataframe after using groupby?

从下面的数据框中,如何在不借助copying/pasting或中间数据结构的情况下获得拥有两只以上宠物的主人列表?

df = pd.DataFrame([['Jack', 'fuzzy',12], ['Jack', 'furry',13], ['Joe', 'scratchy',3], ['Jack', 'chirpy',40], ['Jeff', 'slithery',9], ['Jack', 'swimmy',1], ['Joe', 'feathery',14], ['Joe', 'oinky',11], ['Jack', 'stampy',1]],
                  columns=['Owner', 'Pet', 'Age'])
print(df)


  Owner       Pet  Age
0  Jack     fuzzy   12
1  Jack     furry   13
2   Joe  scratchy    3
3  Jack    chirpy   40
4  Jeff  slithery    9
5  Jack    swimmy    1
6   Joe  feathery   14
7   Joe     oinky   11
8  Jack    stampy    1

获取符合条件的布尔系列很容易:

df.groupby('Owner').count()['Pet']>2

Owner
Jack     True
Jeff    False
Joe      True
Name: Pet, dtype: bool

实际上可以通过复制粘贴 groupby 语句来完成匹配(JackJoe):

df.groupby('Owner').count()['Pet'][df.groupby('Owner').count()['Pet']>2] 

Owner
Jack    5
Joe     3
Name: Pet, dtype: int64

但如果条件语句很长,这会很痛苦,因为每次更改都需要重复。到目前为止发现的唯一其他方法是将系列放回数据框中并使用 query(),但这感觉不太可能:

pd.DataFrame(df.groupby('Owner').count()['Pet']).query('Pet > 2')

       Pet
Owner     
Jack     5
Joe      3

还有比这些更好的方法吗?

您可以使用具有过滤功能的 .loc 索引器。

>>> df.groupby('Owner').Pet.count().loc[lambda p: p > 2]
Owner
Jack    5
Joe     3
Name: Pet, dtype: int64

或者,您可以使用 compress 方法。

>>> df.groupby('Owner').Pet.count().compress(lambda p: p > 2)
Owner
Jack    5
Joe     3
Name: Pet, dtype: int64

选项 1
使用 pd.factorizenp.bincount

f, u = pd.factorize(df.Owner.values)
b = np.bincount(f)
m = b > 2
u[m]

array(['Jack', 'Joe'], dtype=object)

或制作系列

pd.Series(b[m], u[m])

Jack    5
Joe     3
dtype: int64

选项 2
使用相同的 groupby 两次

2.1
时髦 lambda

(lambda p: p[p > 2])(df.groupby('Owner').Pet.count())

Owner
Jack    5
Joe     3
Name: Pet, dtype: int64

2.2
pipe
我宁愿使用@Mitch 的回答也不愿使用这个。

df.groupby('Owner').Pet.count().pipe(lambda p: p[p > 2])

Owner
Jack    5
Joe     3
Name: Pet, dtype: int64

时机
下面的代码

# Multiples of minimum runtime: Smaller is better.
#
       pir1      pir2      pir3      mch1      mch2
10      1.0  2.984347  2.907198  2.422435  2.736712
30      1.0  3.396997  3.464083  3.023355  3.353150
100     1.0  3.646931  3.053890  2.586377  2.859365
300     1.0  4.541890  4.037132  3.054388  3.323939
1000    1.0  2.529670  2.438109  2.214494  2.415056
3000    1.0  3.212312  3.739621  3.062538  2.969489
10000   1.0  2.923211  2.807983  2.970712  2.637492
30000   1.0  2.790350  2.830328  2.978083  2.719900

def pir1(d, c):
    f, u = pd.factorize(d.Owner.values)
    b = np.bincount(f)
    m = b > c
    return pd.Series(b[m], u[m])

pir2 = lambda d, c: (lambda p: p[p > c])(d.groupby('Owner').Pet.count())
pir3 = lambda d, c: d.groupby('Owner').Pet.count().pipe(lambda p: p[p > c])
mch1 = lambda d, c: d.groupby('Owner').Pet.count().loc[lambda p: p > c]
mch2 = lambda d, c: d.groupby('Owner').Pet.count().compress(lambda p: p > c)

res = pd.DataFrame(
    index=[10, 30, 100, 300, 1000, 3000, 10000, 30000],
    columns='pir1 pir2 pir3 mch1 mch2'.split(),
    dtype=float
)

for i in res.index:
    d = pd.concat([df] * i, ignore_index=True)
    c = 2 * i
    for j in res.columns:
        stmt = '{}(d, c)'.format(j)
        setp = 'from __main__ import d, c, {}'.format(j)
        res.at[i, j] = timeit(stmt, setp, number=10)

res.div(res.min(1), 0)

res.plot(loglog=True)