Save a figure with multiple extensions? - matplotlib

In matplotlib, is there any (especially clever) way to save a figure with multiple extensions?
Use case: I generally want .png's for quick-look examination, uploading to the web, etc. But for publication-quality figures, I want .pdf or .eps files. Often, I want all 3.
It's not hard to do:
for suffix in 'png eps pdf'.split():
pl.savefig(figname+"."+suffix)
but it does involve a lot of rewriting code (since I generally just have savefig(figname+'.png') everywhere right now), and this seems like an easy case for a convenient wrapper function.

If you always do
from matplotlib import pyplot as pl
...
pl.savefig
then you could concievably reassign pl.savefig in one place and it would affect all places.
from matplotlib import pyplot as pl
def save_figs(fn,types=('.pdf',)):
fig = pl.gcf()
for t in types:
fig.savefig(fn+t)
pl.savefig = save_figs
If you usually do
fig=pl.figure()
fig.savefig(...)
then that will require more effort.

You can specify the file type as shown in the documentation for savefig by using format. But as far as I know you can only specify a single format.
While not especially clever, to minimised rewriting code you should write a save_fig function. This can then handle automatically creating appropriate file names and a quality.
def save_fig(fig_name, quality="quick-look"):
if "quick-look" in quality:
# save fig with .png etc
if "pub-qual" in quality:
# save fig with increased dpi etc etc.
Probably not the answer you're looking for as it will require you to rewrite every savefig however I feel this kind of thing can help reduce the amount of repeated code overall.

Related

How to make pandas show the entire dataframe without cropping it by columns?

I am trying to represent cubic spline interpolation information for function f(x) as a dataframe.
When trying to print into a spyder, I find that the columns are being cut off. When trying to reproduce the output in Jupiter Lab, I got the same thing.
When I ran in ipython via terminal I got the desired full dataframe output.
I searched the integnet and tried the pandas commands setting options pd.set_options(), but nothing came of it.
I attach a screenshot with the output in ipython.
In Juputer can use:
from IPython.display import display, HTML
and instead of
print(dataframe)
use of in anyway place
display(HTML(dataframe.to_html()))
This will create a nice table.
Unfortunately, this will not work in the spyder. So you can try to adjust the width of the ipython were suggested. But in most cases this will make the output poorly or unreadable.
After trying the dataframe methods, I found what appears to be a cropping setting.
In Spyder I used:
pd.set_option('expand_frame_repr', False)
print(dataframe)
This method explains why increasing max_column didn't help me previously.
You can specify a maximum number for rows or columns using pd.set_options(display.max_columns=1000)
But you don't have to set an arbitrary value, but rather use None instead to make sure every size will be covered.
For rows, use:
pd.set_option('display.max_rows', None)
And for columns, use:
pd.set_option('display.max_columns', None)
It is a result of the display width. You can use the following set_options():
pd.set_options(display.width=1000) #make huge
You may also have to raise max columns but it should be smart enough to adjust automatically after you make width bigger:
pd.set_options(display.max_columns=None)

save pyplot figure "as figure" (not as image)

How can I save a figure using PyPlot in Julia, so that the figure can be reloaded as a figure later in Julia? (not as an image)
You can use serialize to store any Julia object. This beautifully works for plots as well.
Let us start by generating a plot:
using Plots
pyplot()
p = plot(rand(10));
using Serialization
Serialization.serialize("myfile.jld", p);
Note that you need a semicolon after plot command so it does not appear on the screen.
Let us now read the plot (to have a full test I ended the previous Julia session and started a new one):
using Plots
pyplot();
using Serialization
p2 = Serialization.deserialize("myfile.jld");
In order to display it now it is enough to type in REPL:
julia> p2
You might want also want to use plain PyPlot (I strongly recommend Plots for flexibility). In that case your best bet is to follow rules described in object-oriented API of Matplotlib:
using PyPlot
ioff()
fig = subplot()
fig.plot(rand(10))
fig.set_title("Hello world")
using Serialization
serialize("pp.jld", fig)
In order to plot de-serialize back the object:
using PyPlot
ioff()
using Serialization
fig = deserialize("pp.jld")
show()
Finally, note that the serialization is good only for short term storage. If anything changes (e.g. you update Julia packages) you might not be able to de-serialize the plot.
Hence another good alternative for processable plots are saving them to LaTeX or SVG format - both is possible in Julia.

How do I achieve consistent appearance in different backends in HoloViews?

