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