Проект: дашборд аналитики магазина на Seaborn
Финальный проект: одна таблица продаж интернет-магазина, четыре графика — тренд, города, тепловая карта часов, разброс чеков — дашборд 2×2, файл PNG и выводы для руководителя.
Редакция Питоники
Пятница, вечер. Руководитель интернет-магазина электроники пишет вам две строки: «Выгрузила заказы за две недели. Покажи, что происходит, — в понедельник совещание». Файла нет, техзадания нет — есть таблица и вопрос. Это финальный, двадцатый урок курса: за девятнадцать предыдущих вы собрали полный набор инструментов, и теперь — сквозной проект, где всё работает вместе.
План проекта честный, по нему собирается почти любая аналитическая справка: осмотреть данные, посчитать итоговые цифры, построить четыре графика, склеить их в дашборд одним файлом и сформулировать выводы. Каждый график здесь — из пройденных уроков, новые приёмы только в сборке.
Постановка задачи и план
Расплывчатый вопрос «что происходит» переводится в четыре конкретных. Каждому — свой график и своя функция из курса:
| Вопрос заказчика | График | Инструмент |
|---|---|---|
| Растём или падаем по дням? | Линия тренда | sns.lineplot — урок 6 |
| Кто тянет оборот? | Столбцы по городам | sns.barplot — урок 2 |
| Когда приходят заказы? | Тепловая карта час × день | sns.heatmap — урок 12 |
| Стабильны ли чеки по товарам? | Ящики с усами | sns.boxplot — урок 10 |
Шаг 1. Данные: одна таблица заказов
Выгрузка — таблица из 18 заказов за две выходные недели ноября: дата, день недели, час заказа, город доставки, товар и выручка в рублях. Данные зашиты в код литералом — так же работает и наш браузерный интерпретатор, а в реальном проекте на этом месте будет read_csv:
import pandas as pd
df = pd.DataFrame({
"дата": ["2024-11-01", "2024-11-01", "2024-11-01",
"2024-11-02", "2024-11-02", "2024-11-02",
"2024-11-03", "2024-11-03", "2024-11-03",
"2024-11-08", "2024-11-08", "2024-11-08",
"2024-11-09", "2024-11-09", "2024-11-09",
"2024-11-10", "2024-11-10", "2024-11-10"],
"день": ["Пт", "Пт", "Пт", "Сб", "Сб", "Сб",
"Вс", "Вс", "Вс", "Пт", "Пт", "Пт",
"Сб", "Сб", "Сб", "Вс", "Вс", "Вс"],
"час": [10, 14, 18, 10, 14, 18, 10, 14, 18,
10, 14, 18, 10, 14, 18, 10, 14, 18],
"город": ["Москва", "Москва", "Казань",
"Сочи", "Казань", "Москва",
"Казань", "Сочи", "Москва",
"Сочи", "Москва", "Сочи",
"Москва", "Казань", "Москва",
"Казань", "Сочи", "Москва"],
"товар": ["Ноутбук", "Наушники", "Смарт-часы",
"Планшет", "Ноутбук", "Планшет",
"Наушники", "Смарт-часы", "Ноутбук",
"Наушники", "Смарт-часы", "Ноутбук",
"Планшет", "Планшет", "Наушники",
"Ноутбук", "Планшет", "Смарт-часы"],
"выручка": [54990, 8990, 17990,
31990, 57990, 29990,
8990, 18490, 54990,
9490, 17990, 55990,
30990, 31490, 9490,
56490, 30490, 18490],
})
print(df.shape)
print(df["выручка"].sum())
(18, 6) 545320
Первый осмотр — два числа: 18 заказов, 6 колонок, суммарная выручка 545 320 рублей. Таблица чистая, без пропусков и дублей — выгрузка из урока про Pandas была жёстче. Значит, всё внимание достаётся графикам.
Шаг 2. Итоговые цифры до графиков
Прежде чем рисовать, посчитаем выводы в числах — на совещании их спросят первыми, и они же послужат проверкой: если график покажет другое, где-то ошибка:
import pandas as pd
df = pd.DataFrame({
"дата": ["2024-11-01", "2024-11-01", "2024-11-01",
"2024-11-02", "2024-11-02", "2024-11-02",
"2024-11-03", "2024-11-03", "2024-11-03",
"2024-11-08", "2024-11-08", "2024-11-08",
"2024-11-09", "2024-11-09", "2024-11-09",
"2024-11-10", "2024-11-10", "2024-11-10"],
"день": ["Пт", "Пт", "Пт", "Сб", "Сб", "Сб",
"Вс", "Вс", "Вс", "Пт", "Пт", "Пт",
"Сб", "Сб", "Сб", "Вс", "Вс", "Вс"],
"час": [10, 14, 18, 10, 14, 18, 10, 14, 18,
10, 14, 18, 10, 14, 18, 10, 14, 18],
"город": ["Москва", "Москва", "Казань",
"Сочи", "Казань", "Москва",
"Казань", "Сочи", "Москва",
"Сочи", "Москва", "Сочи",
"Москва", "Казань", "Москва",
"Казань", "Сочи", "Москва"],
"товар": ["Ноутбук", "Наушники", "Смарт-часы",
"Планшет", "Ноутбук", "Планшет",
"Наушники", "Смарт-часы", "Ноутбук",
"Наушники", "Смарт-часы", "Ноутбук",
"Планшет", "Планшет", "Наушники",
"Ноутбук", "Планшет", "Смарт-часы"],
"выручка": [54990, 8990, 17990,
31990, 57990, 29990,
8990, 18490, 54990,
9490, 17990, 55990,
30990, 31490, 9490,
56490, 30490, 18490],
})
total = df["выручка"].sum()
orders = len(df)
avg = df["выручка"].mean()
print("Выручка за две недели:", total)
print("Заказов:", orders)
print("Средний чек:", round(avg))
by_city = df.groupby("город")["выручка"].sum().sort_values(ascending=False)
print("Топ-город:", by_city.idxmax(), "-", by_city.max())
by_day = df.groupby("дата")["выручка"].sum()
print("Лучший день:", by_day.idxmax(), "-", by_day.max())
Выручка за две недели: 545320 Заказов: 18 Средний чек: 30296 Топ-город: Москва - 225920 Лучший день: 2024-11-02 - 119970
Цифры уже рассказывают историю: Москва с 225 920 — это 41 процент всего оборота; лучший день — суббота 2 ноября со 119 970. Приёмы ровно из прошлого урока: groupby, sort_values, idxmax. Теперь те же сюжеты — глазами.
Шаг 3. Четыре графика дашборда
График 1 — тренд по дням. Даты превращаем в datetime и агрегируем: raw-заказы с несколькими строками на дату lineplot превратил бы в полосу усреднения, а руководителю нужна сумма дня:
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd
df = pd.DataFrame({
"дата": ["2024-11-01", "2024-11-01", "2024-11-01",
"2024-11-02", "2024-11-02", "2024-11-02",
"2024-11-03", "2024-11-03", "2024-11-03",
"2024-11-08", "2024-11-08", "2024-11-08",
"2024-11-09", "2024-11-09", "2024-11-09",
"2024-11-10", "2024-11-10", "2024-11-10"],
"день": ["Пт", "Пт", "Пт", "Сб", "Сб", "Сб",
"Вс", "Вс", "Вс", "Пт", "Пт", "Пт",
"Сб", "Сб", "Сб", "Вс", "Вс", "Вс"],
"час": [10, 14, 18, 10, 14, 18, 10, 14, 18,
10, 14, 18, 10, 14, 18, 10, 14, 18],
"город": ["Москва", "Москва", "Казань",
"Сочи", "Казань", "Москва",
"Казань", "Сочи", "Москва",
"Сочи", "Москва", "Сочи",
"Москва", "Казань", "Москва",
"Казань", "Сочи", "Москва"],
"товар": ["Ноутбук", "Наушники", "Смарт-часы",
"Планшет", "Ноутбук", "Планшет",
"Наушники", "Смарт-часы", "Ноутбук",
"Наушники", "Смарт-часы", "Ноутбук",
"Планшет", "Планшет", "Наушники",
"Ноутбук", "Планшет", "Смарт-часы"],
"выручка": [54990, 8990, 17990,
31990, 57990, 29990,
8990, 18490, 54990,
9490, 17990, 55990,
30990, 31490, 9490,
56490, 30490, 18490],
})
df["дата"] = pd.to_datetime(df["дата"])
by_day = df.groupby("дата", as_index=False)["выручка"].sum()
sns.lineplot(data=by_day, x="дата", y="выручка", marker="o")
plt.title("Выручка по дням, руб.")
plt.show()
График отображается под редактором
График 2 — города. Агрегат, сортировка по убыванию, barplot — порядок столбцов берётся из строк таблицы:
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd
df = pd.DataFrame({
"дата": ["2024-11-01", "2024-11-01", "2024-11-01",
"2024-11-02", "2024-11-02", "2024-11-02",
"2024-11-03", "2024-11-03", "2024-11-03",
"2024-11-08", "2024-11-08", "2024-11-08",
"2024-11-09", "2024-11-09", "2024-11-09",
"2024-11-10", "2024-11-10", "2024-11-10"],
"день": ["Пт", "Пт", "Пт", "Сб", "Сб", "Сб",
"Вс", "Вс", "Вс", "Пт", "Пт", "Пт",
"Сб", "Сб", "Сб", "Вс", "Вс", "Вс"],
"час": [10, 14, 18, 10, 14, 18, 10, 14, 18,
10, 14, 18, 10, 14, 18, 10, 14, 18],
"город": ["Москва", "Москва", "Казань",
"Сочи", "Казань", "Москва",
"Казань", "Сочи", "Москва",
"Сочи", "Москва", "Сочи",
"Москва", "Казань", "Москва",
"Казань", "Сочи", "Москва"],
"товар": ["Ноутбук", "Наушники", "Смарт-часы",
"Планшет", "Ноутбук", "Планшет",
"Наушники", "Смарт-часы", "Ноутбук",
"Наушники", "Смарт-часы", "Ноутбук",
"Планшет", "Планшет", "Наушники",
"Ноутбук", "Планшет", "Смарт-часы"],
"выручка": [54990, 8990, 17990,
31990, 57990, 29990,
8990, 18490, 54990,
9490, 17990, 55990,
30990, 31490, 9490,
56490, 30490, 18490],
})
by_city = df.groupby("город", as_index=False)["выручка"].sum()
by_city = by_city.sort_values("выручка", ascending=False)
sns.barplot(data=by_city, x="город", y="выручка", color="#4C9F70")
plt.title("Выручка по городам, руб.")
plt.show()
График отображается под редактором
График 3 — тепловая карта «час × день»: сводим выручку pivot_table и наводим порядок в колонках. Без явного порядка reindex Seaborn поставил бы дни по алфавиту — Вс, Пт, Сб — и карта читалась бы неверно:
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd
df = pd.DataFrame({
"дата": ["2024-11-01", "2024-11-01", "2024-11-01",
"2024-11-02", "2024-11-02", "2024-11-02",
"2024-11-03", "2024-11-03", "2024-11-03",
"2024-11-08", "2024-11-08", "2024-11-08",
"2024-11-09", "2024-11-09", "2024-11-09",
"2024-11-10", "2024-11-10", "2024-11-10"],
"день": ["Пт", "Пт", "Пт", "Сб", "Сб", "Сб",
"Вс", "Вс", "Вс", "Пт", "Пт", "Пт",
"Сб", "Сб", "Сб", "Вс", "Вс", "Вс"],
"час": [10, 14, 18, 10, 14, 18, 10, 14, 18,
10, 14, 18, 10, 14, 18, 10, 14, 18],
"город": ["Москва", "Москва", "Казань",
"Сочи", "Казань", "Москва",
"Казань", "Сочи", "Москва",
"Сочи", "Москва", "Сочи",
"Москва", "Казань", "Москва",
"Казань", "Сочи", "Москва"],
"товар": ["Ноутбук", "Наушники", "Смарт-часы",
"Планшет", "Ноутбук", "Планшет",
"Наушники", "Смарт-часы", "Ноутбук",
"Наушники", "Смарт-часы", "Ноутбук",
"Планшет", "Планшет", "Наушники",
"Ноутбук", "Планшет", "Смарт-часы"],
"выручка": [54990, 8990, 17990,
31990, 57990, 29990,
8990, 18490, 54990,
9490, 17990, 55990,
30990, 31490, 9490,
56490, 30490, 18490],
})
pivot = df.pivot_table(
index="час", columns="день", values="выручка", aggfunc="sum",
).reindex(columns=["Пт", "Сб", "Вс"])
sns.heatmap(pivot, annot=True, fmt=".0f", cmap="YlGnBu")
plt.title("Выручка по часам и дням, руб.")
plt.show()
График отображается под редактором
График 4 — boxplot чеков по товарам: где разброс огромный, а где покупки ровные:
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd
df = pd.DataFrame({
"дата": ["2024-11-01", "2024-11-01", "2024-11-01",
"2024-11-02", "2024-11-02", "2024-11-02",
"2024-11-03", "2024-11-03", "2024-11-03",
"2024-11-08", "2024-11-08", "2024-11-08",
"2024-11-09", "2024-11-09", "2024-11-09",
"2024-11-10", "2024-11-10", "2024-11-10"],
"день": ["Пт", "Пт", "Пт", "Сб", "Сб", "Сб",
"Вс", "Вс", "Вс", "Пт", "Пт", "Пт",
"Сб", "Сб", "Сб", "Вс", "Вс", "Вс"],
"час": [10, 14, 18, 10, 14, 18, 10, 14, 18,
10, 14, 18, 10, 14, 18, 10, 14, 18],
"город": ["Москва", "Москва", "Казань",
"Сочи", "Казань", "Москва",
"Казань", "Сочи", "Москва",
"Сочи", "Москва", "Сочи",
"Москва", "Казань", "Москва",
"Казань", "Сочи", "Москва"],
"товар": ["Ноутбук", "Наушники", "Смарт-часы",
"Планшет", "Ноутбук", "Планшет",
"Наушники", "Смарт-часы", "Ноутбук",
"Наушники", "Смарт-часы", "Ноутбук",
"Планшет", "Планшет", "Наушники",
"Ноутбук", "Планшет", "Смарт-часы"],
"выручка": [54990, 8990, 17990,
31990, 57990, 29990,
8990, 18490, 54990,
9490, 17990, 55990,
30990, 31490, 9490,
56490, 30490, 18490],
})
sns.boxplot(data=df, x="товар", y="выручка")
plt.title("Разброс чека по товарам, руб.")
plt.show()
График отображается под редактором
Шаг 4. Дашборд 2×2 одним файлом
Для совещания четыре картинки складываются в одну через subplots: сетка 2×2, каждый график рисуется в свою панель ax=, отступы подгоняет tight_layout, файл пишет savefig из урока про подписи. Данные те же 18 строк:
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd
df = pd.DataFrame({
"дата": ["2024-11-01", "2024-11-01", "2024-11-01",
"2024-11-02", "2024-11-02", "2024-11-02",
"2024-11-03", "2024-11-03", "2024-11-03",
"2024-11-08", "2024-11-08", "2024-11-08",
"2024-11-09", "2024-11-09", "2024-11-09",
"2024-11-10", "2024-11-10", "2024-11-10"],
"день": ["Пт", "Пт", "Пт", "Сб", "Сб", "Сб",
"Вс", "Вс", "Вс", "Пт", "Пт", "Пт",
"Сб", "Сб", "Сб", "Вс", "Вс", "Вс"],
"час": [10, 14, 18, 10, 14, 18, 10, 14, 18,
10, 14, 18, 10, 14, 18, 10, 14, 18],
"город": ["Москва", "Москва", "Казань",
"Сочи", "Казань", "Москва",
"Казань", "Сочи", "Москва",
"Сочи", "Москва", "Сочи",
"Москва", "Казань", "Москва",
"Казань", "Сочи", "Москва"],
"товар": ["Ноутбук", "Наушники", "Смарт-часы",
"Планшет", "Ноутбук", "Планшет",
"Наушники", "Смарт-часы", "Ноутбук",
"Наушники", "Смарт-часы", "Ноутбук",
"Планшет", "Планшет", "Наушники",
"Ноутбук", "Планшет", "Смарт-часы"],
"выручка": [54990, 8990, 17990,
31990, 57990, 29990,
8990, 18490, 54990,
9490, 17990, 55990,
30990, 31490, 9490,
56490, 30490, 18490],
})
df["дата"] = pd.to_datetime(df["дата"])
fig, axes = plt.subplots(2, 2, figsize=(12, 8))
by_day = df.groupby("дата", as_index=False)["выручка"].sum()
sns.lineplot(data=by_day, x="дата", y="выручка", marker="o", ax=axes[0, 0])
axes[0, 0].set_title("Выручка по дням")
by_city = df.groupby("город", as_index=False)["выручка"].sum()
by_city = by_city.sort_values("выручка", ascending=False)
sns.barplot(data=by_city, x="город", y="выручка", color="#4C9F70", ax=axes[0, 1])
axes[0, 1].set_title("Выручка по городам")
pivot = df.pivot_table(
index="час", columns="день", values="выручка", aggfunc="sum",
).reindex(columns=["Пт", "Сб", "Вс"])
sns.heatmap(pivot, annot=True, fmt=".0f", cmap="YlGnBu", ax=axes[1, 0])
axes[1, 0].set_title("Часы против дней")
sns.boxplot(data=df, x="товар", y="выручка", ax=axes[1, 1])
axes[1, 1].set_title("Чеки по товарам")
axes[1, 1].tick_params(axis="x", rotation=15)
plt.tight_layout()
plt.savefig("dashboard.png", dpi=150)
plt.show()
График отображается под редактором
Файл dashboard.png размером 12 × 8 дюймов при dpi=150 — это 1800 × 1200 пикселей: хватает и для презентации, и для печати. Общий заголовок всей фигуры добавляется одной строкой fig.suptitle("Дашборд продаж, ноябрь 2024") перед tight_layout.
Шаг 5. Выводы для руководителя
Дашборд и цифры сходятся в четыре утверждения, которые можно зачитать на совещании дословно:
- за две выходные недели магазин заработал 545 320 рублей на 18 заказах, средний чек — 30 296 рублей;
- Москва приносит 41 процент оборота (225 920), Казань и Сочи делят остаток почти пополам — рынок за пределами столицы недобран;
- пик недели — суббота (119 970 в лучший день), утренний слот 10:00 — самый денежный: дорогое покупают до обеда;
- ноутбуки дают больше половины выручки при всего четырёх заказах — трафик держится на дорогой категории, и падение её продаж будет резким.
И это честный финал курса: от «нарисуй график» вы дошли до «объясни данными, что делать». Куда двигаться дальше? Раздел Pandas — тот же магазин, но с грязным CSV: чистка, сводные, временные ряды; с ними ваши дашборды станут устойчивее к реальным выгрузкам. А раздел pytest научит тестировать конвейер «таблица — цифры — график», чтобы пятничная выгрузка с кривой датой падала в тесте, а не на совещании в понедельник.
Хороший дашборд отвечает на вопросы заказчика по порядку: сначала цифры, потом тренд, потом разбивка и только потом детали.
Сначала предскажи ответ в голове — это главный навык программиста.
import pandas as pd
df = pd.DataFrame({
"город": (["Москва"] * 4) + (["Казань"] * 4) + (["Сочи"] * 4),
"товар": ["Ноутбук", "Наушники", "Планшет", "Смарт-часы"] * 3,
"выручка": [55, 9, 31, 18, 58, 12, 29, 16, 62, 8.5, 34, 20],
})
by_product = df.groupby("товар", as_index=False)["выручка"].sum()
by_product = by_product.sort_values("выручка", ascending=False)
top = by_product.iloc[0]
print("Топ-товар:", top["товар"], "-", round(top["выручка"]))
import pandas as pd
df = pd.DataFrame({
"товар": ["Ноутбуки", "Наушники", "Планшеты", "Смарт-часы"],
"выручка": [501, 240, 190, 120],
})
order = df.sort_values("выручка", ascending=False)["товар"]
print(order.iloc[0], "|", order.iloc[-1])
import pandas as pd
df = pd.DataFrame({
"дата": ["2024-11-01", "2024-11-02", "2024-11-03"],
"выручка": [81970, 119970, 82470],
})
by_day = df.groupby("дата")["выручка"].sum()
print(by_day.idxmax(), "-", by_day.max())
1. Почему перед lineplot выручки по дням делают groupby по дате?
2. Зачем heatmap-проекту вызов .reindex(columns=["Пт", "Сб", "Вс"])?
3. Что показывает boxplot чеков по товарам, чего не видно в barplot средних?
4. Как собрать четыре графика в одну картинку?
5. Сохранение dashboard.png с dpi=150 при figsize=(12, 8) даёт файл какого размера?
Мини-проект на выводах: по таблице заказов посчитайте общую выручку, найдите топ-товар и его долю в процентах. Данные в заготовке; округляйте через round().
Как построить дашборд из нескольких графиков Seaborn?
Через Matplotlib: fig, axes = plt.subplots(2, 2, figsize=(12, 8)) создаёт сетку панелей, затем каждая axes-level функция Seaborn рисует в свою панель через ax=axes[0, 0] и company. Завершают plt.tight_layout() и plt.savefig("dashboard.png", dpi=150). Figure-level функции вроде catplot для дашборда не годятся — они создают собственные фигуры.
Урок 5: subplots в MatplotlibПочему lineplot рисует полосу вокруг линии на моих данных?
Так lineplot показывает доверительный интервал среднего: на оси X несколько значений на точку — несколько заказов на дату. Для отчёта «выручка по дням» это неверно: агрегируйте до графика через groupby("дата", as_index=False)["выручка"].sum(), и линия покажет сумму дня без полосы.
Как сохранить дашборд в PNG для презентации?
plt.savefig("dashboard.png", dpi=150) — после tight_layout и до plt.show(). Пиксели равны дюймам figsize, умноженным на dpi: 12 × 8 дюймов при 150 dpi дают 1800 × 1200 пикселей; для печати увеличьте dpi до 300.
Урок 17: подписи и сохранение графикаЧто изучать после курса Seaborn?
Две ветки. Pandas глубже: тот же магазин, но с грязной выгрузкой — read_csv, чистка пропусков и дублей, сводные и resample; с ними дашборды выдержат реальные данные. И pytest: тесты на конвейер «таблица — цифры — график» ловят сломанные выгрузки до того, как отчёт уйдёт руководителю.
Проект на Pandas: анализ продажПонравился урок? Сошлитесь на него
«Хороший дашборд отвечает на вопросы заказчика по порядку: сначала цифры, потом тренд, потом разбивка и только потом детали.»
Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.
Что читать дальше
Seaborn · Урок 19
Seaborn и Pandas DataFrame: таблица как источник графиков
Seaborn ест DataFrame напрямую, но красивый график начинается до него: groupby, melt из широкого формата в длинный, настоящие даты и правильный порядок категорий.
Pandas · Урок 10
Проект на Pandas: полный анализ продаж интернет-магазина
Финальный проект раздела: сырая выгрузка заказов, чистка по чек-листу, выручка и средний чек, топ-товары, сводные по городам, тренд по месяцам и выводы для руководителя.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
Matplotlib / Seaborn · Урок 10
Проект по визуализации: дашборд данных о фильмах на Matplotlib и Seaborn
Финальный проект курса: датасет из 200 фильмов, EDA, дашборд 2x2 с трендом, жанрами, гистограммой и scatter — плюс чек-лист «график готов к показу» и итоги десяти уроков.
проект визуализация pythonдашборд matplotlib
Matplotlib / Seaborn · Урок 5
Круговые диаграммы и subplots: несколько графиков на одном полотне
pie и donut с честными границами применимости, сетка графиков через plt.subplots, общий заголовок suptitle и мини-дашборд 2 на 2 в финале.
matplotlib subplotssubplot matplotlib несколько графиков
Seaborn · Урок 12
heatmap в Seaborn: тепловая карта матрицы и корреляции
Матрица превращается в картинку: корреляции признаков и активность по дням-часам загораются цветом. annot, fmt, cmap, vmin и vmax — четыре ручки тепловой карты.
seaborn heatmapsns heatmap пример