I'm mystified by how to use HoloViews styles to customize plots and achieve a consistent appearance across backends. HoloViews is billed as a package that provides an abstraction layer to several backends, notably Bokeh and Matplotlib, but I'm completely failing in my attempts to get plots generated using these backends to look the same. Settings in one backend are ignored by another, and each backend has many (most) formatting options missing, so that it is necessary to break through the abstraction to lower level calls directly the the backends.
I suspect I'm just missing something or have failed to discover the appropriate documentation.
The code below for example (using settings that don't attempt to produce the same appearance, but expose some of the issues) results in Matplotlib figures (right) that
ignore the attempt to achieve a uniform appearance for scatter plot point color,
ignore the attempt to override the color of histogram bars,
have marginal histograms with axis labels that are explicitly removed in the Bokeh versions (left),
have marginal histograms that are not framed and lack the vertical axis present in the Bokeh version,
have no control or customizations over the styling of axes, and
have additional subplot labels not present on the Bokeh plot.
In addition, there are many further customizations to both backend's plots (gridlines, frame color, for example) that I can't find settings for.
How do I set styles in HoloViews to achieve full and consistent control over plots produced by Bokeh and Matplotlib?
import numpy as np
import pandas as pd
import holoviews as hv
hv.extension('bokeh', 'matplotlib')
ds = hv.Dataset({'x': np.random.randn(100), 'y1': np.random.randn(100), 'y2': np.random.randn(100), 'y3': np.random.randn(100)},
['x'],['y1', 'y2', 'y3'])
def mpl_style_hook(plot, element):
# Settings required here are neither complete, nor do they correspond directly to the backend's naming
# Where is the correspondence between handles and the backend's names documented?
pass
def bok_style_hook(plot, element):
# Such a small set of abstractions is provided, it is almost always necessary to resort to hooks
plot.state.title.align = "center"
plot.handles['xaxis'].axis_label_text_color = 'red'
plot.handles['yaxis'].axis_label_text_color = 'green'
plot.handles['xaxis'].axis_label_text_font_style = "normal"
plot.handles['yaxis'].axis_label_text_font_style = "normal"
# Attempt to set options that apply to both backends; but ignored by Matplotlib
hv.opts.defaults(hv.opts.Scatter(color='green'), hv.opts.Histogram(fill_color='yellow'))
# Explictily set backend to avoid warnings (`backend=` isn't sufficient)
hv.Store.current_backend = 'bokeh'
hv.opts.defaults(
hv.opts.Scatter(line_color='orange', size=6, fill_alpha=1.0, hooks=[bok_style_hook]),
hv.opts.Histogram(fill_color='cyan', fill_alpha=0.9, line_width=1, line_color='gray', hooks=[bok_style_hook]),
backend='bokeh')
hv.Store.current_backend = 'matplotlib'
hv.opts.defaults(
hv.opts.Scatter(hooks=[mpl_style_hook]),
# Histogram color ignored
hv.opts.Histogram(color='orange', hooks=[mpl_style_hook]),
backend='matplotlib')
hv.Store.current_backend = 'bokeh'
s1 = hv.Scatter(ds, 'x', 'y1').opts(hv.opts.Scatter(labelled=[None, 'y'])).hist(num_bins=51, dimension=['x','y1'])
s2 = hv.Scatter(ds, 'x', 'y2').opts(hv.opts.Scatter(labelled=[None, 'y'])).hist(num_bins=51, dimension='y2')
s3 = hv.Scatter(ds, 'x', 'y3').hist(num_bins=51, dimension='y3')
p = (s1 + s2 + s3).opts(hv.opts.Histogram(labelled=[None, None]), hv.opts.Layout(shared_axes=True)).cols(1)
hv.save(p, '_testHV.html', backend='bokeh')
hv.save(p, '_testHV.png', backend='matplotlib')
p
I don't think you're missing anything in terms of actual software support; what you're missing is that HoloViews in no way promises to make it simple to make plots from different backends to look the same. The plots are meant to show the same data in roughly the same way, but the backends each work in different ways, and some of those differences are in fact reasons to choose that particular backend over another.
There are certainly ways that HoloViews could map from an abstract notion of styling into the details of how that's done in different backends, but that's surprisingly tricky. And very few users ask for that; most pick their favorite backend and just use it, and would rather we spend our limited development time working on other features.
That said, if the backends can produce similar plots, you should be able to work out settings for use with HoloViews that will generate them in matching form. To do this, you'd work out the settings one backend at a time, then apply them per backend. E.g. .opts(line_width=3, backend='bokeh').opts(linewidth=4.5, backend='matplotlib'), with the appropriate option being used when that object is displayed by each backend. Here the two options differ only by one character in their names, but they work very differently for such a seemingly simple concept of line width: matplotlib accepts a width in "points" (which depends on dpi and knowing the absolute size in inches), while bokeh accepts pixels in screen space. They are both widths, but there's not necessarily any direct way to compare the two values, as it depends on separate settings you may have done for dpi and fig_size. You should be able to get it to look similar with enough effort, but trying to achieve that across all plots for all time is a massive task that would need some separate funding and developers to achieve! Still, it's already much easier to do that in HoloViews than it would be to completely rewrite a plot between Matplotlib and Bokeh natively, so HoloViews is still helping a good bit, just not solving everything for you...

Aliasing when saving matplotlib filled contour plot to .pdf or .eps

I'm generating a filed contour plot with the matplotlib.pyplot.contourf() function. The arguments in the call to the function are:
contourf(xvec,xvec,w,levels,cmap=matplotlib.cm.jet)
where
xvec = numpy.linspace(-3.,3.,50)
levels = numpy.linspace(-0.01,0.25,100)
and w is my data.
The resulting plot looks pretty good on screen, but when I save to pdf using a call to matplotlib.pyplot.savefig(), the resulting pdf has a lot of aliasing (I think that is what it is) going on. The call to savefig is simply savefig('filename.pdf'). I have tried using the dpi argument, but without luck. A call to matplotlib.get_backend() spits out 'TkAgg'.
I will attach a figure saved as pdf, compared to a figure saved as png (similar to what it looks like on screen) to demonstrate the problem:
png wihtout aliasing: https://dl.dropbox.com/u/6042643/wigner_g0.17.png
pdf with aliasing: https://dl.dropbox.com/u/6042643/wigner_g0.17.pdf
Please let me know if there are any other details I could give to help you give an answer. I should mention that saving as .eps gives similar bad results as saving to pdf. But the pdf shows the problem even clearer. My goal is to end up with a production quality .eps that I can attach to a latex document to be published as a scientific paper. I would be happy with some kind of work around where I save in one format, then convert it, if I can find a way that gives satisfying results.
Best,
Arne
After using the useful answer by #pelson for a while, I finally found a proper solution to this long-standing problem (currently in Matplotlib 3), which does not require multiple calls to contour or rasterizing the figure.
I refer to my original answer here for a more extensive explanation and examples.
In summary, the solution consists of the following lines:
cnt = plt.contourf(x, y, z)
for c in cnt.collections:
c.set_edgecolor("face")
plt.savefig('test.pdf')
I had no idea that contouring in pdf was so bad. You're right, I think the contours are being anti-aliased by the PDF renderers outside of matplotlib. It is for this reason I think you need to be particularly careful which application you use to view the resulting PDF - the best behaviour I have seen is with GIMP, but I'm sure there are plenty of other viewers which perform well.
To fix this problem (when viewing the PDF with GIMP), I was able to "rasterize" the contours produced with matplotlib to avoid the ugly white line problem:
import matplotlib.pyplot as plt
import numpy as np
xs, ys = np.mgrid[0:30, 0:40]
data = (xs - 15) ** 2 + (ys - 20) ** 2 + (np.sin(ys) + 10) ** 2
cs = plt.contourf(xs, ys, data, 60, cmap='jet')
# Rasterize the contour collections
for c in cs.collections:
c.set_rasterized(True)
plt.savefig('test.pdf')
This produced a contour plot which did not exhibit the problems you've shown.
Another alternative, perhaps better, approach, would be to fool the anti-aliasing by putting coloured lines below the contourf.
import matplotlib.pyplot as plt
import numpy as np
xs, ys = np.mgrid[0:30, 0:40]
data = (xs - 15) ** 2 + (ys - 20) ** 2 + (np.sin(ys) + 10) ** 2
# contour the plot first to remove any AA artifacts
plt.contour(xs, ys, data, 60, cmap='jet', lw=0.1)
cs = plt.contourf(xs, ys, data, 60, cmap='jet')
plt.savefig('test.pdf')
I should note that I don't see these problems if I save the figure as a ".ps" rather than a ".pdf" - perhaps that is a third alternative.
Hope this helps you get the paper looking exactly how you want it.

eps cmyk color matplotlib output

I need to output my plots in EPS with the CMYK color space. Unfortunately this particular format is requested by the journal I am submitting my work to!
This discussion was the only one I could find that has addressed the issue but it is more than 2 years old. I was hoping there might be some updates fixing the problem by now.
All my programming is in Python3 and so far I have been saving my plots in PDF which had no problem. But now that I want to plot EPS there is a problem. For example the code bellow prints the simple plot in .png and .pdf but the .eps output is totally blank!
import numpy as np
import matplotlib.pyplot as plt
X=[1,2,3]
Y=[4,5,6]
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(X,Y)
fig.savefig('test.eps')
fig.savefig('test.pdf')
fig.savefig('test.png')
So I have two questions:
How can I fix the eps output?
How can I set the eps output color space to CMYK?
Thanks in advance.
I too have the same problem. One workaround I have found is to save plots as .svg and then use a program like Inkscape to convert to eps. I used to be able to save in .eps without any issues and then lost the ability after an update.
Update I was able to solve this problem for my specific setup by changing a few lines in my .matplotlibrc, so I will post the relevant lines here in the hope that it may be helpful to you as well. Note this requires that you have xpdf and ghostscript already installed.
For me the important one was
##Saving Figures
ps.usedistiller : xpdf
But I also have
path.simplify : True
savefig.format : eps
Now I am able to save directly to .eps and include them in LaTeX'ed journal articles...