Перейти к содержанию

252 уроков, 17 библиотек и челлендж «Что выведет код?» — бесплатно, код прямо в браузере

Начать обучение
Урок 20 из 20 Средний 35 мин 150 XP

Проект: дашборд аналитики магазина на 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 превратил бы в полосу усреднения, а руководителю нужна сумма дня:

график 1: 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 — порядок столбцов берётся из строк таблицы:

график 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 поставил бы дни по алфавиту — Вс, Пт, Сб — и карта читалась бы неверно:

график 3: heatmap по часам и дням
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 чеков по товарам: где разброс огромный, а где покупки ровные:

график 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 строк:

дашборд: четыре панели и savefig
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())
Проверь себя
0 / 5

1. Почему перед lineplot выручки по дням делают groupby по дате?

2. Зачем heatmap-проекту вызов .reindex(columns=["Пт", "Сб", "Вс"])?

3. Что показывает boxplot чеков по товарам, чего не видно в barplot средних?

4. Как собрать четыре графика в одну картинку?

5. Сохранение dashboard.png с dpi=150 при figsize=(12, 8) даёт файл какого размера?

Карточки терминов
Запомнено: 0 / 6
Практика

Мини-проект на выводах: по таблице заказов посчитайте общую выручку, найдите топ-товар и его долю в процентах. Данные в заготовке; округляйте через round().

practice.py
Вопросы и ответы по уроку

Как построить дашборд из нескольких графиков 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-канал или свой блог — так о проекте узнают новые читатели.

TelegramVK

Похожие уроки по темам

Подобраны автоматически по пересечению тем и ключевых слов.