I’m aiming to animate a scatter plot using the df below. I’m trying to pass the plot and groups function to the animate function. I’m trying to return the values from each function are pass them to subsequent functions but I’m getting aNameError as these values aren't being registered.
The script works if I remove the plot and groups functions and pass objects to animate from the global workspace but then I have to write these out all the time. Rather than house in separate functions.
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import matplotlib as mpl
from matplotlib import animation
df1 = pd.DataFrame({
'Time' : [1,1,1,2,2,2,3,3,3],
'GroupA_X' : [3, 4, 5, 2, 5, 6, 1, 6, 7],
'GroupA_Y' : [2, 4, 5, 2, 5, 5, 2, 6, 5],
'GroupB_X' : [2, 5, 3, 2, 4, 2, 2, 3, 1],
'GroupB_Y' : [2, 4, 3, 3, 3, 4, 4, 2, 5],
})
def plot():
fig, ax = plt.subplots()
ax.grid(False)
xy = 0,0
Oval = mpl.patches.Ellipse(xy, 160, 130, lw = 2, edgecolor = 'black', color = 'blue', alpha = 0.2)
ax.add_patch(Oval)
return fig, ax
def groups():
plot()
Group_A = df1[['Time','GroupA_X','GroupA_Y']]
Group_B = df1[['Time','GroupB_X','GroupB_Y']]
GA_X = np.array(Group_A.groupby(['Time'])['GroupA_X'].apply(list))
GA_Y = np.array(Group_A.groupby(['Time'])['GroupA_Y'].apply(list))
GB_X = np.array(Group_B.groupby(['Time'])['GroupB_X'].apply(list))
GB_Y = np.array(Group_B.groupby(['Time'])['GroupB_Y'].apply(list))
GA = ax.scatter(GA_X[0], GA_Y[0], c = ['blue'], marker = 'o', s = 10, edgecolor = 'black')
GB = ax.scatter(GB_X[0], GB_Y[0], c = ['brown'], marker = 'o', s = 10, edgecolor = 'black')
return GA, GB
def animate(i) :
plot()
groups()
GA.set_offsets(np.c_[GA_X[0+i], GA_Y[0+i]])
GB.set_offsets(np.c_[GB_X[0+i], GB_Y[0+i]])
plot()
groups()
ani = animation.FuncAnimation(fig, animate, np.arange(0,3), interval = 1000, blit = False)
Error Output:
GA = ax.scatter(GA_X[0], GA_Y[0], c = ['blue'], marker = 'o', s = 10, edgecolor = 'black')
NameError: name 'ax' is not defined
animation draws/repeats on the global figure, so you need to create subplots in global scope. If you define subplots inside plot function, every call of plot will create a new subplots
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import matplotlib as mpl
from matplotlib import animation
df1 = pd.DataFrame({
'Time' : [1,1,1,2,2,2,3,3,3],
'GroupA_X' : [3, 4, 5, 2, 5, 16, 21, 36, 47],
'GroupA_Y' : [2, 4, 5, 2, 5, 15, 22, 36, 45],
'GroupB_X' : [2, 5, 3, 2, 4, 12, 22, 33, 41],
'GroupB_Y' : [2, 4, 3, 3, 3, 14, 24, 32, 45],
})
fig, ax = plt.subplots()
def plot():
# fig, ax = plt.subplots() #declared in global scope
ax.grid(False)
xy = 0,0
Oval = mpl.patches.Ellipse(xy, 160, 130, lw = 2, edgecolor = 'black', color = 'blue', alpha = 0.2)
ax.add_patch(Oval)
# return fig, ax #no need return since `fig, ax` are in global scope
def groups():
# plot() #no need since this function use nothing from `plot`
Group_A = df1[['Time','GroupA_X','GroupA_Y']]
Group_B = df1[['Time','GroupB_X','GroupB_Y']]
GA_X = np.array(Group_A.groupby(['Time'])['GroupA_X'].apply(list))
GA_Y = np.array(Group_A.groupby(['Time'])['GroupA_Y'].apply(list))
GB_X = np.array(Group_B.groupby(['Time'])['GroupB_X'].apply(list))
GB_Y = np.array(Group_B.groupby(['Time'])['GroupB_Y'].apply(list))
GA = ax.scatter(GA_X[0], GA_Y[0], c = ['blue'], marker = 'o', s = 10, edgecolor = 'black')
GB = ax.scatter(GB_X[0], GB_Y[0], c = ['brown'], marker = 'o', s = 10, edgecolor = 'black')
return GA, GB, GA_X, GA_Y, GB_X, GB_Y
def animate(i) :
# plot()
GA, GB, GA_X, GA_Y, GB_X, GB_Y = groups()
GA.set_offsets(np.c_[GA_X[0+i], GA_Y[0+i]])
GB.set_offsets(np.c_[GB_X[0+i], GB_Y[0+i]])
plot()
# groups()
ani = animation.FuncAnimation(fig, animate, np.arange(0,3), interval = 1000, blit = False)
Note: I changed values in df1 to make the values changing between each animation clearer. Codes fixing above works. I tested it. However, I don't know whether it is efficient. I basically just fix your codes to make it run.
You said animation runs fine if you declare everything in global scope. Therefore, I assume your system already had ffmpeg installed and you codes is able to call/find ffmpeg.exe to display the repreating/looping animation
Have you tried modifying the function to pass the returned ax to a variable inside groups function as below:
def groups():
_,ax = plot()
As the error states the name ax is not defined in groups function. It has to be defined by capturing the value returned from plot()
Related
Using matplotlib I create a scatter plot animation that shows a new point after each second and shows all old points partly transparent. Each point is defined by x and y, but also by a category s. I want the color of the points to be tied to its category. Ideally that means that the array s contains values 1, 2 and 3, and the colors belonging to those values are defined seperately. However, I can not get this to work.
What I do get to work is to specify the edgecolors of each point individually in s, the code for this is shown below.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as plti
import matplotlib.animation
s = [[1,0,0],[0,1,0],[0,0,1]];
x = [525,480,260];
y = [215,180,180];
img = plti.imread('myimage.png')
fig, ax = plt.subplots()
plt.imshow(img)
plt.axis('off')
x_vals = []
y_vals = []
intensity = []
iterations = len(x)
colors = []
t_vals = np.linspace(0,iterations-1,iterations,dtype=int)
scatter = ax.scatter(x_vals, y_vals, s=100, c=colors, vmin=0, vmax=1)
def init():
pass
def update(t):
global x, y, x_vals, y_vals, intensity
x_vals.extend([x[t]])
y_vals.extend([y[t]])
scatter.set_offsets(np.c_[x_vals,y_vals])
intensity = np.concatenate((np.array(intensity), np.ones(1)))
if len(intensity) > 1:
intensity[-2] = 0.5
scatter.set_array(intensity)
colors.extend([s[t]])
scatter.set_color(colors)
return ani
ani = matplotlib.animation.FuncAnimation(fig, update, frames=t_vals, interval=1000, repeat=False, init_func=init)
plt.show()
Simply changing c=colors to facecolor=colors does not work. Also I have tried to use colormaps but I cannot get it to work using that either.
The resulting animation from the code above looks as below.
However, the animation should look like this..
So my question is; does someone know how to tie the facecolor of each point to the category that that point belongs to?
The normal way to plot plots with points in different colors in matplotlib is to pass a list of colors as a parameter.
E.g.:
import matplotlib.pyplot
matplotlib.pyplot.scatter([1,2,3],[4,5,6],color=['red','green','blue'])
But if for some reason you wanted to do it with just one call, you can make a big list of colors, with a list comprehension and a bit of flooring division:
import matplotlib
import numpy as np
X = [1,2,3,4]
Ys = np.array([[4,8,12,16],
[1,4,9,16],
[17, 10, 13, 18],
[9, 10, 18, 11],
[4, 15, 17, 6],
[7, 10, 8, 7],
[9, 0, 10, 11],
[14, 1, 15, 5],
[8, 15, 9, 14],
[20, 7, 1, 5]])
nCols = len(X)
nRows = Ys.shape[0]
colors = matplotlib.cm.rainbow(np.linspace(0, 1, len(Ys)))
cs = [colors[i//len(X)] for i in range(len(Ys)*len(X))] #could be done with numpy's repmat
Xs=X*nRows #use list multiplication for repetition
matplotlib.pyplot.scatter(Xs,Ys.flatten(),color=cs)
The problem occurred because the line scatter.set_array(intensity) was called before scatter.set_color(colors). So instead of defining the intensity by a seperate variable, it is instead integrated into the colors directly. The following code produces the desired result.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as plti
import matplotlib.animation
s = [1,2,3];
x = [525,480,260];
y = [215,180,180];
img = plti.imread('myimage.png')
fig, ax = plt.subplots()
plt.imshow(img)
plt.axis('off')
x_vals = []
y_vals = []
iterations = len(x)
colors = []
t_vals = np.linspace(0,iterations-1,iterations,dtype=int)
scatter = ax.scatter(x_vals, y_vals, s=100, color=colors, vmin=0, vmax=1)
def init():
pass
def update(t):
global x, y, x_vals, y_vals
x_vals.extend([x[t]])
y_vals.extend([y[t]])
scatter.set_offsets(np.c_[x_vals,y_vals])
if t > 0:
if s[t-1] == 1:
colors[t-1] = [1,0,0,0.5];
elif s[t-1] == 2:
colors[t-1] = [0,1,0,0.5];
else:
colors[t-1] = [0,0,1,0.5];
if s[t] == 1:
colors.extend([[1,0,0,1]])
elif s[t] == 2:
colors.extend([[0,1,0,1]])
else:
colors.extend([[0,0,1,1]])
scatter.set_color(colors);
return ani
ani = matplotlib.animation.FuncAnimation(fig, update, frames=t_vals, init_func=init, interval=1000, repeat=False)
plt.show()
The goal is to create a 3D surface plot featuring uncertainty bars or some other clear visualisation of uncertainties, like this:
Currently, I have the following plot:
This generated using the following code:
from io import StringIO
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import pandas as pd
import seaborn as sns
data_string = StringIO(
"""
V1, V2, V3, V4, V5
110,111,109,107,108
101,101,102,102,102
102,102,103,103,103
103,103,104,104,104
100,101,100,100,100
"""
)
uncertainties_string = StringIO(
"""
V1U,V2U,V3U,V4U,V5U
5, 5, 5, 5, 7
5, 5, 3, 5, 5
6, 5, 5, 5, 5
5, 6, 5, 2, 5
5, 5, 5, 5, 5
"""
)
data = pd.read_csv(data_string)
uncertainties = pd.read_csv(uncertainties_string)
df = data.unstack().reset_index()
df.columns = ["X", "Y", "Z"]
df['X'] = pd.Categorical(df['X'])
df['X'] = df['X'].cat.codes
fig = plt.figure()
ax = fig.gca(projection='3d')
surf = ax.plot_trisurf(df['Y'], df['X'], df['Z'], cmap=plt.cm.jet, linewidth=0.01)
fig.colorbar(surf)
plt.show()
I am not sure if there is built-in functionality for this, but I had fun playing around:
I make two error bars per data point (one for the part above and one for the part below), and plot them with different zorder:
# get errors into the same format as data
df_unc = uncertainties.unstack().reset_index()
df_unc.columns = ["X", "Y", "Z"]
df_unc['X'] = pd.Categorical(df_unc['X'])
df_unc['X'] = df_unc['X'].cat.codes
# compute lower and upper values for errorbars
df['z_low'] = df['Z'] - df_unc['Z']
df['z_high'] = df['Z'] + df_unc['Z']
# plot surface with middle value for zorder
surf = ax.plot_trisurf(df['Y'], df['X'], df['Z'], cmap=plt.cm.jet, linewidth=0.01, zorder=2)
# plot lines with lower and higher zorder, respectively
for ix, row in df.iterrows():
ax.plot((row['Y'], row['Y']), (row['X'], row['X']), (row['z_low'], row['Z']), c='k', zorder=1)
ax.plot((row['Y'], row['Y']), (row['X'], row['X']), (row['Z'], row['z_high']), c='k', zorder=3)
I have the following code right now, to show growth of a curve:
import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d.axes3d as p3
import matplotlib.animation as animation
def move_curve(i, line, x, y, z):
# Add points rather than changing start and end points.
line.set_data(x[:i+1], y[:i+1])
line.set_3d_properties(z[:i+1])
fig = plt.figure()
ax = fig.gca(projection='3d')
x = [1, 3, 8, 11, 17]
y = [7, 2, -5, 3, 5]
z = [5, 7, 9, 13, 18]
i = 0
line = ax.plot([x[i], x[i+1]], [y[i],y[i+1]], [z[i],z[i+1]])[0]
ax.set_xlim3d([1, 17])
ax.set_ylim3d([-5, 7])
ax.set_zlim3d([5, 18])
line_ani = animation.FuncAnimation(fig, move_curve, 5, fargs=(line, x, y, z))
plt.show()
I want to show the different lines in different colours. Also, I want to update the length of the axis as the curve grows.
How to do that? I am new to python so I might be missing something simple. Thanks for the help!
Here is how #MrT's answer would look like using FuncAnimation. The advantage is that you do not need to care about autoscaling; that is done automatically on the fly.
import matplotlib.pyplot as plt
import matplotlib.animation as anim
import mpl_toolkits.mplot3d.axes3d as p3
fig = plt.figure()
ax = fig.gca(projection='3d')
x = [1, 3, 8, 11, 17]
y = [7, 2, -5, 3, 5]
z = [5, 7, 9, 13, 18]
#colour map
colors = ["green", "blue", "red", "orange"]
def init():
ax.clear()
def update(i):
newsegm, = ax.plot([x[i], x[i + 1]], [y[i], y[i + 1]], [z[i], z[i + 1]], colors[i])
ani = anim.FuncAnimation(fig, update, init_func=init,
frames = range(len(x)-1), interval = 300, repeat=True)
plt.show()
You can use ArtistAnimation and attribute an individual colour to each line segment:
import matplotlib.pyplot as plt
import matplotlib.animation as anim
import mpl_toolkits.mplot3d.axes3d as p3
fig = plt.figure()
ax = fig.gca(projection='3d')
x = [1, 3, 8, 11, 17]
y = [7, 2, -5, 3, 5]
z = [5, 7, 9, 13, 18]
#colour map
cmap = ["green", "blue", "red", "orange"]
#set up list of images for animation with empty list
lines=[[]]
for i in range(len(x) - 1):
#create next segment with new color
newsegm, = ax.plot([x[i], x[i + 1]], [y[i], y[i + 1]], [z[i], z[i + 1]], cmap[i])
#append new segment to previous list
lines.append(lines[-1] + [newsegm])
#animate list of line segments
ani = anim.ArtistAnimation(fig, lines, interval = 300)
plt.show()
Output:
I would like to know how to update a graph and or plot in matplotlib every few seconds. Code:
import matplotlib.pyplot as plt
import numpy as np
axes = plt.gca()
axes.set_xlim([0,5])
axes.set_ylim([0,100])
X = [0, 1, 2, 3, 4, 5]
Y = [15, 30, 45, 60, 75, 90]
plt.plot(X, Y)
plt.xlabel('Time spent studying (hours)')
plt.ylabel('Score (percentage)')
plt.show()
What you have written is correct , but in order to make your code dynamic , you can put the code in a function and pass the X and Y coordinates to the function . One example as shown below
def GrapgPlot(X, Y):
"Your code"
GrapgPlot([0, 1, 2, 3, 4, 5],[90, 30, 45, 60, 75, 90])
In the plot if you are certain that X axis will not change than you can fix X axis in the code and take only Y axis values as a list from the user as an input and pass it in the function as an argument.
else the best way if you do want user interaction . Update the X and Y axis list with a loop and pass X and Y values in the function as an argument
Used time.sleep(1) for being able to see the changes and reversed Y for new data to be updated. Hopefully this is what you want:
%matplotlib notebook
import time
import matplotlib.pyplot as plt
X = [0, 1, 2, 3, 4, 5]
Y = [15, 30, 45, 60, 75, 90]
fig, ax = plt.subplots()
ax.set_xlim([0,5])
ax.set_ylim([0,100])
ax.set_xlabel('Time spent studying (hours)')
ax.set_ylabel('Score (percentage)')
l, = ax.plot(X, Y)
for ydata in [Y, Y[::-1]]*2:
l.set_ydata(ydata)
fig.canvas.draw()
time.sleep(0.5)
I am trying to draw two data lines with error bars, each having the same color as the data line. However, I get another thin line with a color I have not specified in each data line when I add an error bar.
Also, I would like to make the caps of the error bars thicker but the option capthick is not valid here.
Could anybody please help me fix these issues?
This is my code.
import matplotlib.pyplot as plt
from pylab import *
import numpy as np
xaxis = [1, 2, 3]
mean1 = [1,2,3.6]
se1 = [0.2, 0.5, 0.9]
mean2 = [10, 29, 14]
se2 = [3, 4, 2]
fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_xlabel('X', fontsize = 16)
ax.set_ylabel('Y', fontsize = 16)
ax.axis([0, 5, 0, 35])
ax.plot(xaxis, mean1, 'r--', linewidth = 4)
ax.errorbar(xaxis, mean1, yerr = se1, ecolor = 'r', elinewidth = 2, capsize = 5)
ax.plot(xaxis, mean2, 'b--', linewidth = 4)
ax.errorbar(xaxis, mean2, yerr = se2, ecolor = 'b', elinewidth = 2, capsize = 5)
plt.show()
The extra thin line is coming from the errorbar() call.
errorbar will draw a line too, what you're doing is changing the colour of the error bars, but not the actual lines (hence it using the standard matplotlib first two colours, blue and green.
it's all in the documentaion, here.
To achieve what you want, you only need to use the errorbar() function;
This does what you want i think, maybe jsut tweak the numbers a bit.
import matplotlib.pyplot as plt
from pylab import *
import numpy as np
xaxis = [1, 2, 3]
mean1 = [1,2,3.6]
se1 = [0.2, 0.5, 0.9]
mean2 = [10, 29, 14]
se2 = [3, 4, 2]
fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_xlabel('X', fontsize = 16)
ax.set_ylabel('Y', fontsize = 16)
ax.axis([0, 5, 0, 35])
linestyle = {"linestyle":"--", "linewidth":4, "markeredgewidth":5, "elinewidth":5, "capsize":10}
ax.errorbar(xaxis, mean1, yerr = se1, color="r", **linestyle)
ax.errorbar(xaxis, mean2, yerr = se2, color="b", **linestyle)
plt.show()
I put the common line style arguments into a dict which gets unpacked.