I would like to sequentially plot a series of x,y coordinates while marking specified coordinates distinctly. It seems that 'markevery' allows users to do this in matplotlib plots, however, when I provide this property in my animation, I receive the error 'ValueError: markevery is iterable but not a valid form of numpy fancy indexing'. Any thoughts?
My actual 'mark_on' array will be much longer, so I think that using a linecollection isn't reasonable here.
frames = 100
def update_pos(num,data,line):
line.set_data(data[...,:num])
return line,
def traj_ani(data):
fig_traj = plt.figure()
l,= plt.plot([],[],'b', markevery = mark_on, marker = '*')
plt.xlim(-90,90)
plt.ylim(-90,90)
pos_ani = animation.FuncAnimation(fig_traj, update_pos, frames = np.shape(data)[1], fargs = (data,l),
interval = 20, blit = True)
pos_ani.save('AgentTrajectory.mp4')
data = pd.read_csv('xy_pos.csv', header = None, skiprows = [0])
data = np.asarray(data)
mark_on = [20, 50, 100, 300, 600]
traj_ani(data)
Thanks!
Here is a complete, mini example of an animation that works:
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import csv
import pandas as pd
import numpy as np
Writer = animation.writers['ffmpeg']
writer = Writer(fps=2000, metadata=dict(artist='Me'), bitrate=1800)
def update_pos(num,data,line):
line.set_data(data[...,:num])
return line,
def traj_ani(data):
fig_traj = plt.figure()
l,= plt.plot([],[],'b')
plt.xlim(0,1)
plt.ylim(0,1)
pos_ani = animation.FuncAnimation(fig_traj, update_pos, frames = 25, fargs = (data,l),
interval = 200, blit = True)
pos_ani.save('AgentTrajectory.mp4')
data = np.random.rand(2,25)
traj_ani(data)
In my full code, I would like to specify certain frames whose x-y coordinate should be marked with either a special character or by a different color.
It seems problematic to set a list of indizes to markevery, which contains indices not present in the ploted array. E.g. if the plotted array has 3 elements but the list set for markevery contains an index 5, a ValueError occurs.
The solution would need to be to set the markevery list in every iteration and make sure it only contains valid indizes.
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
mark_on = np.array([2,5,6,13,17,24])
def update_pos(num,data,line):
line.set_data(data[...,:num])
mark = mark_on[mark_on < num]
line.set_markevery(list(mark))
return line,
def traj_ani(data):
fig_traj = plt.figure()
l,= plt.plot([],[],'b', markevery = [], marker = '*', mfc="red", mec="red", ms=15)
plt.xlim(0,1)
plt.ylim(0,1)
pos_ani = animation.FuncAnimation(fig_traj, update_pos, frames = 25, fargs = (data,l),
interval = 200, blit = True)
plt.show()
data = np.random.rand(2,25)
traj_ani(data)
Related
My current goal is to animate two sets of data simultaneous on a single plot with 2 subplots. I previously asked a question here about clearing the axes data with animated seaborn graphs, but rather than clearing the data on the axes, I need to append/update them (i.e.: something like the animated plots here). To make matters more complicated, one of my sets of data are stored in numpy arrays whereas the other set is stored in a pandas dataframe.
I tried using some test data before using my actual data, but that doesn't work, especially when I try to animate the data stored in the dataframe:
import math, os
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from matplotlib import animation
def update(i,pass_data,fig,axes,sort_yrs,ani,x_val,y_val):
fil_pass_data = pass_data[pass_data['year'] == sort_yrs[i]]
fig.suptitle(r"Year = {}".format(sorted_yrs[i]),fontsize = 35,y = 1.000)
axes[1] = sns.lineplot(x = 'year',y = 'passengers',data = fil_pass_data,hue = 'month',
hue_order = sort_yrs,palette = 'Set1',legend = False)
ani1.set_data(x_val[:i],y_val[:i])
#Read/Generate Data
passenger_df = sns.load_dataset('flights')
yrs_of_int = ['Jan','Mar','Jun','Jul','Dec']
fil_pass_df = passenger_df[passenger_df['month'].isin(yrs_of_int)]
sorted_yrs = sorted(fil_pass_df['year'].unique())
x = np.linspace(0,2*np.pi,fil_pass_df['year'].unique().shape[0])
y = np.sin(x)
#Initialize figure and plot initial points
fig,axes = plt.subplots(ncols = 2,figsize = (15,7))
axes[0].plot(x[0],y[0],color = 'tab:red',label = 'Initial Point',marker = 'o',markersize = 5)
sns.lineplot(x = 'year',y = 'passengers',
data = fil_pass_df[fil_pass_df['year'] == sorted_yrs[0]],
hue = 'month',hue_order = yrs_of_int,palette = 'Set1')
ani1, = axes[0].plot(x[0],y[0],color = 'tab:blue',label = 'Dynamic Response')
#Formatting
axes[0].legend(loc='lower right')
axes[0].set_ylim(-1.1*np.pi,1.1*np.pi)
axes[0].set_xlim(-0.1,1.1*2*np.pi)
axes[1].set_xlim(fil_pass_df['year'].min()-1,fil_pass_df['year'].min()+1)
axes[1].set_ylim(100,700)
axes[0].set_title('Test Plot',fontsize = 15,pad = 5)
axes[1].set_title('Passengers by Month',fontsize = 15,pad = 5)
axes[0].set_ylabel(r"$\sin(x)$",fontsize = 20,labelpad = 10)
axes[0].set_xlabel(r"$x$",fontsize = 20,labelpad = 10)
axes[1].set_ylabel("Passenger Count",fontsize = 20,labelpad = 10)
axes[1].set_xlabel("Year",fontsize = 20,labelpad = 10)
#Create animation and save it
animation = animation.FuncAnimation(fig, update, fargs =
(fil_pass_df,fig,axes,sorted_yrs,ani1,x,y),
frames=range(0,len(sorted_yrs)),interval=0.5,
blit=False,repeat=True)
animation.save('test.mp4',
fps = 1, extra_args=['-vcodec', 'libx264'],dpi = 200)
The test mp4 file that is generated from here animates the left most plot (numpy array data), but fails to plot the right most plot (seaborn data). I have four theories as to why it doesn't work:
I'm not supposed to initialize the right most figure parameters before the update call. Instead, the figure parameters need to be set in update.
The fact that I'm specifying hue in the update function is somehow messing up with matplotlib's animation.
The fact that my data is either in numpy arrays or a pandas dataframe.
I'm overthinking this and forgot to one command in my code that makes this work.
I tried moving the right figure formatting into update but that didn't seem to work, so it might be bullet points #2-4.
Does anyone know why this is happening/how to solve it? For subplot animations, is there a rule as to whether everything should be stored in the same data type?
I'm trying to plot data that is generated in runtime. In order to do so I'm using matplotlib.animation.FuncAnimation.
While the data is displayed correctly, the axis values are not updating accordingly to the values that are being displayed:
The x axis displays values from 0 to 10 eventhough I update them in every iteration in the update_line function (see code below).
DataSource contains the data vector and appends values at runtime, and also returns the indexes of the values being returned:
import numpy as np
class DataSource:
data = []
display = 10
# Append one random number and return last 10 values
def getData(self):
self.data.append(np.random.rand(1)[0])
if(len(self.data) <= self.display):
return self.data
else:
return self.data[-self.display:]
# Return the index of the last 10 values
def getIndexVector(self):
if(len(self.data) <= self.display):
return list(range(len(self.data)))
else:
return list(range(len(self.data)))[-self.display:]
I've obtained the plot_animation function from the matplotlib docs.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from datasource import DataSource
def update_line(num, source, line):
data = source.getData()
indexs = source.getIndexVector()
if indexs[0] != 0:
plt.xlim(indexs[0], indexs[-1])
dim=np.arange(indexs[0],indexs[-1],1)
plt.xticks(dim)
line.set_data(indexs,data)
return line,
def plot_animation():
fig1 = plt.figure()
source = DataSource()
l, = plt.plot([], [], 'r-')
plt.xlim(0, 10)
plt.ylim(0, 1)
plt.xlabel('x')
plt.title('test')
line_ani = animation.FuncAnimation(fig1, update_line, fargs=(source, l),
interval=150, blit=True)
# To save the animation, use the command: line_ani.save('lines.mp4')
plt.show()
if __name__ == "__main__":
plot_animation()
How can I update the x axis values in every iteration of the animation?
(I appreciate suggestions to improve the code if you see any mistakes, eventhough they might not be related to the question).
Here is a simple case of how you can achieve this.
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
%matplotlib notebook
#data generator
data = np.random.random((100,))
#setup figure
fig = plt.figure(figsize=(5,4))
ax = fig.add_subplot(1,1,1)
#rolling window size
repeat_length = 25
ax.set_xlim([0,repeat_length])
ax.set_ylim([-2,2])
#set figure to be modified
im, = ax.plot([], [])
def func(n):
im.set_xdata(np.arange(n))
im.set_ydata(data[0:n])
if n>repeat_length:
lim = ax.set_xlim(n-repeat_length, n)
else:
lim = ax.set_xlim(0,repeat_length)
return im
ani = animation.FuncAnimation(fig, func, frames=data.shape[0], interval=30, blit=False)
plt.show()
#ani.save('animation.gif',writer='pillow', fps=30)
Solution
My problem was in the following line:
line_ani = animation.FuncAnimation(fig1, update_line, fargs=(source, l),
interval=150, blit=True)
What I had to do is change the blit parameter to False and the x axis started to move as desired.
I am attempting to produce an animated histogram that uses rows of data from a data frame I created. The code I am using to produce the histogram is below. The code works with data = np.random.randn(1000) but does not animate the histogram when I replace it with data = df['GDP'] instead it outputs a non-animated histogram. I am trying to fit a column from a data frame into this code:
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import matplotlib.path as path
import matplotlib.animation as animation
fig, ax = plt.subplots()
# histogram our data with numpy
data = np.random.randn(1000)
n, bins = np.histogram(data, 100)
# get the corners of the rectangles for the histogram
left = np.array(bins[:-1])
right = np.array(bins[1:])
bottom = np.zeros(len(left))
top = bottom + n
nrects = len(left)
# here comes the tricky part -- we have to set up the vertex and path
# codes arrays using moveto, lineto and closepoly
# for each rect: 1 for the MOVETO, 3 for the LINETO, 1 for the
# CLOSEPOLY; the vert for the closepoly is ignored but we still need
# it to keep the codes aligned with the vertices
nverts = nrects*(1+3+1)
verts = np.zeros((nverts, 2))
codes = np.ones(nverts, int) * path.Path.LINETO
codes[0::5] = path.Path.MOVETO
codes[4::5] = path.Path.CLOSEPOLY
verts[0::5,0] = left
verts[0::5,1] = bottom
verts[1::5,0] = left
verts[1::5,1] = top
verts[2::5,0] = right
verts[2::5,1] = top
verts[3::5,0] = right
verts[3::5,1] = bottom
barpath = path.Path(verts, codes)
patch = patches.PathPatch(barpath, facecolor='green', edgecolor='yellow', alpha=0.5)
ax.add_patch(patch)
ax.set_xlim(left[0], right[-1])
ax.set_ylim(bottom.min(), top.max())
def animate(i):
# simulate new data coming in
data = np.random.randn(1000)
n, bins = np.histogram(data, 100)
top = bottom + n
verts[1::5,1] = top
verts[2::5,1] = top
ani = animation.FuncAnimation(fig, animate, 100, repeat=False)
from IPython.display import HTML
HTML(ani.to_jshtml())
To fit my own data I am replacing :
# histogram our data with numpy
data = np.random.randn(1000)
and:
# simulate new data coming in
data = np.random.randn(1000)
with a column in my data frame that has 247 rows:
data = df['GDP']
The output is a histogram with my own data however it is not animated as is with data = np.random.randn(1000)
I am using a github repository called ptitprince, which is derived from seaborn and matplotlib, to generate graphs.
For example, this is the code using the ptitprince repo:
# coding: utf8
import pandas as pd
import ptitprince as pt
import seaborn as sns
import os
import matplotlib.pyplot as plt
#sns.set(style="darkgrid")
#sns.set(style="whitegrid")
#sns.set_style("white")
sns.set(style="whitegrid",font_scale=2)
import matplotlib.collections as clt
df = pd.read_csv ("u118phag.csv", sep= ",")
df.head()
savefigs = True
figs_dir = 'figs'
if savefigs:
# Make the figures folder if it doesn't yet exist
if not os.path.isdir('figs'):
os.makedirs('figs')
#automation
f, ax = plt.subplots(figsize=(4, 5))
#f.subplots_adjust(hspace=0,wspace=0)
dx = "Treatment"; dy = "score"; ort = "v"; pal = "Set2"; sigma = .2
ax=pt.RainCloud(x = dx, y = dy, data = df, palette = pal, bw = sigma,
width_viol = .6, ax = ax, move=.2, offset=.1, orient = ort, pointplot = True)
f.show()
if savefigs:
f.savefig('figs/figure20.png', bbox_inches='tight', dpi=500)
which generates the following graph
The raw code not using ptitprince is as follows and produces the same graph as above:
# coding: utf8
import pandas as pd
import ptitprince as pt
import seaborn as sns
import os
import matplotlib.pyplot as plt
#sns.set(style="darkgrid")
#sns.set(style="whitegrid")
#sns.set_style("white")
sns.set(style="whitegrid",font_scale=2)
import matplotlib.collections as clt
df = pd.read_csv ("u118phag.csv", sep= ",")
df.head()
savefigs = True
figs_dir = 'figs'
if savefigs:
# Make the figures folder if it doesn't yet exist
if not os.path.isdir('figs'):
os.makedirs('figs')
f, ax = plt.subplots(figsize=(7, 5))
dy="Treatment"; dx="score"; ort="h"; pal = sns.color_palette(n_colors=1)
#adding color
pal = "Set2"
f, ax = plt.subplots(figsize=(7, 5))
ax=pt.half_violinplot( x = dx, y = dy, data = df, palette = pal, bw = .2, cut = 0.,
scale = "area", width = .6, inner = None, orient = ort)
ax=sns.stripplot( x = dx, y = dy, data = df, palette = pal, edgecolor = "white",
size = 3, jitter = 1, zorder = 0, orient = ort)
ax=sns.boxplot( x = dx, y = dy, data = df, color = "black", width = .15, zorder = 10,\
showcaps = True, boxprops = {'facecolor':'none', "zorder":10},\
showfliers=True, whiskerprops = {'linewidth':2, "zorder":10},\
saturation = 1, orient = ort)
if savefigs:
f.savefig('figs/figure21.png', bbox_inches='tight', dpi=500)
Now, what I'm trying to do is to figure out how to modify the graph so that I can (1) move the plots closer together, so there is not so much white space between them, and (2) shift the x-axis to the right, so that I can make the distribution (violin) plot wider without it getting cut in half by the y-axis.
I have tried to play around with subplots_adjust() as you can see in the first box of code, but I receive an error. I cannot figure out how to appropriately use this function, or even if that will actually bring the different graphs closer together.
I also know that I can increase the distribution size by increasing this value width = .6, but if I increase it too high, the distribution plot begins to being cut off by the y-axis. I can't figure out if I need to adjust the overall plot using the plt.subplots,or if I need to move each individual plot.
Any advice or recommendations on how to change the visuals of the graph? I've been staring at this for awhile, and I can't figure out how to make seaborn/matplotlib play nicely with ptitprince.
You may try to change the interval of X-axis being shown using ax.set_xbound (put a lower value than you currently have for the beginning).
I have made an animation from a set of images like this (10 snapshots):
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Circle
import time
infile = open ('out.txt')
frame_counter = 0
N_p = 100
N_step = 10
N_line = N_p*N_step
for s in xrange(N_step):
x, y = [], []
for i in xrange(N_p):
data = infile.readline()
raw = data.split()
x.append(float(raw[0]))
y.append(float(raw[1]))
xnp = np.array(x)
ynp = np.array(y)
fig = plt.figure(0)
ax = fig.add_subplot(111, aspect='equal')
for x, y in zip(xnp, ynp):
cir = Circle(xy = (x, y), radius = 1)
cir.set_facecolor('red')
ax.add_artist(cir)
cir.set_clip_box(ax.bbox)
ax.set_xlim(-10, 150)
ax.set_ylim(-10, 150)
fig.savefig("step.%04d.png" % frame_counter)
ax.remove()
frame_counter +=1
Now I want to add a legend to each image showing the time step.
For doing this I must set legends to each of these 10 images. The problem is that I have tested different things like ax.set_label , cir.set_label, ...
and I get errors like this:
UserWarning: No labelled objects found. Use label='...' kwarg on individual plots
According to this error I must add label to my individual plots, but since this is a plot of Artists, I don't know how I can do this.
If for whatever reason you need a legend, you can show your Circle as the handle and use some text as the label.
ax.legend(handles=[cir], labels=["{}".format(frame_counter)])
If you don't really need a legend, you can just use some text to place inside the axes.
ax.text(.8,.8, "{}".format(frame_counter), transform=ax.transAxes)