Одной из привлекательных особенностей pandas является наличие богатой библиотеки методов для управления данными. Однако бывают случаи, когда неясно, что делают функции и как их использовать. Если вы подходите к проблеме с точки зрения Excel, может быть сложно перевести решение в незнакомую команду pandas. Одна из таких "неизвестных" функций - метод transform
.
Оригинал статьи Криса тут
Даже после длительного использования pandas у меня никогда не было возможности использовать эту функцию, поэтому я потратил время на выяснение, как она может пригодиться для анализа реального мира. В этой статье будет рассмотрен пример, в котором transform
используется для эффективного суммирования данных.
Лучшее описание этой темы я нашел в книге Python Data Science Handbook
Джейка Вандерпласа (Jake VanderPlas).
книга в оригинале свободно доступна на сайте
Как сказано в книге, transform
- это операция, используемая вместе с groupby
(которая является одной из самых полезных в pandas).
Я подозреваю, что большинство пользователей pandas использовали aggregate
, filter
или apply
с groupby
для обобщения данных. Однако transform
немного сложнее понять, особенно из мира Excel.
Поскольку Джейк сделал свою книгу доступной через Jupyter блокноты, это хорошее место, чтобы понять уникальность transform:
В то время как агрегирующая функция должна возвращать сокращенную версию данных, преобразование может вернуть версию полного набора данных, преобразованную ради дальнейшей их перекомпоновки. При подобном преобразовании форма выходных данных совпадает с формой входных. Распространенный пример — центрирование данных путем вычитания среднего значения по группам.
Используя это базовое определение, я рассмотрю еще один пример.
В этом примере проанализируем фиктивные данные о сделках купли-продажи:
import pandas as pd
df = pd.read_excel("https://github.com/chris1610/pbpython/blob/master/data/sales_transactions.xlsx?raw=true")
df
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
и 10006
) и что каждый заказ состоит из нескольких продуктов (sku
).
Вопрос, на который мы бы хотели ответить: "Какой процент от общей суммы составляет каждый продукт (sku
)?"
Например, если мы посмотрим на заказ 10001
на общую сумму 576,12 у.е.
, то разбивка будет следующая:
B1-20000
= $235.83
или 40.9%
S1-27722
= $232.32
или 40.3%
B1-86481
= $107.97
или 18.7%
Сложность заключается в том, что нам нужно получить общую сумму для каждого заказа и объединить ее обратно на уровне транзакции, чтобы получить проценты.
В Excel вы можете использовать какую-либо версию промежуточного итога, чтобы вычислить значения.
Если вы знакомы с pandas, то первым желанием будет сгруппировать данные в новый DataFrame
и затем объединить их.
Вот как будет выглядеть этот подход. Определим итоговую сумму (ext price
) для заказов (order
) с помощью стандартной groupby
агрегации:
df.groupby('order')["ext price"].sum()
order 10001 576.12 10005 8185.49 10006 3724.49 Name: ext price, dtype: float64
Вот схема, показывающая, что происходит в стандартной функции groupby
:
Сложная часть - придумать, как объединить полученные данные обратно с исходным DataFrame
.
Первое желание - создать новый DataFrame
с итогами по заказам (order
) и затем объединить его с оригиналом с помощью merge
.
Мы могли бы сделать что-то вроде такого:
order_total = df.groupby('order')["ext price"].sum().rename("Order_Total").reset_index()
order_total
order | Order_Total | |
---|---|---|
0 | 10001 | 576.12 |
1 | 10005 | 8185.49 |
2 | 10006 | 3724.49 |
df_1 = df.merge(order_total)
df_1
account | name | order | sku | quantity | unit price | ext price | Order_Total | |
---|---|---|---|---|---|---|---|---|
0 | 383080 | Will LLC | 10001 | B1-20000 | 7 | 33.69 | 235.83 | 576.12 |
1 | 383080 | Will LLC | 10001 | S1-27722 | 11 | 21.12 | 232.32 | 576.12 |
2 | 383080 | Will LLC | 10001 | B1-86481 | 3 | 35.99 | 107.97 | 576.12 |
3 | 412290 | Jerde-Hilpert | 10005 | S1-06532 | 48 | 55.82 | 2679.36 | 8185.49 |
4 | 412290 | Jerde-Hilpert | 10005 | S1-82801 | 21 | 13.62 | 286.02 | 8185.49 |
5 | 412290 | Jerde-Hilpert | 10005 | S1-06532 | 9 | 92.55 | 832.95 | 8185.49 |
6 | 412290 | Jerde-Hilpert | 10005 | S1-47412 | 44 | 78.91 | 3472.04 | 8185.49 |
7 | 412290 | Jerde-Hilpert | 10005 | S1-27722 | 36 | 25.42 | 915.12 | 8185.49 |
8 | 218895 | Kulas Inc | 10006 | S1-27722 | 32 | 95.66 | 3061.12 | 3724.49 |
9 | 218895 | Kulas Inc | 10006 | B1-33087 | 23 | 22.55 | 518.65 | 3724.49 |
10 | 218895 | Kulas Inc | 10006 | B1-33364 | 3 | 72.30 | 216.90 | 3724.49 |
11 | 218895 | Kulas Inc | 10006 | B1-20000 | -1 | 72.18 | -72.18 | 3724.49 |
df_1["Percent_of_Order"] = df_1["ext price"] / df_1["Order_Total"]
df_1
account | name | order | sku | quantity | unit price | ext price | Order_Total | Percent_of_Order | |
---|---|---|---|---|---|---|---|---|---|
0 | 383080 | Will LLC | 10001 | B1-20000 | 7 | 33.69 | 235.83 | 576.12 | 0.409342 |
1 | 383080 | Will LLC | 10001 | S1-27722 | 11 | 21.12 | 232.32 | 576.12 | 0.403249 |
2 | 383080 | Will LLC | 10001 | B1-86481 | 3 | 35.99 | 107.97 | 576.12 | 0.187409 |
3 | 412290 | Jerde-Hilpert | 10005 | S1-06532 | 48 | 55.82 | 2679.36 | 8185.49 | 0.327330 |
4 | 412290 | Jerde-Hilpert | 10005 | S1-82801 | 21 | 13.62 | 286.02 | 8185.49 | 0.034942 |
5 | 412290 | Jerde-Hilpert | 10005 | S1-06532 | 9 | 92.55 | 832.95 | 8185.49 | 0.101759 |
6 | 412290 | Jerde-Hilpert | 10005 | S1-47412 | 44 | 78.91 | 3472.04 | 8185.49 | 0.424170 |
7 | 412290 | Jerde-Hilpert | 10005 | S1-27722 | 36 | 25.42 | 915.12 | 8185.49 | 0.111798 |
8 | 218895 | Kulas Inc | 10006 | S1-27722 | 32 | 95.66 | 3061.12 | 3724.49 | 0.821890 |
9 | 218895 | Kulas Inc | 10006 | B1-33087 | 23 | 22.55 | 518.65 | 3724.49 | 0.139254 |
10 | 218895 | Kulas Inc | 10006 | B1-33364 | 3 | 72.30 | 216.90 | 3724.49 | 0.058236 |
11 | 218895 | Kulas Inc | 10006 | B1-20000 | -1 | 72.18 | -72.18 | 3724.49 | -0.019380 |
Безусловно, этот способ работает, но необходимо выполнить несколько шагов, чтобы объединить данные нужным нам образом!
Используя исходные данные, давайте попробуем вызвать 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 Name: ext price, dtype: float64
Вместо того, чтобы показывать только итоги по трем заказам (orders
), transform
сохраняет формат исходного набора данных. Это уникальная особенность transform
!
Последний шаг довольно прост:
df["Order_Total"] = df.groupby('order')["ext price"].transform('sum')
df["Percent_of_Order"] = df["ext price"] / df["Order_Total"]
df
account | name | order | sku | quantity | unit price | ext price | Order_Total | Percent_of_Order | |
---|---|---|---|---|---|---|---|---|---|
0 | 383080 | Will LLC | 10001 | B1-20000 | 7 | 33.69 | 235.83 | 576.12 | 0.409342 |
1 | 383080 | Will LLC | 10001 | S1-27722 | 11 | 21.12 | 232.32 | 576.12 | 0.403249 |
2 | 383080 | Will LLC | 10001 | B1-86481 | 3 | 35.99 | 107.97 | 576.12 | 0.187409 |
3 | 412290 | Jerde-Hilpert | 10005 | S1-06532 | 48 | 55.82 | 2679.36 | 8185.49 | 0.327330 |
4 | 412290 | Jerde-Hilpert | 10005 | S1-82801 | 21 | 13.62 | 286.02 | 8185.49 | 0.034942 |
5 | 412290 | Jerde-Hilpert | 10005 | S1-06532 | 9 | 92.55 | 832.95 | 8185.49 | 0.101759 |
6 | 412290 | Jerde-Hilpert | 10005 | S1-47412 | 44 | 78.91 | 3472.04 | 8185.49 | 0.424170 |
7 | 412290 | Jerde-Hilpert | 10005 | S1-27722 | 36 | 25.42 | 915.12 | 8185.49 | 0.111798 |
8 | 218895 | Kulas Inc | 10006 | S1-27722 | 32 | 95.66 | 3061.12 | 3724.49 | 0.821890 |
9 | 218895 | Kulas Inc | 10006 | B1-33087 | 23 | 22.55 | 518.65 | 3724.49 | 0.139254 |
10 | 218895 | Kulas Inc | 10006 | B1-33364 | 3 | 72.30 | 216.90 | 3724.49 | 0.058236 |
11 | 218895 | Kulas Inc | 10006 | B1-20000 | -1 | 72.18 | -72.18 | 3724.49 | -0.019380 |
В качестве дополнительного бонуса можно объединить все в один отчет, если не хотите отображать итоги отдельных заказов:
df["Percent_of_Order"] = df["ext price"] / df.groupby('order')["ext price"].transform('sum')
df
account | name | order | sku | quantity | unit price | ext price | Order_Total | Percent_of_Order | |
---|---|---|---|---|---|---|---|---|---|
0 | 383080 | Will LLC | 10001 | B1-20000 | 7 | 33.69 | 235.83 | 576.12 | 0.409342 |
1 | 383080 | Will LLC | 10001 | S1-27722 | 11 | 21.12 | 232.32 | 576.12 | 0.403249 |
2 | 383080 | Will LLC | 10001 | B1-86481 | 3 | 35.99 | 107.97 | 576.12 | 0.187409 |
3 | 412290 | Jerde-Hilpert | 10005 | S1-06532 | 48 | 55.82 | 2679.36 | 8185.49 | 0.327330 |
4 | 412290 | Jerde-Hilpert | 10005 | S1-82801 | 21 | 13.62 | 286.02 | 8185.49 | 0.034942 |
5 | 412290 | Jerde-Hilpert | 10005 | S1-06532 | 9 | 92.55 | 832.95 | 8185.49 | 0.101759 |
6 | 412290 | Jerde-Hilpert | 10005 | S1-47412 | 44 | 78.91 | 3472.04 | 8185.49 | 0.424170 |
7 | 412290 | Jerde-Hilpert | 10005 | S1-27722 | 36 | 25.42 | 915.12 | 8185.49 | 0.111798 |
8 | 218895 | Kulas Inc | 10006 | S1-27722 | 32 | 95.66 | 3061.12 | 3724.49 | 0.821890 |
9 | 218895 | Kulas Inc | 10006 | B1-33087 | 23 | 22.55 | 518.65 | 3724.49 | 0.139254 |
10 | 218895 | Kulas Inc | 10006 | B1-33364 | 3 | 72.30 | 216.90 | 3724.49 | 0.058236 |
11 | 218895 | Kulas Inc | 10006 | B1-20000 | -1 | 72.18 | -72.18 | 3724.49 | -0.019380 |
Вот схема, показывающая, что происходит:
Потратив время на понимание transform
, я думаю, вы согласитесь, что этот инструмент может быть очень мощным, даже, если это отличный от стандартного мышления Excel подход.
Я постоянно поражаюсь способности pandas делать сложные числовые манипуляции очень эффективными. Несмотря на то, что я длительное время работал с pandas, я никогда не тратил время на понимание работы transform
. Теперь, когда я знаю, как это работает, уверен, что смогу использовать его в будущем анализе, и надеюсь, что вы сочтете этот пример полезным.