Better ticks and tick labels with log scale - matplotlib

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

Related

Matplotlib increase the number of minor ticks

When I turn on minor ticks in a plot with something like plt.minorticks_on(), I often want to have a larger number of minor ticks.
Is there a simple method to achieve that?
I found a good answer to my question, so in order to be able to find it more quickly next time, I'm including it here:
To have minor ticks every 10 and major ticks every 100 on the x-axis:
import matplotlib.pyplot as plt
from matplotlib.ticker import MultipleLocator
plt.plot(my_data)
plt.minorticks_on()
ax = plt.gca()
ax.xaxis.set_major_locator(MultipleLocator(100)) # major ticks every 100 (optional)
ax.xaxis.set_minor_locator(MultipleLocator(10)) # minor ticks every 10
In my original plot, ticks defaulted to 100 for major ticks and 20 for minor (5 minor for every major). With this code I get 10 minor ticks for every major.
This is not quite what I was after, but makes it easy enough to get the desired effect.

plt.imshow(Z,norm=logNorm()) gives grey outline when Z=0

Sorry for no pictures, but this code reproduces the problem:
x=np.random.randn(1000)
y=np.random.randn(1000)
h,_,_=np.histogram2d(x,y)
plt.imshow(h, norm=LogNorm(), cmap=plt.cm.Greys)
I would expect a smooth white transition from very small values to 0 values, but there seems to be a blurred border I'd like to get rid of. Is there any way to do this?
This is to be expected because values less or equal to zero are masked and then positive values are normalized. That might mean that LogNorm is not the best option for you, but if you insist on using it you can try adding the minimum positive value to the histogram. In your case it would be 1 but let's do it more general for, say, normed histograms.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LogNorm
x = np.random.randn(1000)
y = np.random.randn(1000)
h, _, _ = np.histogram2d(x, y)
im = plt.imshow(h, norm=LogNorm(), cmap=plt.cm.Greys,
interpolation='bilinear')
plt.colorbar(im)
im = plt.imshow(h + np.min(h[h > 0]), norm=LogNorm(), cmap=plt.cm.Greys,
interpolation='bilinear')
plt.colorbar(im)
Note that this change won't affect bilinear interpolation but might affect other interpolation algorithms. To ensure that interpolation is not affected you would have to create a custom subclass of Normalize.
The above figures were made using matplotlib 2.0.0rc1 which applies color mapping after interpolation. If you use a previous version you will see even more artifacts in the first figure.

Python matplotlib: fractional logscale

I would like to plot some data with a fractional logscale, such that the y axis has the ticks at 10^(-0.1), 10^(-0.2), 10^(-0.3), etc.
The problem is that when I plot my data, there are only ticks at 10^0 and 10^-1, which leaves the slope of the line too slight to see.
Is is possible to set a fractional logscale this way?
Thanks
It sounds like you want tick labels, not the tick marks in particular. In most figures, the minor tick marks are already there where you want them.
The following may then work, though I would think there's an easier way. Note that I'm applying labels to the minor tick marks only: the (two) major tick marks already have a label. Unfortunately, the fonts of the two types of tick marks are not the same; I think that's a result of the LaTeX equation usage.
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
X = np.logspace(0, 3)
Y = X**-0.2
plt.plot(X,Y)
plt.yscale('log')
yticks = np.linspace(-0.1, -0.9, 9)
ax.set_yticks(10**yticks, minor=True)
ax.set_ylim(0.1, 1)
ax.set_yticklabels(['$10^{{{:.1f}}}$'.format(ytick) for ytick in yticks], minor=True)
plt.show()
which results in:
For the issue of the different label fonts, you can manually change the major tick labels:
ax.set_yticks([1, 0.1])
ax.set_yticklabels(['$10^0$', '$10^{-1}$'])
(and probably the same for the x-axis).

Change figsize in matplotlib polar contourf

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.

matplotlib ticks thickness

Is there a way to increase the thickness and size of ticks in matplotlib without having to write a long piece of code like this:
for line in ax1.yaxis.get_ticklines():
line.set_markersize(25)
line.set_markeredgewidth(3)
The problem with this piece of code is that it uses a loop which costs usually a lot of CPU usage.
A simpler way is to use the set_tick_params function of axis objects:
ax.xaxis.set_tick_params(width=5)
ax.yaxis.set_tick_params(width=5)
Doing it this way means you can change this on a per-axis basis with out worrying about global state and with out making any assumptions about the internal structure of mpl objects.
If you want to set this for all the ticks in your axes,
ax = plt.gca()
ax.tick_params(width=5,...)
Take a look at set_tick_params doc and tick_params valid keywords
You can change all matplotlib defaults using rcParams like in
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
# set tick width
mpl.rcParams['xtick.major.size'] = 20
mpl.rcParams['xtick.major.width'] = 4
mpl.rcParams['xtick.minor.size'] = 10
mpl.rcParams['xtick.minor.width'] = 2
x = np.linspace(0., 10.)
plt.plot(x, np.sin(x))
plt.show()
You can use matplotlib.pyplot.setp
plt.setp(ax.yaxis.get_ticklines(), 'markersize', 25)
plt.setp(ax.yaxis.get_ticklines(), 'markeredgewidth', 3)
You can also use list comprehension, although not having a return value probably does not make much sense, besides reducing the number of lines in the code see e.g. here
[line.set_markersize(25) for line in ax1.yaxis.get_ticklines()]
[line.set_markeredgewidth(3) for line in ax1.yaxis.get_ticklines()]