«Кофейная лавка» продаёт через три канала: собственный сайт, мобильное приложение и маркетплейс. Владелец решает, куда вложить рекламный бюджет, и просит сравнить каналы. «Лучший» можно понимать по-разному — поэтому посмотрим с нескольких сторон. Новых методов в проекте нет.
import pandas as pd
orders = pd.read_csv("data/shop_orders.csv")
orders["revenue"] = orders["price"] * orders["quantity"]
orders.head(3)| order_id | date | customer_id | city | channel | category | product | price | quantity | revenue | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 10001 | 2025-01-01 | C095 | Москва | маркетплейс | Аксессуары | Кофемолка ручная | 3200 | 2 | 6400 |
| 1 | 10001 | 2025-01-01 | C095 | Москва | маркетплейс | Чай | Травяной сбор 50 г | 260 | 1 | 260 |
| 2 | 10002 | 2025-01-01 | C059 | Москва | приложение | Сладости | Шоколад горький | 180 | 1 | 180 |
Шаг 1. Общая картина
Сохраните в channels таблицу по каналам (канал — в индексе) с тремя столбцами: total — выручка,
orders — число разных заказов, buyers — число разных покупателей.
orders.groupby("channel").agg(total=("revenue", "sum"), orders=("order_id", "nunique"), buyers=("customer_id", "nunique")).Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
channels = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
channels = orders.groupby("channel").agg(
total=("revenue", "sum"),
orders=("order_id", "nunique"),
buyers=("customer_id", "nunique"),
)Шаг 2. Показатели
Абсолютные числа трудно сравнивать: у сайта всего больше. Перейдём к относительным.
Добавьте в channels три столбца: share — доля канала в общей выручке, check — средний чек (выручка
на заказ), orders_per_buyer — сколько заказов в среднем сделал один покупатель канала.
channels["total"] / channels["total"].sum() — доля.check — total / orders; orders_per_buyer — orders / buyers.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
# добавьте в channels столбцы share, check и orders_per_buyerУпражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
channels["share"] = channels["total"] / channels["total"].sum()
channels["check"] = channels["total"] / channels["orders"]
channels["orders_per_buyer"] = channels["orders"] / channels["buyers"]channels.round(3)| total | orders | buyers | share | check | orders_per_buyer | |
|---|---|---|---|---|---|---|
| channel | ||||||
| маркетплейс | 876070 | 407 | 152 | 0.265 | 2152.506 | 2.678 |
| приложение | 902430 | 454 | 159 | 0.273 | 1987.731 | 2.855 |
| сайт | 1522920 | 715 | 193 | 0.461 | 2129.958 | 3.705 |
Сайт даёт 46 % выручки — почти столько же, сколько два других канала вместе. Средний чек у сайта и маркетплейса почти одинаковый, у приложения — на 7 % ниже. Зато на сайт возвращаются чаще: 3.7 заказа на покупателя против 2.7–2.9.
Шаг 3. Каналы по городам
Одинакова ли картина в разных городах? Нужна доля канала в выручке своего города.
Сохраните в city_channel плоскую таблицу со столбцами city, channel, revenue — выручку по каждой паре
«город, канал». Добавьте столбец share — долю этой выручки в выручке города.
orders.groupby(["city", "channel"], as_index=False)["revenue"].sum().city_channel.groupby("city")["revenue"].transform("sum").Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
city_channel = ...
# добавьте в city_channel столбец shareУпражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
city_channel = orders.groupby(["city", "channel"], as_index=False)["revenue"].sum()
city_channel["share"] = city_channel["revenue"] / city_channel.groupby("city")["revenue"].transform("sum")Сохраните в app строки таблицы city_channel для приложения, отсортированные по убыванию доли, а в app_city —
город, где доля приложения самая высокая. То же для маркетплейса: market и market_city.
city_channel[city_channel["channel"] == "приложение"].sort_values("share", ascending=False).app.iloc[0]["city"].Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
app = ...
app_city = ...
market = ...
market_city = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
app = city_channel[city_channel["channel"] == "приложение"].sort_values("share", ascending=False)
app_city = app.iloc[0]["city"]
market = city_channel[city_channel["channel"] == "маркетплейс"].sort_values("share", ascending=False)
market_city = market.iloc[0]["city"]Шаг 4. Что продаёт каждый канал
Сохраните в mix плоскую таблицу со столбцами channel, category, revenue — выручку по парам «канал,
категория». Добавьте столбец share — долю категории в выручке канала, округлённую до трёх знаков.
В dishes сохраните строки категории «Посуда».
["channel", "category"]; для доли — transform("sum") с группировкой по channel.mix[mix["category"] == "Посуда"].Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
mix = ...
# добавьте в mix столбец share
dishes = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
mix = orders.groupby(["channel", "category"], as_index=False)["revenue"].sum()
mix["share"] = (mix["revenue"] / mix.groupby("channel")["revenue"].transform("sum")).round(3)
dishes = mix[mix["category"] == "Посуда"]На маркетплейсе посуда даёт 19 % выручки — вдвое больше, чем на сайте (8.5 %). Там чаще покупают разовые дорогие вещи, а за кофе приходят на сайт и в приложение.
Шаг 5. Обычный заказ
Средний чек чувствителен к редким крупным заказам. Посмотрим на медиану — но для этого сначала нужны суммы заказов: сейчас одна строка — это позиция, а не заказ.
Сохраните в order_totals плоскую таблицу channel, order_id, revenue — сумму каждого заказа с его
каналом. По ней посчитайте typical — таблицу по каналам со столбцами mean и median суммы заказа.
["channel", "order_id"], сумма revenue.channel в таблице order_totals: ["revenue"].agg(["mean", "median"]).Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
order_totals = ...
typical = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
order_totals = orders.groupby(["channel", "order_id"], as_index=False)["revenue"].sum()
typical = order_totals.groupby("channel")["revenue"].agg(["mean", "median"])Это приём «группировка в два шага»: сначала свернуть строки до нужной единицы (заказ), потом считать статистику по этим единицам.
Итог
report = channels[["total", "share", "orders", "check", "orders_per_buyer"]].copy()
report["median_check"] = typical["median"]
report.round(2).sort_values("total", ascending=False)| total | share | orders | check | orders_per_buyer | median_check | |
|---|---|---|---|---|---|---|
| channel | ||||||
| сайт | 1522920 | 0.46 | 715 | 2129.96 | 3.70 | 1610.0 |
| приложение | 902430 | 0.27 | 454 | 1987.73 | 2.86 | 1450.0 |
| маркетплейс | 876070 | 0.27 | 407 | 2152.51 | 2.68 | 1620.0 |
Выводы
- Сайт — основа бизнеса: 46 % выручки и самые лояльные покупатели (3.7 заказа на человека).
- Приложение даёт столько же выручки, сколько маркетплейс, но чек там ниже: средний — 1990 ₽, медианный — 1450 ₽. Сильнее всего приложение в Екатеринбурге — 35 % выручки города.
- Маркетплейс — канал разовых дорогих покупок: посуда занимает там 19 % выручки, а заказов на покупателя меньше всего. Его доля выше всего в Санкт-Петербурге — 31 %.
- Медианный чек везде на четверть ниже среднего: типичный заказ скромнее, чем кажется по среднему.
Однозначно «лучшего» канала нет: по выручке и лояльности лидирует сайт, по среднему чеку с ним наравне маркетплейс. Куда вкладывать бюджет, зависит от цели — и теперь у владельца есть числа для решения.