Minor ticks on logarithmic axis in Matplotlib - matplotlib

I have a scatter plot with logarithmic x- and y axis (because I'm mainly interested in the lower values along both).
However, I want the tick labels to be in decimal format, not as 10^x.
I'm using this:
# axis limits:
ax.set_xlim(xmin=0, xmax = 1.2)
ax.set_ylim(ymin=0,ymax=1000)
# log scales:
ax.set_yscale('log')
ax.set_xscale('log')
# set y-ticks:
ax.set_yticks((1,10,100,1000))
ax.set_yticklabels(("1","10","100","1000"))
This works (though introducing ax.set_yscale('log') or ax.set_xscale('log') brings up the following warning (any idea what's up with that?):
Warning (from warnings module):
File "C:\Python27\lib\site-packages\numpy\ma\core.py", line 3785
warnings.warn("Warning: converting a masked element to nan.")
UserWarning: Warning: converting a masked element to nan.
But when I try the same for the x-axis, I get a MaskError:
# set x-ticks:
ax.set_xticks((0, 0.2, 0.4, 0.8, 1))
ax.set_xticklabels(("0", "0.2", "0.4", "0.8", "1"))
[snip long long traceback]
File "C:\Python27\lib\site-packages\numpy\ma\core.py", line 3795, in __int__
raise MaskError, 'Cannot convert masked element to a Python int.'
MaskError: Cannot convert masked element to a Python int.
I think it has something to do with minor vs major ticks. I have tried to play around with ticker, but always run into the same error in the end.
I'd be immensely grateful for any help!
Edit after answer:
Problem solved by replacing
ax.set_yticks((1,10,100,1000))
ax.set_yticklabels(("1","10","100","1000"))
ax.set_xticks((0, 0.2, 0.4, 0.8, 1))
ax.set_xticklabels(("0", "0.2", "0.4", "0.8", "1"))
with
ax.yaxis.set_major_formatter(FormatStrFormatter('%1.0f'))
ax.xaxis.set_major_formatter(FormatStrFormatter('%.1f'))
ax.xaxis.set_minor_formatter(FormatStrFormatter('%.1f'))

You can use ticker as:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import FormatStrFormatter
fig = plt.figure(1, [5,4])
ax = fig.add_subplot(111)
ax.plot( range(1,100) , range(1,100) , color='#aaaaff')
ax.set_xscale('log')
ax.xaxis.set_major_formatter(FormatStrFormatter('%.03f'))
plt.show()

Related

How can I place the y-axis origin at 0? [duplicate]

I want to draw a figure in matplotib where the axis are displayed within the plot itself not on the side
I have tried the following code from here:
import math
import numpy as np
import matplotlib.pyplot as plt
def sigmoid(x):
a = []
for item in x:
a.append(1/(1+math.exp(-item)))
return a
x = np.arange(-10., 10., 0.2)
sig = sigmoid(x)
plt.plot(x,sig)
plt.show()
The above code displays the figure like this:
What I would like to draw is something as follows (image from Wikipedia)
This question describes a similar problem, but it draws a reference line in the middle but no axis.
One way to do it is using spines:
import math
import numpy as np
import matplotlib.pyplot as plt
def sigmoid(x):
a = []
for item in x:
a.append(1/(1+math.exp(-item)))
return a
x = np.arange(-10., 10., 0.2)
sig = sigmoid(x)
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
# Move left y-axis and bottom x-axis to centre, passing through (0,0)
ax.spines['left'].set_position('center')
ax.spines['bottom'].set_position('center')
# Eliminate upper and right axes
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
# Show ticks in the left and lower axes only
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
plt.plot(x,sig)
plt.show()
shows:
Basically, I want to comment on the accepted answer (but my rep doesn't allow that).
The use of
ax.spines['bottom'].set_position('center')
draws the x-axes such that it intersect the y-axes in its center. In case of asymmetric ylim this means that x-axis passes NOT through y=0. Jblasco's answer has this drawback, the intersect is at y=0.5 (the center between ymin=0.0 and ymax=1.0)
However, the reference plot of the original question has axes that intersect each other at 0.0 (which is somehow conventional or at least common).
To achieve this behaviour,
ax.spines['bottom'].set_position('zero')
has to be used.
See the following example, where 'zero' makes the axes intersect at 0.0 despite asymmetrically ranges in both x and y.
import numpy as np
import matplotlib.pyplot as plt
#data generation
x = np.arange(-10,20,0.2)
y = 1.0/(1.0+np.exp(-x)) # nunpy does the calculation elementwise for you
fig, [ax0, ax1] = plt.subplots(ncols=2, figsize=(8,4))
# Eliminate upper and right axes
ax0.spines['top'].set_visible(False)
ax0.spines['right'].set_visible(False)
# Show ticks on the left and lower axes only
ax0.xaxis.set_tick_params(bottom='on', top='off')
ax0.yaxis.set_tick_params(left='on', right='off')
# Move remaining spines to the center
ax0.set_title('center')
ax0.spines['bottom'].set_position('center') # spine for xaxis
# - will pass through the center of the y-values (which is 0)
ax0.spines['left'].set_position('center') # spine for yaxis
# - will pass through the center of the x-values (which is 5)
ax0.plot(x,y)
# Eliminate upper and right axes
ax1.spines['top'].set_visible(False)
ax1.spines['right'].set_visible(False)
# Show ticks on the left and lower axes only (and let them protrude in both directions)
ax1.xaxis.set_tick_params(bottom='on', top='off', direction='inout')
ax1.yaxis.set_tick_params(left='on', right='off', direction='inout')
# Make spines pass through zero of the other axis
ax1.set_title('zero')
ax1.spines['bottom'].set_position('zero')
ax1.spines['left'].set_position('zero')
ax1.set_ylim(-0.4,1.0)
# No ticklabels at zero
ax1.set_xticks([-10,-5,5,10,15,20])
ax1.set_yticks([-0.4,-0.2,0.2,0.4,0.6,0.8,1.0])
ax1.plot(x,y)
plt.show()
Final remark: If ax.spines['bottom'].set_position('zero') is used but zerois not within the plotted y-range, then the axes is shown at the boundary of the plot closer to zero.
The title of this question is how to draw the spine in the middle and the accepted answer does exactly that but what you guys draw is the sigmoid function and that one passes through y=0.5. So I think what you want is the spine centered according to your data. Matplotlib offers the spine position data for that (see documentation)
import numpy as np
import matplotlib.pyplot as plt
def sigmoid(x):
return 1 / (1 + np.exp(-x))
sigmoid = np.vectorize(sigmoid) #vectorize function
values=np.linspace(-10, 10) #generate values between -10 and 10
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
#spine placement data centered
ax.spines['left'].set_position(('data', 0.0))
ax.spines['bottom'].set_position(('data', 0.0))
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
plt.plot(values, sigmoid(values))
plt.show()
Looks like this (Github):
You can simply add:
plt.axhline()
plt.axvline()
It's not fixed to the center, but it does the job very easily.
Working example:
import matplotlib.pyplot as plt
import numpy as np
def f(x):
return np.sin(x) / (x/100)
delte = 100
Xs = np.arange(-delte, +delte +1, step=0.01)
Ys = np.array([f(x) for x in Xs])
plt.axhline(color='black', lw=0.5)
plt.axvline(color='black', lw=0.5)
plt.plot(Xs, Ys)
plt.show()
If you use matplotlib >= 3.4.2, you can use Pandas syntax and do it in only one line:
plt.gca().spines[:].set_position('center')
You might find it cleaner to do it in 3 lines:
ax = plt.gca()
ax.spines[['top', 'right']].set_visible(False)
ax.spines[['left', 'bottom']].set_position('center')
See documentation here.
Check your matplotlib version with pip freeze and update it with pip install -U matplotlib.
According to latest MPL Documentation:
ax = plt.axes()
ax.spines.left.set_position('zero')
ax.spines.bottom.set_position('zero')

Matplotlib minor grid lines are incomplete

I have this code:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.ticker import EngFormatter, LogLocator
fig, ax0 = plt.subplots(figsize=(10, 8))
fmin, fmax = 1, 1e9
zmin, zmax = 1e-3, 1e6
ax0.set_xscale('log', base=10)
ax0.set_yscale('log', base=10)
ax0.set_xlim(fmin, fmax)
ax0.set_ylim(zmin, zmax)
ax0.xaxis.set_major_formatter(EngFormatter(unit='Hz'))
ax0.yaxis.set_major_formatter(EngFormatter(unit='Ω'))
ax0.xaxis.set_major_locator(LogLocator(base=10, numticks=100))
locmin = LogLocator(base=10.0,subs=(0.2,0.4,0.6,0.8))
ax0.xaxis.set_minor_locator(locmin)
ax0.yaxis.set_minor_locator(locmin)
ax0.grid(which='both')
plt.show()
It produces the follwoing output.
Anyone's guess why the minor grid lines are missing on the last two decades?
I am on matplotlib 3.4.3.
You shouldn't reuse the LogLocator object for both x&y axis, assigning it will modify it's properties. The docstring already hints at this:
numticks : None or int, default: None
The maximum number of ticks to allow on a given axis. The default
of None will try to choose intelligently as long as this
Locator has already been assigned to an axis using
~.axis.Axis.get_tick_space, but otherwise falls back to 9.
So in this specific case, first assigning to y before x would make it "work". I assume that means the last assignment determines some of the properties.
But of course it's best to simply create a separate instance of the LogLocator object for each axis (both x/y & major/minor).
I'm not sure why the minor-x ticks won't show when leaving numticks=None.
So:
fig, ax0 = plt.subplots(figsize=(10, 8), facecolor='w')
fmin, fmax = 1, 1e9
zmin, zmax = 1e-3, 1e6
ax0.set_xscale('log', base=10)
ax0.set_yscale('log', base=10)
ax0.set_xlim(fmin, fmax)
ax0.set_ylim(zmin, zmax)
ax0.xaxis.set_major_formatter(EngFormatter(unit='Hz'))
ax0.yaxis.set_major_formatter(EngFormatter(unit='Ω'))
ax0.xaxis.set_major_locator(LogLocator(base=10, subs=(1.0,), numticks=100))
ax0.xaxis.set_minor_locator(LogLocator(base=10.0, subs=(0.2, 0.4, 0.6, 0.8), numticks=100))
ax0.yaxis.set_minor_locator(LogLocator(base=10.0, subs=(0.2, 0.4, 0.6, 0.8), numticks=100))
ax0.grid(which='major', axis='both', color='k', alpha=0.6)
ax0.grid(which='minor', axis='both', color='k', alpha=0.2)
plt.show()

Understanding the subs argument in LogLocator

I am trying to understand the subs argument in the LogLocator class, which determines where major / minor ticks should be located on a MatPlotLib graph.
Here is my code:
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import numpy as np
x = np.arange(30)
y = np.power(x, 1.5)
fig = plt.figure(1)
ax = fig.add_subplot(111)
ax.set_yscale("log")
ax.plot(x, y)
plt.show()
And this produces the following graph:
However, what I want to do is to have labels on the y-axis for all those ticks, rather than just at 10^0, 10^1, 10^2. I believe that the way to do this is to use a LogLocator, and so I have tried inserting the following into my code:
ax.yaxis.set_major_locator(ticker.LogLocator(base=10, subs=np.arange(2, 10) * 0.1))
The idea here is that it would then show labels on 0.1, 0.2, 0.3 .... X 10^0, 10^1, 10^3 ...
However, instead, the graph appears to have removed all labels entirely:
So what should I be using in the subs argument to get my desired behaviour?
Just add the following line after plt.plot(). You will have a bit of space problem between the minor ticks due to the log spacing. But you can control that via the fontsize of the minor tick labels.
ax.yaxis.set_minor_formatter(FormatStrFormatter("%.1f"))
Output

LogFormatter tickmarks scientific format limits

I'm trying to plot over a wide range with a log-scaled axis, but I want to show 10^{-1}, 10^0, 10^1 as just 0.1, 1, 10. ScalarFormatter will change everything to integers instead of scientific notation, but I'd like most of the tickmark labels to be scientific; I'm only wanting to change a few of the labels. So the MWE is
import numpy as np
import matplotlib as plt
fig = plt.figure(figsize=[7,7])
ax1 = fig.add_subplot(111)
ax1.set_yscale('log')
ax1.set_xscale('log')
ax1.plot(np.logspace(-4,4), np.logspace(-4,4))
plt.show()
and I want the middle labels on each axis to read 0.1, 1, 10 instead of 10^{-1}, 10^0, 10^1
Thanks for any help!
When setting set_xscale('log'), you're using a LogFormatterSciNotation (not a ScalarFormatter). You may subclass LogFormatterSciNotation to return the desired values 0.1,1,10 if they happen to be marked as ticks.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import LogFormatterSciNotation
class CustomTicker(LogFormatterSciNotation):
def __call__(self, x, pos=None):
if x not in [0.1,1,10]:
return LogFormatterSciNotation.__call__(self,x, pos=None)
else:
return "{x:g}".format(x=x)
fig = plt.figure(figsize=[7,7])
ax = fig.add_subplot(111)
ax.set_yscale('log')
ax.set_xscale('log')
ax.plot(np.logspace(-4,4), np.logspace(-4,4))
ax.xaxis.set_major_formatter(CustomTicker())
plt.show()
Update: With matplotlib 2.1 there is now a new option
Specify minimum value to format as scalar for LogFormatterMathtext
LogFormatterMathtext now includes the option to specify a minimum value exponent to format as a scalar (i.e., 0.001 instead of 10-3).
This can be done as follows, by using the rcParams (plt.rcParams['axes.formatter.min_exponent'] = 2):
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['axes.formatter.min_exponent'] = 2
fig = plt.figure(figsize=[7,7])
ax = fig.add_subplot(111)
ax.set_yscale('log')
ax.set_xscale('log')
ax.plot(np.logspace(-4,4), np.logspace(-4,4))
plt.show()
This results in the same plot as above.
Note however that this limit is symmetric, it would not allow to set only 1 and 10, but not 0.1. Hence the initial solution is more generic.

matplotlib: Stretch image to cover the whole figure

I am quite used to working with matlab and now trying to make the shift matplotlib and numpy. Is there a way in matplotlib that an image you are plotting occupies the whole figure window.
import numpy as np
import matplotlib.pyplot as plt
# get image im as nparray
# ........
plt.figure()
plt.imshow(im)
plt.set_cmap('hot')
plt.savefig("frame.png")
I want the image to maintain its aspect ratio and scale to the size of the figure ... so when I do savefig it exactly the same size as the input figure, and it is completely covered by the image.
Thanks.
I did this using the following snippet.
#!/usr/bin/env python
import numpy as np
import matplotlib.cm as cm
import matplotlib.mlab as mlab
import matplotlib.pyplot as plt
from pylab import *
delta = 0.025
x = y = np.arange(-3.0, 3.0, delta)
X, Y = np.meshgrid(x, y)
Z1 = mlab.bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0)
Z2 = mlab.bivariate_normal(X, Y, 1.5, 0.5, 1, 1)
Z = Z2-Z1 # difference of Gaussians
ax = Axes(plt.gcf(),[0,0,1,1],yticks=[],xticks=[],frame_on=False)
plt.gcf().delaxes(plt.gca())
plt.gcf().add_axes(ax)
im = plt.imshow(Z, cmap=cm.gray)
plt.show()
Note the grey border on the sides is related to the aspect rario of the Axes which is altered by setting aspect='equal', or aspect='auto' or your ratio.
Also as mentioned by Zhenya in the comments Similar StackOverflow Question
mentions the parameters to savefig of bbox_inches='tight' and pad_inches=-1 or pad_inches=0
You can use a function like the one below.
It calculates the needed size for the figure (in inches) according to the resolution in dpi you want.
import numpy as np
import matplotlib.pyplot as plt
def plot_im(image, dpi=80):
px,py = im.shape # depending of your matplotlib.rc you may
have to use py,px instead
#px,py = im[:,:,0].shape # if image has a (x,y,z) shape
size = (py/np.float(dpi), px/np.float(dpi)) # note the np.float()
fig = plt.figure(figsize=size, dpi=dpi)
ax = fig.add_axes([0, 0, 1, 1])
# Customize the axis
# remove top and right spines
ax.spines['right'].set_color('none')
ax.spines['left'].set_color('none')
ax.spines['top'].set_color('none')
ax.spines['bottom'].set_color('none')
# turn off ticks
ax.xaxis.set_ticks_position('none')
ax.yaxis.set_ticks_position('none')
ax.xaxis.set_ticklabels([])
ax.yaxis.set_ticklabels([])
ax.imshow(im)
plt.show()
Here's a minimal object-oriented solution:
fig = plt.figure(figsize=(8, 8))
ax = fig.add_axes([0, 0, 1, 1], frameon=False, xticks=[], yticks=[])
Testing it out with
ax.imshow([[0]])
fig.savefig('test.png')
saves out a uniform purple block.
edit: As #duhaime points out below, this requires the figure to have the same aspect as the axes.
If you'd like the axes to resize to the figure, add aspect='auto' to imshow.
If you'd like the figure to resize to be resized to the axes, add
from matplotlib import tight_bbox
bbox = fig.get_tightbbox(fig.canvas.get_renderer())
tight_bbox.adjust_bbox(fig, bbox, fig.canvas.fixed_dpi)
after the imshow call. This is the important bit of matplotlib's tight_layout functionality which is implicitly called by things like Jupyter's renderer.