savefig('filename.png') saves a blank image in the folder - matplotlib

fig, ax = plt.xticks(datechange_index,datechange)
datacursor(display='single')
plt.ylabel('Temp in Kelvin')
plt.figure(figsize=(200,20))
plt.savefig('temp_plot.png')
plt.show()
The block of code above is what I tried to save a plot in matplotlib. However, it ends up generating a blank image. I've tried a few solutions commonly suggested (which is why I have plt.savefig now above plt.show(), to no avail.

#BigBen has answered this question:
plt.figure(figsize=(200,20)) creates a blank figure, right before you savefig
which explains why the OP initially was showing a blank figure.
Also, #BigBen went on further to flag that the naming convention used in the variables for the xticks function was misleading.

Related

Turn off x-axis marginal distribution axes on jointplot using seaborn package

There is a similar question here, however I fail to adapt the provided solutions to my case.
I want to have a jointplot with kind=hex while removing the marginal plot of the x-axis as it contains no information. In the linked question the suggestion is to use JointGrid directly, however Seaborn then seems to to be unable to draw the hexbin plot.
joint_kws = dict(gridsize=70)
g = sns.jointplot(data=all_data, x="Minute of Hour", y="Frequency", kind="hex", joint_kws=joint_kws)
plt.ylim([49.9, 50.1])
plt.xlim([0, 60])
g.ax_joint.axvline(x=30,ymin=49, ymax=51)
plt.show()
plt.close()
How to remove the margin plot over the x-axis?
Why is the vertical line not drawn?
Also is there a way to exchange the right margin to a plot which more clearly resembles the density?
edit: Here is a sample of the dataset (33kB). Read it with pd.read_pickle("./data.pickle")
I've been fiddling with an analog problem (using a scatterplot instead of the hexbin). In the end, the solution to your first point is awkwardly simple. Just add this line :
g.ax_marg_x.remove()
Regarding your second point, I've no clue as to why no line is plotted. But a workaround seems to be to use vlines instead :
g.ax_joint.vlines(x=30, ymin=49, ymax=51)
Concerning your last point, I'm afraid I haven't understood it. If you mean increasing/reducing the margin between the subplots, you can use the space argument stated in the doc.

Matplotlib video creation

EDIT: ImportanceOfBeingErnest provided the answer, however I am still inviting you all to explain, why is savefig logic different from animation logic.
I want to make a video in matplotlib. I went through manuals and examples and I just don't get it. (regarding matplotlib, I always copy examples, because after five years of python and two years of mathplotlib I still understand 0.0% of matplotlib syntax)
After half a dozen hours here is what I came up to. Well, I get empty video. No idea why.
import os
import math
import matplotlib
matplotlib.use("Agg")
from matplotlib import pyplot as plt
import matplotlib.animation as animation
# Set up formatting for the movie files
Writer = animation.writers['ffmpeg']
writer = Writer(fps=15, metadata=dict(artist='Me'), bitrate=1800)
numb=100
temp=[0.0]*numb
cont=[0.0]*numb
for i in range(int(4*numb/10),int(6*numb/10)):
temp[i]=2
cont[i]=2
fig = plt.figure()
plts=fig.add_subplot(1,1,1)
plts.set_ylim([0,2.1])
plts.get_xaxis().set_visible(False)
plts.get_yaxis().set_visible(False)
ims = []
for i in range(1,10):
line1, = plts.plot(range(0,numb),temp, linewidth=1, color='black')
line2, = plts.plot(range(0,numb),cont, linewidth=1, color='red')
# savefig is here for testing, works perfectly!
# fig.savefig('test'+str(i)+'.png', bbox_inches='tight', dpi=300)
ims.append([line1,line2])
plts.lines.remove(line1)
plts.lines.remove(line2)
for j in range(1,10):
tempa=0
for k in range (1,numb-1):
tempb=temp[k]+0.51*(temp[k-1]-2*temp[k]+temp[k+1])
temp[k-1]=tempa
tempa=tempb
temp[numb-1]=0
for j in range(1,20):
conta=0
for k in range (1,numb-1):
contb=cont[k]+0.255*(cont[k-1]-2*cont[k]+cont[k+1])
cont[k-1]=conta
conta=contb
cont[numb-1]=0
im_ani = animation.ArtistAnimation(fig, ims, interval=50, repeat_delay=3000,blit=True)
im_ani.save('im.mp4', writer=writer)
Can someone help me with this?
If you want to have a plot which is not empty, the main idea would be not to remove the lines from the plot.
That is, delete the two lines
plts.lines.remove(line1)
plts.lines.remove(line2)
If you delete these two lines the output will look something like this
[Link to orginial size animation]
Now one might ask, why do I not need to remove the artist in each iteration step, as otherwise all of the lines would populate the canvas at once?
The answer is that the ArtistAnimation takes care of this. It will only show those artists in the supplied list that correspond to the given time step. So while at the end of the for loop you end up with all the lines drawn to the canvas, once the animation starts they will all be removed and only one set of artists is shown at a time.
In such a case it is of course not a good idea to use the loop for saving the individual images as the final image would contain all of the drawn line at once,
The solution is then either to make two runs of the script, one for the animation, and one where the lines are removes in each timestep. Or, maybe better, use the animation istself to create the images.
im_ani.save('im.png', writer="imagemagick")
will create the images as im-<nr>.png in the current folder. It will require to have imagemagick installed.
I'm trying here to answer the two questions from the comments:
1. I have appended line1 and line2 before deleting them. Still they disappeared in the final result. How come?
You have appended the lines to a list. After that you removed the lines from the axes. Now the lines are in the list but not part of the axes. When doing the animation, matplotlib finds the lines in the list and makes them visible. But they are not in the axes (because they have been removed) so the visibility of some Line2D object, which does not live in any axes but only somewhere in memory, is changed. But that isn't reflected in the plot because the plot doesn't know this line any more.
2. If I understand right, when you issue line1, = plts.plot... command then the line1 plot object is added to the plts graph object. However, if you change the line1 plot object by issuing line1, = plts.plot... command again, matplotlib does change line1 object but before that saves the old line1 to the plts graph object permanently. Is this what caused my problem?
No. The first step is correct, line1, = plts.plot(..) adds a Line2D object to the axes. However, in a later loop step line1, = plts.plot() creates another Line2D object and puts it to the canvas. The initial Line2D object is not changed and it doesn't know that there is now some other line next to it in the plot. Therefore, if you don't remove the lines they will all be visible in the static plot at the end.

