1

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)
bismo
  • 927
  • 8
  • 18
  • 1
    it will works only if you use `matplotlib` functions to draw lines - ie. `plt.plot()`. `seaborn` may need more code to get line from `AxesSubplot` – furas Jul 28 '20 at 01:02
  • @furas plt.plot() still returns the non-animated graph – bismo Jul 28 '20 at 01:15
  • 1
    did you do `line1, = plt.plot(...) ` instead of `sns.lineplot()` ? – furas Jul 28 '20 at 01:16
  • @furas correct. Tried ```ax.plot(...)``` too. – bismo Jul 28 '20 at 01:17
  • Hold on a second, ```line1, = plt.plot(...)``` and ```line1 = plt.plot(...)``` return different things. The one with the comma messes it up. Neither are animated. – bismo Jul 28 '20 at 01:18

1 Answers1

1

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.collections.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.collections.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
furas
  • 119,752
  • 10
  • 94
  • 135
  • I copied this exact code and I'm getting a blank plot with no animation. I am in google colab if that matters. – bismo Jul 28 '20 at 01:25
  • 1
    I run it normally `python script.py`. I never try to do it in Google Colab. Maybe it needs something more to display animation. Maybe some magic function `%` – furas Jul 28 '20 at 01:29
  • 1
    [Embedding Matplotlib Animations in Python (google colab notebook)](https://stackoverflow.com/questions/61103994/embedding-matplotlib-animations-in-python-google-colab-notebook) – furas Jul 28 '20 at 01:29
  • How would I go about animating the ```fill_between()``` as well? Thanks for all the great help by the way, I'm learning a lot – bismo Jul 28 '20 at 02:26
  • 1
    [Matplotlib animate fill_between shape](https://stackoverflow.com/questions/16120801/matplotlib-animate-fill-between-shape) - in `update()` you will have to remove it `ax.colections.clear()` and draw again with `[:num]` – furas Jul 28 '20 at 03:08
  • 1
    I added `fill_between()` in `update()` in examples. – furas Jul 28 '20 at 03:14