how to get ticks on both sides at same tick locations - matplotlib

I want y-ticks on both sides(left & right), but with different labels at the same y points. I tried following, but I'm not able to position ticks at same location.
I'm newbie to matplotlib. I have gone through the matplotlib example, but couldn't figure it out the solution to my problem.
http://matplotlib.org/examples/pylab_examples/barchart_demo2.html
Greatly appreciate any suggestions.
Here is the code:
import numpy as np
import matplotlib.pyplot as plt
groups = [ 1, 2, 3, 4, 5 ]
members = [ 1, 2, 3, 4 ]
colors = [ 'r', 'y', 'b', 'k']
#store score of members for the groups
scores = {member: 100*np.random.rand(len(groups)) for member in members}
group_cnt = group_cnt = sum([scores[member] for member in members])
print scores
fig = plt.figure(figsize=(8,6))
ax = fig.add_subplot(111)
width_bar = 0.5
width_space = 0.2
#position of barh
total_space = len(groups)*(len(members)*width_bar)+(len(groups)-1)*width_space
ind_space = len(members)*width_bar
step = ind_space/2.
#pos for labels
pos = np.arange(step, total_space+width_space, ind_space+width_space)
#pos for grin lines
minor_pos = np.arange(ind_space, total_space+width_space, ind_space+width_space)
for idx in xrange(len(members)):
ax.barh(pos-step+idx*width_bar, scores[members[idx]], width_bar, edgecolor='k', color=colors[idx], linewidth=3)
ax.invert_yaxis()
ax.set_yticks(pos)
ax.set_yticklabels(groups)
ax.set_yticks(minor_pos, minor=True)
ax.grid(True, which='minor')
ax.set_ylabel('Groups')
ax2 = ax.twinx()
ax2.set_ylabel('Group totals')
ax2.set_yticks(pos)
ax2.set_yticklabels(group_cnt)
ax2.invert_yaxis()
plt.show()

