将函数应用于 pandas 的 groupby

66

我有一个名为my_labels的列,其中包含字符串:'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'。我该如何在 Pandas 中应用一个函数来计算它呢?

5个回答

60

apply需要一个应用于每个值的函数,而不是序列,并接受kwargs参数。因此,这些值没有.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() 方法在这里采用一个应用于 groupby 对象 的所有值的函数。


34

从Pandas 0.22版本开始,除了apply方法外还有一个替代方案:pipe。相比于使用apply方法,pipe方法可以更快地运行(您也可以查看此问题以了解两种功能之间的更多差异)。

对于您的例子:

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)

@Cleb,在第一个代码片段中你使用了 / df.shape[0],而在第二个代码片段中使用了 / grp.size().sum()。为什么呢?我发现如果你把第一个替换成第二个,就会出现“int is not callable”的错误。我读了与管道/应用程序差异有关的链接问题,但这不是关于组间的事情——它似乎像管道将对象包装在列表或其他东西中,而应用程序则没有... - alexey

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

2
你也可以使用 histo = gg.size() 来简化操作。 - Reservedegotist

7
关于“size”问题,size不是数据帧上的函数,而是一个属性。因此,不要使用size(),而应该使用plain size。
除此之外,像这样的方法应该可以工作。
def doCalculation(df):
    groupCount = df.size
    groupSum = df['my_labels'].notnull().sum()
    
    return groupCount / groupSum

dataFrame.groupby('my_labels').apply(doCalculation)

1

我曾在 S.O. 上看到一种嵌套函数的技巧,用于计算加权平均值,修改该技巧可以解决您的问题。

def group_weight(overall_size):
    def inner(group):
        return len(group)/float(overall_size)
    inner.__name__ = 'weight'
    return inner

d = {"my_label": pd.Series(['A','B','A','C','D','D','E'])}
df = pd.DataFrame(d)
print df.groupby('my_label').apply(group_weight(len(df)))



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

以下是如何在分组内进行加权平均的方法

def wavg(val_col_name,wt_col_name):
    def inner(group):
        return (group[val_col_name] * group[wt_col_name]).sum() / group[wt_col_name].sum()
    inner.__name__ = 'wgt_avg'
    return inner



d = {"P": pd.Series(['A','B','A','C','D','D','E'])
     ,"Q": pd.Series([1,2,3,4,5,6,7])
    ,"R": pd.Series([0.1,0.2,0.3,0.4,0.5,0.6,0.7])
     }

df = pd.DataFrame(d)
print df.groupby('P').apply(wavg('Q','R'))

P
A    2.500000
B    2.000000
C    4.000000
D    5.545455
E    7.000000
dtype: float64

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