I can't figure how to plot a continuous function using Matplotlib. I got how to plot a scatterplot, but I would like a continuous plot.
Here is my code:
import matplotlib.pyplot as plt
from matplotlib.pyplot import autoscale
import matplotlib.animation as animation
import numpy
class MyPlot():
def __init__(self):
self.index = 0
self.setup()
def setup(self):
plt.ion()
self.fig, self.ax = plt.subplots()
self.line = self.ax.plot([],[])
autoscale()
plt.show()
def anim(self, i):
self.line.set_ydata(i) # update the data
return self.line,
def add(self, val):
print self.index, val
self.ax.plot(self.index, val)
animation.FuncAnimation(self.fig, self.anim, repeat=False)
plt.pause(0.05)
#if(self.index >= ntests):
self.index+=1
if __name__== "__main__":
import time
from random import random
p = MyPlot()
for i in range(100):
p.add(random())
time.sleep(0.5)
This works, but doesn't draw anything. The plot resizes itself, though.
You are only plotting a line with a single point at a time (which doesn't exist), so nothing shows up. If you replace self.ax.plot with self.ax.scatter, it plots correctly.
If you really want lines, you can just keep track of the last index and value and plot a line connecting the last index and value with the current index and value each time.
Add these two lines to add()
self.ax.plot([self.index-1, self.index], [self.lastval, val])
self.lastval = val
as well as a line initializing self.lastval to numpy.nan in setup()
You can actually append values to a line plot in matplotlib:
self.line.set_xdata(numpy.append(self.line.get_xdata(), self.index))
self.line.set_ydata(numpy.append(self.line.get_ydata(), val))
This way, you do not have to do any of the bookkeeping yourself.
More details can be found at https://stackoverflow.com/a/10944967/2988730
Related
My task is to plot a numpy array in real time using matplotlib. Please note that I don't want to use animation function to do this.
import numpy as np
import time
from matplotlib.lines import Line2D
import matplotlib
class Plot:
def __init__(self,f,axis,data):
self.fig = f
self.axis = axis
self.data = data
def plotting(self,i):
xs = [self.data[i,0],self.data[i+1,0]]
ys = [self.data[i,1],self.data[i+1,1]]
line, = self.axis.plot(xs,ys,'g-')
self.fig.canvas.draw()
data = np.random.rand(10,2) #numpy array
f = plt.figure()
axis = f.add_axes([0,0,0.9,0.9])
plotData = Plot(f,axis,data)
for i in range(len(data)-1):
plotData.plotting(i)
time.sleep(1)
plt.show()
But everytime I run this code it returns me one empty figure. How do I rectify it?
import matplotlib.pyplot as plt
import numpy as np
# use ggplot style for more sophisticated visuals
plt.style.use('ggplot')
def live_plotter(x_vec,y1_data,line1,identifier='',pause_time=0.1):
if line1==[]:
# this is the call to matplotlib that allows dynamic plotting
plt.ion()
fig = plt.figure(figsize=(13,6))
ax = fig.add_subplot(111)
# create a variable for the line so we can later update it
line1, = ax.plot(x_vec,y1_data,'-o',alpha=0.8)
#update plot label/title
plt.ylabel('Y Label')
plt.title('Title: {}'.format(identifier))
plt.show()
# after the figure, axis, and line are created, we only need to update the y-data
line1.set_ydata(y1_data)
# adjust limits if new data goes beyond bounds
if np.min(y1_data)<=line1.axes.get_ylim()[0] or np.max(y1_data)>=line1.axes.get_ylim()[1]:
plt.ylim([np.min(y1_data)-np.std(y1_data),np.max(y1_data)+np.std(y1_data)])
# this pauses the data so the figure/axis can catch up - the amount of pause can be altered above
plt.pause(pause_time)
# return line so we can update it again in the next iteration
return line1
A few notes on the function above:
line1.set_ydata(y1_data) can also be switched to line1.set_data(x_vec,y1_data) to change both x and y data on the plots.
plt.pause() is necessary to allow the plotter to catch up - I've been able to use a pause time of 0.01s without any issues
The user will need to return line1 to control the line as it is updated and sent back to the function
The user can also customize the function to allow dynamic changes of title, x-label, y-label, x-limits, etc.
I am trying to create a program that can visualize the change of a portfolio in real time. To do this, I update my data and create a new plot with it. When I run the code below in PyCharm, SciView stops displaying the plots after 30 iterations. Ideally, I would like to have it only show the most recent plot, but it would also be fine if it just truncated the history so that I at least always see the current plot. Is there any way to do this? I tried different ways to close the figures (e. g. using plt.close()), but did not achieve the desired result.
Code to reproduce:
import matplotlib.pyplot as plt
import numpy as np
import random
class RealTimeVisualizer:
def __init__(self, x, y):
self.x = x
self.y = y
def update_data(self, x_value, y_value):
"""
Appends values to the data arrays.
"""
self.x.append(x_value)
self.y.append(y_value)
def create_plot(self):
"""
Takes an x and a y (both 1D arrays and constructs a plot from it)
:return: a pyplot figure object
"""
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
# Draw x and y lists
ax.clear()
ax.plot(self.x, self.y)
# Format plot
plt.xticks(rotation=90)
plt.title('Portfolio')
plt.ylabel('Value')
plt.show()
plt.close('all')
if __name__ == '__main__':
portfolio_cash = 10000
tick = 0
real_time_visualizer = RealTimeVisualizer([tick], [portfolio_cash])
for i in np.arange(50):
tick += 1
portfolio_cash += random.randint(-50, 50)
real_time_visualizer.update_data(tick, portfolio_cash)
real_time_visualizer.create_plot()
Rather than creating a new plot and window every time, you can also update the current Matplotlib figure data in each iteration. You then need to view the plot in an interactive Matplotlib environment.
Live updating Matplotlib plots
You can use code similar to this to update the data inside the plot:
import matplotlib.pyplot as plt
import random
plt.ion() # Set pyplot to interactive mode
fig = plt.figure() # Create a figure
ax = fig.add_subplot(111) # Add a subplot to the figure
# Variables for our updating data
x = []
y = []
for i in range(50):
# Generate random data
x.append(i)
y.append(random.random())
# Update the plot with the new x, y data
ax.plot(x, y, 'ro-')
fig.canvas.draw()
fig.canvas.flush_events()
Allow for interactive Matplotlib mode when using SciView
Deactivate SciView or manually set your backend to another interactive GUI to see the updating plot.
This code snipped automatically chooses the correct backend (same list as in the Matplotlib code):
import matplotlib.pyplot as plt
candidates = ["macosx", "qt5agg", "gtk3agg", "tkagg", "wxagg"]
for candidate in candidates:
try:
plt.switch_backend(candidate)
print('Using backend: ' + candidate)
break
except (ImportError, ModuleNotFoundError):
pass
Applied to your code
Your code with suggested modifications would look like this:
import matplotlib.pyplot as plt
import numpy as np
import random
class RealTimeVisualizer:
def __init__(self, x, y):
self.x = x
self.y = y
def update_data(self, x_value, y_value):
"""
Appends values to the data arrays.
"""
self.x.append(x_value)
self.y.append(y_value)
def update_plot(self, fig, ax):
import _tkinter
try:
ax.plot(self.x, self.y, 'ro-')
fig.canvas.draw()
fig.canvas.flush_events()
# Capture an error in case the plotting window is being closed
except _tkinter.TclError:
pass
if __name__ == '__main__':
portfolio_cash = 10000
tick = 0
real_time_visualizer = RealTimeVisualizer([tick], [portfolio_cash])
# Choose the right backend
candidates = ["macosx", "qt5agg", "gtk3agg", "tkagg", "wxagg"]
for candidate in candidates:
try:
plt.switch_backend(candidate)
print('Using backend: ' + candidate)
break
except (ImportError, ModuleNotFoundError):
pass
# Create plot
plt.ion() # Set pyplot to interactive mode
fig = plt.figure() # Create a figure
ax = fig.add_subplot(111) # Add a subplot to the figure
for i in np.arange(50):
tick += 1
portfolio_cash += random.randint(-50, 50)
real_time_visualizer.update_data(tick, portfolio_cash)
real_time_visualizer.update_plot(fig, ax) # Update the plot the new data
Same issue here.
The workaround I found is to change the matplotlib backend to plot outside the PyCharm.
import matplotlib
matplotlib.use('qt5Agg')
matplotlib.pyplot.ioff()
Then you have to explicit open a new figure and show
for i in range(100):
plt.figure()
...
...
plt.show()
I recently wrote this to scrape a log and show a matplotlib.pyplot.bar plot of the most used words in it
import re
from datetime import datetime
from collections import Counter
import matplotlib.pyplot as plt
from matplotlib import animation
def read_log(path, index, separator=chr(9)):
data = []
my_file = open(path,"r+")
rows = my_file.readlines()
for row in rows:
line = re.sub(r'\r\n|\r|\n','',row, flags=re.M)
if line != '':
data.append(line.split(separator)[index])
my_file.close()
return Counter(data)
def set_plot(counter_data):
plt.title('This is a title')
plt.bar(range(len(counter_data)), list(counter_data.values()), align='center')
plt.xticks(range(len(counter_data)), list(counter_data.keys()))
plt.tight_layout()
plt.show()
counter_data = read_log(r'logfile.txt',2)
print(counter_data)
set_plot(counter_data)
I would love to animate said plot, however, I can't grasp animation.FuncAnimation()
Can you help me out?
I added these lines:
fig = plt.Figure()
animation.FuncAnimation(fig, set_plot(counter_data), frames=20)
and deleted plt.show()
So I could give FuncAnimation an empty figure (fig) and the function. But it doesn't work. EDIT: And it doesn't print an error either.
It seems your data is static (you get it from file once and it doesn't change), so I don't really understand what you are trying to animate. But, your code contains errors that need to be fixed, so for demonstration purposes I will add increment each of the heights in each step of animation.
The first mistake is in the way you pass arguments to your function. For arguments you have to use fargs parameter, otherwise in your version you are passing the result of function not the function itself.
You must have a function (animate in my version, set_plot in yours) that updates the plot for each step of your animation. (in your case you just put the same data every time)
That function needs to accept at least one parameter (val) which is used my FuncAnimation which passes values got from iterator passed to its frames parameter.
The final code looks like this
import re
from datetime import datetime
from collections import Counter
import matplotlib.pyplot as plt
from matplotlib import animation
# uncomment if using in jupyter notebook
# %matplotlib nbagg
def read_log(path, index, separator=chr(9)):
data = []
my_file = open(path,"r+")
rows = my_file.readlines()
for row in rows:
line = re.sub(r'\r\n|\r|\n','',row, flags=re.M)
if line != '':
data.append(line.split(separator)[index])
my_file.close()
return Counter(data)
fig = plt.figure()
ax = fig.add_subplot()
counter_data = read_log(r'tmp.csv',2)
plt.title('This is a title')
bar = ax.bar(range(len(counter_data)), list(counter_data.values()), align='center')
plt.xticks(range(len(counter_data)), list(counter_data.keys()))
plt.tight_layout()
plt.ylim((0, 30))
def animate(val, counter_data):
data = list(counter_data.values())
for i in range(len(data)):
bar[i].set_height(data[i]+val)
animation.FuncAnimation(fig, func=animate, frames=20, fargs=[counter_data], save_count=10)
and we get the following animation:
Edit:
For errors you can try to save your animation to gif, and the errors will show up
anim = animation.FuncAnimation(fig, func=animate, frames=20, fargs=[counter_data], save_count=10)
anim.save('anim.gif', 'imagemagick')
The main problem is that FuncAnimation expects a callable which returns artist objects. The callable will be called repeatedly with a frame argument.
In your example, set_plot() is called once. It's return value (None) is passed to FuncAnimation. Instead you should have a method, e.g. update_plot(), which loads the data from the file, updates the bar plot and returns the bar plot. This function (the function itself) should be passed to FuncAnimation
animation.FuncAnimation(fig, update_plot, frames=20)
without calling it! Note the missing parenthesis after update_plot. The animitation documentation shows examples how this can be done.
I am trying to draw scatter plot with dynamic data. I am able to draw the data points through looping; but everytime it creates new colorbar.
Here is my code:
import time
from threading import Thread
import pandas as pd
import matplotlib.pyplot as plt
import random
class RealTime:
def __init__(self):
self.flight_complete = True
self.data = pd.DataFrame(data=None, columns=list('ABC'))
self.fig=None
self.axis = None
def on_launch(self):
plt.ion()
self.fig = plt.figure()
self.axis = self.fig.add_subplot(111)
def create_data(self):
x = round(random.uniform(-1, 1), 2)
y = round(random.uniform(-1.65, 1.65), 2)
z = 0.5
temp_data = pd.DataFrame([[x, y, z]], columns=list('ABC'))
self.data = self.data.append(temp_data, ignore_index=True)
# Plot the data
self.plot()
def start_fly(self):
self.on_launch()
fly = Thread(target=self.fly_running)
fly.start()
def fly_running(self):
for _ in range(10):
print("Flying")
# Create the data
self.create_data()
time.sleep(0.1)
return
def plot(self):
plt.gca().cla()
self.data.plot(kind="scatter", x="A", y="B", alpha=0.4,
s=50, label="Real Time Position",
c="C", cmap=plt.get_cmap("jet"), colorbar=True, ax=self.axis)
plt.colormaps()
plt.title("Flight Path Map")
self.fig.canvas.draw()
self.fig.canvas.flush_events()
if __name__ == '__main__':
obj = RealTime()
obj.on_launch()
obj.fly_running()
I have read this post : How to retrieve colorbar instance from figure in matplotlib. But I couldn't really work with that.
Do you know why it creates a new colorbar? and how to avoid it?
Best Regards
Panda's plot is creating new colobar because you're asking it to create one (colorbar=True), and it looks like there is now way to tell the function that there is already a colorbar and that it should use that instead.
There are many ways to go around this problem.
the first one would be not not use DataFrame.plot() but instead use matplotlib directly to generate the plot. That will give you more control over the axes that are used and will let you recycle the colorbar from frame to frame. Here are some links that might be relevant:
How do you add a colormap to a matplotlib Animation?
Updating the positions and colors of pyplot.scatter
the second option if you want to keep your code close to what it is now it to erase the whole figure at each frame, and let pandas recreate the axes it need every time. i.e.:
def plot(self):
self.fig.clf()
self.axis = self.fig.add_subplot(111)
self.axis = self.data.plot(kind="scatter", x="A", y="B", alpha=0.4,
s=50, label="Real Time Position",
c="C", cmap=plt.get_cmap("jet"), colorbar=True, ax=self.axis)
I have a large dataset (~30GB) that I want to visualize by looking at it scrolling past. A great example is the top graph in this video.
My data is coming from CSV files.
What I have tried so far is importing the massive CSV files into a numpy array and using np.roll() to shift in a new column from the right side (like in the video) repeatedly until I hit the last column of the array (by calling np.roll() in the mpl.animation.FuncAnimation iterations.
This takes a large amount of CPU, and a much larger amount of memory.
Any suggestion on how to approach this? I couldn't find very many examples online that could help me with this.
here is some code from the mat plot lib tutorials.
import numpy as np
from matplotlib.lines import Line2D
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import math
class Scope(object):
def __init__(self, ax, maxt=2, dt=0.02):
self.ax = ax
self.dt = dt
self.maxt = maxt
self.tdata = [0]
self.ydata = [0]
self.line = Line2D(self.tdata, self.ydata)
self.ax.add_line(self.line)
self.ax.set_ylim(-.1, 1.1)
self.ax.set_xlim(0, self.maxt)
def update(self, y):
lastt = self.tdata[-1]
if lastt > self.tdata[0] + self.maxt: # reset the arrays
self.tdata = [self.tdata[-1]]
self.ydata = [self.ydata[-1]]
self.ax.set_xlim(self.tdata[0], self.tdata[0] + self.maxt)
self.ax.figure.canvas.draw()
t = self.tdata[-1] + self.dt
self.tdata.append(t)
self.ydata.append(y)
self.line.set_data(self.tdata, self.ydata)
return self.line,
def emitter(x=0):
'return a random value with probability p, else 0'
while True:
if x<361:
x = x + 1
yield math.sin(math.radians(x))
else:
x=0
x =x + 1
yield math.sin(math.radians(x))
# Fixing random state for reproducibility
np.random.seed(19680801)
fig, ax = plt.subplots()
scope = Scope(ax)
# pass a generator in "emitter" to produce data for the update func
ani = animation.FuncAnimation(fig, scope.update, emitter, interval=10,
blit=True)
plt.show()
my suggestion is to build a generator that yields the next data set you want to display every time it is called. this way you wont need to load the whole file into memory. more on that here. replace emitter function with generator that will pull from you file. disadvantage of this is I don't believe the full array will be available in the plot.