I think you got caught by a bit of trickery in that example. There is a plot([100, 100], [0, 5]) in the demo code which is doing a lot of non-obvious work (I am working on submitting a PR to improve the demo) in making sure that the ylimits are the same for both yaxis.
You just need to add a
ax2.set_ylim(ax.get_ylim())
before you call show.
You also have an un-related error ax2.set_yticklabels(group_cnt) -> ax2.set_yticklabels(groups).
[side note, generated PR #2327]

Related

Matplotlib - Change the white space between specific subplots in a grid grid

I have a grid of subplots and I would like to adjust the white space between only two of them such that the shared x labels are centred without overlapping either graph.
This question has a solution for when these are the only two subplots. However I'm struggling to adjust this to two specific subplots in a grid of many.
This code can be used to illustrate my problem.
In [1]
fig = plt.figure(figsize = (15, 10))
gs = fig.add_gridspec(2,4)
ax1 = fig.add_subplot(gs[0, 0])
ax2 = fig.add_subplot(gs[0, 1:3])
ax3 = fig.add_subplot(gs[0, 3])
ax4 = fig.add_subplot(gs[1, 0])
ax5 = fig.add_subplot(gs[1, 1])
ax6 = fig.add_subplot(gs[1, 2])
ax7 = fig.add_subplot(gs[1, 3])
np.random.seed(19680801)
# Example data
people = ('Really Really Long Name', 'Another Long Name', 'Short Name', 'Name', 'N')
y_pos = np.arange(len(people))
performance = 3 + 10 * np.random.rand(len(people))
ax5.barh(y_pos, performance, align='center')
ax5.set_yticks(y_pos, labels=people)
ax5.invert_xaxis()
ax5.set_xlabel('Label')
ax5.set_title('Bar 1')
ax6.barh(y_pos, performance, align='center')
ax6.set_yticks(y_pos, labels=people)
ax6.set_xlabel('Label')
ax6.set_title('Bar 2')
Out [1]
If I apply the solution to the linked question here then every subplot's white space is effected. I know this is because it calls on fig.dpi_scale_trans which effects the whole figure but I'm new to transforms and can't work out what to use in its place
In [2]
fig.tight_layout()
fig.subplots_adjust(wspace=0.7)
plt.setp(axes[0].yaxis.get_majorticklabels(), ha='center')
# Create offset transform by some points in x direction
dx = 60 / 72.
dy = 0 / 72.
offset = mlb.transforms.ScaledTranslation(dx, dy, fig.dpi_scale_trans)
# apply offset transform to all y ticklabels.
for label in ax6.yaxis.get_majorticklabels():
label.set_transform(label.get_transform() + offset)
Out [2]
I figured out how to solve this so posting my own answer in case anybody has a similar problem in the future.
This question and answer from 7 years ago contained the necessary help to solve the problem.
Essentially you must plot and position different GridSpecs in the figure using GridSpec from matplotlib.gridspec rather than calling one with fig.add_gridspec()
Link to GridSpec documentation
Following on from my example above I wanted to create a 2x4 grid. To do that we can plot the following grids in set positions of the figure:
Left: 1x2
Top Centre: 1x1
Bottom Centre: 2x1
Right: 1x2
In [1]:
from matplotlib.gridspec import GridSpec
fig = plt.figure(figsize = (15, 10))
# Example Data
people = ('Really Really Long Name', 'Another Long Name', 'Short Name', 'Name',
'N')
y_pos = np.arange(len(people))
performance = 3 + 10 * np.random.rand(len(people))
# Left portion of grid (2x1).
# 'left' and 'right' tell the grid where it should start and finish
gs1 = GridSpec(2, 1)
gs1.update(left = 0, right = 0.2)
# Plotting empty subplots for illustration purposes
for i in gs1:
ax = plt.subplot(i)
# Mirroring on the right portion of the grid
gs2 = GridSpec(2, 1)
gs2.update(left = 0.8, right = 1)
for i in gs2:
ax = plt.subplot(i)
# Plotting in top center
# Note here we only need to plot a 1x1
gs3 = GridSpec(1, 1)
gs3.update(left = 0.25, right = 0.75, bottom = 0.53) #0.53 aligns with sides
ax3 = plt.subplot(gs3[0])
# Plotting the barh in the bottom center
# wsapce only adjusts this grid not the entire figure
gs4 = GridSpec(1, 2)
gs4.update(left = 0.2, right = 0.8, top = 0.45, wspace = 0.75)
# Left barh
ax5 = plt.subplot(gs4[0])
ax5.barh(y_pos, performance, align='center')
ax5.set_yticks([])
ax5.invert_xaxis()
ax5.set_xlabel('Label')
ax5.set_title('Bar 1')
# Right barh
ax6 = plt.subplot(gs4[1])
ax6.barh(y_pos, performance, align='center')
ax6.set_yticks(y_pos, labels=people)
ax6.set_xlabel('Label')
ax6.set_title('Bar 2')
plt.show()
Out [1]:

How to align a legend relative to a GridSpec cell?

I am creating a figure like this:
fig = plt.figure(figsize = (7, 8))
outer_grid = gridspec.GridSpec(2, 1, height_ratios = [2, 1])
inner_grid1 = gridspec.GridSpecFromSubplotSpec(4, 3, subplot_spec=outer_grid[0])
inner_grid2 = gridspec.GridSpecFromSubplotSpec(2, 3, subplot_spec=outer_grid[1])
Now I would like to have one legend for all plots in inner_grid1 and a separate legend for all plots in inner_grid2. And I would like those legends to be placed nicely, even though they are higher than a single plot, and cannot have more than one column to not make the figure too wide.
Here is an example where I tried to align the legends with trial and error with method 2 below, however this took ages to make.
So I see three options to achieve this, none of which work:
Place the legend as part of an Axes object, but manually move it outside of the actual plot using axes.legend([...], bbox_to_anchor=(x, y)). This does not work when the legend is higher as a single plot, because it rescales the plots to fit the legend into its grid cell.
Place the legend globally on the Figure object. This works, but makes the correct placement really hard. I cannot use loc = "center right", since it centers it for the full figure instead of just the inner_grid1 or inner_grid2 plots.
Place the legend locally on the GridSpecFromSubplotSpec object. This would be perfect. However there is no method to create a legend on a GridSpecFromSubplotSpec or related classes, and the pyplot.legend method misses parameters to restrict the loc to parts of a grid.
Is there a way to place a legend as described?
As requested, a small code example generating something similar as desired.
This example uses method 2:
#!/usr/bin/env python3
import pandas as pd, seaborn as sns
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
GENOMES = ["spneumoniae", "ecoliK12", "scerevisiae", "celegans", "bmori", "hg38"]
fig = plt.figure(figsize = (7, 8))
outer_grid = gridspec.GridSpec(2, 1, height_ratios = [2, 1])
inner_grid1 = gridspec.GridSpecFromSubplotSpec(4, 3, subplot_spec=outer_grid[0])
inner_grid2 = gridspec.GridSpecFromSubplotSpec(2, 3, subplot_spec=outer_grid[1])
# plots are in sets of six, 2 rows by 3 columns each
for index, genome in enumerate(GENOMES):
data = pd.DataFrame({"x": [0, 1, 2, 3, 0, 1, 2, 3], "y": [1, 0, 3, 2, 1, 0, 3, 2], "hue": ["a", "a", "a", "a", "b", "b", "b", "b"]})
# first set of six
ax1 = plt.Subplot(fig, inner_grid1[index])
ax1 = sns.lineplot(data = data, x = "x", y = "y", hue = "hue", ax = ax1)
ax1.set_xlabel("")
ax1.set_ylabel("")
if index == 2:
ax1.legend()
handles, labels = ax1.get_legend_handles_labels()
fig.legend(handles, labels, loc = "center left", title = "", bbox_to_anchor=(0.9, 2/3 - 0.03))
ax1.legend([], [], loc = "lower center", title = f"{genome}")
fig.add_subplot(ax1)
# second set of six
ax2 = plt.Subplot(fig, inner_grid1[index + 6])
ax2 = sns.lineplot(data = data, x = "x", y = "y", hue = "hue", ax = ax2)
ax2.set_xlabel("")
ax2.set_ylabel("")
ax2.legend([], [], loc = "upper center", title = f"{genome}")
fig.add_subplot(ax2)
#third set of six
ax3 = plt.Subplot(fig, inner_grid2[index])
ax3 = sns.lineplot(data = data, x = "x", y = "y", hue = "hue", ax = ax3)
ax3.set_xlabel("")
ax3.set_ylabel("")
if index == 2:
ax3.legend(["#unitigs", "avg. unitig len."])
handles, labels = ax3.get_legend_handles_labels()
fig.legend(handles, labels, loc = "center left", title = "", bbox_to_anchor=(0.9, 1/6 + 0.05))
ax3.legend([], [], loc = "upper center", title = f"{genome}")
fig.add_subplot(ax3)
plt.savefig("stackoverflow_test.pdf", bbox_inches="tight")

Colormap is not categorizing the data properly

Here is my script to plot data from a Geogtiff file using basemap. The data is categorical and there are 13 categories within this domain. The problem is that some categories get bunched up into one colour and thus some resolution is lost.
Unfortunately, I do not know how to fix this. I read that plt.cm.get_cmp is better for discrete datasets but I have not gotten it to work unfortunately.
gtif = 'some_dir'
ds = gdal.Open(gtif)
data = ds.ReadAsArray()
gt = ds.GetGeoTransform()
proj = ds.GetProjection()
xres = gt[1]
yres = gt[5]
xmin = gt[0] + xres
xmax = gt[0] + (xres * ds.RasterXSize) - xres
ymin = gt[3] + (yres * ds.RasterYSize) + yres
ymax = gt[3] - yres
xy_source = np.mgrid[xmin:xmax+xres:xres, ymax+yres:ymin:yres]
ds = None
fig2 = plt.figure(figsize=[12, 11])
ax2 = fig2.add_subplot(111)
ax2.set_title("Land use plot")
bm2 = Basemap(ax=ax2,projection='cyl',llcrnrlat=ymin,urcrnrlat=ymax,llcrnrlon=xmin,urcrnrlon=xmax,resolution='l')
bm2.drawcoastlines(linewidth=0.2)
bm2.drawcountries(linewidth=0.2)
data_new=np.copy(data)
data_new[data_new==255] = 0
nbins = np.unique(data_new).size
cb =plt.cm.get_cmap('jet', nbins+1)
img2 =bm2.imshow(np.flipud(data_new), cmap=cb)
ax2.set_xlim(3, 6)
ax2.set_ylim(50,53)
plt.show()
labels = [str(i) for i in np.unique(data_new)]
cb2=bm2.colorbar(img2, "right", size="5%", pad='3%', label='NOAH Land Use Category')
cb2.set_ticklabels(labels)
cb2.set_ticks(np.unique(data_new))
Here are the categories that are found within the domain (numbered classes):
np.unique(data_new)
array([ 0, 1, 4, 5, 7, 10, 11, 12, 13, 14, 15, 16, 17], dtype=uint8)
Thanks so much for any help here. I have also attached the output image that shows the mismatch. (not working)
First, this colormap problem is independent of the use of basemap. The following is therefore applicable to any matplotlib plot.
The problem here is that creating a colormap from n values distributes those values equally over the colormap range. Some values from the image therefore fall into the same colorrange within the colormap.
To prevent this, one can generate a colormap with the initial number of categories as shown below.
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.colors
# generate some data
data = np.array( [ 0, 1, 4, 5, 7, 10]*8 )
np.random.shuffle(data)
data = data.reshape((8,6))
# generate colormap and norm
unique = np.unique(data)
vals = np.arange(int(unique.max()+1))/float(unique.max())
cols = plt.cm.jet(vals)
cmap = matplotlib.colors.ListedColormap(cols, int(unique.max())+1)
norm=matplotlib.colors.Normalize(vmin=-0.5, vmax=unique.max()+0.5)
fig, ax = plt.subplots(figsize=(5,5))
im = ax.imshow(data, cmap=cmap, norm=norm)
for i in range(data.shape[0]):
for j in range(data.shape[1]):
ax.text(j,i,data[i,j], color="w", ha="center", va="center")
cb = fig.colorbar(im, ax=ax, norm=norm)
cb.set_ticks(unique)
plt.show()
This can be extended to exclude the colors not present in the image as follows:
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.colors
# generate some data
data = np.array( [ 0, 1, 4, 5, 7, 10]*8 )
np.random.shuffle(data)
data = data.reshape((8,6))
unique, newdata = np.unique(data, return_inverse=1)
newdata = newdata.reshape(data.shape)
# generate colormap and norm
new_unique = np.unique(newdata)
vals = np.arange(int(new_unique.max()+1))/float(new_unique.max())
cols = plt.cm.jet(vals)
cmap = matplotlib.colors.ListedColormap(cols, int(new_unique.max())+1)
norm=matplotlib.colors.Normalize(vmin=-0.5, vmax=new_unique.max()+0.5)
fig, ax = plt.subplots(figsize=(5,5))
im = ax.imshow(newdata, cmap=cmap, norm=norm)
for i in range(newdata.shape[0]):
for j in range(newdata.shape[1]):
ax.text(j,i,data[i,j], color="w", ha="center", va="center")
cb = fig.colorbar(im, ax=ax, norm=norm)
cb.ax.set_yticklabels(unique)
plt.show()

Matplotlib: combining two bar charts

I'm trying to generate 'violin'-like bar charts, however i'm running in several difficulties described bellow...
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
# init data
label = ['aa', 'b', 'cc', 'd']
data1 = [5, 7, 6, 9]
data2 = [7, 3, 6, 1]
data1_minus = np.array(data1)*-1
gs = gridspec.GridSpec(1, 2, top=0.95, bottom=0.07,)
fig = plt.figure(figsize=(7.5, 4.0))
# adding left bar chart
ax1 = fig.add_subplot(gs[0])
ax1.barh(pos, data1_minus)
ax1.yaxis.tick_right()
ax1.yaxis.set_label(label)
# adding right bar chart
ax2 = fig.add_subplot(gs[1], sharey=ax1)
ax2.barh(pos, data2)
Trouble adding 'label' as labels for both charts to share.
Centering the labels between the both plots (as well as vertically in the center of each bar)
Keeping just the ticks on the outer yaxis (not inner, where the labels would go)
If I understand the question correctly, I believe these changes accomplish what you're looking for:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
# init data
label = ['aa', 'b', 'cc', 'd']
data1 = [5, 7, 6, 9]
data2 = [7, 3, 6, 1]
data1_minus = np.array(data1)*-1
gs = gridspec.GridSpec(1, 2, top=0.95, bottom=0.07,)
fig = plt.figure(figsize=(7.5, 4.0))
pos = np.arange(4)
# adding left bar chart
ax1 = fig.add_subplot(gs[0])
ax1.barh(pos, data1_minus, align='center')
# set tick positions and labels appropriately
ax1.yaxis.tick_right()
ax1.set_yticks(pos)
ax1.set_yticklabels(label)
ax1.tick_params(axis='y', pad=15)
# adding right bar chart
ax2 = fig.add_subplot(gs[1], sharey=ax1)
ax2.barh(pos, data2, align='center')
# turn off the second axis tick labels without disturbing the originals
[lbl.set_visible(False) for lbl in ax2.get_yticklabels()]
plt.show()
This yields this plot:
As for keeping the actual numerical ticks (if you want those), the normal matplotlib interface ties the ticks pretty closely together when the axes are shared (or twinned). However, the axes_grid1 toolkit can allow you more control, so if you want some numerical ticks you can replace the entire ax2 section above with the following:
from mpl_toolkits.axes_grid1 import host_subplot
ax2 = host_subplot(gs[1], sharey=ax1)
ax2.barh(pos, data2, align='center')
par = ax2.twin()
par.set_xticklabels('')
par.set_yticks(pos)
par.set_yticklabels([str(x) for x in pos])
[lbl.set_visible(False) for lbl in ax2.get_yticklabels()]
which yields:

matplotlib long legend name

I have a following code which produces a graph -
# imports specific to the plots in this example
import sys
import numpy as np
from matplotlib import cm
import matplotlib.pyplot as plt
resultsDirectory='results'
outputFile=resultsDirectory+".pdf"
axisLabelFontSize=16
borderWidth=0.0
# Twice as wide as it is tall.
fig = plt.figure(figsize=plt.figaspect(0.5))
ax = fig.add_subplot(111)
# Set up the Grid
[i.set_linewidth(borderWidth) for i in ax.spines.itervalues()]
unsatX=[680,2775,3821,680,4073,941,1202,1463]
unsatY=[1,1,1,4,1,2,2,2]
paretoX=[680, 1203, 1726, 4870]
paretoY=[10,7, 4,1]
satX=[4870,680,1727,1726,1203,680]
satY=[1,13,7,4,7,10]
typeX=[680, 1727]
typeY=[13, 7]
leftX=[680]
leftY=[12]
c = np.rec.fromarrays([paretoX, paretoY], names='x,y')
c.sort()
paretoX=c.x
paretoY=c.y
markrsz=8
l4, = plt.plot(paretoX, paretoY, '#000000', lw=2, label='Pareto Curve(unfolding, period locality)',markersize=markrsz,zorder = 10)
l1, = plt.plot(satX, satY, 'bo', label='Sat Points',markersize=markrsz,zorder = 10)
l2, = plt.plot(unsatX, unsatY, 'ro',marker='s',label='Unsat Points',markersize=markrsz,zorder = 10)
l5, = plt.plot(leftX, leftY, 'gp',label='Proc. count pareto points',markersize=markrsz)
l6, = plt.plot(typeX, typeY, 'w*',label='Modulo pareto points',markersize=markrsz,zorder=10)
leg=plt.legend(bbox_to_anchor=(0.,-0.200, 1., 1.102), loc=3, numpoints=1,
ncol=3, mode="expand", borderaxespad=0., fancybox=True, shadow=True,prop={'size':axisLabelFontSize})
rect = leg.get_frame()
rect.set_facecolor('#cccccc') # a grayscale intensity
#leg.set_frame_on(False)
latency=[680,2775,4870, 680,3821,4868, 680,1727,4341,4864, 680,1203,1726,1203, 680,4073,4334,4595,4856, 941,1202,1463,1724]
processor=[1, 1, 1,13, 1, 1, 7, 7, 1, 1, 4, 4, 4, 7,10,1, 1, 1, 1, 2, 2, 2, 2]
ax.set_xlabel('Period',size=axisLabelFontSize,labelpad=10)
ax.set_ylabel('Processors',size=axisLabelFontSize,labelpad=10)
ax.set_xlim(0, max(latency)+100)
ax.set_ylim(0, max(processor)+1)
# Set Border width zero
[i.set_linewidth(0) for i in ax.spines.itervalues()]
gridLineWidth=0.1
ax.set_axisbelow(False)
gridlines = ax.get_xgridlines()+ax.get_ygridlines()
#ax.set_axisbelow(True)
plt.setp(gridlines, 'zorder', 5)
ax.yaxis.grid(True, linewidth=gridLineWidth, linestyle='-', color='0.6',alpha='0.3')
ax.xaxis.grid(False)
ax.xaxis.set_ticks_position('none')
ax.yaxis.set_ticks_position('none')
fig.savefig(outputFile, format="pdf", bbox_inches='tight')
The legends in the plot get messed up.
Could someone tell me how do i fix a long legend entry which overwrites into area of other entry? What would be ideal if, I could do 3 legend entries in first row and two legend entries in the second row.
Besides the workaround found by the question author, a possibility is to add new lines for long labels:
...
l4, = plt.plot(paretoX, paretoY, '#000000', lw=2,
label='Pareto Curve \n(unfolding, period locality)',markersize=markrsz,zorder = 10)
...
The following modification to the code also solved my problem -
leg=plt.legend(bbox_to_anchor=(0.,-0.350, 1., 1.102), loc=3, numpoints=1, ncol=2 , borderaxespad=0., fancybox=True, shadow=True,prop={'size':axisLabelFontSize})