How to speed up Matplotlib? - python

I am new to Matplotlib and that's why there might be a more efficient way to run my program.
It is plotting a bunch of points with different colours (depending on some factors). It is constantly producing new pictures in a loop of the current colour state.
Basically it looks like this:
import matplotlib.pyplot as plt
def getColour():
#calculate some stuff with x and y and the changing factors
while True:
fig = plt.figure(figsize=(17,10))
plt.scatter(x, y , c=getColour())
plt.show()
plt.close(fig)
I was trying out clf() as well. However, it didn't change the pace at all. Does anyone have ideas? What am I doing wrong?
Thank you!
Edit:
The target is to produce a picture each time it goes through the loop. Since my program is doing this quite slowly, my question is whether there is a way to make it run faster.
I am working with python 2.7

Something like an animation:
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np
ms_between_frames = 100
n_points = 100
x = np.arange(n_points, dtype=float) #EDIT
y = np.random.random(n_points)
z = np.random.random(n_points)
def getColour(x, y, z):
c = np.empty((len(x),3))
for i in range(len(x)):
c[i] = [x[i]/n_points, z[i], 1.-z[i]]
return c
def update(frame_number):
global x, y
z = np.random.random(n_points)
c = getColour(x, y, z)
graph.set_color(c)
fig = plt.figure(figsize=(17,10))
ax = fig.add_subplot(111)
graph = ax.scatter(x, y , c=getColour(x, y, z))
animation = FuncAnimation(fig, update, interval=ms_between_frames)
plt.show()
EDIT: made x hold floats so the division inside getColour would not return 0 (could also have made /float(n_points))
By the way, it should be possible to define only one function to update the colours, depending on the arguments you require to do so, to avoid the call overhead.

Related

Python - Replace i value instead of appending

I am trying to plot real time data. I managed to plot the data but I would like for the bar graph to go up and down on a single x-value rather than produce new x-values for every new datapoint. I believe I have to replace the function x.append(i) with something like a replace, any ideas? Thank you!!
So far this is what I came up with:
import time
import psutil
import matplotlib.pyplot as plt
%matplotlib notebook
fig = plt.figure()
ax = fig.add_subplot(111)
fig.show()
plt.axis('off')
i = 0
x, y = [], []
while True:
x.append(i)
y.append(psutil.cpu_percent())
ax.bar(x, y, color='b')
fig.canvas.draw()
ax.set_xlim(left=max(0, i-50), right=i+50)
time.sleep(0.1)
i += 1
For the bar graph you can create a list inside the while loop, and instantly update it there. First you need to import a random in order get random value for y axis, or you can use cpu_percent.
import psutil
import random
These two should work.
And then:
while True:
x_axis = [str(_) for _ in range(100, 200)]
y_axis = [8 * random.random() for _ in range(100, 200)]
ax.bar(x, y, color='b')
fig.canvas.draw()
time.sleep(0.1)
However, matplotlib is not convenient for real data plotting, I strongly recommend you to use bokeh. You can find bokeh documentation here. It is really cool for creating any kind of real time plot. And at the same time, you can integrate it with your web browser. Hope this will help you)
If you just want to display the latest value, you can consider doing something like:
plt.ion()
graph = plt.bar(["Now"], [0])[0]
plt.axis('off')
i = 0
data = {}
while True:
cpu_percent = psutil.cpu_percent()
graph.set_ydata(cpu_percent)
plt.draw()
plt.pause(0.1)
data[i] = cpu_percent
i += 1
This way, you still have a record of all the datapoints to play with later (x, y) but you will only display 1 x value at a time on the graph.
Further reading

Plot a rolling window with Matplotlib

I want to plot a time series in a while loop as a rolling window: The graph should always show the 10 most recent observations.
My idea was to use a deque object with maxlen=10 and plot it in every step.
To my great surprise the plot appends new values to the old plot; apparently it remembers values that are no longer inside the deque! Why is that and how can I switch it off?
This is a minimal example of what I am trying to do. The plotting part is based on this post (although plt.ion() did not change anything for me, so I left it out):
from collections import deque
import matplotlib.pyplot as plt
import numpy as np
x = 0
data = deque(maxlen=10)
while True:
x += np.abs(np.random.randn())
y = np.random.randn()
data.append((x, y))
plt.plot(*zip(*data), c='black')
plt.pause(0.1)
I also tried to use Matplotlib's animation functions instead, but could not figure out how to do that in an infinite while loop...
Nowadays, it's much easier (and offers much better performance) to use the animation module than to use multiple calls to plt.plot:
from collections import deque
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
def animate(i):
global x
x += np.abs(np.random.randn())
y = np.random.randn()
data.append((x, y))
ax.relim()
ax.autoscale_view()
line.set_data(*zip(*data))
fig, ax = plt.subplots()
x = 0
y = np.random.randn()
data = deque([(x, y)], maxlen=10)
line, = plt.plot(*zip(*data), c='black')
ani = animation.FuncAnimation(fig, animate, interval=100)
plt.show()

