Python matplotlib pyplot module always draws on existing figure window when figure title is the same - python-3.x

I am a new python user but an experienced Matlab user. I am recently debugging a python script, and when I manually re-run the script multiple times, I found a somewhat annoying issue of matplotlib: it always draws on existing figure window, overlapping on existing plot, if the figure title is the same.
The script I am debugging looks like this:
import matplotlib.pyplot as plt
# Some calculations here
plt.figure('Results') # The script will only create one figure
# plot the data
# End of the script
A simple search on Google shows that if I don't explicitly specify figure title, or give each figure a different handle, matplotlib can create separate figure windows, and true, it works.
However, is there a way to create multiple figure windows with the same title, without giving them different handles (which in my case, I had to do it manually) in python? In Matlab it will always create separate figure window no matter what figure title you give it.

The argument to figure is an identifier. If it is left empty anew figure will be created, else the figure with that identifier will be activiated. The documentation makes this rather clear:
matplotlib.pyplot.figure(num=None, ...)
num : integer or string, optional, default: none
If not provided, a new figure will be created, and the figure number will be incremented. The figure objects holds this number in a number attribute. If num is provided, and a figure with this id already exists, make it active, and returns a reference to it. If this figure does not exists, create it and returns it. If num is a string, the window title will be set to this figure’s num.
Hence in order to create a new figure, leave this argument out or specify differing ones. In order to set the window's title, use set_window_title.
The following will create two figures with the same window title.
import matplotlib.pyplot as plt
plt.figure()
plt.gcf().canvas.set_window_title('Results')
plt.plot([1,2,3])
plt.figure()
plt.gcf().canvas.set_window_title('Results')
plt.plot([2,3,1], color="crimson")
plt.show()

From the first paragraph of your question, ...
when I manually re-run the script multiple times, I found a somewhat
annoying issue of matplotlib: it always draws on existing figure
window
I think that simply clearing the figure (at the start of the script) would make your repeated runs of the script useable.
import matplotlib.pyplot as plt
# compute results here - random here as a standin.
import numpy as np
x = np.random.randn(500)
plt.figure("Results"); plt.clf()
# plot results here...
plt.hist(x, bins=20, histtype='step')
Now, each time you run the script, you will draw the results on a blank canvas and not over the top of the old results.
The figures below illustrate the difference, after 3 runs of the script (in ipython): left - without the plt.clf(), and right - with plt.clf() at the start.

Related

Display seaborn plots at some point later in code

Let's say at some point in my code, I have following two graphs: i.e. graph_p_changes and graph_p_contrib
line_grapgh_p_changes = df_p_change[['year','interest accrued', 'trade debts', 'other financial assets']].melt('year', var_name='variables', value_name='p_changes')
graph_p_changes = sns.factorplot(x="year", y="p_changes", hue='variables', data=line_grapgh_p_changes, height=5, aspect=2)
graph_p_changes.set(xlabel='year', ylabel='percentage change in self value across the years')
line_grapgh_p_contrib = df_p_contrib[['year','interest accrued', 'trade debts', 'other financial assets']].melt('year', var_name='variables', value_name='p_changes')
graph_p_contrib = sns.factorplot(x="year", y="p_changes", hue='variables', data=line_grapgh_p_contrib, height=5, aspect=2)
graph_p_contrib.set(xlabel='year', ylabel='percentage chnage in contribution to total value')
Now at some point later in my code, I just want to display one of the above two graphs. But when I do plt.show(), it displays both of the above graphs in my jupyter notebook. How can I display only one graph at any point in my code.
You'll want to refer to the assigned variable for each plot and then add .fig after that to redisplay it in a Jupyter notebook cell.
Specifically, in your case you'd reference graph_p_changes.fig or graph_p_contrib.fig in a cell and execute that cell to see an individual plot again.
This is similar to how you can show Seaborn's ClusterGrids again, see here. Because the title of your question said 'seaborn plots', I'll add for sake of completeness, this doesn't hold for plots like Seaborn's line plot (lineplot) or bar plot (barplot) , that produce AxesSubplot objects. There you use .figure, for example ax.figure to recall most of the examples listed on Seaborn's lineplot documentation.
Example catplots with code
This is using example code from here and seaborn's catplot documentation (see below) to make two plots. If this code was in one cell and then that cell was run, you'd see two plots in the output below it.
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
titanic = sns.load_dataset("titanic")
exercise = sns.load_dataset("exercise")
g = sns.catplot("alive", col="deck",
col_wrap=3, data=titanic[titanic.deck.notnull()],
kind="count", height=2.5, aspect=.8)
another_plot = sns.catplot(x="time", y="pulse", hue="kind", data=exercise)
Later, each can be displayed again individually as output of other cells with g.fig or another_plot.fig, depending on which plot you want to show.
Additionaly, I'll suggest to improve your long-term code viability, you may want to move on to using catplot in your plotting calls as that is what factorplot is now called in seaborn. See here where it says "factorplot still exists and will pass its arguments through to catplot() with a warning. It may be removed eventually, but the transition will be as gradual as possible."
UPDATE:
OP commented that what was desired was code allowing interspersed stdout/stderr output with plots at precise points among that stream and not just at the end.
For some reason, Seaborn plots (even simple line plots) don't seem to get 'captured' correctly with io.capture_output(), and so I had to use the %%capture cell magic command in the producing cell and combine the output in a separate cell. However, Plotly plots I tried based on example code are captured by io.capture_output() and allow facile intermixing all in the same cell. This is all illustrated in an example notebook available here; it is best viewed in static form here at nbviewer because nbviewer renders the Plotly plots while GitHub doesn't. The top of that notebook includes a link where you can launch an active Jupyter session where it will run.
Update related to this UPDATE:
In an insightful answer to 'seaborn stop figure from being visualized', ffrosch suggests you "can temporarily disable/enable the inline creation with plt.ioff() and plt.ion()." This may offer yet another way to fine-tune when Seabor plots show among the output and/or offer another way to constrain ouput since %%capture cell magic worked yet io.capture_output() did not. (I have yet to try this.)

