I want to have 10 moving points. I used the code below. I'm experimenting with matplotlib which I don't know very well.
from matplotlib import pyplot as plt
import numpy as np
from matplotlib import animation
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
# second option - move the point position at every frame
def update_point(n, x, y, z, point):
point.set_data(np.array([x[n], y[n]]))
point.set_3d_properties(z[n], 'z')
return point
def x(i):
return np.cos(t*i)
for i in range(10):
t=np.arange(0, 2*np.pi, 2*np.pi/100)
y=np.sin(t)
z=t/(2.*np.pi)
point, = ax.plot([x(i)[0]], [y[0]], [z[0]], 'o')
ani=animation.FuncAnimation(fig, update_point, 99, fargs=(x(i), y, z, point))
ax.legend()
ax.set_xlim([-1.5, 1.5])
ax.set_ylim([-1.5, 1.5])
ax.set_zlim([-1.5, 1.5])
plt.show()
I hoped that if I turn x to a function of i, then I will have 10 points in the for loop, but nothing happened. Only one point is moving. What am I doing wrong?
For a start, you place your animation object anim into the loop, so not only the point data but also the animation object is repeatedly overwritten. For ease of use, let's put the data points into numpy arrays, where rows represent the time and columns the different points you want to animate. Then, we calculate the x, y, and z arrays based on the t array (for aesthetics, a seamless loop along the columns with length 2*pi, with each column shifted so that the points are equally distributed) and simply update the x, y, and z data row-wise in each animation step. Closely related to your script, this would look like:
from matplotlib import pyplot as plt
import numpy as np
from matplotlib import animation
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
num_of_points = 7
num_of_frames = 50
t=np.linspace(0, 2*np.pi, num_of_frames, endpoint=False)[:, None] + np.linspace(0, 2*np.pi, num_of_points, endpoint=False)[None, :]
x=np.cos(t)
y=np.sin(t)
z=np.sin(t)*np.cos(t)
points, = ax.plot([], [], [], 'o')
def update_points(n):
points.set_data(np.array([x[n, :], y[n, :]]))
points.set_3d_properties(z[n, :], 'z')
return points,
ax.set_xlim([-1.5, 1.5])
ax.set_ylim([-1.5, 1.5])
ax.set_zlim([-1.5, 1.5])
ani=animation.FuncAnimation(fig, update_points, num_of_frames, interval=10, blit=True, repeat=True)
plt.show()
Sample output:
As you chose to animate line plots (these are animated markers without visible lines, scatter plots are different in structure), you cannot use different colors unless you plot each point separately. On the plus side, you can use blitting to make the animation faster.
And another point regarding your code - I suggest not using np.arange(), as this can lead to float problems at the endpoint. Use instead np.linspace(). As default, the endpoint is included but in this script, we changed it to False, so that time point [0] is the next step in the 2*pi cycle after time point [-1].
For different point characteristics, you just have to fill your arrays differently. As I said, each consists of columns for each point and rows for the different time points:
from matplotlib import pyplot as plt
import numpy as np
from matplotlib import animation
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
num_of_points = 4
num_of_frames = 100
#different rotation frequencies
t = np.linspace(0, 2*np.pi, num_of_frames, endpoint=False)[:, None] * np.arange(1, num_of_points+1)
#different x-y centers
x = np.cos(t) + np.asarray([0, 4, 0, 3])
y = np.sin(t) + np.asarray([0, 0, 5, 2])
#different heights
z = np.zeros(num_of_frames)[:, None] + np.arange(num_of_points)
#point 4 gets random altitude fluctuations
z[:, 3] += np.random.random(num_of_frames)/5
points, = ax.plot([], [], [], 'o')
def update_points(n):
points.set_data(np.array([x[n, :], y[n, :]]))
points.set_3d_properties(z[n, :], 'z')
return points,
ax.set_xlim([x.min()-0.5, x.max()+0.5])
ax.set_ylim([y.min()-0.5, y.max()+0.5])
ax.set_zlim([z.min()-0.5, z.max()+0.5])
ani=animation.FuncAnimation(fig, update_points, num_of_frames, interval=20, blit=True, repeat=True)
plt.show()
As the time information is derived from the row number, you could also forget the t helper array and fill directly the x, y, and z arrays with the desired or random data as the following example shows. However, for an animation, you have to ensure smooth transitions between states, so incremental changes along axis 0 are essential.
...
num_of_points = 4
num_of_frames = 100
#random walk
x = np.random.random((num_of_frames, num_of_points))-0.4
y = np.random.random((num_of_frames, num_of_points))-0.3
z = np.random.random((num_of_frames, num_of_points))-0.5
x[:] = x.cumsum(axis=0)
y[:] = y.cumsum(axis=0)
z[:] = z.cumsum(axis=0)
points, = ax.plot([], [], [], 'o')
...
Related
I have oscillatory data to which I would like to add a specific contour line. For example, the data pass through a value several times, and I would like to pick a specific instance of that value to highlight with a contour. As an example, consider a Bessel function. Below, I plot the contours with a single level, 0.2. I would like to choose to show only the outer contour, however, and not the other interior ones.
from scipy.special import jv
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-20,20,num=500)
y = np.linspace(-20,20,num=500)
[X,Y] = np.meshgrid(x,y)
Z = jv(1,np.sqrt(X**2.+Y**2.))
fig = plt.figure()
ax = fig.add_subplot(111)
cb = ax.pcolormesh(X,Y,Z)
ax.contour(X,Y,Z,[.2],linestyles='dashed')
cbar = fig.colorbar(cb)
plt.show()
If helpful, this is a plot of my actual data (the code used to create is far too long to include here). I would only like to plot the outermost purple contour:
Thank you
Let's see how you like this ;) ... I plot all contour lines invisibly, but extract the contour object and replot the first one (that I just figured out by trial and error, and might be different in your case).
from scipy.special import jv
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-20, 20, num=500)
y = np.linspace(-20, 20, num=500)
[X, Y] = np.meshgrid(x, y)
Z = jv(1, np.sqrt(X**2. + Y**2.))
fig = plt.figure()
ax = fig.add_subplot(111)
cb = ax.pcolormesh(X, Y, Z)
cont = ax.contour(X, Y, Z, [.2], alpha=0) # alpha = 0 -> invisible
the_interesting_one = cont.allsegs[0][0]
plt.plot(the_interesting_one[:, 0], the_interesting_one[:, 1], "k--")
cbar = fig.colorbar(cb)
plt.show()
I have an array x_trj that has shape (50,3), and I want to plot a 2-D trajectory using the 1st and the 2nd columns of this array (x & y coordinates respectively). This trajectory will be on top of a circle. Here is my code so far:
from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as plt
fig = plt.figure()
ax = plt.axes(xlim=(-5, 5), ylim=(-5, 5))
line, = ax.plot([], [], lw=2)
# Plot circle
theta = np.linspace(0, 2*np.pi, 100)
plt.plot(r*np.cos(theta), r*np.sin(theta), linewidth=5)
ax = plt.gca()
def animate(n):
# Plot resulting trajecotry of car
for n in range(x_trj.shape[0]):
line.set_xdata(x_trj[n,0])
line.set_ydata(x_trj[n,1])
return line,
anim = FuncAnimation(fig, animate,frames=200, interval=20)
However, the animation turns out to be a stationary figure. I checked out the Matplotlib animation example on the documentation page, but I still can't figure out what my animate(n) function should look like in this case. Can someone give me some hints?
The code below makes the following changes:
added some test data
in animate:
remove the for loop
only copy the part of the trajectory until the given n
in the call to FuncAnimation:
`frames should be equal to the given number of points (200 frames and 50 points doesn't work well)
interval= set to a larger number, as 20 milliseconds make things too fast for only 50 frames
added plt.show() (depending on the environment where the code is run, plt.show() will trigger the animation to start)
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np
# create some random test data
x_trj = np.random.randn(50, 3).cumsum(axis=0)
x_trj -= x_trj.min(axis=0, keepdims=True)
x_trj /= x_trj.max(axis=0, keepdims=True)
x_trj = x_trj * 8 - 4
fig = plt.figure()
ax = plt.axes(xlim=(-5, 5), ylim=(-5, 5))
line, = ax.plot([], [], lw=2)
# Plot circle
theta = np.linspace(0, 2 * np.pi, 100)
r = 4
ax.plot(r * np.cos(theta), r * np.sin(theta), linewidth=5)
def animate(n):
line.set_xdata(x_trj[:n, 0])
line.set_ydata(x_trj[:n, 1])
return line,
anim = FuncAnimation(fig, animate, frames=x_trj.shape[0], interval=200)
# anim.save('test_trajectory_animation.gif')
plt.show()
I've been toying around with this problem and am close to what I want but missing that extra line or two.
Basically, I'd like to plot a single line whose color changes given the value of a third array. Lurking around I have found this works well (albeit pretty slowly) and represents the problem
import numpy as np
import matplotlib.pyplot as plt
c = np.arange(1,100)
x = np.arange(1,100)
y = np.arange(1,100)
cm = plt.get_cmap('hsv')
fig = plt.figure(figsize=(5,5))
ax1 = plt.subplot(111)
no_points = len(c)
ax1.set_color_cycle([cm(1.*i/(no_points-1))
for i in range(no_points-1)])
for i in range(no_points-1):
bar = ax1.plot(x[i:i+2],y[i:i+2])
plt.show()
Which gives me this:
I'd like to be able to include a colorbar along with this plot. So far I haven't been able to crack it just yet. Potentially there will be other lines included with different x,y's but the same c, so I was thinking that a Normalize object would be the right path.
Bigger picture is that this plot is part of a 2x2 sub plot grid. I am already making space for the color bar axes object with matplotlib.colorbar.make_axes(ax4), where ax4 with the 4th subplot.
Take a look at the multicolored_line example in the Matplotlib gallery and dpsanders' colorline notebook:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.collections as mcoll
def multicolored_lines():
"""
http://nbviewer.ipython.org/github/dpsanders/matplotlib-examples/blob/master/colorline.ipynb
http://matplotlib.org/examples/pylab_examples/multicolored_line.html
"""
x = np.linspace(0, 4. * np.pi, 100)
y = np.sin(x)
fig, ax = plt.subplots()
lc = colorline(x, y, cmap='hsv')
plt.colorbar(lc)
plt.xlim(x.min(), x.max())
plt.ylim(-1.0, 1.0)
plt.show()
def colorline(
x, y, z=None, cmap='copper', norm=plt.Normalize(0.0, 1.0),
linewidth=3, alpha=1.0):
"""
http://nbviewer.ipython.org/github/dpsanders/matplotlib-examples/blob/master/colorline.ipynb
http://matplotlib.org/examples/pylab_examples/multicolored_line.html
Plot a colored line with coordinates x and y
Optionally specify colors in the array z
Optionally specify a colormap, a norm function and a line width
"""
# Default colors equally spaced on [0,1]:
if z is None:
z = np.linspace(0.0, 1.0, len(x))
# Special case if a single number:
# to check for numerical input -- this is a hack
if not hasattr(z, "__iter__"):
z = np.array([z])
z = np.asarray(z)
segments = make_segments(x, y)
lc = mcoll.LineCollection(segments, array=z, cmap=cmap, norm=norm,
linewidth=linewidth, alpha=alpha)
ax = plt.gca()
ax.add_collection(lc)
return lc
def make_segments(x, y):
"""
Create list of line segments from x and y coordinates, in the correct format
for LineCollection: an array of the form numlines x (points per line) x 2 (x
and y) array
"""
points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
return segments
multicolored_lines()
Note that calling plt.plot hundreds of times tends to kill performance.
Using a LineCollection to build multi-colored line segments is much much faster.
I'd like to make a scatter plot where each point is colored by the spatial density of nearby points.
I've come across a very similar question, which shows an example of this using R:
R Scatter Plot: symbol color represents number of overlapping points
What's the best way to accomplish something similar in python using matplotlib?
In addition to hist2d or hexbin as #askewchan suggested, you can use the same method that the accepted answer in the question you linked to uses.
If you want to do that:
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import gaussian_kde
# Generate fake data
x = np.random.normal(size=1000)
y = x * 3 + np.random.normal(size=1000)
# Calculate the point density
xy = np.vstack([x,y])
z = gaussian_kde(xy)(xy)
fig, ax = plt.subplots()
ax.scatter(x, y, c=z, s=100)
plt.show()
If you'd like the points to be plotted in order of density so that the densest points are always on top (similar to the linked example), just sort them by the z-values. I'm also going to use a smaller marker size here as it looks a bit better:
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import gaussian_kde
# Generate fake data
x = np.random.normal(size=1000)
y = x * 3 + np.random.normal(size=1000)
# Calculate the point density
xy = np.vstack([x,y])
z = gaussian_kde(xy)(xy)
# Sort the points by density, so that the densest points are plotted last
idx = z.argsort()
x, y, z = x[idx], y[idx], z[idx]
fig, ax = plt.subplots()
ax.scatter(x, y, c=z, s=50)
plt.show()
Plotting >100k data points?
The accepted answer, using gaussian_kde() will take a lot of time. On my machine, 100k rows took about 11 minutes. Here I will add two alternative methods (mpl-scatter-density and datashader) and compare the given answers with same dataset.
In the following, I used a test data set of 100k rows:
import matplotlib.pyplot as plt
import numpy as np
# Fake data for testing
x = np.random.normal(size=100000)
y = x * 3 + np.random.normal(size=100000)
Output & computation time comparison
Below is a comparison of different methods.
1: mpl-scatter-density
Installation
pip install mpl-scatter-density
Example code
import mpl_scatter_density # adds projection='scatter_density'
from matplotlib.colors import LinearSegmentedColormap
# "Viridis-like" colormap with white background
white_viridis = LinearSegmentedColormap.from_list('white_viridis', [
(0, '#ffffff'),
(1e-20, '#440053'),
(0.2, '#404388'),
(0.4, '#2a788e'),
(0.6, '#21a784'),
(0.8, '#78d151'),
(1, '#fde624'),
], N=256)
def using_mpl_scatter_density(fig, x, y):
ax = fig.add_subplot(1, 1, 1, projection='scatter_density')
density = ax.scatter_density(x, y, cmap=white_viridis)
fig.colorbar(density, label='Number of points per pixel')
fig = plt.figure()
using_mpl_scatter_density(fig, x, y)
plt.show()
Drawing this took 0.05 seconds:
And the zoom-in looks quite nice:
2: datashader
Datashader is an interesting project. It has added support for matplotlib in datashader 0.12.
Installation
pip install datashader
Code (source & parameterer listing for dsshow):
import datashader as ds
from datashader.mpl_ext import dsshow
import pandas as pd
def using_datashader(ax, x, y):
df = pd.DataFrame(dict(x=x, y=y))
dsartist = dsshow(
df,
ds.Point("x", "y"),
ds.count(),
vmin=0,
vmax=35,
norm="linear",
aspect="auto",
ax=ax,
)
plt.colorbar(dsartist)
fig, ax = plt.subplots()
using_datashader(ax, x, y)
plt.show()
It took 0.83 s to draw this:
There is also possibility to colorize by third variable. The third parameter for dsshow controls the coloring. See more examples here and the source for dsshow here.
3: scatter_with_gaussian_kde
def scatter_with_gaussian_kde(ax, x, y):
# https://stackoverflow.com/a/20107592/3015186
# Answer by Joel Kington
xy = np.vstack([x, y])
z = gaussian_kde(xy)(xy)
ax.scatter(x, y, c=z, s=100, edgecolor='')
It took 11 minutes to draw this:
4: using_hist2d
import matplotlib.pyplot as plt
def using_hist2d(ax, x, y, bins=(50, 50)):
# https://stackoverflow.com/a/20105673/3015186
# Answer by askewchan
ax.hist2d(x, y, bins, cmap=plt.cm.jet)
It took 0.021 s to draw this bins=(50,50):
It took 0.173 s to draw this bins=(1000,1000):
Cons: The zoomed-in data does not look as good as in with mpl-scatter-density or datashader. Also you have to determine the number of bins yourself.
5: density_scatter
The code is as in the answer by Guillaume.
It took 0.073 s to draw this with bins=(50,50):
It took 0.368 s to draw this with bins=(1000,1000):
Also, if the number of point makes KDE calculation too slow, color can be interpolated in np.histogram2d [Update in response to comments: If you wish to show the colorbar, use plt.scatter() instead of ax.scatter() followed by plt.colorbar()]:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from matplotlib.colors import Normalize
from scipy.interpolate import interpn
def density_scatter( x , y, ax = None, sort = True, bins = 20, **kwargs ) :
"""
Scatter plot colored by 2d histogram
"""
if ax is None :
fig , ax = plt.subplots()
data , x_e, y_e = np.histogram2d( x, y, bins = bins, density = True )
z = interpn( ( 0.5*(x_e[1:] + x_e[:-1]) , 0.5*(y_e[1:]+y_e[:-1]) ) , data , np.vstack([x,y]).T , method = "splinef2d", bounds_error = False)
#To be sure to plot all data
z[np.where(np.isnan(z))] = 0.0
# Sort the points by density, so that the densest points are plotted last
if sort :
idx = z.argsort()
x, y, z = x[idx], y[idx], z[idx]
ax.scatter( x, y, c=z, **kwargs )
norm = Normalize(vmin = np.min(z), vmax = np.max(z))
cbar = fig.colorbar(cm.ScalarMappable(norm = norm), ax=ax)
cbar.ax.set_ylabel('Density')
return ax
if "__main__" == __name__ :
x = np.random.normal(size=100000)
y = x * 3 + np.random.normal(size=100000)
density_scatter( x, y, bins = [30,30] )
You could make a histogram:
import numpy as np
import matplotlib.pyplot as plt
# fake data:
a = np.random.normal(size=1000)
b = a*3 + np.random.normal(size=1000)
plt.hist2d(a, b, (50, 50), cmap=plt.cm.jet)
plt.colorbar()
I have a 3D array which has one time index and two space indices. I am trying to animate over the first index to visualize the 2D solution in time. I found another stack question about this here, but I am not entirely sure how it was resolved, I'm still a little confused. Basically I have a solution array which is A[n,i,j] where n is the time index, and x and y are the spacial indices. As I mentioned I want to animate over the 2D arrays A[:,i,j]. How do I use the animation module in matplotlib to do this?
Here's an example based on the one you linked to where the data is in the format you describe:
from matplotlib import pyplot as plt
import numpy as np
from matplotlib import animation
# Fake Data
x = y = np.arange(-3.0, 3.01, 0.025)
X, Y = np.meshgrid(x, y)
s = np.shape(X)
nFrames = 20
A = np.zeros((nFrames, s[0], s[1]))
for i in range(1,21):
A[i-1,:,:] = plt.mlab.bivariate_normal(X, Y, 0.5+i*0.1, 0.5, 1, 1)
# Set up plotting
fig = plt.figure()
ax = plt.axes()
# Animation function
def animate(i):
z = A[i,:,:]
cont = plt.contourf(X, Y, z)
return cont
anim = animation.FuncAnimation(fig, animate, frames=nFrames)
plt.show()