Acquire x-axis values in Python matplotlib

Before I ask this question, I have already searched the internet for a while without success. To many experts this surely appears to be fairly simple. Please bear with me.
I am having a plot made by matplotlib and it is returned as a plf.Figure. See the following:
def myplotcode():
x = np.linspace(0, 2*np.pi)
y = np.sin(x)
print("x in external function", x)
y2 = np.cos(x)
fig = plf.Figure()
ax = fig.add_subplot(111)
ax.plot(x, y, 'bo', x, y2,'gs')
ax.set_ylabel("Some function")
return fig, ax
What I want to do in the function that call this one is to be able to get all these x values from the returned ax or fig. Well, I understand one simple solution is just to return x array too. However, I am trying to keep the number of returns as small as possible.
So, my question is: Can I acquire this x-axis array from fig or ax?
Thank you so much in advance.
You can do:
l = ax.axes.lines[0] # If you have more curves, just change the index
x, y = l.get_data()
That will give you two arrays, with the x and y data

Python matplotlib -- delete data out of axis limits

I am plotting iteratively using matplotlib in python. I am setting the axis of the plot, so as to display e.g. only 50 lines at a time. A pseudo code is given below as an example:
x = 0
y = 1
line_plot = 50
axis.set_ylim(0 , line_plot)
while True:
plot(x,y)
y = y+1
if y > line_plot :
axis.set_ylim(y , y+line_plot)
This code will run indefinitely, and eventually the memory required for the plot will get quite large, even if only 50 lines are present on the graph (since all data points are kept in memory). I would like to know if there is a command in python to delete all data that is out of axis limits, freeing some memory space.
Thank you,
Gaelle
This will depend a little bit on how exactly your script looks like. You need some method to determine the y-coordinates of every line, and based on some criteria remove them or not. But if you do something like:
x = np.arange(1)
y = np.ones(1)
pl.figure()
l1 = pl.plot(x,y)[0]
y[:] += 1
l2 = pl.plot(x,y)[0]
and call get_ydata() on both lines, they will have the same y-values, so get_ydata() seems to return the original array, not necessarily the values drawn in the plot (which apparently is a bug, see: this matplotlib issue). If, instead of y[:] += 1 you make an actual copy of the array (y = y.copy()+1), you can use get_ydata(). If this is the case in your real-world problem, such a solution might work:
import matplotlib
import matplotlib.pylab as pl
import numpy as np
pl.close('all')
x = np.arange(100000)
y = np.ones(x.size)
pl.figure()
ax = pl.gca()
line_plot = 50
ax.set_ylim(0, line_plot)
for i in range(200):
pl.plot(x, y)
y = y.copy() + 1
if y[0] > line_plot:
ax.set_ylim(y[0]-line_plot, y[0])
for l in ax.get_lines():
yval = l.get_ydata()[0]
if(yval < ax.get_ylim()[0]):
l.remove()
If I remove the for l in ax.get_lines part, the memory usage scales with i, with this part included the memory usage stays constant, even for very large values of i
You want look at the animation examples
# make a figure and axes object
fig, ax = plt.subplots()
# make a Line2D artist
ln, = ax.plot([], [], linestyle='', marker='o')
# local version of the data
xdata, ydata = [], []
for j in range(200):
# update your copy of the data
xdata.append(j)
ydata.append(j*j)
xdata = xdata[-50:]
ydata = ydata[-50:]
# update the Line2D objects copy of the data
ln.set_data(xdata, ydata)
# autoscale limits to new data
ax.relim()
ax.autoscale()
# needed in non-interactive mode and/or mpl < 1.5
# fig.canvas.draw_idle()
# sleep, but run the GUI event loop
plt.pause(.1)

Plotting mplot3d / axes3D xyz surface plot with log scale?