gmplot Marker does not work after it marks 256 points

I am trying to mark a bunch of points on the map with gmplot and observed that after a certain point it stops marking and wipes out all the previously marked points. I debugged the gmplot.py module and saw that when the length of points array exceeds 256 this is happening without giving any error and warning.
self.points = [] on gmplot.py
Since I am very new to Python and OOPs concept, is there a way to override this and mark more than 256 points?
Are you using gmplot.GoogleMapPlotter.Scatter or gmplot.GoogleMapPlotter.Marker. I used either and was able to get 465 points for a project that I was working on. Is it possible it is an API key issue for you?
partial snippet of my code
import gmplot
import pandas as pd
# df is the database with Lat, Lon and formataddress columns
# change to list, not sure you need to do this. I think you can cycle through
# directly using iterrows. I have not tried that though
latcollection=df['Lat'].tolist()
loncollection=df['Lon'].tolist()
addcollection=df['formataddress'].tolist()
# center map with the first co-ordinates
gmaps2 = gmplot.GoogleMapPlotter(latcollection[0],loncollection[0],13,apikey='yourKey')
for i in range(len(latcollection)):
gmaps2.marker(latcollection[i],loncollection[i],color='#FF0000',c=None,title=str(i)+' '+ addcollection[i])
gmaps2.draw(newdir + r'\laplot_marker_full.html')
I could hover over the 465th point since I knew approximately where it was and I was able to get the title with str(464) <formataddress(464)>, since my array is indexed from 0
Make sure you check the GitHub site to modify your gmplot file, in case you are working with windows.

Main figure legend outside of subplots

I have a number of subplots within a single figure. Each figure plots multiple lines that represent the same thing (represented by color) but in different situations (different subplots). I would like to create a legend at the base of the figure showing what the color of the line means. However, I running into a problem with getting the legend to not overlap the subplots and if I can adjust the axes, getting the legend to save.
I have tried a few different solutions with some help here but have been unable to adapt to subplots. Below is an example code that I am working with.
import numpy as np
import matplotlib.pyplot as plt
m1=1
m2=10
x=np.linspace(0,100,num=101,endpoint=True)
y1m1=m1*x**2
y2m1=m1*x**0.5
y1m2=m2*x**2
y2m2=m2*x**0.5
fig=plt.figure(figsize=(4,4))
ax1=fig.add_subplot(211)
ax1.plot(x,y1m1,'b',label=r'$x^2$')
ax1.plot(x,y2m1,'r',label=r'$\sqrt{x}$')
ax2=fig.add_subplot(212)
ax2.plot(x,y1m2,'b')
ax2.plot(x,y2m2,'r')
fig.legend(loc='lower center',ncol=2)
fig.tight_layout()
fig.savefig('examplefig.png',dpi=300)
plt.show()
My goal is to save the output to a png for a good figure.
This is one way of doing it using the suggestion provided here. The idea is to add the legend at position with respect to a given axis object. In your case, since you want to add the legend at the base, it is preferred you specify the position relative to ax2. Using ncol=2 is a matter of personal choice.
fig=plt.figure(figsize=(4,4))
ax1=fig.add_subplot(211)
l1, = ax1.plot(x,y1m1,'b')
l2, = ax1.plot(x,y2m1,'r')
ax2=fig.add_subplot(212)
ax2.plot(x,y1m2, 'b')
ax2.plot(x,y2m2, 'r')
ax2.legend(handles = [l1,l2] , labels=[r'$x^2$', r'$\sqrt{x}$'],
bbox_to_anchor=(0.7, -0.2), ncol=2)
fig.tight_layout()

Maximize figures before saving

