对pandas分组应用函数

67 投票
5 回答
218485 浏览
提问于 2025-04-17 18:53

我有一个叫做 my_labels 的 pandas 数据框,这个列里面有一些字符串,比如 'A', 'B', 'C', 'D', 'E'。我想要统计每个字符串出现的次数,然后把这些次数除以所有次数的总和。我想在 Pandas 中这样做:

func = lambda x: x.size() / x.sum()
data = frame.groupby('my_labels').apply(func)

但是这段代码报错了,提示 'DataFrame object has no attribute 'size'(数据框对象没有 'size' 属性)。我该如何在 Pandas 中应用一个函数来计算这个呢?

5 个回答

9

试试这个:

g = pd.DataFrame(['A','B','A','C','D','D','E'])

# Group by the contents of column 0 
gg = g.groupby(0)  

# Create a DataFrame with the counts of each letter
histo = gg.apply(lambda x: x.count())

# Add a new column that is the count / total number of elements    
histo[1] = histo.astype(np.float)/len(g) 

print histo

输出结果:

   0         1
0             
A  2  0.285714
B  1  0.142857
C  1  0.142857
D  2  0.285714
E  1  0.142857
34

从Pandas版本0.22开始,除了apply方法,还有一个替代方案:pipe,这个方法在某些情况下比apply要快得多(你也可以查看这个问题,了解这两者之间的更多区别)。

对于你的例子:

df = pd.DataFrame({"my_label": ['A','B','A','C','D','D','E']})

  my_label
0        A
1        B
2        A
3        C
4        D
5        D
6        E

使用apply的方法

df.groupby('my_label').apply(lambda grp: grp.count() / df.shape[0])

得到的结果是

          my_label
my_label          
A         0.285714
B         0.142857
C         0.142857
D         0.285714
E         0.142857

而使用pipe的方法

df.groupby('my_label').pipe(lambda grp: grp.size() / grp.size().sum())

得到的结果是

my_label
A    0.285714
B    0.142857
C    0.142857
D    0.285714
E    0.142857

所以结果是一样的,但时间上差别很大(至少对于这个小的数据框来说):

%timeit df.groupby('my_label').apply(lambda grp: grp.count() / df.shape[0])
100 loops, best of 3: 5.52 ms per loop

%timeit df.groupby('my_label').pipe(lambda grp: grp.size() / grp.size().sum())
1000 loops, best of 3: 843 µs per loop

把它放进一个函数里也很简单:

def get_perc(grp_obj):
    gr_size = grp_obj.size()
    return gr_size / gr_size.sum()

现在你可以调用

df.groupby('my_label').pipe(get_perc)

得到的结果是

my_label
A    0.285714
B    0.142857
C    0.142857
D    0.285714
E    0.142857

不过,对于这个特定的情况,其实你根本不需要groupby,你可以直接使用value_counts,像这样:

df['my_label'].value_counts(sort=False) / df.shape[0]

得到的结果是

A    0.285714
C    0.142857
B    0.142857
E    0.142857
D    0.285714
Name: my_label, dtype: float64

对于这个小的数据框来说,这样做相当快

%timeit df['my_label'].value_counts(sort=False) / df.shape[0]
1000 loops, best of 3: 770 µs per loop

正如@anmol所指出的,最后一句话也可以简化为

df['my_label'].value_counts(sort=False, normalize=True)
61

apply 是用来对每一个值应用一个函数,而不是对整个系列进行操作,并且它可以接受额外的参数。所以,这些值是没有 .size() 这个方法的。

也许这样做会有效:

from pandas import *

d = {"my_label": Series(['A','B','A','C','D','D','E'])}
df = DataFrame(d)


def as_perc(value, total):
    return value/float(total)

def get_count(values):
    return len(values)

grouped_count = df.groupby("my_label").my_label.agg(get_count)
data = grouped_count.apply(as_perc, total=df.my_label.count())

这里的 .agg() 方法是对一个 分组对象 的所有值应用一个函数。

撰写回答