Matplotlib — Реализация нескольких масштабов по оси y в анимированном линейном графике

#python #dataframe #matplotlib #animation #data-visualization

Вопрос:

Я пытаюсь переделать существующий анимированный линейный график, который я сделал, где каждая строка имеет уникальную масштабированную ось y-одна слева, одна справа. На графике сравнивается стоимость двух криптовалют, которые имеют совершенно разные размеры (eth/btc), поэтому мне нужно несколько масштабов, чтобы действительно увидеть изменения.

Мои данные были отформатированы в формате pd df (числа здесь случайны).:

                    Date  ETH Price     BTC Price
0   2020-10-30 00:00:00   0.155705  1331.878496
1   2020-10-31 00:00:00   0.260152  1337.174272
..                  ...        ...           ...
290 2021-08-15 16:42:09   0.141994  2846.719819
[291 rows x 3 columns]
 

И код примерно такой:

 import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as ani

color = ['cyan', 'orange', 'red']
fig = plt.figure()
plt.xticks(rotation=45, ha="right", rotation_mode="anchor") 
plt.subplots_adjust(bottom = 0.2, top = 0.9) 
plt.ylabel('Coin Value (USD)')
plt.xlabel('Date')

def buildChart(i=int):
    df1 = df.set_index('Date', drop=True)
    plt.legend(["ETH Price", "BTC Price"])
    p = plt.plot(df1[:i].index, df1[:i].values) 
    for i in range(0,2):
        p[i].set_color(color[i])

animator = ani.FuncAnimation(fig, buildChart, interval = 10)
plt.show()
 

Результирующая Анимация

Я попытался создать вторую ось с двойным x на первой оси.

 color = ['cyan', 'orange', 'blue']
fig, ax1 = plt.subplots() #Changes over here
plt.xticks(rotation=45, ha="right", rotation_mode="anchor") 
plt.subplots_adjust(bottom = 0.2, top = 0.9) 
plt.ylabel('Coin Value (USD)')
plt.xlabel('Date')

def buildChart(i=int):
    df1 = df.set_index('Date', drop=True)
    plt.legend(["ETH Price", "Bitcoin Price"])
    data1 = df1.iloc[:i, 0:1] # Changes over here
    # ------------- More Changes Start
    ax2 = ax1.twinx() 
    ax2.set_ylabel('Cost of Coin (USD)') 
    data2 = df1.iloc[:i, 1:2] 
    ax2.plot(df1[:i].index, data2)
    ax2.tick_params(axis='y')
    # -------------- More Changes End
    p = plt.plot(df1[:i].index, data1) 
    for i in range(0,1):
        p[i].set_color(color[i])

import matplotlib.animation as ani
animator = ani.FuncAnimation(fig, buildChart, interval = 10)
plt.show()
 

Результирующая Анимация После Изменений

Текущие проблемы:

  • Ось X начинается в ~1999, а не поздно 2020 —- Заставляет все изменения на оси y быть почти вертикальной линией
  • Левая метка оси Y по шкале от 0 до 1?
  • Правые метки по оси y повторяются, перекрываются, перемещаются.

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

Ответ №1:

Я изменил структуру вашего кода, чтобы легко настроить анимацию вторичной оси.
Здесь код анимации с одной осью y:

 import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation


df = pd.DataFrame({'date': pd.date_range(start = '2020-01-01', end = '2020-04-01', freq = 'D')})
df['ETH'] = 2*df.index   300   100*np.random.randn(len(df))
df['BTC'] = 5*df.index   13000   200*np.random.randn(len(df))


def update(i):
    ax.cla()

    ax.plot(df.loc[:i, 'date'], df.loc[:i, 'ETH'], label = 'ETH Price', color = 'red')
    ax.plot(df.loc[:i, 'date'], df.loc[:i, 'BTC'], label = 'BTC Price', color = 'blue')

    ax.legend(frameon = True, loc = 'upper left', bbox_to_anchor = (1.15, 1))

    ax.set_ylim(0.9*min(df['ETH'].min(), df['BTC'].min()), 1.1*max(df['ETH'].max(), df['BTC'].max()))

    ax.tick_params(axis = 'x', which = 'both', top = False)
    ax.tick_params(axis = 'y', which = 'both', right = False)

    plt.setp(ax.xaxis.get_majorticklabels(), rotation = 45)

    ax.set_xlabel('Date')
    ax.set_ylabel('ETH Coin Value (USD)')

    plt.tight_layout()


fig, ax = plt.subplots(figsize = (6, 4))

ani = FuncAnimation(fig = fig, func = update, frames = len(df), interval = 100)

plt.show()
 

введите описание изображения здесь

Начиная с приведенного выше кода, вы должны вывести ось из update функции: если вы будете оставаться ax.twinx() внутри функции, эта операция будет повторяться на каждой итерации, и каждый раз вы будете получать новую ось.
Ниже приведен код анимации со вторичной осью:

 import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation


df = pd.DataFrame({'date': pd.date_range(start = '2020-01-01', end = '2020-04-01', freq = 'D')})
df['ETH'] = 2*df.index   300   100*np.random.randn(len(df))
df['BTC'] = 5*df.index   13000   200*np.random.randn(len(df))


def update(i):
    ax1.cla()
    ax2.cla()

    line1 = ax1.plot(df.loc[:i, 'date'], df.loc[:i, 'ETH'], label = 'ETH Price', color = 'red')
    line2 = ax2.plot(df.loc[:i, 'date'], df.loc[:i, 'BTC'], label = 'BTC Price', color = 'blue')

    lines = line1   line2
    labels = [line.get_label() for line in lines]
    ax1.legend(lines, labels, frameon = True, loc = 'upper left', bbox_to_anchor = (1.15, 1))

    ax1.set_ylim(0.9*df['ETH'].min(), 1.1*df['ETH'].max())
    ax2.set_ylim(0.9*df['BTC'].min(), 1.1*df['BTC'].max())

    ax1.tick_params(axis = 'x', which = 'both', top = False)
    ax1.tick_params(axis = 'y', which = 'both', right = False, colors = 'red')
    ax2.tick_params(axis = 'y', which = 'both', right = True, labelright = True, left = False, labelleft = False, colors = 'blue')

    plt.setp(ax1.xaxis.get_majorticklabels(), rotation = 45)

    ax1.set_xlabel('Date')
    ax1.set_ylabel('ETH Coin Value (USD)')
    ax2.set_ylabel('BTC Coin Value (USD)')

    ax1.yaxis.label.set_color('red')
    ax2.yaxis.label.set_color('blue')

    ax2.spines['left'].set_color('red')
    ax2.spines['right'].set_color('blue')

    plt.tight_layout()


fig, ax1 = plt.subplots(figsize = (6, 4))
ax2 = ax1.twinx()

ani = FuncAnimation(fig = fig, func = update, frames = len(df), interval = 100)

plt.show()
 

введите описание изображения здесь