Change figsize in matplotlib polar contourf - matplotlib

I am using the following example Example to create two polar contour subplots. When I create as the pdf there is a lot of white space which I want to remove by changing figsize.
I know how to change figsize usually but I am having difficulty seeing where to put it in this code example. Any guidance or hint would be greatly appreciated.
Many thanks!
import numpy as np
import matplotlib.pyplot as plt
#-- Generate Data -----------------------------------------
# Using linspace so that the endpoint of 360 is included...
azimuths = np.radians(np.linspace(0, 360, 20))
zeniths = np.arange(0, 70, 10)
r, theta = np.meshgrid(zeniths, azimuths)
values = np.random.random((azimuths.size, zeniths.size))
#-- Plot... ------------------------------------------------
fig, ax = plt.subplots(subplot_kw=dict(projection='polar'))
ax.contourf(theta, r, values)
plt.show()

Another way to do this would be to use the figsize kwarg in your call to plt.subplots.
fig, ax = plt.subplots(figsize=(6,6), subplot_kw=dict(projection='polar')).
Those values are in inches, by the way.

You can easily just put plt.figsize(x,y) at the beginning of the code, and it will work. plt.figsize changes the size of all future plots, not just the current plot.
However, I think your problem is not what you think it is. There tends to be quite a bit of whitespace in generated PDFs unless you change options around. I usually use
plt.savefig( 'name.pdf', bbox_inches='tight', pad_inches=0 )
This gives as little whitespace as possible. bbox_inches='tight' tries to make the bounding box as small as possible, while pad_inches sets how many inches of whitespace there should be padding it. In my case I have no extra padding at all, as I add padding in whatever I'm using the figure for.

Related

How to gain control over annotate arrows

I'm trying to insert arrows (brackets) in plots using the annotate package, but I cannot figure out what the input parameters mean. I read the documentation and I'm still unsure of how to control the arrows. Here's an example starting point:
import matplotlib.pyplot as pl
import numpy as np
fig = pl.figure(figsize=(3.25, 2.5))
ax0 = fig.add_subplot(111)
x, y = np.arange(10), np.arange(10) * -1
for offset in range(5):
ax0.plot(x + offset, y, lw=1)
# add annotation arrow
bbox = dict(facecolor="w",
alpha=0.95,
ls="None",
boxstyle="round",
pad=0.1)
ax0.annotate(text="Example",
xy=(7.5, -5),
xytext=(0, -9),
arrowprops=dict(arrowstyle="-[",
linewidth=1,
connectionstyle="arc,armA=90,angleA=0,angleB=-40,armB=85,rad=0"),
verticalalignment="bottom",
horizontalalignment="left",
fontsize=8,
bbox=bbox)
fig.show()
I want the bracket to span the width of all of the drawn lines (as if to say "these" lines are what the annotation refers to), but I cannot figure out how to change the bracket width.
Another issue is interpreting armA and armB (the arrow lines currently look ugly). I understand these refer to the length of line segments, but I cannot figure out what the units are (pixels?), much less how to automate generating their lengths.
Can you please provide guidance on how to adjust the width of the bracket and what the connectionstyle parameters mean? If this is documented somewhere I would appreciate the reference (even if it comes with a RTFM-type comment).
I think the parameter you want is mutation_scale.
I changed your annotate command to this and I think it looks reasonable now, but it took some manual adjustment. If you had a consistent pattern in multiple figures you could probably calculate the angles and lengths that you want and use them as inputs, but for your example this seems to work reasonably well.
ax0.annotate(text="Example",
xy=(8.5, -6.5),
xytext=(0, -9),
arrowprops=dict(arrowstyle="-[",
linewidth=1,
mutation_scale=22,
connectionstyle="arc,armA=70, \
angleA=0, \
angleB=-45, \
armB=50, \
rad=0"), \
verticalalignment="bottom",
horizontalalignment="left",
fontsize=8,
bbox=bbox)

How do I use colourmaps with variable alpha in a Seaborn kdeplot without seeing the contour lines?

