译:理解pandas中的tansform函数

原文链接:Understanding the Transform Function in Pandas

transform示例

引言

pandas库拥有着丰富的方法操控数据,这是他的一大优势,但有时候,因为庞大的功能,你总会碰到一些不了解功能和用法的函数。如果你的大脑保持着Excel处理数据的思维惯性,在切换到pandas解决类似问题时就会遇到困难[1]transform就是这样一个函数。我已经使用pandas有一段时间了,但是还没有机会使用它,所我最近花了点时间去了解下他的用法,以及他能干些什么。这篇文章通过一个例子来演示transform如何在汇总数据时提示效率的。

什么是transform?

对于transform介绍的最好的是jake VanderPlas的书Python Data Science Handbook,就像书中提到的,transform函数是用来配合groupby操作的。这里假定大多数读者都可能都经常使用groupby中的aggregate,filter或者apply来汇总数据。但是transform跟这些相比理解起来有点小小困难,特别是对那些Excel用户来说。这里并不是说transform概念复杂,而是需要一个思路上的转换。jake将他的书已jupyter notebook形式发布,通过阅读可以了解下为什么transform'非常特别。

aggregation操作返回压缩版本的数据,transformation可以返回经过完整数据经过转换的版本。经过转换,输出的数据和输入的数据结构相同

With that basic definition, I will go through another example that can explain how this is useful in other instances outside of centering data.

problem set

在这个例子中,我们将会虚构一个销售数据:点我下载,为了节约篇幅,这里只保留12行。

account name order sku quantity unit price ext price
0 383080 Will LLC 10001 B1-20000 7 33.69 235.83
1 383080 Will LLC 10001 S1-27722 11 21.12 232.32
2 383080 Will LLC 10001 B1-86481 3 35.99 107.97
3 412290 Jerde-Hilpert 10005 S1-06532 48 55.82 2679.36
4 412290 Jerde-Hilpert 10005 S1-82801 21 13.62 286.02
5 412290 Jerde-Hilpert 10005 S1-06532 9 92.55 832.95
6 412290 Jerde-Hilpert 10005 S1-47412 44 78.91 3472.04
7 412290 Jerde-Hilpert 10005 S1-27722 36 25.42 915.12
8 218895 Kulas Inc 10006 S1-27722 32 95.66 3061.12
9 218895 Kulas Inc 10006 B1-33087 23 22.55 518.65
10 218895 Kulas Inc 10006 B1-33364 3 72.30 216.90
11 218895 Kulas Inc 10006 B1-20000 -1 72.18 -72.18

如果所见,表中有三个订单(10001, 10005 and 10006) ,每个订单包含多个产品。

现在我们想通过以上数据知道:每个产品占总订单百分比
例如,加入我们知道订单10001总计金额是576.12,那么这个订单下面每个产品的价值比例依次是。

  • B1-20000 = $235.83 or 40.9%
  • S1-27722 = $232.32 or 40.3%
  • B1-86481 = $107.97 or 18.7%
    通常要计算这些信息,并且添加到原表中应该如何实现呢?先计算每个订单的总价,然后将这些数据置回明细表,得到百分比。在Excel中,你可以通过分类汇总计算值。

第一种方法-合并

如果你非常熟悉pandas,你可能会先进行分组,得到一个新的DataFrame,然后进行合并,经过系列步骤得到最终结果,你可能通常是这样擦做的
导入模块,读取数据

import pandas as pd
df = pd.read_excel("sales_transactions.xlsx")

然后,你会通过groupby根据订单号进行分组,然后使用sum函数对价格进行汇总。

df.groupby('order')["ext price"].sum()
order
10001     576.12
10005    8185.49
10006    3724.49
Name: ext price, dtype: float64

下面这张图演示了进行groupby操作时发生了什么。

groupby:split、apply、combine

最关键的部分是,如何将分组后的计算结果拼接到原始数据中,本能的想法是利用聚合数据创建一个新的dataframe,然后通过merge执行右连接拼接到原始数据。

