引言
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
操作时发生了什么。
最关键的部分是,如何将分组后的计算结果拼接到原始数据中,本能的想法是利用聚合数据创建一个新的
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
使用之前的数据,我们看看transform
和groupby
能得到什么
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
功能非常强大。
译者讨论
以前我解决这类问题的思路是先groupby
,然后通过自己写的lambda函数查找到结果在apply到新列中,通过第一个例子,才知道merge原来是这样用的,缺少sql的思维真是害死人。
-
这段翻译可能谬误比较大 ↩