Search code examples
pythonpandasdataframematplotlibmatplotlib-animation

Animating 2 lines on the same plot


I am trying to animate two lines on the same plot. After searching around, I came across this post that seems to be getting me on the right track. When I run this code, the stagnant graph shows with no animation, giving me an error AttributeError: 'AxesSubplot' object has no attribute 'set_data'. I looked up set_data and it says it 'accepts: 2D array (rows are x, y) or two 1D arrays'. Is the animation not working since I'm assigning line1 and line2 to plots and not 2D arrays? I would appreciate any help with getting these lines to animate on my plot, I've tried and tried to no avail. Thanks!

fig, ax = plt.subplots(figsize=(16,8))

#Plot Lines
line1 = sns.lineplot('game_seconds_remaining', 'away_wp', data=game, color='#4F2683',linewidth=2)
line2 = sns.lineplot('game_seconds_remaining', 'home_wp', data=game, color='#869397',linewidth=2)
#Add Fill
ax.fill_between(game['game_seconds_remaining'], 0.5, game['away_wp'], where=game['away_wp']>.5, color = '#4F2683',alpha=0.3)
ax.fill_between(game['game_seconds_remaining'], 0.5, game['home_wp'], where=game['home_wp']>.5, color = '#869397',alpha=0.3)


#Plot Aesthetics - Can Ignore
plt.ylabel('Win Probability %', fontsize=16)
plt.xlabel('', fontsize=16)
plt.axvline(x=900, color='white', alpha=0.7)
plt.axvline(x=1800, color='white', alpha=0.7)
plt.axvline(x=2700, color='white', alpha=0.7)
plt.axhline(y=.50, color='white', alpha=0.7)
plt.suptitle('Minnesota Vikings @ Dallas Cowboys', fontsize=20, style='italic',weight='bold')
plt.title('Min 28, DAL 24 - Week 10 ', fontsize=16, style = 'italic',weight='semibold')


#Labels (And variable assignment for animation below)
x = ax.set_xticks(np.arange(0, 3601,900))
y1 = game['away_wp']
y2 = game['home_wp']
plt.gca().invert_xaxis()
x_ticks_labels = ['End','End Q3','Half','End Q1','Kickoff']
ax.set_xticklabels(x_ticks_labels, fontsize=12)


#Animation - Not working
def update(num, x, y1, y2, line1, line2):
    line1.set_data(x[:num], y1[:num])
    line2.set_data(x[:num], y2[:num])
    return [line1,line2]

ani = animation.FuncAnimation(fig, update, len(x), fargs=[x, y1, y2, line1, line2],
                  interval=295, blit=False)

Solution

  • Tested in python 3.11.2, pandas 2.0.0, matplotlib 3.7.1, seaborn 0.12.2

    it seems sns gives AxesSublot and you have to get line(s) for this Axes

    ax1 = sns.lineplot(...)
    ax2 = sns.lineplot(...)
    
    line1 = ax1.lines[0]
    line2 = ax2.lines[1]
    

    Or (because both lines are on the same Axes)

    sns.lineplot(x=x, y='away_wp', data=game)
    sns.lineplot(x=x, y='home_wp', data=game)
    
    ax = plt.gca()
    
    line1 = ax.lines[0]
    line2 = ax.lines[1]
    

    EDIT:

    For Google Colab it needs

    from matplotlib import rc
    rc('animation', html='jshtml')
    
    # code without `plt.show()`
    
    ani   # display it
    

    Source: Embedding Matplotlib Animations in Python (google colab notebook)


    Minimal working code with random data

    import random
    import seaborn as sns
    import pandas as pd
    import matplotlib.pyplot as plt
    from matplotlib import animation
    from matplotlib import rc
    rc('animation', html='jshtml')
    
    game = pd.DataFrame({
        'away_wp': [random.randint(-10,10) for _ in range(100)],
        'home_wp': [random.randint(-10,10) for _ in range(100)],
        'game_seconds_remaining': list(range(100)),
    })
    
    x = range(len(game))
    y1 = game['away_wp']
    y2 = game['home_wp']
    
    fig = plt.gcf()
    ax = plt.gca()
    
    sns.lineplot(x='game_seconds_remaining', y='away_wp', data=game)
    sns.lineplot(x='game_seconds_remaining', y='home_wp', data=game)
    
    line1 = ax.lines[0]
    line2 = ax.lines[1]
    
    ax.fill_between(game['game_seconds_remaining'], 0.5, game['away_wp'], where=game['away_wp']>.5, color = '#4F2683',alpha=0.3)
    ax.fill_between(game['game_seconds_remaining'], 0.5, game['home_wp'], where=game['home_wp']>.5, color = '#869397',alpha=0.3)
    #print(ax.collections)
    
    def update(num, x, y1, y2, line1, line2):
        line1.set_data(x[:num], y1[:num])
        line2.set_data(x[:num], y2[:num])
    
        ax.clear()
        ax.fill_between(game['game_seconds_remaining'][:num], 0.5, game['away_wp'][:num], where=game['away_wp'][:num]>.5, color = '#4F2683',alpha=0.3)
        ax.fill_between(game['game_seconds_remaining'][:num], 0.5, game['home_wp'][:num], where=game['home_wp'][:num]>.5, color = '#869397',alpha=0.3)
    
        return line1,line2
    
    ani = animation.FuncAnimation(fig, update, len(x), fargs=[x, y1, y2, line1, line2], interval=295, blit=False)
    
    #plt.show()
    
    ani   # display it
    

    EDIT:

    The same without seaborn but only plt.plot().

    At start I create empty line line1, = plt.plot([], [])

    import random
    import pandas as pd
    import matplotlib.pyplot as plt
    from matplotlib import animation
    from matplotlib import rc
    rc('animation', html='jshtml')
    
    game = pd.DataFrame({
        'away_wp': [random.randint(-10,10) for _ in range(100)],
        'home_wp': [random.randint(-10,10) for _ in range(100)],
        'game_seconds_remaining': list(range(100)),
    })
    
    x = range(len(game))
    y1 = game['away_wp']
    y2 = game['home_wp']
    
    fig = plt.gcf()
    ax = plt.gca()
    
    # empty lines at start
    line1, = plt.plot([], [])
    line2, = plt.plot([], [])
    
    # doesn't draw fill_between at start
    
    # set limits 
    ax.set_xlim(0, 100)
    ax.set_ylim(-10, 10)
    
    def update(num, x, y1, y2, line1, line2):
        line1.set_data(x[:num], y1[:num])
        line2.set_data(x[:num], y2[:num])
        # autoscale 
        #ax.relim()
        #ax.autoscale_view()
    
        ax.clear()
        ax.fill_between(game['game_seconds_remaining'][:num], 0.5, game['away_wp'][:num], where=game['away_wp'][:num]>.5, color = '#4F2683',alpha=0.3)
        ax.fill_between(game['game_seconds_remaining'][:num], 0.5, game['home_wp'][:num], where=game['home_wp'][:num]>.5, color = '#869397',alpha=0.3)
        
        return line1,line2
    
    ani = animation.FuncAnimation(fig, update, len(x), fargs=[x, y1, y2, line1, line2], interval=295, blit=False)
    
    #plt.show()
    
    ani
    

    enter image description here