How to do dynamic matplotlib plotting with a fixed pandas dataframe?

后端 未结 2 1815
醉酒成梦
醉酒成梦 2021-01-23 10:57

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 an

相关标签:
2条回答
  • 2021-01-23 11:35

    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)
    
    0 讨论(0)
  • 2021-01-23 11:44

    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)
    

    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)
    


    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().

    0 讨论(0)
提交回复
热议问题