Линейные графики и área
Линия — это история во времени. Наклон рассказывает о росте, падении и переломах.
«Линейный график подразумевает непрерывность. Используйте его только там, где между точками есть осмысленный переход».
Линейный график показывает, как величина меняется вдоль упорядоченной оси — чаще всего времени. Он идеален, когда точек много (десятки и сотни) и важен тренд, а не отдельные значения. Несколько линий на одной фигуре позволяют сравнить динамику серий.
В реальной аналитике линия — рабочая лошадка дашбордов: выручка по дням, посещаемость по неделям, температура сервера по минутам, цена акции по тикам. Сила формы в том, что человеческий глаз мгновенно считывает наклон как скорость изменения, а перегиб — как событие: запуск кампании, релиз, авария. Именно поэтому линию ставят туда, где важен ответ «куда движется метрика и когда что-то сломалось», а не «сколько именно было в конкретный вторник». Если же стоит вопрос точного значения отдельной точки, линию дополняют маркерами или вообще переходят к таблице.
Важно различать непрерывную и дискретную ось. Линия честна, когда между соседними точками есть осмысленный переход: температура между двумя замерами действительно где-то была, выручка перетекала изо дня в день. Если же по оси X стоят несвязанные сущности — города, отделы, продукты — соединять их линией нельзя: вы рисуете «переход», которого не существует. Это первый признак того, что линию надо заменить на bar. Ещё одна тонкость — неравномерный шаг по времени: если точки сняты в произвольные моменты, обязательно используйте datetime-ось, а не порядковый индекс, иначе три дня простоя сожмутся до одного сегмента и тренд исказится.
fig, ax = plt.subplots(figsize=(9, 4))
ax.plot(days, users_a, label="Продукт A")
ax.plot(days, users_b, label="Продукт B", linestyle="--")
# заполненная область (area) для накопления/объёма
ax.fill_between(days, users_a, alpha=0.2)
ax.set_xlabel("День")
ax.set_ylabel("Активные пользователи")
ax.legend()
ax.grid(True, alpha=0.3)
Как работает под капотом
Линия — это последовательность сегментов между соседними точками. Matplotlib просто соединяет (x[i], y[i]) с (x[i+1], y[i+1]). Поэтому порядок точек критичен: если перемешать данные, линия превратится в «зигзаг-спагетти». Area (fill_between) заливает площадь между линией и базой — она хороша для накопленных величин, но плоха для сравнения нескольких перекрывающихся серий.
Под капотом ax.plot создаёт объект Line2D, который хранит массивы x и y и стиль (цвет, толщина, тип штриха, маркеры). Matplotlib не «понимает» смысл данных — он буквально протягивает прямые отрезки между заданными координатами, а кривизны добивается лишь за счёт большого числа точек. Отсюда вытекает важное следствие: пропуски в данных лучше кодировать значением NaN (или None), а не выкидывать строку — тогда Matplotlib честно оставит разрыв в линии вместо того, чтобы провести через пропуск прямую и нарисовать тренд, которого не было. Параметр linestyle и маркеры стоит использовать как второй канал кодирования: когда график печатают в чёрно-белом виде, серии должны различаться не только цветом, иначе они сольются.
Сглаживание скользящим средним — это компромисс между шумом и задержкой. Чем шире окно, тем глаже кривая, но тем сильнее она «опаздывает» за реальными переломами: окно из 7 точек сдвигает реакцию на тренд примерно на половину окна назад. Поэтому для оперативного мониторинга берут короткие окна или экспоненциальное сглаживание (которое весит свежие точки сильнее), а длинные окна оставляют для сезонных обзоров. Сырой ряд почти всегда рисуют под сглаженным полупрозрачной линией, чтобы читатель видел и тренд, и реальный разброс, а не верил «причёсанной» кривой на слово.
Шумные ряды часто сглаживают скользящим средним. Посчитаем его вручную.
# Скользящее среднее (moving average) — сглаживание шумного ряда
series = [10, 14, 9, 16, 22, 18, 25, 19, 30, 28, 35, 33]
window = 3
ma = []
for i in range(len(series)):
if i < window - 1:
ma.append(None) # окно ещё не заполнено
else:
chunk = series[i - window + 1:i + 1]
ma.append(sum(chunk) / window)
for i, (raw, m) in enumerate(zip(series, ma)):
label = "{:.1f}".format(m) if m is not None else " -"
print("t={:>2} raw={:>3} MA={}".format(i, raw, label))
«Попробуй сам ▶» — сглаженный ряд часто рисуют поверх сырого, чтобы показать тренд без шума.
Частые ошибки
Линия по категориям без порядка — ложный тренд. Слишком много линий («спагетти-график») — выделите 1–2 цветом, остальные приглушите. Несортированная ось X — зигзаги. Площадь (area) для нескольких перекрывающихся серий — нижние не видны. Разрывы в данных, нарисованные как сплошная линия.
Ещё несколько типичных промахов. Двойная ось Y (twinx) с двумя линиями в разных масштабах — частый источник манипуляций: подбором диапазонов можно заставить любые две метрики выглядеть синхронными, поэтому к двойной оси относятся с подозрением и подписывают обе шкалы. Неравномерные интервалы по времени, отрисованные как равные сегменты, сжимают и растягивают тренд — пользуйтесь datetime-осью. Интерполяция между редкими замерами создаёт иллюзию знания: если вы измеряли раз в месяц, не делайте вид, будто между точками шло плавное движение. Наконец, слишком плотная сетка и яркий фон отвлекают от самих линий — данные должны быть самым контрастным элементом фигуры, а вспомогательная разметка — приглушённой.
Best practices
- Ось X — строго упорядочена (время или последовательность).
- Не более 3–4 линий; выделяйте главную, приглушайте фон.
- Для динамики ось Y не обязана начинаться с нуля (в отличие от bar), но подпишите масштаб.
- Разрывы данных показывайте как разрывы, не достраивайте.
Итог: линия — про тренды. Теперь перейдём к scatter — графику связи двух переменных.