Remove margins from a matplotlib figure

I'd like to plot a NumPy array using imshow in matplotlib and save it as a JPEG image. However, I can't manage to remove margins/paddings/borders from the image.
My code:
plt.imshow(np.arange(20).reshape(5,4)) ;
plt.axis('off')
plt.savefig('test.jpg', bbox_inches='tight', pad_inches=0, facecolor='black')
I've followed all recommendations that I could find here on Stackoverflow but none of them would help removing uneven white borders (I made them black in this figure) seen below:
setting pad_inches = -1 solved this for me (saving as png).
I suspect the pad_inches=0 was being interpreted as "falsey" and ignored
As it was described in this answer: https://stackoverflow.com/a/26610602/265289, it's important to also call:
fig.axes.get_xaxis().set_visible(False)
fig.axes.get_yaxis().set_visible(False)
alongside pad_inches=0. This removes the extra space to the left and bottom of the image.

matplotlib duplicate axes or figure?

I have several lines, bars and artists in one figure/axes. I would like to add different sets of components onto the original figure/axes. Is that possible to duplicate all the objects of the original figure/axes into another figure/axes without redraw everything by code?
One way will be remove all the new added components before drawing another set of components. However, if I want to put several axes into one figure, this will not work. Some discussion has been done here. But it copy/add all objects one by one, which is not better than redraw everything when there are many objects.
#Greg, thanks a lot for replying. If I just plot out data, it will be simple, just replot the data, or even copy some lines. However, this figures contain lots of artists, which can also be added by user through GUI interface, or bot by on-the-fly script, their types I might not known at runtime. And the plot are generated on-the-fly. Of course, I can try to copy all the data, record all the artists type, properties and replot them again. But it just too much and involved in modifying the software which generates those figures. Maybe I can loop through all possible objects do copy and add_xxx. But I hoope there will be a better way.
Thanks to #Joe Kington and his post: "add an axes instance to another figure".
I wrapped up a way to duplicate the axes and insert the axes into a subplot:
def test_pickleAxes():
import pickle
import numpy as npy
x = npy.arange(0,4*npy.pi,0.2)
y = npy.sin(x)
fig, ax = plt.subplots()
p = pickle.dumps(ax)
ax2 = pickle.loads(p)
ax.change_geometry(2,1,1)
ax2.change_geometry(2,1,2)
fig._axstack.add(fig._make_key(ax2), ax2)
plt.show()
However, in most cases it is seems no better than blit so far. Why is that? Because the pickle of the axes is actually the pickle of the entire figure. When unpickle it, it will create a new figure and the loaded axes instance will associate with it. Even we managed to add the axes into the old figure. ax2 are still ONLY associate with the new figure. Thus when we try to interact with the old figure, the ax2 will not interact. Instead, if we scale/pan the new figure, ax2 in both figures will change. If we just save svg or pdf file, it seems a quite OK solution.
Still try to find a way to decouple the ax2 from new figure and make it couple with the old one.

Matplotlib annotate doesn't work on log scale?

I am making log-log plots for different data sets and need to include the best fit line equation. I know where in the plot I should place the equation, but since the data sets have very different values, I'd like to use relative coordinates in the annotation. (Otherwise, the annotation would move for every data set.)
I am aware of the annotate() function of matplotlib, and I know that I can use textcoords='axes fraction' to enable relative coordinates. When I plot my data on the regular scale, it works. But then I change at least one of the scales to log and the annotation disappears. I get no error message.
Here's my code:
plt.clf()
samplevalues = [100,1000,5000,10^4]
ax = plt.subplot(111)
ax.plot(samplevalues,samplevalues,'o',color='black')
ax.annotate('hi',(0.5,0.5), textcoords='axes fraction')
ax.set_xscale('log')
ax.set_yscale('log')
plt.show()
If I comment out ax.set_xcale('log') and ax.set_ycale('log'), the annotation appears right in the middle of the plot (where it should be). Otherwise, it doesn't appear.
Thanks in advance for your help!
It may really be a bug as pointed out by #tcaswell in the comment but a workaround is to use text() in axis coords:
plt.clf()
samplevalues = [100,1000,5000,10^4]
ax = plt.subplot(111)
ax.loglog(samplevalues,samplevalues,'o',color='black')
ax.text(0.5, 0.5,'hi',transform=ax.transAxes)
plt.show()
Another approach is to use figtext() but that is more cumbersome to use if there are already several plots (panels).
By the way, in the code above, I plotted the data using log-log scale directly. That is, instead of:
ax.plot(samplevalues,samplevalues,'o',color='black')
ax.set_xscale('log')
ax.set_yscale('log')
I did:
ax.loglog(samplevalues,samplevalues,'o',color='black')