ПИТОН

Лучшая визуализация данных с помощью двухосевых графиков в Python

Использование библиотеки Plotly для анализа и демонстрации ваших данных в удобной для чтения форме

Визуализация данных упрощает понимание тенденций и принятие обоснованных решений. Важной частью визуализации данных является выбор правильной диаграммы для демонстрации ваших данных. Кроме того, даже определенные диаграммы, такие как гистограммы и линейные диаграммы, можно дополнительно настроить, чтобы лучше объяснить данные, которые вы хотите продемонстрировать.

В дополнение к косметическим изменениям, таким как цвет и шрифт, вы также можете рассмотреть возможность добавления дополнительных функций, таких как средние линии тренда, прогнозы или реализовать двойную ось. В этой статье мы расскажем, как использовать двойную ось на линейной диаграмме, чтобы сделать корреляции и тенденции между точками данных более очевидными для вашей аудитории. Мы также быстро рассмотрим, как будет выглядеть обычная диаграмма без двойной оси, чтобы вы могли решить, какая из двух лучше всего соответствует вашим потребностям в визуализации.

Мы будем использовать библиотеку Plotly в Python для визуализации данных, а также Pandas для некоторой предварительной обработки данных, поэтому убедитесь, что у вас уже установлены эти два пакета. Затем импортируйте следующее и будьте готовы следовать!

import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import random

Простая реализация обычного линейного графика с помощью Plotly

Во-первых, чтобы сгенерировать некоторые образцы данных, запустите следующий код.

expense_data = {
    "Person": random.choices(["A", "B"], k=20),
    "Amount": random.sample(range(100, 200), 10) + random.sample(range(0, 99), 10),
    "Category": ["Groceries"] * 10 + ["Restaurant"] * 10,
    "Date": pd.to_datetime(pd.date_range('2020-01-01','2020-10-01', freq='MS').tolist() * 2)
}
df = pd.DataFrame(data=expense_data)

Данные, которые мы собираемся визуализировать, будут основаны на случайно сгенерированных данных о личных расходах. Выше вы можете видеть, что мы просто случайным образом создаем данные о расходах за 10 месяцев и загружаем их в Pandas DataFrame. Приведенный выше код должен вывести 20 строк данных.

Наша цель этого анализа - сравнить категории расходов «Продовольственные товары» и «Ресторан» с течением времени. Таким образом, давайте теперь сгруппируем данные по полям «Дата» и «Категория», используя несколько строк панд.

df_grouped = df.groupby(by=[pd.Grouper(key="Date", freq="1M"), "Category"])["Amount"]
df_grouped = df_grouped.sum().reset_index()

Наконец, используя только одну строку Plotly Express, мы можем создать линейную диаграмму с сгруппированным DataFrame в качестве входных данных.

px.line(df_grouped, x="Date", y="Amount", color="Category")

Plotly Express позволяет нам создать приведенную выше диаграмму только с одной линией, в которую мы вводим наш DataFrame, значения оси x, значения оси y и, необязательно, параметр color, чтобы иметь несколько цветных линий на диаграмме (по одной для каждой категории ).

Диаграмма выглядит неплохо, но, как видите, сравнивать две категории немного сложно. Это интуитивно понятно, поскольку можно было бы ожидать, что люди в целом ежемесячно тратят больше денег на продукты, чем на походы в ресторан. (Кроме того, мы жестко встроили эту логику в код, который генерирует этот образец данных - вы можете дважды проверить это выше.)

Затем давайте посмотрим, как реализовать двойную ось, чтобы упростить сравнение двух разных категорий расходов.

Создание двухосной линейной диаграммы с помощью Plotly

Во-первых, нам нужно создать пустую фигуру подзаголовка, используя make_subplots (который мы импортировали ранее). Мы также определим две переменные, чтобы назвать наши целевые категории.

# making dual axis and defining categories
fig = make_subplots(specs=[[{"secondary_y": True}]])
category_1 = "Groceries"
category_2 = "Restaurant"

Мы еще ничего не выводим, но стоит отметить, что в методе make_subplots мы передаем "secondary_y": True внутри specs, чтобы в дальнейшем мы могли правильно реализовать двойную ось.

Затем мы вручную создадим первую линию на нашей линейной диаграмме.

# creating first plot
fig.add_trace(
    go.Scatter(
        y=df_grouped.loc[df_grouped["Category"]==category_1, "Amount"], 
        x=df_grouped.loc[df_grouped["Category"]==category_1, "Date"], 
        name=category_1
    ),
    secondary_y=False,
)

Раньше использование Plotly Express позволяло нам просто передать одну строку кода для генерации всего. Использование обычной библиотеки Plotly означает, что нам нужно написать немного больше кода. Выше мы использовали метод add_trace для объекта fig, который мы определили ранее, чтобы добавить данные из нашего ранее сгруппированного DataFrame. Мы также импортировали plotly.graph_objects как go ранее, поэтому мы можем передавать значения столбцов x и y. Наконец, мы устанавливаем secondary_y на False, поскольку это только первая строка на графике.