Python version: 3.6.4 (Anaconda on Windows)
Seaborn: 0.8.1
Matplotlib: 2.1.2
I'm trying to create a 2D Kernel Density plot using Seaborn but I want each step in the colourmap to have a different alpha value. I had a look at this question to create a matplotlib colourmap with alpha values: Add alpha to an existing matplotlib colormap.
I have a problem in that the lines between contours are visible. The result I get is here:
I thought that I had found the answer when I found this question: Hide contour linestroke on pyplot.contourf to get only fills. I tried the method outlined in the answer (using set_edgecolor("face") but it did not work in this case. That question also seemed to be related to vector graphics formats and I am just writing out a PNG.
Here is my script:
import numpy as np
import seaborn as sns
import matplotlib.colors as cols
import matplotlib.pyplot as plt
def alpha_cmap(cmap):
my_cmap = cmap(np.arange(cmap.N))
# Set a square root alpha.
x = np.linspace(0, 1, cmap.N)
my_cmap[:,-1] = x ** (0.5)
my_cmap = cols.ListedColormap(my_cmap)
return my_cmap
xs = np.random.uniform(size=100)
ys = np.random.uniform(size=100)
kplot = sns.kdeplot(data=xs, data2=ys,
cmap=alpha_cmap(plt.cm.viridis),
shade=True,
shade_lowest=False,
n_levels=30)
plt.savefig("example_plot.png")
Guided by some comments on this question I have tried some other methods that have been successful when this problem has come up. Based on this question (Matplotlib Contourf Plots Unwanted Outlines when Alpha < 1) I have tried altering the plot call to:
sns.kdeplot(data=xs, data2=ys,
cmap=alpha_cmap(plt.cm.viridis),
shade=True,
shade_lowest=False,
n_levels=30,
antialiased=True)
With antialiased=True the lines between contours are replaced by a narrow white line:
I have also tried an approach similar to this question - Pyplot pcolormesh confused when alpha not 1. This approach is based on looping over the PathCollections in kplot.collections and tuning the parameters of the edges so that they become invisible. I have tried adding this code and tweaking the linewidth -
for thing in kplot.collections:
thing.set_edgecolor("face")
thing.set_linewidth(0.01)
fig.canvas.draw()
This results in a mix of white and dark lines - .
I believe that I will not be able to tune the line width to make the lines disappear because of the variable width of the contour bands.
Using both methods (antialiasing + linewidth) makes this version, which looks cool but isn't quite what I want:
I also found this question - Changing Transparency of/Remove Contour Lines in Matplotlib
This one suggests overplotting a second plot with a different number of contour levels on the same axis, like:
kplot = sns.kdeplot(data=xs, data2=ys,
ax=ax,
cmap=alpha_cmap(plt.cm.viridis),
shade=True,
shade_lowest=False,
n_levels=30,
antialiased=True)
kplot = sns.kdeplot(data=xs, data2=ys,
ax=ax,
cmap=alpha_cmap(plt.cm.viridis),
shade=True,
shade_lowest=False,
n_levels=35,
antialiased=True)
This results in:
This is better, and almost works. The problem here is I need variable (and non-linear) alpha throughout the colourmap. The variable banding and lines seem to be a result of the combinations of alpha when contours are plotted over each other. I also still see some clear/white lines in the result.

How to change Bar-Chart Figure Size [duplicate]

I can't figure out how to rotate the text on the X Axis. Its a time stamp, so as the number of samples increase, they get closer and closer until they overlap. I'd like to rotate the text 90 degrees so as the samples get closer together, they aren't overlapping.
Below is what I have, it works fine with the exception that I can't figure out how to rotate the X axis text.
import sys
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import datetime
font = {'family' : 'normal',
'weight' : 'bold',
'size' : 8}
matplotlib.rc('font', **font)
values = open('stats.csv', 'r').readlines()
time = [datetime.datetime.fromtimestamp(float(i.split(',')[0].strip())) for i in values[1:]]
delay = [float(i.split(',')[1].strip()) for i in values[1:]]
plt.plot(time, delay)
plt.grid(b='on')
plt.savefig('test.png')
This works for me:
plt.xticks(rotation=90)
Many "correct" answers here but I'll add one more since I think some details are left out of several. The OP asked for 90 degree rotation but I'll change to 45 degrees because when you use an angle that isn't zero or 90, you should change the horizontal alignment as well; otherwise your labels will be off-center and a bit misleading (and I'm guessing many people who come here want to rotate axes to something other than 90).
Easiest / Least Code
Option 1
plt.xticks(rotation=45, ha='right')
As mentioned previously, that may not be desirable if you'd rather take the Object Oriented approach.
Option 2
Another fast way (it's intended for date objects but seems to work on any label; doubt this is recommended though):
fig.autofmt_xdate(rotation=45)
fig you would usually get from:
fig = plt.gcf()
fig = plt.figure()
fig, ax = plt.subplots()
fig = ax.figure
Object-Oriented / Dealing directly with ax
Option 3a
If you have the list of labels:
labels = ['One', 'Two', 'Three']
ax.set_xticks([1, 2, 3])
ax.set_xticklabels(labels, rotation=45, ha='right')
In later versions of Matplotlib (3.5+), you can just use set_xticks alone:
ax.set_xticks([1, 2, 3], labels, rotation=45, ha='right')
Option 3b
If you want to get the list of labels from the current plot:
# Unfortunately you need to draw your figure first to assign the labels,
# otherwise get_xticklabels() will return empty strings.
plt.draw()
ax.set_xticks(ax.get_xticks())
ax.set_xticklabels(ax.get_xticklabels(), rotation=45, ha='right')
As above, in later versions of Matplotlib (3.5+), you can just use set_xticks alone:
ax.set_xticks(ax.get_xticks(), ax.get_xticklabels(), rotation=45, ha='right')
Option 4
Similar to above, but loop through manually instead.
for label in ax.get_xticklabels():
label.set_rotation(45)
label.set_ha('right')
Option 5
We still use pyplot (as plt) here but it's object-oriented because we're changing the property of a specific ax object.
plt.setp(ax.get_xticklabels(), rotation=45, ha='right')
Option 6
This option is simple, but AFAIK you can't set label horizontal align this way so another option might be better if your angle is not 90.
ax.tick_params(axis='x', labelrotation=45)
Edit:
There's discussion of this exact "bug" but a fix hasn't been released (as of 3.4.0):
https://github.com/matplotlib/matplotlib/issues/13774
Easy way
As described here, there is an existing method in the matplotlib.pyplot figure class that automatically rotates dates appropriately for you figure.
You can call it after you plot your data (i.e.ax.plot(dates,ydata) :
fig.autofmt_xdate()
If you need to format the labels further, checkout the above link.
Non-datetime objects
As per languitar's comment, the method I suggested for non-datetime xticks would not update correctly when zooming, etc. If it's not a datetime object used as your x-axis data, you should follow Tommy's answer:
for tick in ax.get_xticklabels():
tick.set_rotation(45)
Try pyplot.setp. I think you could do something like this:
x = range(len(time))
plt.xticks(x, time)
locs, labels = plt.xticks()
plt.setp(labels, rotation=90)
plt.plot(x, delay)
Appart from
plt.xticks(rotation=90)
this is also possible:
plt.xticks(rotation='vertical')
I came up with a similar example. Again, the rotation keyword is.. well, it's key.
from pylab import *
fig = figure()
ax = fig.add_subplot(111)
ax.bar( [0,1,2], [1,3,5] )
ax.set_xticks( [ 0.5, 1.5, 2.5 ] )
ax.set_xticklabels( ['tom','dick','harry'], rotation=45 ) ;
If you want to apply rotation on the axes object, the easiest way is using tick_params. For example.
ax.tick_params(axis='x', labelrotation=90)
Matplotlib documentation reference here.
This is useful when you have an array of axes as returned by plt.subplots, and it is more convenient than using set_xticks because in that case you need to also set the tick labels, and also more convenient that those that iterate over the ticks (for obvious reasons)
If using plt:
plt.xticks(rotation=90)
In case of using pandas or seaborn to plot, assuming ax as axes for the plot:
ax.set_xticklabels(ax.get_xticklabels(), rotation=90)
Another way of doing the above:
for tick in ax.get_xticklabels():
tick.set_rotation(45)
My answer is inspired by cjohnson318's answer, but I didn't want to supply a hardcoded list of labels; I wanted to rotate the existing labels:
for tick in ax.get_xticklabels():
tick.set_rotation(45)
The simplest solution is to use:
plt.xticks(rotation=XX)
but also
# Tweak spacing to prevent clipping of tick-labels
plt.subplots_adjust(bottom=X.XX)
e.g for dates I used rotation=45 and bottom=0.20 but you can do some test for your data
import pylab as pl
pl.xticks(rotation = 90)
To rotate the x-axis label to 90 degrees
for tick in ax.get_xticklabels():
tick.set_rotation(45)
It will depend on what are you plotting.
import matplotlib.pyplot as plt
x=['long_text_for_a_label_a',
'long_text_for_a_label_b',
'long_text_for_a_label_c']
y=[1,2,3]
myplot = plt.plot(x,y)
for item in myplot.axes.get_xticklabels():
item.set_rotation(90)
For pandas and seaborn that give you an Axes object:
df = pd.DataFrame(x,y)
#pandas
myplot = df.plot.bar()
#seaborn
myplotsns =sns.barplot(y='0', x=df.index, data=df)
# you can get xticklabels without .axes cause the object are already a
# isntance of it
for item in myplot.get_xticklabels():
item.set_rotation(90)
If you need to rotate labels you may need change the font size too, you can use font_scale=1.0 to do that.

Better ticks and tick labels with log scale

I am trying to get better looking log-log plots and I almost got what I want except for a minor problem.
The reason my example throws off the standard settings is that the x values are confined within less than one decade and I want to use decimal, not scientific notation.
Allow me to illustrate with an example:
import matplotlib.pyplot as plt
%matplotlib inline
import matplotlib as mpl
import numpy as np
x = np.array([0.6,0.83,1.1,1.8,2])
y = np.array([1e-5,1e-4,1e-3,1e-2,0.1])
fig1,ax = plt.subplots()
ax.plot(x,y)
ax.set_xscale('log')
ax.set_yscale('log')
which produces:
There are two problems with the x axis:
The use of scientific notation, which in this case is counterproductive
The horrible "offset" at the lower right corner
After much reading, I added three lines of code:
ax.xaxis.set_major_formatter(mpl.ticker.ScalarFormatter())
ax.xaxis.set_minor_formatter(mpl.ticker.ScalarFormatter())
ax.ticklabel_format(style='plain',axis='x',useOffset=False)
This produces:
My understanding of this is that there are 5 minor ticks and 1 major one. It is much better, but still not perfect:
I would like some additional ticks between 1 and 2
Formatting of label at 1 is wrong. It should be "1.0"
So I inserted the following line before the formatter statement:
ax.xaxis.set_major_locator(mpl.ticker.MultipleLocator(0.2))
I finally get the ticks I want:
I now have 8 major and 2 minor ticks. Now, this almost looks right except for the fact that the tick labels at 0.6, 0.8 and 2.0 appear bolder than the others. What is the reason for this and how can I correct it?
The reason, some of the labels appear bold is that they are part of the major and minor ticklabels. If two texts perfectly overlap, they appear bolder due to the antialiasing.
You may decide to only use minor ticklabels and set the major ones with a NullLocator.
Since the locations of the ticklabels you wish to have is really specific there is no automatic locator that would provide them out of the box. For this special case it may be easiest to use a FixedLocator and specify the labels you wish to have as a list.
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import numpy as np
x = np.array([0.6,0.83,1.1,1.8,2])
y = np.array([1e-5,1e-4,1e-3,1e-2,0.1])
fig1,ax = plt.subplots(dpi=72, figsize=(6,4))
ax.plot(x,y)
ax.set_xscale('log')
ax.set_yscale('log')
locs = np.append( np.arange(0.1,1,0.1),np.arange(1,10,0.2))
ax.xaxis.set_minor_locator(ticker.FixedLocator(locs))
ax.xaxis.set_major_locator(ticker.NullLocator())
ax.xaxis.set_minor_formatter(ticker.ScalarFormatter())
plt.show()
For a more generic labeling, one could of course subclass a locator, but we would then need to know the logic to use to determine the ticklabels. (As I do not see a well defined logic for the desired ticks from the question, I feel it would be wasted effort to provide such a solution for now.)

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')