Нашли ошибку?
Скачать чистый
урок 4 из 5≈ 20 минупражнения: 0 из 4

transform

Вернуть итог группы в каждую строку таблицы: transform. Доля строки внутри группы, отклонение от среднего группы, сумма заказа рядом с каждой позицией, отбор строк по свойству всей группы.

Python запустится при первом выполнении

Группировка с sum или mean сворачивает таблицу: было 2448 строк — стало пять. Но иногда итог группы нужен рядом с каждой строкой: «какую долю выручки своего города дала эта покупка?», «насколько этот день теплее обычного для своего месяца?». Для таких вопросов нужно сравнить строку с её группой.

Свернуть или раздать

Маленький пример: пять игроков, две команды.

[ ]mean: по одному числу на команду
import pandas as pd

scores = pd.DataFrame({
    "team": ["A", "A", "B", "B", "B"],
    "score": [10, 30, 5, 5, 20],
})
scores.groupby("team")["score"].mean()
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
team
A    20.0
B    10.0
Name: score, dtype: float64

Два числа — по одному на группу. Метод transform считает те же средние, но раздаёт их обратно по строкам: у каждого игрока оказывается среднее его команды.

[ ]transform: по одному числу на строку
scores.groupby("team")["score"].transform("mean")
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
0    20.0
1    20.0
2    10.0
3    10.0
4    10.0
Name: score, dtype: float64

Результат — Series той же длины и с тем же индексом, что у исходной таблицы. Поэтому его можно сразу записать в столбец и сравнить с ним каждую строку:

[ ]Среднее команды и отклонение игрока от него
scores["team_mean"] = scores.groupby("team")["score"].transform("mean")
scores["diff"] = scores["score"] - scores["team_mean"]
scores
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
team score team_mean diff
0 A 10 20.0 -10.0
1 A 30 20.0 10.0
2 B 5 10.0 -5.0
3 B 5 10.0 -5.0
4 B 20 10.0 10.0

Название функции передают строкой: "sum", "mean", "max", "min", "count", "size" и другие.

А что будет, если записать в столбец результат обычного mean() — без transform?

[ ]Столбец из пропусков
scores["wrong"] = scores.groupby("team")["score"].mean()
scores[["team", "score", "team_mean", "wrong"]]
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
team score team_mean wrong
0 A 10 20.0 NaN
1 A 30 20.0 NaN
2 B 5 10.0 NaN
3 B 5 10.0 NaN
4 B 20 10.0 NaN

Ошибки нет, но столбец wrong пуст. Дело в правиле, которое до сих пор работало незаметно: когда в столбец записывают Series, pandas расставляет значения по меткам индекса, а не по порядку. У таблицы метки строк — 0, 1, 2…, а у результата mean() метки — A и B. Ни одна не совпала — и во всех строках пропуск. transform возвращает Series с метками самой таблицы, поэтому его значения встают на свои места.

Вопрос

В таблице scores пять строк и две команды. Сколько значений в scores.groupby("team")["score"].transform("sum")?

Доля внутри группы

[ ]Упражнение 1Вклад покупки в выручку города решено

Добавьте в orders столбец city_total — выручку города, к которому относится строка, и столбец city_share — долю выручки строки в выручке её города.

orders = pd.read_csv("data/shop_orders.csv")
orders["revenue"] = orders["price"] * orders["quantity"]
# добавьте в orders столбцы city_total и city_share

Проверка: доли внутри каждой группы должны давать в сумме единицу.

[ ]Сумма долей по городам
orders.groupby("city")["city_share"].sum()
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
city
Екатеринбург       1.0
Казань             1.0
Москва             1.0
Новосибирск        1.0
Санкт-Петербург    1.0
Name: city_share, dtype: float64
Вывод появится, когда решите упражнение «Вклад покупки в выручку города», — иначе он подскажет ответ. Ячейку можно и выполнить.

Итог заказа рядом с позицией

Одна строка таблицы — позиция заказа. Сумму всего заказа даёт группировка по order_id, а transform ставит её рядом с каждой позицией:

[ ]Сумма заказа в каждой его строке
orders["order_total"] = orders.groupby("order_id")["revenue"].transform("sum")
orders[["order_id", "product", "revenue", "order_total"]].head(4)
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
order_id product revenue order_total
0 10001 Кофемолка ручная 6400 6660
1 10001 Травяной сбор 50 г 260 6660
2 10002 Шоколад горький 180 530
3 10002 Чёрный чай 100 г 350 530

