How to do dynamic matplotlib plotting with a fixed

2020-04-21 01:39发布

I have a dataframe called benchmark_returns and strategy_returns. Both have the same timespan. I want to find a way to plot the datapoints in a nice animation style so that it shows all the points loading in gradually. I am aware that there is a matplotlib.animation.FuncAnimation(), however this typically is only used for a real-time updating of csv files etc but in my case I know all the data I want to use.

I have also tried using the crude plt.pause(0.01) method, however this drastically slows down as the number of points get plotted.

Here is my code so far

x = benchmark_returns.index
y = benchmark_returns['Crypto 30'] 
y2 = benchmark_returns['Dow Jones 30']
y3 = benchmark_returns['NASDAQ'] 
y4 = benchmark_returns['S&P 500']


fig, ax = plt.subplots()
line, = ax.plot(x, y, color='k')
line2, = ax.plot(x, y2, color = 'b')
line3, = ax.plot(x, y3, color = 'r')
line4, = ax.plot(x, y4, color = 'g')

def update(num, x, y, y2, y3, y4, line): 
    line.set_data(x[:num], y[:num])
    line2.set_data(x[:num], y2[:num])
    line3.set_data(x[:num], y3[:num])
    line4.set_data(x[:num], y4[:num])

    return line, line2, line3, line4,

ani = animation.FuncAnimation(fig, update, fargs=[x, y, y2, y3, y4, line], 
                              interval = 1, blit = True)
plt.show()

2条回答
太酷不给撩
2楼-- · 2020-04-21 02:11

You can just update the data into the line element like so:

fig = plt.figure()
ax = fig.add_subplot(111)
liner, = ax.plot()
plt.ion()
plt.show()
for i in range(len(benchmark_returns.values)):
    liner.set_ydata(benchmark_returns['Crypto 30'][:i])
    liner.set_xdata(benchmark_returns.index[:i])
    plt.pause(0.01)
查看更多
够拽才男人
3楼-- · 2020-04-21 02:27

You could try matplotlib.animation.ArtistAnimation. It operates similar to FuncAnimation in that you can specify the frame interval, looping behavior, etc, but all the plotting is done at once, before the animation step. Here is an example

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

n = 150
x = np.linspace(0, np.pi*4, n)
df = pd.DataFrame({'cos(x)' : np.cos(x), 
                   'sin(x)' : np.sin(x),
                   'tan(x)' : np.tan(x),
                   'sin(cos(x))' : np.sin(np.cos(x))})

fig, axs = plt.subplots(nrows=2, ncols=2, figsize=(10,10))
lines = []
artists = [[]]
for ax, col in zip(axs.flatten(), df.columns.values):
    lines.append(ax.plot(df[col])[0])
    artists.append(lines.copy())

anim = ArtistAnimation(fig, artists, interval=500, repeat_delay=1000)

enter image description here

The drawback here is that each artist is either drawn or not, i.e. you can't draw only part of a Line2D object without doing clipping. If this is not compatible with your use case then you can try using FuncAnimation with blit=True and chunking the data to be plotted each time as well as using set_data() instead of clearing and redrawing on every iteration. An example of this using the same data from above:

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

n = 500
nf = 100
x = np.linspace(0, np.pi*4, n)
df = pd.DataFrame({'cos(x)' : np.cos(x), 
                   'sin(x)' : np.sin(x),
                   'tan(x)' : np.tan(x),
                   'sin(cos(x))' : np.sin(np.cos(x))})

fig, axs = plt.subplots(2, 2, figsize=(5,5), dpi=50)
lines = []
for ax, col in zip(axs.flatten(), df.columns):
    lines.append(ax.plot([], lw=0.5)[0])
    ax.set_xlim(x[0] - x[-1]*0.05, x[-1]*1.05)
    ax.set_ylim([min(df[col].values)*1.05, max(df[col].values)*1.05])
    ax.tick_params(labelbottom=False, bottom=False, left=False, labelleft=False)
plt.subplots_adjust(hspace=0, wspace=0, left=0.02, right=0.98, bottom=0.02, top=0.98)
plt.margins(1, 1)
c = int(n / nf)
def animate(i):
    if (i != nf - 1):
        for line, col in zip(lines, df.columns):
            line.set_data(x[:(i+1)*c], df[col].values[:(i+1)*c])
    else:
        for line, col in zip(lines, df.columns):
            line.set_data(x, df[col].values)        
    return lines

anim = FuncAnimation(fig, animate, interval=2000/nf, frames=nf, blit=True)

enter image description here


Edit

In response to the comments, here is the implementation of a chunking scheme using the updated code in the question:

x = benchmark_returns.index
y = benchmark_returns['Crypto 30'] 
y2 = benchmark_returns['Dow Jones 30']
y3 = benchmark_returns['NASDAQ'] 
y4 = benchmark_returns['S&P 500']

line, = ax.plot(x, y, color='k')
line2, = ax.plot(x, y2, color = 'b')
line3, = ax.plot(x, y3, color = 'r')
line4, = ax.plot(x, y4, color = 'g')

n = len(x)  # Total number of rows
c = 50      # Chunk size
def update(num):
    end = num * c if num * c < n else n - 1
    line.set_data(x[:end], y[:end])
    line2.set_data(x[:end], y2[:end])
    line3.set_data(x[:end], y3[:end])
    line4.set_data(x[:end], y4[:end])

    return line, line2, line3, line4,

ani = animation.FuncAnimation(fig, update, interval = c, blit = True)
plt.show()

or, more succinctly

cols = benchmark_returns.columns.values
# or, for only a subset of the columns
# cols = ['Crypto 30', 'Dow Jones 30', 'NASDAQ', 'S&P 500']
colors = ['k', 'b', 'r', 'g']
lines = []
for c, col in zip(cols, colors):
    lines.append(ax.plot(benchmark_returns.index, benchmark_returns[col].values, c=c)[0])

n = len(benchmark_returns.index)
c = 50  # Chunk size
def update(num):
    end = num * c if num * c < n else n - 1
    for line, col in zip(lines, cols):
        line.set_data(benchmark_returns.index, benchmark_returns[col].values[:end])

    return lines

anim = animation.FuncAnimation(fig, update, interval = c, blit=True)
plt.show()

and if you need it to stop updating after a certain time simply set the frames argument and repeat=False in FuncAnimation().

查看更多
登录 后发表回答