Если вы запустите fig.show(), вы должны увидеть что-то вроде этого:

Немного банально, но пока неплохо! Прямо сейчас у нас есть только данные из продуктовых магазинов. Чтобы добавить вторую строку с данными о ресторане, мы запустим следующее.

# creating next plot
fig.add_trace(
    go.Scatter(
        y=df_grouped.loc[df_grouped["Category"]==category_2, "Amount"], 
        x=df_grouped.loc[df_grouped["Category"]==category_2, "Date"], 
        name=category_2
    ),        
    secondary_y=True,
)

Это почти тот же код, за исключением того, что мы используем category_2 и вместо этого передаем secondary_y=True. Затем, если вы снова запустите fig.show(), вы должны увидеть что-то вроде этого:

Выглядит еще лучше! В отличие от предыдущего, нам легче увидеть, как эти две категории данных о расходах ведут себя во времени по отношению друг к другу. Это просто случайно сгенерированные данные, но через несколько месяцев вы можете увидеть, что, когда расходы на продукты питания высоки, расходы в ресторанах сравнительно ниже (и наоборот).

Мы можем сделать это немного более понятным, написав немного больше кода для добавления меток осей, например:

fig.update_yaxes(title_text=category_1, secondary_y=False)
fig.update_yaxes(title_text=category_2, secondary_y=True)

Здесь мы используем те же update_yaxes методы, но передаем сначала False, а затем True в параметр secondary_y, чтобы соответствующим образом пометить обе оси.

Пока это неплохо, но я думаю, что мы можем немного очистить ситуацию и переместить всю логику в одну функцию, подходящую для повторного использования. Вот моя реализация:

def create_dual_axis_graph(input_df, *args):
    # making dual axis initial fig
    dual_axis_fig = make_subplots(specs=[[{"secondary_y": True}]])
    
    # defining categories from kwargs
    categories = [*args]
    assert len(categories) == 2, f"Must only provide two categories. You provided {len(categories)}."
    # creating graph with loop
    for count, category in enumerate(categories):
        dual_axis_fig.add_trace(
            go.Scatter(
                y=input_df.loc[input_df["Category"]==category, "Amount"], 
                x=input_df.loc[input_df["Category"]==category, "Date"], 
                name=category
            ),
            secondary_y=count,
        )
    dual_axis_fig.update_yaxes(title_text=category, secondary_y=count)
    return dual_axis_fig

Мы делаем то же самое, что и раньше, за исключением того, что убираем часть повторений, которые были полезны в демонстрационных целях. Функция create_dual_axis_graph принимает input_df в качестве основного аргумента (где вы должны указать свой уже сгруппированный DataFrame, как мы делали раньше), а затем *args в качестве имен двух категорий, которые вы хотите изучить.

Мы помещаем *args в список (а также проверяем, что список содержит только два элемента), затем перебираем этот список и снова используем метод add_trace, чтобы добавить данные для осей x и y. Мы также будем использовать enumerate в этом цикле, поэтому 0 или 1 (которые являются логическими) могут быть переданы в параметр secondary_y для add_trace и update_yaxes, как мы это делали ранее.

Запуск функции должен занимать одну строку вроде этого:

create_dual_axis_graph(df_grouped, "Groceries", "Restaurant").show()

Это даст вам точно такой же двухосный график, что и раньше. Чтобы показать, как будет выглядеть неправильная реализация, вы можете попробовать следующее:

create_dual_axis_graph(df_grouped, "Groceries", "Restaurant", "Appliances").show()

Мы определили AssertionError ранее, чтобы убедиться, что в список категорий переданы только два аргумента. Вы также можете определить более полный этап проверки данных, например, проверку того, имеет ли DataFrame правильные столбцы, имеют ли значения столбцов правильные типы данных, действительно ли предоставленные категории находятся в DataFrame и многое другое. Однако для этой демонстрации будет достаточно указанной выше функции.

И все, ребята!

Надеюсь, эта быстрая демонстрация (полунепрерывного) анализа данных и визуализации была для вас полезной! Мне нравится двухосевой график, потому что он позволяет легко сравнивать две категории, которые не нужны в одном масштабе, и поскольку у Plotly Express не было собственного способа сделать это, я подумал, что было бы здорово поделиться эту реализацию со всеми.

Желаем удачи опробовать это и использовать на своих данных!

Еще раз спасибо за чтение! Если вы думаете о том, чтобы стать платным участником на Medium, я буду очень признателен, если вы зарегистрируетесь, используя мою реферальную ссылку ниже! Это позволило бы мне напрямую получать часть ваших членских взносов, так что это было бы большим подспорьем.



More by me:
- Check for a Substring in a Pandas DataFrame
- Conditional Selection and Assignment With .loc in Pandas
- 2 Easy Ways to Get Tables From a Website With Pandas
- 5 (and a half) Lines of Code for Understanding Your Data with Pandas
- Top 4 Repositories on GitHub to Learn Pandas