Теперь строки можно отбирать по свойству всей группы — например, оставить позиции только крупных заказов:

[ ]Строки заказов от 10 000 ₽
big = orders[orders["order_total"] >= 10000]
print(len(big), big["order_id"].nunique())
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
30 12

30 строк из 12 заказов. Обычным фильтром по revenue этого не сделать: условие относится к заказу целиком, а не к отдельной строке.

[ ]Упражнение 2Большие заказы решено

Добавьте в orders столбец lines — сколько позиций (строк) в заказе, к которому относится строка. Сохраните в multi строки заказов из трёх и более позиций, а в n_multi — число таких заказов.

# добавьте в orders столбец lines
multi = ...
n_multi = ...

Отклонение от нормы

Группировать можно по нескольким столбцам. Посчитаем для погоды «норму» — средний дневной максимум своего города в своём месяце:

[ ]Норма месяца рядом с каждым днём
weather = pd.read_csv("data/weather.csv", parse_dates=["date"])
weather["month"] = weather["date"].dt.month
weather["norm"] = weather.groupby(["city", "month"])["temp_max"].transform("mean")
weather[["date", "city", "temp_max", "norm"]].head(3)
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
date city temp_max norm
0 2025-01-01 Москва -3.2 -3.829032
1 2025-01-02 Москва -3.2 -3.829032
2 2025-01-03 Москва -3.7 -3.829032

Средний максимум января в Москве — −3.8 градуса, и это число стоит в каждой январской строке Москвы.

Метод abs возвращает модуль числа — значение без знака. Он пригодится, чтобы искать отклонения в обе стороны.

[ ]Упражнение 3Необычные дни решено

Добавьте в weather столбец anomaly — отклонение temp_max от нормы norm (день минус норма). В warmest сохраните строку дня с наибольшим отклонением вверх. В n_unusual — число дней, когда отклонение в любую сторону больше 8 градусов.

# добавьте в weather столбец anomaly
warmest = ...
n_unusual = ...

Самый необычно тёплый день года — 29 марта в Сочи: 26.7 градуса при мартовской норме 14.7.

Пояснение с ответом появится, когда решите упражнение «Необычные дни».

Лучшая строка каждой группы

Ещё один приём: сравнить значение строки с максимумом её группы. Совпало — значит, это и есть рекордная строка:

[ ]Позиции с наибольшей выручкой в своей категории
orders["cat_max"] = orders.groupby("category")["revenue"].transform("max")
orders.loc[orders["revenue"] == orders["cat_max"], ["category", "product", "quantity", "revenue"]]
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
category product quantity revenue
0 Аксессуары Кофемолка ручная 2 6400
7 Аксессуары Кофемолка ручная 2 6400
348 Аксессуары Кофемолка ручная 2 6400
661 Аксессуары Кофемолка ручная 2 6400
780 Аксессуары Кофемолка ручная 2 6400
962 Посуда Френч-пресс 4 7560
1081 Аксессуары Кофемолка ручная 2 6400
1144 Аксессуары Кофемолка ручная 2 6400
1255 Чай Улун 100 г 7 3780
1353 Аксессуары Кофемолка ручная 2 6400
1378 Аксессуары Кофемолка ручная 2 6400
1473 Чай Улун 100 г 7 3780
1633 Сладости Миндаль в шоколаде 8 2560
1739 Чай Улун 100 г 7 3780
1911 Кофе Бразилия 1 кг 7 9030
1995 Кофе Бразилия 1 кг 7 9030
2349 Кофе Бразилия 1 кг 7 9030

В отличие от sort_values и head(1) из прошлого урока, этот способ возвращает все строки с рекордным значением, если их несколько. Здесь их много: две ручные кофемолки в одной позиции покупали не раз.

[ ]Упражнение 4Дороже среднего в своей категории решено

Добавьте в orders столбец cat_price — среднюю цену (price) по строкам категории — и столбец above_avg: True, если цена строки выше cat_price. В above_share сохраните долю таких строк в каждой категории.

# добавьте в orders столбцы cat_price и above_avg
above_share = ...

Итоги

  • groupby(...)[...].transform("функция") считает итог группы и возвращает его для каждой строки.
  • Результат той же длины, что таблица, — его записывают в столбец.
  • Доля внутри группы — значение, делённое на transform("sum"); отклонение — значение минус transform("mean").
  • Отбор строк по свойству группы: transform + обычная маска.
  • sum() сворачивает, transform("sum") раздаёт: первое — для отчёта, второе — для расчётов по строкам.

Подробнее в справочнике

Нашли ошибку?