I've been looking high and low for a solution to this simple problem but I can't find it anywhere! There are a loads of posts detailing semilog / loglog plotting of data in 2D e.g. plt.setxscale('log') however I'm interested in using log scales on a 3d plot(mplot3d).
I don't have the exact code to hand and so can't post it here, however the simple example below should be enough to explain the situation. I'm currently using Matplotlib 0.99.1 but should shortly be updating to 1.0.0 - I know I'll have to update my code for the mplot3d implementation.
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FixedLocator, FormatStrFormatter
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = Axes3D(fig)
X = np.arange(-5, 5, 0.025)
Y = np.arange(-5, 5, 0.025)
X, Y = np.meshgrid(X, Y)
R = np.sqrt(X**2 + Y**2)
Z = np.sin(R)
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.jet, extend3d=True)
ax.set_zlim3d(-1.01, 1.01)
ax.w_zaxis.set_major_locator(LinearLocator(10))
ax.w_zaxis.set_major_formatter(FormatStrFormatter('%.03f'))
fig.colorbar(surf)
plt.show()
The above code will plot fine in 3D, however the three scales (X, Y, Z) are all linear. My 'Y' data spans several orders of magnitude (like 9!), so it would be very useful to plot it on a log scale. I can work around this by taking the log of the 'Y', recreating the numpy array and plotting the log(Y) on a linear scale, but in true python style I'm looking for smarter solution which will plot the data on a log scale.
Is it possible to produce a 3D surface plot of my XYZ data using log scales, ideally I'd like X & Z on linear scales and Y on a log scale?
Any help would be greatly appreciated. Please forgive any obvious mistakes in the above example, as mentioned I don't have my exact code to have and so have altered a matplotlib gallery example from my memory.
Thanks
Since I encountered the same question and Alejandros answer did not produced the desired Results here is what I found out so far.
The log scaling for Axes in 3D is an ongoing issue in matplotlib. Currently you can only relabel the axes with:
ax.yaxis.set_scale('log')
This will however not cause the axes to be scaled logarithmic but labeled logarithmic.
ax.set_yscale('log') will cause an exception in 3D
See on github issue 209
Therefore you still have to recreate the numpy array
I came up with a nice and easy solution taking inspiration from Issue 209. You define a small formatter function in which you set your own notation.
import matplotlib.ticker as mticker
# My axis should display 10⁻¹ but you can switch to e-notation 1.00e+01
def log_tick_formatter(val, pos=None):
return f"$10^{{{int(val)}}}$" # remove int() if you don't use MaxNLocator
# return f"{10**val:.2e}" # e-Notation
ax.zaxis.set_major_formatter(mticker.FuncFormatter(log_tick_formatter))
ax.zaxis.set_major_locator(mticker.MaxNLocator(integer=True))
set_major_locator sets the exponential to only use integers 10⁻¹, 10⁻² without 10^-1.5 etc. Source
Important! remove the cast int() in the return statement if you don't use set_major_locator and you want to display 10^-1.5 otherwise it will still print 10⁻¹ instead of 10^-1.5.
Example:
Try it yourself!
from mpl_toolkits.mplot3d import axes3d
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
fig = plt.figure(figsize=(11,8))
ax1 = fig.add_subplot(121,projection="3d")
# Grab some test data.
X, Y, Z = axes3d.get_test_data(0.05)
# Now Z has a range from 10⁻³ until 10³, so 6 magnitudes
Z = (np.full((120, 120), 10)) ** (Z / 20)
ax1.plot_wireframe(X, Y, Z, rstride=10, cstride=10)
ax1.set(title="Linear z-axis (small values not visible)")
def log_tick_formatter(val, pos=None):
return f"$10^{{{int(val)}}}$"
ax2 = fig.add_subplot(122,projection="3d")
# You still have to take log10(Z) but thats just one operation
ax2.plot_wireframe(X, Y, np.log10(Z), rstride=10, cstride=10)
ax2.zaxis.set_major_formatter(mticker.FuncFormatter(log_tick_formatter))
ax2.zaxis.set_major_locator(mticker.MaxNLocator(integer=True))
ax2.set(title="Logarithmic z-axis (much better)")
plt.savefig("LinearLog.png", bbox_inches='tight')
plt.show()
in osx: ran ax.zaxis._set_scale('log') (notice the underscore)
There is no solution because of the issue 209. However, you can try doing this:
ax.plot_surface(X, np.log10(Y), Z, cmap='jet', linewidth=0.5)
If in "Y" there is a 0, it is going to appear a warning but still works. Because of this warning color maps don´t work, so try to avoid 0 and negative numbers. For example:
Y[Y != 0] = np.log10(Y[Y != 0])
ax.plot_surface(X, Y, Z, cmap='jet', linewidth=0.5)
I wanted a symlog plot and, since I fill the data array by hand, I just made a custom function to calculate the log to avoid having negative bars in the bar3d if the data is < 1:
import math as math
def manual_log(data):
if data < 10: # Linear scaling up to 1
return data/10
else: # Log scale above 1
return math.log10(data)
Since I have no negative values, I did not implement handling this values in this function, but it should not be hard to change it.

Categories

Resources