The question about how to do maximize a window before saving has been asked several times and has several questions (still no one is portable, though), How to maximize a plt.show() window using Python
and How do you change the size of figures drawn with matplotlib?
I created a small function to maximize a figure window before saving the plots. It works with QT5Agg backend.
import matplotlib.pyplot as plt
def maximize_figure_window(figures=None, tight=True):
"""
Maximize all the open figure windows or the ones indicated in figures
Parameters
----------
figures: (list) figure numbers
tight : (bool) if True, applies the tight_layout option
:return:
"""
if figures is None:
figures = plt.get_fignums()
for fig in figures:
plt.figure(fig)
manager = plt.get_current_fig_manager()
manager.window.showMaximized()
if tight is True:
plt.tight_layout()
Problems:
I have to wait for the windows to be actually maximized before using the plt.savefig() command, otherwise it is saved with as not maximized. This is a problem if I simply want to use the above function in a script
(minor problems:)
2. I have to use the above function twice in order to get the tight_layout option working, i.e. the first time tight=True has no effect.
The solution is not portable. Of course I can add all the possible backend I might use, but that's kind of ugly.
Questions:
how to make the script wait for the windows to be maximized? I don't want to use time.sleep(tot_seconds) because tot_seconds would be kind of arbitrary and makes the function even less portable
how to solve problem 2 ? I guess it is related to problem 1.
is there a portable solution to "maximize all the open windows" problem?
-- Edit --
For problem 3. #DavidG suggestion sounds good. I use tkinter to automatically get width and height, convert them to inches, and use them in fig.set_size_inches or directly during the figure creation via fig = plt.figure(figsize=(width, height)).
So a more portable solution is, for example.
import tkinter as tk
import matplotlib.pyplot as plt
def maximize_figure(figure=None):
root = tk.Tk()
width = root.winfo_screenmmwidth() / 25.4
height = root.winfo_screenmmheight() / 25.4
if figure is not None:
plt.figure(figure).set_size_inches(width, height)
return width, height
where I allow the figure to be None so that I can use the function to just retrieve width and height and use them later.
Problem 1 is still there, though.
I use maximize_figure() in a plot function that I created (let's say my_plot_func()) but still the saved figure doesn't have the right dimensions when saved on file.
I also tried with time.sleep(5) in my_plot_func() right after the figure creation. Not working.
It works only if a manually run in the console maximize_figure() and then run my_plot_func(figure=maximized_figure) with the figure already maximized. Which means that dimension calculation and saving parameters are correct.
It does not work if I run in the console maximize_figure() and my_plot_func(figure=maximized_figure) altogether, i.e. with one call the the console! I really don't get why.
I also tried with a non-interactive backend like 'Agg', so that the figure doesn't get actually created on screen. Not working (wrong dimensions) no matter if I call the functions altogether or one after the other.
To summarize and clarify (problem 1):
by running these two pieces of code in console, figure gets saved correctly.
plt.close('all')
plt.switch_backend('Qt5Agg')
fig = plt.figure()
w, h = maximize_figure(fig.number)
followed by:
my_plot_func(out_file='filename.eps', figure=fig.number)
by running them together (like it would be in a script) figure is not saved correctly.
plt.close('all')
plt.switch_backend('Qt5Agg')
fig = plt.figure()
w, h = maximize_figure(fig.number)
my_plot_func(out_file='filename.eps', figure=fig.number)
Using
plt.switch_backend('Agg')
instead of
plt.switch_backend('Qt5Agg')
it does not work in both cases.

Python3x + MatPlotLib - Updating a chart?

I am new to both the python and matplotlib languages and working on something for my husband.
I hope you guys can help me out.
I would like to pull in a file using Open, read it, and update a graph with it's values.
Sounds easy enough right? Not so much in practice.
Here is what I have so far to open and chart the file. This works fine as it is to chart the file 1 time.
import matplotlib.pyplot as plt
fileopen = open('.../plotresults.txt', 'r').read()
fileopen = eval(fileopen) ##because the file contains a dict and security is not an issue.
print(fileopen) ## So I can see it working
for key,value in fileopen.items():
plot1 = value
plt.plot(plot1, label=str(key))
plt.legend()
plt.show()
Now I would like to animate the chart or update it so that I can see changes to the data. I have tried to use matplotlib's animation feature but it is advanced beyond my current knowledge.
Is there a simple way to update this chart, say every 5 minutes?
Note:
I tried using Schedule but it breaks the program (maybe a conflict between schedule and having matplotlib figures open??).
Any help would be deeply appreciated.
Unfortunately you will just waste time trying to get a clean solution without either using matplotlib's animation feature or using the matplotlib OO interface.
As a dirty hack you can use the following:
from threading import Timer
from matplotlib import pyplot as plt
import numpy
# Your data generating code here
def get_data():
data = numpy.random.random(100)
label = str(data[0]) # dummy label
return data, label
def update():
print('update')
plt.clf()
data, label = get_data()
plt.plot(data, label=label)
plt.legend()
plt.draw()
t = Timer(0.5, update) # restart update in 0.5 seconds
t.start()
update()
plt.show()
It spins off however a second thread by Timer. So to kill the script, you have to hit Ctrl-C twice on the console.
I myself would be interested if there is a cleaner way to do this in this simple manner in the confines of the pyplot machinery.
Edits in italic.

Resources