Что такое логарифмический масштаб и когда он полезен?
Логарифмический масштаб — это способ отображения числовых данных на оси, где расстояние между делениями соответствует отношению чисел, а не их разности. На оси с логарифмическим масштабом числа 1, 10, 100, 1000 расположены на равных промежутках, если используется логарифм по основанию 10. Это кардинально отличается от линейного масштаба, где равные промежутки соответствуют равным аддитивным разностям (например, 1, 2, 3, 4 или 10, 20, 30, 40).
Логарифмический масштаб особенно полезен в случаях, когда:
- Данные имеют очень широкий диапазон значений, от очень малых до очень больших. Линейный масштаб в этом случае «сжимает» все малые значения вблизи нуля, делая их неразличимыми, в то время как логарифмический масштаб позволяет увидеть детали во всем диапазоне.
- Интерес представляет относительное или процентное изменение значений, а не абсолютное. Экспоненциальный рост или степенные зависимости на логарифмической шкале часто выглядят как прямые линии, что упрощает их анализ.
- Данные распределены согласно степенному закону (например, распределение Парето, распределение Ципфа), что часто встречается в социологии, экономике (распределение доходов), биологии (размер городов, видовое богатство) и интернет-аналитике (распределение трафика по сайтам, популярность контента).
Краткий обзор Matplotlib и его основных компонентов
Matplotlib – это мощная библиотека для создания статических, интерактивных и анимированных визуализаций в Python. Она предоставляет объектно-ориентированный API для встраивания графиков в приложения, а также процедурный интерфейс (pyplot), который имитирует MATLAB и идеально подходит для быстрого создания визуализаций.
Основные компоненты, с которыми мы будем работать при создании графиков:
- Figure: Контейнер верхнего уровня, который содержит все элементы графика.
- Axes: Область для построения графика (может быть несколько в одной Figure). Каждый Axes имеет оси (XAxis, YAxis), заголовок (title), метки осей (xlabel, ylabel) и сами данные.
- Axis: Управляет пределами осей, метками (ticks) и форматом меток.
- Artist: Базовый класс для всех видимых элементов на Figure (линии, текст, метки, заголовки и т.д.).
Для работы с масштабами осей мы будем использовать методы объектов Axes или соответствующие функции в модуле pyplot.
Необходимость логарифмического масштаба по оси Y: примеры из реальных данных
Рассмотрим типичные сценарии из области интернет-маркетинга или веб-аналитики, где логарифмический масштаб по оси Y является предпочтительным:
- Рост трафика сайта: Если вы отслеживаете ежедневное или ежемесячное количество посещений нового сайта, трафик может расти экспоненциально или очень быстро. Линейный график покажет резкий взлет в конце, но детали роста на ранних этапах будут практически невидимы, если начальные значения малы. Логарифмическая шкала позволяет увидеть темп роста на всем периоде.
- Распределение доходов от рекламы: Доход от различных рекламных кампаний или источников трафика может сильно различаться – от нескольких долларов до тысяч. На линейном графике с общим масштабом большинство кампаний с небольшим доходом будут выглядеть как плоская линия на нуле. Логарифмическая шкала поможет сравнить эффективность даже низкодоходных источников относительно друг друга.
- Время отклика сервера: Медианное время отклика может быть низким (сотни миллисекунд), но могут быть и редкие выбросы в несколько секунд. Логарифмическая шкала помогает увидеть распределение большинства значений и при этом не «обрезать» визуально выбросы, показывая их значимость.
Во всех этих случаях логарифмический масштаб по оси Y делает график более информативным и позволяет выявить закономерности, скрытые на линейной шкале.
Создание простого графика с логарифмической шкалой по оси Y
Самый простой способ построить график с логарифмическим масштабом по оси Y в Matplotlib – это использовать функцию plt.yscale() после создания графика.
Импорт библиотеки Matplotlib
Начнем с импорта необходимых библиотек:
import matplotlib.pyplot as plt
import numpy as np
from typing import Tuple
Мы импортируем pyplot для создания графиков, numpy для генерации данных и typing для аннотации типов.
Генерация данных для графика
Сгенерируем набор данных, который демонстрирует экспоненциальный рост, что идеально подходит для логарифмической шкалы. Например, смоделируем рост числа пользователей продукта:
def generate_user_growth_data(days: int) -> Tuple[np.ndarray, np.ndarray]:
"""
Генерирует синтетические данные о росте числа пользователей.
Args:
days: Количество дней для симуляции.
Returns:
Кортеж из двух массивов numpy: дни и количество пользователей.
"""
x_days: np.ndarray = np.arange(1, days + 1)
# Моделируем экспоненциальный рост с некоторыми флуктуациями
# Начинаем с 10 пользователей
y_users: np.ndarray = 10 * np.exp(0.05 * x_days) + np.random.normal(0, 5, days)
# Убедимся, что нет отрицательных значений (недопустимо для лог-шкалы)
y_users[y_users <= 0] = 1 # Заменяем 0 или отрицательные на 1
return x_days, y_users
# Генерируем данные за 100 дней
days_count = 100
day_numbers, user_counts = generate_user_growth_data(days_count)
Здесь мы создали функцию, которая генерирует дни и соответствующее количество пользователей, моделируя экспоненциальный рост. Важно убедиться, что значения для логарифмической оси не являются нулевыми или отрицательными.
Использование plt.yscale('log') для установки логарифмического масштаба
Теперь построим график, сначала используя линейный масштаб, а затем логарифмический, чтобы увидеть разницу.
# Создание графика с линейным масштабом (для сравнения)
plt.figure(figsize=(10, 5))
plt.plot(day_numbers, user_counts)
plt.title('Рост пользователей (Линейный масштаб)')
plt.xlabel('День')
plt.ylabel('Количество пользователей')
plt.grid(True, which='both', linestyle='--', linewidth=0.5)
plt.show()
# Создание графика с логарифмическим масштабом по оси Y
plt.figure(figsize=(10, 5))
plt.plot(day_numbers, user_counts)
plt.yscale('log') # <-- Вот эта функция устанавливает логарифмический масштаб по оси Y
# Настройка меток и заголовка (аналогично линейному графику)
plt.title('Рост пользователей (Логарифмический масштаб по Y)')
plt.xlabel('День')
plt.ylabel('Количество пользователей (логарифмическая шкала)')
plt.grid(True, which='both', linestyle='--', linewidth=0.5) # Сетка полезна на лог-шкале
plt.show()
Функция plt.yscale('log') (или метод ax.set_yscale('log') для объектно-ориентированного API) является ключевой. Она автоматически настраивает ось Y для использования логарифмической шкалы, обычно по основанию 10.
Настройка меток и заголовка графика
Как видно из примера выше, заголовки и метки осей добавляются стандартным образом с помощью plt.title(), plt.xlabel(), plt.ylabel(). Важно явно указывать в заголовке или метке оси, что используется логарифмический масштаб, чтобы избежать недопонимания у читателя графика.
Настройка внешнего вида логарифмического графика
Хотя plt.yscale('log') работает «из коробки», для лучшей читаемости и специфических нужд часто требуется дополнительная настройка.
Изменение базового логарифма (base)
По умолчанию Matplotlib использует основание 10 для логарифмической шкалы. Вы можете изменить это с помощью аргумента base:
plt.figure(figsize=(10, 5))
plt.plot(day_numbers, user_counts)
# Установка логарифмического масштаба по основанию e (натуральный логарифм)
plt.yscale('log', base=np.e) # Или base=2 для двоичного логарифма
plt.title('Рост пользователей (Логарифмический масштаб по Y, основание e)')
plt.xlabel('День')
plt.ylabel('Количество пользователей (log_e шкала)')
plt.grid(True, which='both', linestyle='--', linewidth=0.5)
plt.show()
Основание 10 (декадный логарифм) является наиболее распространенным для визуализации широкого диапазона значений. Натуральный логарифм (основание $e$) может быть полезен, когда данные естественным образом связаны с экспоненциальными процессами, описываемыми через $e^x$. Двоичный логарифм (основание 2) иногда используется в информатике.
Форматирование меток на оси Y: убрать экспоненциальную запись
На логарифмических шкалах Matplotlib часто использует экспоненциальную запись (например, 1e2 для 100). Если вы хотите отображать полные числа (10, 100, 1000), особенно для логарифма по основанию 10, можно использовать matplotlib.ticker.
import matplotlib.ticker as mticker
plt.figure(figsize=(10, 5))
plt.plot(day_numbers, user_counts)
plt.yscale('log', base=10)
# Получаем текущие оси
ax = plt.gca()
# Устанавливаем форматтер для главной и вспомогательной сетки
# ScalarFormatter убирает экспоненциальную запись для лог-шкалы по основанию 10
formatter = mticker.ScalarFormatter()
ax.yaxis.set_major_formatter(formatter)
ax.yaxis.set_minor_formatter(formatter)
# При необходимости можно также вручную задать расположение меток
# locmaj = mticker.LogLocator(base=10.0, numticks=12)
# ax.yaxis.set_major_locator(locmaj)
# locmin = mticker.LogLocator(base=10.0, subs=np.arange(2, 10) * .1, numticks=12)
# ax.yaxis.set_minor_locator(locmin)
# ax.yaxis. لها مجموعة ثانوية من الموضع أيضًا
plt.title('Рост пользователей (Логарифмический масштаб по Y, явные метки)')
plt.xlabel('День')
plt.ylabel('Количество пользователей')
plt.grid(True, which='both', linestyle='--', linewidth=0.5)
plt.show()
Использование mticker.ScalarFormatter часто решает проблему с экспоненциальной записью для стандартных логарифмических шкал по основанию 10. Для более сложного форматирования можно использовать mticker.FuncFormatter.
Добавление сетки для улучшения читаемости
Сетка особенно важна на логарифмических графиках, так как она помогает визуально соотносить точки данных с нелинейной шкалой. Мы уже добавляли сетку в предыдущих примерах:
plt.grid(True, which='both', linestyle='--', linewidth=0.5)
Аргумент which='both' включает как основную (major), так и вспомогательную (minor) сетку, что очень полезно на логарифмической шкале. Основные линии сетки соответствуют степеням основания (10, 100, 1000), а вспомогательные – промежуточным значениям (20, 30, …, 90; 200, 300, …, 900 и т.д.).
Управление пределами оси Y
По умолчанию Matplotlib автоматически определяет пределы оси Y. Иногда требуется вручную задать эти пределы, например, чтобы включить определенные значения или улучшить внешний вид:
plt.figure(figsize=(10, 5))
plt.plot(day_numbers, user_counts)
plt.yscale('log', base=10)
# Установка явных пределов для оси Y
plt.ylim(1, 10000) # Устанавливаем нижний предел 1 и верхний 10000
plt.title('Рост пользователей (Логарифмический масштаб по Y, заданные пределы)')
plt.xlabel('День')
plt.ylabel('Количество пользователей')
plt.grid(True, which='both', linestyle='--', linewidth=0.5)
plt.show()
Функция plt.ylim(ymin, ymax) (или метод ax.set_ylim(ymin, ymax)) позволяет задать минимальное и максимальное значения на оси Y. При работе с логарифмической шкалой ymin должен быть больше нуля.
Решение распространенных проблем при работе с логарифмическими шкалами
Логарифмические шкалы имеют одно фундаментальное ограничение: логарифм определен только для положительных чисел. Это создает определенные проблемы при работе с данными, которые могут содержать нули или отрицательные значения.
Обработка нулевых и отрицательных значений
Если ваши данные для оси Y содержат значения 0 или меньше, попытка построить их на логарифмической шкале приведет к ошибке или предупреждению Matplotlib, и эти точки просто не будут отображены.
Пример проблемы:
problematic_data_y = np.array([10, 50, 0, 200, -5, 1000])
x_values = np.arange(len(problematic_data_y))
# Этот код вызовет предупреждение или ошибку, и точки с 0 и -5 не будут показаны
# plt.figure()
# plt.plot(x_values, problematic_data_y)
# plt.yscale('log')
# plt.show()
Решение: Необходимо предварительно обработать данные, исключив или преобразовав некорректные значения.
-
Фильтрация: Самый простой способ – полностью удалить точки данных, где значение Y неположительно. Это подходит, если эти точки немногочисленны и не нарушают целостность временного ряда или последовательности.
valid_indices = problematic_data_y > 0 filtered_x = x_values[valid_indices] filtered_y = problematic_data_y[valid_indices] plt.figure(figsize=(8, 4)) plt.plot(filtered_x, filtered_y, 'o-') # Используем маркеры, чтобы показать, где были пропуски plt.yscale('log') plt.title('Данные с фильтрацией неположительных значений') plt.xlabel('Индекс') plt.ylabel('Значение (log шкала)') plt.grid(True, which='both', linestyle='--', linewidth=0.5) plt.show()
Проблемы с отображением данных вблизи нуля
Даже если все значения положительны, очень маленькие положительные числа (близкие к нулю, например, 1e-10) могут создавать проблемы с визуализацией, так как логарифм таких чисел уходит в отрицательную бесконечность. Нижний предел логарифмической шкалы не может быть равен нулю, он должен быть больше нуля.
Если ваши данные содержат очень малые положительные значения, убедитесь, что нижний предел оси Y (plt.ylim) установлен корректно и больше самого маленького значения данных (или хотя бы разумно близко к нему). Часто устанавливают минимальное значение, равное 1, или минимальное положительное значение в данных.
Использование маскирования для исключения проблемных данных
Вместо полного удаления точек данных можно использовать маскированные массивы NumPy (numpy.ma). Маскирование позволяет временно «скрыть» определенные элементы массива от операций (включая построение графиков), не изменяя размерности или порядок массива. Это полезно, например, при построении временных рядов с пропусками.
import numpy.ma as ma
problematic_data_y = np.array([10, 50, 0, 200, -5, 1000, 1])
x_values = np.arange(len(problematic_data_y))
# Создаем маскированный массив, скрывая неположительные значения
masked_y = ma.masked_where(problematic_data_y <= 0, problematic_data_y)
plt.figure(figsize=(8, 4))
plt.plot(x_values, masked_y, 'o-') # Matplotlib игнорирует замаскированные точки
plt.yscale('log')
# Устанавливаем нижний предел оси Y, чтобы избежать проблем с очень малыми значениями, если они есть
# plt.ylim(bottom=0.1) # Пример, если бы были очень малые положительные числа
plt.title('Данные с маскированием неположительных значений')
plt.xlabel('Индекс')
plt.ylabel('Значение (log шкала)')
plt.grid(True, which='both', linestyle='--', linewidth=0.5)
plt.show()
Маскирование является более «чистым» способом обработки некорректных данных для визуализации, поскольку оно сохраняет соответствие между X и Y значениями и не требует изменения исходных массивов.
Примеры продвинутого использования логарифмических шкал
Логарифмические шкалы с использованием matplotlib.ticker
Мы уже кратко коснулись matplotlib.ticker для форматирования меток. Этот модуль предоставляет полный контроль над расположением и внешним видом меток на осях.
LogLocator: Используется для определения расположения основных и вспомогательных меток на логарифмической оси. Позволяет указать основание логарифма (base) и подразбиения (subs).LogFormatter/ScalarFormatter: Используются для форматирования текстового представления меток.
Пример с более тонкой настройкой меток: (см. пример форматирования меток выше)
# Пример кода был представлен в разделе 'Форматирование меток на оси Y'
# Он показывает, как использовать ScalarFormatter для явных меток 10, 100, 1000 и т.д.
# Более сложные сценарии могут потребовать комбинации LogLocator и FuncFormatter.
Для большинства типичных задач достаточно plt.yscale('log') и, возможно, ScalarFormatter через ax.yaxis.set_major_formatter. Однако для уникальных требований к визуализации, matplotlib.ticker предоставляет необходимые инструменты.
Создание двойных логарифмических графиков (log-log plots)
Log-log plot – это график, где обе оси (X и Y) имеют логарифмический масштаб. Такие графики часто используются для идентификации степенных законов, поскольку $y = ax^k$ на log-log графике выглядит как прямая линия ($_log(y) = _log(a) + k _log(x)$).
Создать log-log график так же просто, как применить логарифмический масштаб к обеим осям:
def generate_power_law_data(size: int) -> Tuple[np.ndarray, np.ndarray]:
"""
Генерирует данные, приблизительно соответствующие степенному закону.
y = x^k с шумом.
Args:
size: Количество точек данных.
Returns:
Кортеж из двух массивов numpy: x и y.
"""
x: np.ndarray = np.logspace(0.1, 3, size) # Генерируем x в лог-масштабе
# Моделируем y = x^k + шум
k = -1.5 # Пример показателя степени (распределение с "тяжелым хвостом")
y: np.ndarray = x**k + np.random.normal(0, 0.05 * x**k, size) # Шум пропорционален значению y
# Убедимся, что нет неположительных значений
y[y <= 0] = np.min(y[y > 0]) if np.any(y > 0) else 1e-3 # Заменяем на минимальное положит. или малое число
return x, y
# Генерируем данные степенного закона
data_size = 200
power_x, power_y = generate_power_law_data(data_size)
plt.figure(figsize=(10, 6))
plt.plot(power_x, power_y, 'o', markersize=4, alpha=0.6)
# Установка логарифмического масштаба по обеим осям
plt.xscale('log')
plt.yscale('log')
plt.title('Log-Log график данных со степенной зависимостью')
plt.xlabel('X (log шкала)')
plt.ylabel('Y (log шкала)')
plt.grid(True, which='both', linestyle='--', linewidth=0.5)
plt.show()
На этом графике точки должны выстроиться примерно вдоль прямой линии, если зависимость действительно близка к степенному закону.
Использование логарифмических шкал с другими типами графиков (scatter, bar)
Логарифмический масштаб по оси Y применим не только к линейным графикам (plt.plot), но и к другим типам визуализаций, таким как точечные диаграммы (plt.scatter) или столбчатые диаграммы (plt.bar).
-
Scatter Plot: Часто используется для визуализации распределений или зависимостей между двумя переменными, где одна или обе оси могут иметь очень широкий диапазон. Логарифмический масштаб помогает увидеть структуру данных в областях с высокой плотностью или выявить редкие, но большие значения.
# Используем те же данные степенного закона, что и выше plt.figure(figsize=(10, 6)) plt.scatter(power_x, power_y, s=10, alpha=0.6) # s - размер маркера # Логарифмическая шкала по Y plt.yscale('log') # Можно добавить и по X, если нужно log-log scatter plot # plt.xscale('log') plt.title('Scatter Plot с логарифмической шкалой по Y') plt.xlabel('X') plt.ylabel('Y (log шкала)') plt.grid(True, which='both', linestyle='--', linewidth=0.5) plt.show() -
Bar Plot: Хотя и менее распространены с логарифмическими шкалами, могут быть полезны, когда высоты столбцов сильно различаются. Однако следует быть осторожным, так как визуальное сравнение высот на логарифмической шкале может быть неочевидным для читателя.
# Пример данных: количество посещений разных страниц сайта page_labels = ['Главная', 'Продукт A', 'Блог', 'Контакты', 'FAQ'] visits_counts = np.array([15000, 800, 3500, 120, 40]) plt.figure(figsize=(8, 5)) bars = plt.bar(page_labels, visits_counts) # Логарифмическая шкала по Y plt.yscale('log') plt.title('Посещаемость страниц (Логарифмический масштаб по Y)') plt.xlabel('Страница') plt.ylabel('Количество посещений (log шкала)') plt.grid(True, which='both', linestyle='--', linewidth=0.5, axis='y') # Сетка только по Y # Можно добавить метки над столбцами, показывающие точное значение # for bar in bars: # yval = bar.get_height() # plt.text(bar.get_x() + bar.get_width()/2.0, yval, # int(yval), va='bottom' if yval > 0 else 'top', ha='center') # va='bottom' может потребовать доп. настройки для log шкалы plt.show()
При использовании логарифмической шкалы со столбчатыми диаграммами, визуальное восприятие разницы высот искажается, но отношение высот сохраняется. Важно четко указывать, что ось имеет логарифмический масштаб, и, возможно, добавлять текстовые метки с точными значениями.
Создание графиков с логарифмическим масштабом по оси Y в Matplotlib – это мощный инструмент для визуализации данных с большим динамическим диапазоном или экспоненциальным поведением. Освоив базовые функции plt.yscale('log') и методы настройки через matplotlib.ticker, а также научившись правильно обрабатывать некорректные данные, вы сможете создавать более информативные и точные визуализации.