Related
I do have a question with matplotlib in python. I create different figures, where every figure should have the same height to print them in a publication/poster next to each other.
If the y-axis has a label on the very top, this shrinks the height of the box with the plot. So I use MaxNLocator to remove the upper and lower y-tick. In some plots, I want to have the 1.0 as a number on the y-axis, because I have normalized data. So I need a solution, which expands in these cases the y-axis and ensures 1.0 is a y-Tick, but does not corrupt the size of the figure using tight_layout().
Here is a minimal example:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import MaxNLocator
x = np.linspace(0,1,num=11)
y = np.linspace(1,.42,num=11)
fig,axs = plt.subplots(1,1)
axs.plot(x,y)
locator=MaxNLocator(prune='both',nbins=5)
axs.yaxis.set_major_locator(locator)
plt.tight_layout()
fig.show()
Here is a link to a example-pdf, which shows the problems with height of upper boxline.
I tried to work with adjust_subplots() but this is of no use for me, because I vary the size of the figures and want to have same the font size all the time, which changes the margins.
Question is:
How can I use MaxNLocator and specify a number which has to be in the y-axis?
Hopefully someone of you has some advice.
Greetings,
Laenan
Assuming that you know in advance how many plots there will be in 1 row on a page one way to solve this would be to put all those plots into one figure - matplotlib will make sure they are alinged on axes:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import MaxNLocator
x = np.linspace(0, 1, num=11)
y = np.linspace(1, .42, num=11)
fig, (ax1, ax2) = plt.subplots(1,2, figsize=(8,3), gridspec_kw={'wspace':.2})
ax1.plot(x,y)
ax2.plot(x,y)
locator=MaxNLocator(prune='both', nbins=5)
ax1.yaxis.set_major_locator(locator)
# You don't need to use tight_layout and using it might give an error
# plt.tight_layout()
fig.show()
I can't get any tick marks to appear when I have a narrow range of data and log formatting. I found a similar problem that talked about forcing a minimum number of ticks and tried that solution, but it did not seem to help.
What I want to do is have the Y range be automatically expanded until at least two ticks can be included, including one major tick (so it gets a label). I can't do anything to manual or custom because a lot of different data goes through this routine and it is only rarely that the range is so tight that no labels appear.
Here is an example that preserves as much of my local environment as possible:
import matplotlib
import numpy as np
import pylab as plt
fig=plt.figure(figsize=(15, 20))
locmin = matplotlib.ticker.LogLocator(base=10.0,subs=(.1,.2,.3,.4,.5,.6,.7,.8,.9),numticks=15)
ax6 = plt.subplot(616)
plt.plot(np.random.random(1000)*4+14, 'b')
plt.plot(np.random.random(1000)*4+14, 'r')
plt.minorticks_on()
plt.ylabel('Y')
plt.yscale('log')
ax6.yaxis.set_minor_locator(locmin)
ax6.yaxis.set_minor_formatter(matplotlib.ticker.NullFormatter())
plt.show()
The result is this plot here, which has no Y labels...
You can get the array of major_ticks and minor_ticklocs. Then find the bounds for the given scaled y limits. Then you can explicitly set the ylim of the plot. Since the values in the example scales between 10 and 20, the 10 from major_ticks and 20 from minor_ticks are shown. Consider below code:
import matplotlib
import numpy as np
import pylab as plt
fig=plt.figure(figsize=(15, 20))
locmin = matplotlib.ticker.LogLocator(base=10.0,subs=(.1,.2,.3,.4,.5,.6,.7,.8,.9),numticks=15)
ax6 = plt.subplot(616)
plt.plot(np.random.random(1000)*4+14, 'b')
plt.plot(np.random.random(1000)*4+14, 'r')
plt.minorticks_on()
plt.ylabel('Y')
plt.yscale('log')
ax6.yaxis.set_minor_locator(locmin)
ax6.yaxis.set_minor_formatter(matplotlib.ticker.NullFormatter())
plt.tick_params(axis='y', which='minor')
ax6.yaxis.set_minor_formatter(matplotlib.ticker.FormatStrFormatter("%.1f"))
tickArr = np.concatenate((plt.yticks()[0], ax6.yaxis.get_minorticklocs()))
ylim_min = tickArr[tickArr < plt.ylim()[0]].max()
ylim_max = tickArr[tickArr > plt.ylim()[1]].min()
plt.ylim([ylim_min, ylim_max])
plt.show()
When making a semi-log plot (y is log), the minor tick marks (8 in a decade) on the y axis appear automatically, but it seems that when the axis range exceeds 10**10, they disappear. I tried many ways to force them back in, but to no avail. It might be that they go away for large ranges to avoid overcrowding, but one should have a choice?
solution for matplotlib >= 2.0.2
Let's consider the following example
which is produced by this code:
import matplotlib.pyplot as plt
import matplotlib.ticker
import numpy as np
y = np.arange(12)
x = 10.0**y
fig, ax=plt.subplots()
ax.plot(x,y)
ax.set_xscale("log")
plt.show()
The minor ticklabels are indeed gone and usual ways to show them (like plt.tick_params(axis='x', which='minor')) fail.
The first step would then be to show all powers of 10 on the axis,
locmaj = matplotlib.ticker.LogLocator(base=10,numticks=12)
ax.xaxis.set_major_locator(locmaj)
where the trick is to set numticks to a number equal or larger the number of ticks (i.e. 12 or higher in this case).
Then, we can add minor ticklabels as
locmin = matplotlib.ticker.LogLocator(base=10.0,subs=(0.2,0.4,0.6,0.8),numticks=12)
ax.xaxis.set_minor_locator(locmin)
ax.xaxis.set_minor_formatter(matplotlib.ticker.NullFormatter())
Note that I restricted this to include 4 minor ticks per decade (using 8 is equally possible but in this example would overcrowd the axes). Also note that numticks is again (quite unintuitively) 12 or larger.
Finally we need to use a NullFormatter() for the minor ticks, in order not to have any ticklabels appear for them.
solution for matplotlib 2.0.0
The following works in matplotlib 2.0.0 or below, but it does not work in matplotlib 2.0.2.
Let's consider the following example
which is produced by this code:
import matplotlib.pyplot as plt
import matplotlib.ticker
import numpy as np
y = np.arange(12)
x = 10.0**y
fig, ax=plt.subplots()
ax.plot(x,y)
ax.set_xscale("log")
plt.show()
The minor ticklabels are indeed gone and usual ways to show them (like plt.tick_params(axis='x', which='minor')) fail.
The first step would then be to show all powers of 10 on the axis,
locmaj = matplotlib.ticker.LogLocator(base=10.0, subs=(0.1,1.0, ))
ax.xaxis.set_major_locator(locmaj)
Then, we can add minor ticklabels as
locmin = matplotlib.ticker.LogLocator(base=10.0, subs=(0.1,0.2,0.4,0.6,0.8,1,2,4,6,8,10 ))
ax.xaxis.set_minor_locator(locmin)
ax.xaxis.set_minor_formatter(matplotlib.ticker.NullFormatter())
Note that I restricted this to include 4 minor ticks per decade (using 8 is equally possible but in this example would overcrowd the axes). Also note - and that may be the key here - that the subs argument, which gives the multiples of integer powers of the base at which to place ticks (see documentation), is given a list ranging over two decades instead of one.
Finally we need to use a NullFormatter() for the minor ticks, in order not to have any ticklabels appear for them.
From what I can tell, as of Matplotlib 3.5.2:
With 8 or fewer major tick marks, the minor ticks show
with 9 to 11 major tick marks, subs="auto" will show the minor tick marks
with 12 or more, you need to set subs manually.
Using subs="auto"
from matplotlib import pyplot as plt, ticker as mticker
fig, ax = plt.subplots()
y = np.arange(11)
x = 10.0**y
ax.semilogx(x, y)
ax.xaxis.set_major_locator(mticker.LogLocator(numticks=999))
ax.xaxis.set_minor_locator(mticker.LogLocator(numticks=999, subs="auto"))
Setting subs manually
from matplotlib import pyplot as plt, ticker as mticker
fig, ax = plt.subplots()
y = np.arange(12)
x = 10.0**y
ax.semilogx(x, y)
ax.xaxis.set_major_locator(mticker.LogLocator(numticks=999))
ax.xaxis.set_minor_locator(mticker.LogLocator(numticks=999, subs=(.2, .4, .6, .8)))
Major ticks with empty labels will generate ticks but no labels.
ax.set_yticks([1.E-6,1.E-5,1.E-4,1.E-3,1.E-2,1.E-1,1.E0,1.E1,1.E2,1.E3,1.E4,1.E5,])
ax.set_yticklabels(['$10^{-6}$','','','$10^{-3}$','','','$1$','','','$10^{3}$','',''])
Wrapping the excellent answer from importanceofbeingernest for matplotlib >= 2.0.2 into a function:
import matplotlib.pyplot as plt
from typing import Optional
def restore_minor_ticks_log_plot(
ax: Optional[plt.Axes] = None, n_subticks=9
) -> None:
"""For axes with a logrithmic scale where the span (max-min) exceeds
10 orders of magnitude, matplotlib will not set logarithmic minor ticks.
If you don't like this, call this function to restore minor ticks.
Args:
ax:
n_subticks: Number of Should be either 4 or 9.
Returns:
None
"""
if ax is None:
ax = plt.gca()
# Method from SO user importanceofbeingernest at
# https://stackoverflow.com/a/44079725/5972175
locmaj = mpl.ticker.LogLocator(base=10, numticks=1000)
ax.xaxis.set_major_locator(locmaj)
locmin = mpl.ticker.LogLocator(
base=10.0, subs=np.linspace(0, 1.0, n_subticks + 2)[1:-1], numticks=1000
)
ax.xaxis.set_minor_locator(locmin)
ax.xaxis.set_minor_formatter(mpl.ticker.NullFormatter())
This function can then be called as
plt.plot(x,y)
plt.xscale("log")
restore_minor_ticks_log_plot()
or more explicitly
_, ax = plt.subplots()
ax.plot(x, y)
ax.set_xscale("log")
restore_minor_ticks_log_plot(ax)
The answers here ignore the convenient fact that the log-scaled axis already has the requisite locators. At least as of Matplotlib 3.6, it is enough to use set_params() with values that force minor ticks:
import matplotlib.pyplot as plt
import numpy as np
y = np.arange(12)
x = 10.0**y
fig, ax = plt.subplots()
ax.plot(x, y)
ax.set_xscale('log')
ax.xaxis.get_major_locator().set_params(numticks=99)
ax.xaxis.get_minor_locator().set_params(numticks=99, subs=[.2, .4, .6, .8])
plt.show()
For the plot
sns.countplot(x="HostRamSize",data=df)
I got the following graph with x-axis label mixing together, how do I avoid this? Should I change the size of the graph to solve this problem?
Having a Series ds like this
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np; np.random.seed(136)
l = "1234567890123"
categories = [ l[i:i+5]+" - "+l[i+1:i+6] for i in range(6)]
x = np.random.choice(categories, size=1000,
p=np.diff(np.array([0,0.7,2.8,6.5,8.5,9.3,10])/10.))
ds = pd.Series({"Column" : x})
there are several options to make the axis labels more readable.
Change figure size
plt.figure(figsize=(8,4)) # this creates a figure 8 inch wide, 4 inch high
sns.countplot(x="Column", data=ds)
plt.show()
Rotate the ticklabels
ax = sns.countplot(x="Column", data=ds)
ax.set_xticklabels(ax.get_xticklabels(), rotation=40, ha="right")
plt.tight_layout()
plt.show()
Decrease Fontsize
ax = sns.countplot(x="Column", data=ds)
ax.set_xticklabels(ax.get_xticklabels(), fontsize=7)
plt.tight_layout()
plt.show()
Of course any combination of those would work equally well.
Setting rcParams
The figure size and the xlabel fontsize can be set globally using rcParams
plt.rcParams["figure.figsize"] = (8, 4)
plt.rcParams["xtick.labelsize"] = 7
This might be useful to put on top of a juypter notebook such that those settings apply for any figure generated within. Unfortunately rotating the xticklabels is not possible using rcParams.
I guess it's worth noting that the same strategies would naturally also apply for seaborn barplot, matplotlib bar plot or pandas.bar.
You can rotate the x_labels and increase their font size using the xticks methods of pandas.pyplot.
For Example:
import matplotlib.pyplot as plt
plt.figure(figsize=(10,5))
chart = sns.countplot(x="HostRamSize",data=df)
plt.xticks(
rotation=45,
horizontalalignment='right',
fontweight='light',
fontsize='x-large'
)
For more such modifications you can refer this link:
Drawing from Data
If you just want to make sure xticks labels are not squeezed together, you can set a proper fig size and try fig.autofmt_xdate().
This function will automatically align and rotate the labels.
plt.figure(figsize=(15,10)) #adjust the size of plot
ax=sns.countplot(x=df['Location'],data=df,hue='label',palette='mako')
ax.set_xticklabels(ax.get_xticklabels(), rotation=40, ha="right") #it will rotate text on x axis
plt.tight_layout()
plt.show()
you can try this code & change size & rotation according to your need.
I don't know whether it is an option for you but maybe turning the graphic could be a solution (instead of plotting on x=, do it on y=), such that:
sns.countplot(y="HostRamSize",data=df)
I would like to zoom a portion of data/image and plot it inside the same figure. It looks something like this figure.
Is it possible to insert a portion of zoomed image inside the same plot. I think it is possible to draw another figure with subplot but it draws two different figures. I also read to add patch to insert rectangle/circle but not sure if it is useful to insert a portion of image into the figure. I basically load data from the text file and plot it using a simple plot commands shown below.
I found one related example from matplotlib image gallery here but not sure how it works. Your help is much appreciated.
from numpy import *
import os
import matplotlib.pyplot as plt
data = loadtxt(os.getcwd()+txtfl[0], skiprows=1)
fig1 = plt.figure()
ax1 = fig1.add_subplot(111)
ax1.semilogx(data[:,1],data[:,2])
plt.show()
Playing with runnable code is one of the
fastest ways to learn Python.
So let's start with the code from the matplotlib example gallery.
Given the comments in the code, it appears the code is broken up into 4 main stanzas.
The first stanza generates some data, the second stanza generates the main plot,
the third and fourth stanzas create the inset axes.
We know how to generate data and plot the main plot, so let's focus on the third stanza:
a = axes([.65, .6, .2, .2], axisbg='y')
n, bins, patches = hist(s, 400, normed=1)
title('Probability')
setp(a, xticks=[], yticks=[])
Copy the example code into a new file, called, say, test.py.
What happens if we change the .65 to .3?
a = axes([.35, .6, .2, .2], axisbg='y')
Run the script:
python test.py
You'll find the "Probability" inset moved to the left.
So the axes function controls the placement of the inset.
If you play some more with the numbers you'll figure out that (.35, .6) is the
location of the lower left corner of the inset, and (.2, .2) is the width and
height of the inset. The numbers go from 0 to 1 and (0,0) is the located at the
lower left corner of the figure.
Okay, now we're cooking. On to the next line we have:
n, bins, patches = hist(s, 400, normed=1)
You might recognize this as the matplotlib command for drawing a histogram, but
if not, changing the number 400 to, say, 10, will produce an image with a much
chunkier histogram, so again by playing with the numbers you'll soon figure out
that this line has something to do with the image inside the inset.
You'll want to call semilogx(data[3:8,1],data[3:8,2]) here.
The line title('Probability')
obviously generates the text above the inset.
Finally we come to setp(a, xticks=[], yticks=[]). There are no numbers to play with,
so what happens if we just comment out the whole line by placing a # at the beginning of the line:
# setp(a, xticks=[], yticks=[])
Rerun the script. Oh! now there are lots of tick marks and tick labels on the inset axes.
Fine. So now we know that setp(a, xticks=[], yticks=[]) removes the tick marks and labels from the axes a.
Now, in theory you have enough information to apply this code to your problem.
But there is one more potential stumbling block: The matplotlib example uses
from pylab import *
whereas you use import matplotlib.pyplot as plt.
The matplotlib FAQ says import matplotlib.pyplot as plt
is the recommended way to use matplotlib when writing scripts, while
from pylab import * is for use in interactive sessions. So you are doing it the right way, (though I would recommend using import numpy as np instead of from numpy import * too).
So how do we convert the matplotlib example to run with import matplotlib.pyplot as plt?
Doing the conversion takes some experience with matplotlib. Generally, you just
add plt. in front of bare names like axes and setp, but sometimes the
function come from numpy, and sometimes the call should come from an axes
object, not from the module plt. It takes experience to know where all these
functions come from. Googling the names of functions along with "matplotlib" can help.
Reading example code can builds experience, but there is no easy shortcut.
So, the converted code becomes
ax2 = plt.axes([.65, .6, .2, .2], axisbg='y')
ax2.semilogx(t[3:8],s[3:8])
plt.setp(ax2, xticks=[], yticks=[])
And you could use it in your code like this:
from numpy import *
import os
import matplotlib.pyplot as plt
data = loadtxt(os.getcwd()+txtfl[0], skiprows=1)
fig1 = plt.figure()
ax1 = fig1.add_subplot(111)
ax1.semilogx(data[:,1],data[:,2])
ax2 = plt.axes([.65, .6, .2, .2], axisbg='y')
ax2.semilogx(data[3:8,1],data[3:8,2])
plt.setp(ax2, xticks=[], yticks=[])
plt.show()
The simplest way is to combine "zoomed_inset_axes" and "mark_inset", whose description and
related examples could be found here:
Overview of AxesGrid toolkit
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import zoomed_inset_axes
from mpl_toolkits.axes_grid1.inset_locator import mark_inset
import numpy as np
def get_demo_image():
from matplotlib.cbook import get_sample_data
import numpy as np
f = get_sample_data("axes_grid/bivariate_normal.npy", asfileobj=False)
z = np.load(f)
# z is a numpy array of 15x15
return z, (-3,4,-4,3)
fig, ax = plt.subplots(figsize=[5,4])
# prepare the demo image
Z, extent = get_demo_image()
Z2 = np.zeros([150, 150], dtype="d")
ny, nx = Z.shape
Z2[30:30+ny, 30:30+nx] = Z
# extent = [-3, 4, -4, 3]
ax.imshow(Z2, extent=extent, interpolation="nearest",
origin="lower")
axins = zoomed_inset_axes(ax, 6, loc=1) # zoom = 6
axins.imshow(Z2, extent=extent, interpolation="nearest",
origin="lower")
# sub region of the original image
x1, x2, y1, y2 = -1.5, -0.9, -2.5, -1.9
axins.set_xlim(x1, x2)
axins.set_ylim(y1, y2)
plt.xticks(visible=False)
plt.yticks(visible=False)
# draw a bbox of the region of the inset axes in the parent axes and
# connecting lines between the bbox and the inset axes area
mark_inset(ax, axins, loc1=2, loc2=4, fc="none", ec="0.5")
plt.draw()
plt.show()
The nicest way I know of to do this is to use mpl_toolkits.axes_grid1.inset_locator (part of matplotlib).
There is a great example with source code here: https://github.com/NelleV/jhepc/tree/master/2013/entry10
The basic steps to zoom up a portion of a figure with matplotlib
import numpy as np
from matplotlib import pyplot as plt
# Generate the main data
X = np.linspace(-6, 6, 1024)
Y = np.sinc(X)
# Generate data for the zoomed portion
X_detail = np.linspace(-3, 3, 1024)
Y_detail = np.sinc(X_detail)
# plot the main figure
plt.plot(X, Y, c = 'k')
# location for the zoomed portion
sub_axes = plt.axes([.6, .6, .25, .25])
# plot the zoomed portion
sub_axes.plot(X_detail, Y_detail, c = 'k')
# insert the zoomed figure
# plt.setp(sub_axes)
plt.show()