I have a running times dataset that I have broken down into six months (Jan - Jun). I want to plot an animation of a scatter plot showing distance on the x-axis and time on the y-axis.
Without any animations I have:
plt.figure(figsize = (8,8))
plt.scatter(data = strava_df, x = 'Distance', y = 'Elapsed Time', c = col_list, alpha = 0.7)
plt.xlabel('Distance (km)')
plt.ylabel('Elapsed Time (min)')
plt.title('Running Distance vs. Time')
plt.show()
Which gives me:
What I'd like is an animation that plots the data for the first month, then after a delay the second month, and so on.
from matplotlib.animation import FuncAnimation
fig = plt.figure(figsize=(10,10))
ax = plt.axes(xlim=(2,15), ylim=(10, 80))
x = []
y = []
scat = plt.scatter(x, y)
def animate(i):
for m in range(0,6):
x.append(strava_df.loc[strava_df['Month'] == m,strava_df['Distance']])
y.append(strava_df.loc[strava_df['Month'] == m,strava_df['Elapsed Time']])
FuncAnimation(fig, animate, frames=12, interval=6, repeat=False)
plt.show()
This is what I've come up with, but it isn't working. Any advice?
The animate function should update the matplotlib object created by a call to scat = ax.scatter(...) and also return that object as a tuple. The positions can be updated calling scat.set_offsets() with an nx2 array of xy values. The color can be updated with scat.set_color() with a list or array of colors.
Supposing col_list is a list of color names or rgb-values, the code could look like:
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import pandas as pd
import numpy as np
strava_df = pd.DataFrame({'Month': np.random.randint(0, 6, 120),
'Distance': np.random.uniform(2, 13, 120),
'Color': np.random.choice(['blue', 'red', 'orange', 'cyan'], 120)
})
strava_df['Elapsed Time'] = strava_df['Distance'] * 5 + np.random.uniform(0, 5, 120)
fig = plt.figure(figsize=(10, 10))
ax = plt.axes(xlim=(2, 15), ylim=(10, 80))
scat = ax.scatter([], [], s=20)
def animate(i):
x = np.array([])
y = np.array([])
c = np.array([])
for m in range(0, i + 1):
x = np.concatenate([x, strava_df.loc[strava_df['Month'] == m, 'Distance']])
y = np.concatenate([y, strava_df.loc[strava_df['Month'] == m, 'Elapsed Time']])
c = np.concatenate([c, strava_df.loc[strava_df['Month'] == m, 'Color']])
scat.set_offsets(np.array([x, y]).T)
scat.set_color(c)
return scat,
anim = FuncAnimation(fig, animate, frames=12, interval=6, repeat=False)
plt.show()
Related
I have written the following code with function animation with plot_surface which is not drawing, just giving the first picture
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
x = np.outer(np.linspace(-2, 2, 50), np.ones(50))
#print(x)
y = x.copy().T # transpose
fig = plt.figure()
ax = plt.axes(projection='3d')
def animation_frame(i):
z = np.cos(x ** 2 + y ** 2) + np.cos(x ** (2*i) + y ** (2*i))
# print (z)
ax.plot_surface(x, y, z,cmap='viridis', edgecolor='none')
# return ax,
animation = FuncAnimation(fig, func=animation_frame, frames=np.arange(0, 10, 1), interval=1000, blit=False)
#plt.show()
animation
You should call the plt.show() method at the end. Moreover, you should erase the previous plot with ax.cla() at the beginning of the animation_frame.
Whole code
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
x = np.outer(np.linspace(-2, 2, 50), np.ones(50))
y = x.copy().T
fig = plt.figure()
ax = plt.axes(projection = '3d')
def animation_frame(i):
ax.cla()
z = np.cos(x**2 + y**2) + np.cos(x**(2*i) + y**(2*i))
ax.plot_surface(x, y, z, cmap = 'viridis', edgecolor = 'none')
animation = FuncAnimation(fig, func = animation_frame, frames = np.arange(0, 10, 1), interval = 250, blit = False)
plt.show()
basically I am trying to have a sine wave be displayed by matplotlib and then when a certain x value is reached (block_start_pos) for the animation speed to change (slow down in this case). I understand that FuncAnimation repeatedly calls update_plot based on the given parameters but I was wondering if there was a way to change the interval mid animation. My code (mostly taken from a youtube video) is shown below. Thanks!
from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as plt
import tkinter as tk
x = np.arange(0, 10*np.pi, 0.01)
index_of_refraction = 10
index_of_refraction_lst = [1, 200, 3, 4, 5]
medium = 20*index_of_refraction
w = 1
y = np.cos(w*x)
fig = plt.figure()
ax = plt.subplot(1, 1, 1)
data_skip = 50
block_start_pos = 6*np.pi
def init_func():
ax.clear()
plt.xlabel('pi')
plt.ylabel('sin(pi)')
plt.xlim((x[0], x[-1]))
plt.ylim((-1, 1))
def update_plot(i):
ax.plot(x[i:i+data_skip], y[i:i+data_skip], color='k')
ax.scatter(x[i], y[i], marker='o', color='r')
return medium_test(i)
def medium_test(i):
if x[i] > block_start_pos:
index_of_refraction = index_of_refraction_lst[1]
medium = 20*index_of_refraction
medium = 20*index_of_refraction
anim = FuncAnimation(fig,
update_plot,
frames=np.arange(0, len(x), data_skip),
init_func=init_func,
interval=medium)
plt.show()
# anim.save('sine.mp4', dpi=150, fps = 30, writer='ffmpeg')```
I use Windows 10 / 64 / Google chrome
I found a good set-up for animation over Jupyter with the call %matplotlib notebook as here :
import numpy as np
import scipy.stats as st
%matplotlib notebook
import matplotlib.pyplot as plt
import matplotlib.animation as animation
For exemple, this one is working pretty well :
n = 100
X = st.norm(0,1).rvs(200)
number_of_frames = np.size(X)
def update_hist(num, second_argument):
plt.cla()
plt.hist(X[:num], bins = 20)
plt.title("{}".format(num))
plt.legend()
fig = plt.figure()
hist = plt.hist(X)
ani = animation.FuncAnimation(fig, update_hist, number_of_frames, fargs=(X, ), repeat = False )
plt.show()
But, weirdly the code below doesn't work while it's the same structure, it puzzles me :
X = np.linspace(-5,5, 150)
number_of_frames = np.size(X)
N_max = 100
N = np.arange(1,N_max+1)
h = 1/np.sqrt(N)
def update_plot(n, second_argument):
#plt.cla()
plt.plot(X, [f(x) for x in X], c = "y", label = "densité")
plt.plot(X, [fen(sample_sort[:n],h[n],x) for x in X], label = "densité")
plt.title("n = {}".format(n))
fig = plt.figure(6)
plot = plt.plot(X, [f(x) for x in X], c = "y", label = "densité")
ani = animation.FuncAnimation(fig, update_plot, number_of_frames, fargs=(X, ), repeat = False )
plt.show()
Thanks for your help, best regards.
EDIT : You don't have the funciton fen(sample_sort[:n],h[n],x) it is a function from float to float taking a x in argument and returning a flot. The argument sample_sort[:n],h[n] it is just maths things I'm trying to understand some statistics anyway, you can remplace with line with what you want np.cos(N[:n]) for exemple.
EDIT : New code according to the suggestion :
N_max = 100
X = np.linspace(-5,5, N_max )
number_of_frames = np.size(X)
N = np.arange(1,N_max+1)
h = 1/np.sqrt(N)
def update_plot(n):
#plt.cla()
lines.set_data(X, np.array([fen(sample_sort[:n],h[n],x) for x in X]))
ax.set_title("n = {}".format(n))
return lines
fig = plt.figure()
ax = plt.axes(xlim=(-4, 4), ylim=(-0.01, 1))
ax.plot(X, np.array([f(x) for x in X]), 'y-', lw=2, label="d")
lines, = ax.plot([], [], 'b--', lw=3, label="f")
ani = animation.FuncAnimation(fig, update_plot, number_of_frames, repeat = False )
plt.show()
EDIT 2:
I found a code over internet which does exactly what I would like
# Fermi-Dirac Distribution
def fermi(E: float, E_f: float, T: float) -> float:
return 1/(np.exp((E - E_f)/(k_b * T)) + 1)
# Create figure and add axes
fig = plt.figure(figsize=(6, 4))
ax = fig.add_subplot(111)
# Get colors from coolwarm colormap
colors = plt.get_cmap('coolwarm', 10)
# Temperature values
T = np.array([100*i for i in range(1,11)])
# Create variable reference to plot
f_d, = ax.plot([], [], linewidth=2.5)
# Add text annotation and create variable reference
temp = ax.text(1, 1, '', ha='right', va='top', fontsize=24)
# Set axes labels
ax.set_xlabel('Energy (eV)')
ax.set_ylabel('Fraction')
# Animation function
def animate(i):
x = np.linspace(0, 1, 100)
y = fermi(x, 0.5, T[i])
f_d.set_data(x, y)
f_d.set_color(colors(i))
temp.set_text(str(int(T[i])) + ' K')
temp.set_color(colors(i))
# Create animation
ani = animation.FuncAnimation(fig, animate, frames=range(len(T)), interval=500, repeat=False)
# Ensure the entire plot is visible
fig.tight_layout()
# show animation
plt.show()
What I want to draw is a curve at random because the actual state of the function is unknown. The basic structure looks like this, so please modify it based on this.
import numpy as np
import scipy.stats as st
# %matplotlib notebook
import matplotlib.pyplot as plt
import matplotlib.animation as animation
# from IPython.display import HTML
# from matplotlib.animation import PillowWriter
X = np.linspace(-5,5, 100)
number_of_frames = np.size(X)
N_max = 100
N = np.arange(1,N_max+1)
h = 1/np.sqrt(N)
def update_plot(n):
#plt.cla()
lines.set_data(X[:n], h[:n])
lines2.set_data(X[:n], h[:n]*-1)
ax.set_title("n = {}".format(n))
return lines, lines2
fig = plt.figure()
ax = plt.axes(xlim=(-5, 5), ylim=(-1, 1))
lines, = ax.plot([], [], 'y-', lw=2, label="densité")
lines2, = ax.plot([], [], 'b--', lw=3, label="densité2")
ani = animation.FuncAnimation(fig, update_plot, frames=number_of_frames, repeat=False )
plt.show()
# ani.save('lines_ani2.gif', writer='pillow')
# plt.close()
# HTML(ani.to_html5_video())
I have a dataframe that I want to animate (line chart) using matplotlib. My x and y values:
here x = df.index and y = df['Likes']
x y
0 200000
1 50000
2 1000000
.so on.. ....
Code I tried:
from matplotlib import pyplot as plt
from matplotlib import animation
import pandas as pd
df = pd.read_csv("C:\\Users\\usr\\Documents\\Sublime\\return_to_windows\\Files\\cod2019.txt", sep='\t')
fig = plt.figure()
ax = plt.axes(xlim=(0, 18), ylim=(6514, 209124))
line, = ax.plot([], [], lw=2)
def init():
line.set_data([], [])
return line,
def animate(i):
line.set_data(df.index[i], df['Likes'][i])
return line,
anim = animation.FuncAnimation(fig, animate, frames=len(df['Likes']), init_func=init, interval=300, blit=True)
plt.show()
I have tried this, but it is showing blank output with no error message. I am using python 3.83, windows machine. Can I do this using numpy? Almost all of the examples used numpy data in FuncAnimation.
I have solved it myself, I have used code of "vkakerbeck" from github as a guide to add more data points:
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
df = pd.read_csv("C:\\Users\\usr\\Documents\\Sublime\\return_to_windows\\Files\\cod2019.txt", sep='\t')
dg = df['Likes']
x_data = []
y_data = []
fig, ax = plt.subplots()
ax.set_xlim(0, len(dg))
ax.set_ylim(0, dg.max() * 1.04) # multiplied with 1.04 to add some gap in y-axis
line, = ax.plot(0, 0)
This part is for formatting
ax.set_xlabel('Part No')
ax.set_ylabel('Number of Likes')
ax.set_title('Likes in Call of Duty 2019')
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
fig = plt.gcf()
fig.set_size_inches(12.8, 7.2) # 720p output
I have used this from that guide to add more data points to make the animation less jumpy:
x = np.array(dg.index)
y = np.array(dg)
def augment(xold, yold, numsteps):
xnew = []
ynew = []
for i in range(len(xold) - 1):
difX = xold[i + 1] - xold[i]
stepsX = difX / numsteps
difY = yold[i + 1] - yold[i]
stepsY = difY / numsteps
for s in range(numsteps):
xnew = np.append(xnew, xold[i] + s * stepsX)
ynew = np.append(ynew, yold[i] + s * stepsY)
return xnew, ynew
XN, YN = augment(x, y, 3)
augmented = pd.DataFrame(YN, XN)
ylikes = augmented[0].reset_index() # Index reset to avoid key error
Main Function:
def animation_frame(i):
x_data.append(augmented.index[i])
y_data.append(ylikes[0][i])
line.set_xdata(x_data)
line.set_ydata(y_data)
return line,
plt.cla()
plt.tight_layout()
anima = animation.FuncAnimation(fig, func=animation_frame, frames=len(augmented), interval=80)
plt.show()
Export as mp4
Writer = animation.writers['ffmpeg']
writer = Writer(fps=15, bitrate=1000)
anima.save('lines3.mp4', writer=writer)
I have an animation where the range of the data varies a lot. I would like to have a colorbar which tracks the max and the min of the data (i.e. I would like it not to be fixed). The question is how to do this.
Ideally I would like the colorbar to be on its own axis.
I have tried the following four things
1. Naive approach
The problem: A new colorbar is plottet for each frame
#!/usr/bin/env python
"""
An animated image
"""
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig = plt.figure()
ax = fig.add_subplot(111)
def f(x, y):
return np.exp(x) + np.sin(y)
x = np.linspace(0, 1, 120)
y = np.linspace(0, 2 * np.pi, 100).reshape(-1, 1)
frames = []
for i in range(10):
x += 1
curVals = f(x, y)
vmax = np.max(curVals)
vmin = np.min(curVals)
levels = np.linspace(vmin, vmax, 200, endpoint = True)
frame = ax.contourf(curVals, vmax=vmax, vmin=vmin, levels=levels)
cbar = fig.colorbar(frame)
frames.append(frame.collections)
ani = animation.ArtistAnimation(fig, frames, blit=False)
plt.show()
2. Adding to the images
Changing the for loop above to
initFrame = ax.contourf(f(x,y))
cbar = fig.colorbar(initFrame)
for i in range(10):
x += 1
curVals = f(x, y)
vmax = np.max(curVals)
vmin = np.min(curVals)
levels = np.linspace(vmin, vmax, 200, endpoint = True)
frame = ax.contourf(curVals, vmax=vmax, vmin=vmin, levels=levels)
cbar.set_clim(vmin = vmin, vmax = vmax)
cbar.draw_all()
frames.append(frame.collections + [cbar])
The problem: This raises
AttributeError: 'Colorbar' object has no attribute 'set_visible'
3. Plotting on its own axis
The problem: The colorbar is not updated.
#!/usr/bin/env python
"""
An animated image
"""
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig = plt.figure()
ax1 = fig.add_subplot(121)
ax2 = fig.add_subplot(122)
def f(x, y):
return np.exp(x) + np.sin(y)
x = np.linspace(0, 1, 120)
y = np.linspace(0, 2 * np.pi, 100).reshape(-1, 1)
frames = []
for i in range(10):
x += 1
curVals = f(x, y)
vmax = np.max(curVals)
vmin = np.min(curVals)
levels = np.linspace(vmin, vmax, 200, endpoint = True)
frame = ax1.contourf(curVals, vmax=vmax, vmin=vmin, levels=levels)
cbar = fig.colorbar(frame, cax=ax2) # Colorbar does not update
frames.append(frame.collections)
ani = animation.ArtistAnimation(fig, frames, blit=False)
plt.show()
A combination of 2. and 4.
The problem: The colorbar is constant.
A similar question is posted here, but it looks like the OP is satisfied with a fixed colorbar.
While I'm not sure how to do this specifically using an ArtistAnimation, using a FuncAnimation is fairly straightforward. If I make the following modifications to your "naive" version 1 it works.
Modified Version 1
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from mpl_toolkits.axes_grid1 import make_axes_locatable
fig = plt.figure()
ax = fig.add_subplot(111)
# I like to position my colorbars this way, but you don't have to
div = make_axes_locatable(ax)
cax = div.append_axes('right', '5%', '5%')
def f(x, y):
return np.exp(x) + np.sin(y)
x = np.linspace(0, 1, 120)
y = np.linspace(0, 2 * np.pi, 100).reshape(-1, 1)
frames = []
for i in range(10):
x += 1
curVals = f(x, y)
frames.append(curVals)
cv0 = frames[0]
cf = ax.contourf(cv0, 200)
cb = fig.colorbar(cf, cax=cax)
tx = ax.set_title('Frame 0')
def animate(i):
arr = frames[i]
vmax = np.max(arr)
vmin = np.min(arr)
levels = np.linspace(vmin, vmax, 200, endpoint = True)
cf = ax.contourf(arr, vmax=vmax, vmin=vmin, levels=levels)
cax.cla()
fig.colorbar(cf, cax=cax)
tx.set_text('Frame {0}'.format(i))
ani = animation.FuncAnimation(fig, animate, frames=10)
plt.show()
The main difference is that I do the levels calculations and contouring in a function instead of creating a list of artists. The colorbar works because you can clear the axes from the previous frame and redo it every frame.
Doing this redo is necessary when using contour or contourf, because you can't just dynamically change the data. However, as you have plotted so many contour levels and the result looks smooth, I think you may be better off using imshow instead - it means you can actually just use the same artist and change the data, and the colorbar updates itself automatically. It's also much faster!
Better Version
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from mpl_toolkits.axes_grid1 import make_axes_locatable
fig = plt.figure()
ax = fig.add_subplot(111)
# I like to position my colorbars this way, but you don't have to
div = make_axes_locatable(ax)
cax = div.append_axes('right', '5%', '5%')
def f(x, y):
return np.exp(x) + np.sin(y)
x = np.linspace(0, 1, 120)
y = np.linspace(0, 2 * np.pi, 100).reshape(-1, 1)
# This is now a list of arrays rather than a list of artists
frames = []
for i in range(10):
x += 1
curVals = f(x, y)
frames.append(curVals)
cv0 = frames[0]
im = ax.imshow(cv0, origin='lower') # Here make an AxesImage rather than contour
cb = fig.colorbar(im, cax=cax)
tx = ax.set_title('Frame 0')
def animate(i):
arr = frames[i]
vmax = np.max(arr)
vmin = np.min(arr)
im.set_data(arr)
im.set_clim(vmin, vmax)
tx.set_text('Frame {0}'.format(i))
# In this version you don't have to do anything to the colorbar,
# it updates itself when the mappable it watches (im) changes
ani = animation.FuncAnimation(fig, animate, frames=10)
plt.show()