order_total = df.groupby('order')["extprice"].sum().rename("Order_Total").reset_index()
df_1 = df.merge(order_total)
#merge的时候省略了一些细节,因为有共同字段Order,所以自动对齐,实际上相当于df.merge(order_total,on ='order',how = left)
df_1["Percent_of_Order"] = df_1["ext price"] / df_1["Order_Total"]
account name order sku quantity unit price ext price order total Order_Total Percent_of_Order
0 383080 Will LLC 10001 B1-20000 7 33.69 235.83 576.12 576.12 0.409342
1 383080 Will LLC 10001 S1-27722 11 21.12 232.32 576.12 576.12 0.403249
2 383080 Will LLC 10001 B1-86481 3 35.99 107.97 576.12 576.12 0.187409
3 412290 Jerde-Hilpert 10005 S1-06532 48 55.82 2679.36 8185.49 8185.49 0.327330
4 412290 Jerde-Hilpert 10005 S1-82801 21 13.62 286.02 8185.49 8185.49 0.034942
5 412290 Jerde-Hilpert 10005 S1-06532 9 92.55 832.95 8185.49 8185.49 0.101759
6 412290 Jerde-Hilpert 10005 S1-47412 44 78.91 3472.04 8185.49 8185.49 0.424170
7 412290 Jerde-Hilpert 10005 S1-27722 36 25.42 915.12 8185.49 8185.49 0.111798
8 218895 Kulas Inc 10006 S1-27722 32 95.66 3061.12 3724.49 3724.49 0.821890
9 218895 Kulas Inc 10006 B1-33087 23 22.55 518.65 3724.49 3724.49 0.139254
10 218895 Kulas Inc 10006 B1-33364 3 72.30 216.90 3724.49 3724.49 0.058236
11 218895 Kulas Inc 10006 B1-20000 -1 72.18 -72.18 3724.49 3724.49 -0.019380

这样当然没问题,但是要经过很多中介步骤。

第二种方法:使用Transform

使用之前的数据,我们看看transformgroupby能得到什么

df.groupby('order')["ext price"].transform('sum')
0      576.12
1      576.12
2      576.12
3     8185.49
4     8185.49
5     8185.49
6     8185.49
7     8185.49
8     3724.49
9     3724.49
10    3724.49
11    3724.49
dtype: float64

你可以看到,单纯的groupby进行sum聚合得到的数据尺寸和transform不同。普通聚合函数返回聚合以后的结果,而transform返回的计算结果保留的原始数据的索引,这就是transform的独特性。
借此,我们可以不用费什么脑子,两部结算处结果

df["Order_Total"] = df.groupby('order')["ext price"].transform('sum')
df["Percent_of_Order"] = df["ext price"] / df["Order_Total"]
account name order sku quantity unit price ext price order total Order_Total Percent_of_Order
0 383080 Will LLC 10001 B1-20000 7 33.69 235.83 576.12 576.12 0.409342
1 383080 Will LLC 10001 S1-27722 11 21.12 232.32 576.12 576.12 0.403249
2 383080 Will LLC 10001 B1-86481 3 35.99 107.97 576.12 576.12 0.187409
3 412290 Jerde-Hilpert 10005 S1-06532 48 55.82 2679.36 8185.49 8185.49 0.327330
4 412290 Jerde-Hilpert 10005 S1-82801 21 13.62 286.02 8185.49 8185.49 0.034942
5 412290 Jerde-Hilpert 10005 S1-06532 9 92.55 832.95 8185.49 8185.49 0.101759
6 412290 Jerde-Hilpert 10005 S1-47412 44 78.91 3472.04 8185.49 8185.49 0.424170
7 412290 Jerde-Hilpert 10005 S1-27722 36 25.42 915.12 8185.49 8185.49 0.111798
8 218895 Kulas Inc 10006 S1-27722 32 95.66 3061.12 3724.49 3724.49 0.821890
9 218895 Kulas Inc 10006 B1-33087 23 22.55 518.65 3724.49 3724.49 0.139254
10 218895 Kulas Inc 10006 B1-33364 3 72.30 216.90 3724.49 3724.49 0.058236
11 218895 Kulas Inc 10006 B1-20000 -1 72.18 -72.18 3724.49 3724.49 -0.019380

如果你不需要中间字段(汇总字段),可以一步得出百分比

df["Percent_of_Order"] = df["ext price"] / df.groupby('order')["ext price"].transform('sum')

下图演示了transform的工作逻辑,可以对照下之前groupby

transform-example.png

通过上面的例子,我相信你也认为transform功能非常强大。

译者讨论

以前我解决这类问题的思路是先groupby,然后通过自己写的lambda函数查找到结果在apply到新列中,通过第一个例子,才知道merge原来是这样用的,缺少sql的思维真是害死人。


  1. 这段翻译可能谬误比较大

推荐阅读更多精彩内容