axis limit and sharex in matplotlib - python-3.x

I would like to show two images like these.
import matplotlib as plt
import numpy as np
fig, axes = plt.subplots(2, 1, )
axes[0].imshow(np.random.random((3, 3)))
axes[1].imshow(np.random.random((6, 3)))
Then, I tried sharex=True, which unexpectedly changed the ylim of the two plots. Why?? Is it possible to align the plots without changing the y axis limits?
fig, axes = plt.subplots(2, 1, sharex=True)
axes[0].imshow(np.random.random((3, 3)))
axes[1].imshow(np.random.random((6, 3)))
I use python 3.5.2 and matplotlib 1.5.1.

By default imshow axes have an equal aspect ratio. To preserve this, the limits are changed.
You have two options:
a) Dispense with equal aspect
Set the aspect to "auto". This allows the subplots to take the available space and share their axis.
import matplotlib.pyplot as plt
import numpy as np
fig, axes = plt.subplots(2, 1,sharex=True )
axes[0].imshow(np.random.random((3, 3)), aspect="auto")
axes[1].imshow(np.random.random((6, 3)), aspect="auto")
plt.show()
b) Adjust the figure size or spacings
You can adjust the figure size or the spacings such that the axes actually match. You'd then also need to set the height_ratios according to the image dimensions.
import matplotlib.pyplot as plt
import numpy as np
fig, axes = plt.subplots(2, 1,sharex=True, figsize=(3,5),
gridspec_kw={"height_ratios":[1,2]} )
plt.subplots_adjust(top=0.9, bottom=0.1, left=0.295, right=0.705, hspace=0.2)
axes[0].imshow(np.random.random((3, 3)))
axes[1].imshow(np.random.random((6, 3)))
plt.show()
This method either involves some trial and error or a sophisticated calculation, as e.g. in this answer.

Related

Matplotlib - maintain plot size of uneven subplots

I've been creating uneven subplots in matplotlib based on this question. The gridspec solution (third answer) worked a little better for me as it gives a bit more flexibility for the exact sizes of the subplots.
When I add a plot of a 2D array with imshow() the affected subplot is resized to the shape of the array. Is there any way to avoid that and keep the subplot-sizes (or rather aspect-ratio) fixed?
Here's the example code and the resulting image with the subplot-sizes I'm happy with:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import gridspec
# generate data
x = np.arange(0, 10, 0.2)
y = np.sin(x)
# plot
fig = plt.figure(figsize=(12, 9))
gs = gridspec.GridSpec(20, 20)
ax1 = fig.add_subplot(gs[0:5,0:11])
ax1.plot(x, y)
ax2 = fig.add_subplot(gs[6:11,0:11])
ax2.plot(y, x)
ax3 = fig.add_subplot(gs[12:20,0:11])
ax3.plot(y, x)
ax4 = fig.add_subplot(gs[0:9,13:20])
ax4.plot(x, y)
ax5 = fig.add_subplot(gs[11:20,13:20])
ax5.plot(y, x)
plt.show()
This is what happens if I additionally plot data from a 2D array with the following lines (insert before plt.show):
2Ddata = np.arange(0, 10, 0.1).reshape(10, 10)
im = ax3.imshow(2Ddata, cmap='rainbow')
How can I restore the original size of the subplot from ax3 (lower left corner)?
Including the line ax3.set_aspect('auto') seems to have solved the issue.

Bar missing while plotting using Matplotlib's Twinx

I'm using matplotlib.axes.Axes.twinx to have a shared x-axis in matplotlib for both . I dont know why instead of 13 bars to be plotted, only 12 of them are getting plotted.
Link of Data set
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
dataFrame=pd.read_csv("NEM.csv",sep=',')
dataFrame['ratio']=dataFrame['Expert']/dataFrame['Novice']
fig, ax1 = plt.subplots(figsize=(9, 6))
ax1.set_title('N-E Analysis')
xticklabels=dataFrame['Task'].tolist()
ax1.plot('Novice', data=dataFrame, marker='', color='dodgerblue', linewidth=2,label='Novice',zorder=100)
ax1.plot('Expert', data=dataFrame, marker='', color='darkorange', linewidth=2,label='Expert',zorder=200)
plt.ylim(0,120)
ax2 = ax1.twinx()
ax2.bar('Task','ratio', data=dataFrame, color='gray',width=0.35,label='NE',zorder=0)
ax1.spines['top'].set_visible(False)
ax1.spines['right'].set_visible(False)
ax1.spines['left'].set_visible(False)
ax2.spines['top'].set_visible(False)
ax2.spines['right'].set_visible(False)
ax2.spines['left'].set_visible(False)
ax1.set_xticklabels(xticklabels, rotation = 45, ha="right")
ax1.yaxis.grid()
ax1.tick_params(left='off',bottom='off')
ax2.tick_params(right='off')
plt.ylim(0,12)
h1, l1 = ax1.get_legend_handles_labels()
h2, l2 = ax2.get_legend_handles_labels()
p=ax1.legend(h2+h1, l2+l1, loc=2,frameon=False)
fig.tight_layout()
plt.show()
When using plots, it could be good practice to say explicitily how many bars or points you are going to plot. For instance, you can create an x-axis this way:
x_axis = np.arange(len(dataFrame[Task].tolist())
then:
ax1.plot(x_axis, dataFrame['Novice'].tolist(), ...)
after that you rename the xticklabels like this:
ax1.set_xticks(x_axis)
ax1.set_xticklabels(dataFrame[Task].tolist())
Do the same with the bar graph:
ax2.bar(x_axis, dataFrame['Ratio'].tolist(), ...)
This should do the trick.
Hope it helps.

How to show horizontal lines at tips of error bar plot using matplotlib?

I can generate an error-bar plot using the code below. The graph produced by the code shows vertical lines that represent the errors in y. I would like to have horizontal lines at the tips of these errors ("error bars") and am not sure how to do so.
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(1, 10, 10, dtype=int)
y = 2**x
yerr = np.sqrt(y)*10
fig, ax = plt.subplots()
ax.errorbar(x, y, yerr, solid_capstyle='projecting')
ax.grid(alpha=0.5, linestyle=':')
plt.show()
plt.close(fig)
The code generates the figure below. I've played with the solid_capstyle kwarg. Is there a specific kwarg that does what I am trying to do?
And as an example of what I'd like, the figure below:
In case it's relevant, I am using matplotlib 2.2.2
The argument you are looking for is capsize= in ax.errorbar(). The default is None so the length of the cap will default to the value of matplotlib.rcParams["errorbar.capsize"]. The number you give will be the length of the cap in points:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(1, 10, 10, dtype=int)
y = 2**x
yerr = np.sqrt(y)*10
fig, ax = plt.subplots()
ax.errorbar(x, y, yerr, solid_capstyle='projecting', capsize=5)
ax.grid(alpha=0.5, linestyle=':')
plt.show()

How can I add a normal distribution curve to multiple histograms?

With the following code I create four histograms:
import numpy as np
import pandas as pd
data = pd.DataFrame(np.random.normal((1, 2, 3 , 4), size=(100, 4)))
data.hist(bins=10)
I want the histograms to look like this:
I know how to make it one graph at the time, see here
But how can I do it for multiple histograms without specifying each single one? Ideally I could use 'pd.scatter_matrix'.
Plot each histogram seperately and do the fit to each histogram as in the example you linked or take a look at the hist api example here. Essentially what should be done is
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.mlab as mlab
fig = plt.figure()
ax1 = fig.add_subplot(221)
ax2 = fig.add_subplot(222)
ax3 = fig.add_subplot(223)
ax4 = fig.add_subplot(224)
for ax in [ax1, ax2, ax3, ax4]:
n, bins, patches = ax.hist(**your_data_here**, 50, normed=1, facecolor='green', alpha=0.75)
bincenters = 0.5*(bins[1:]+bins[:-1])
y = mlab.normpdf( bincenters, mu, sigma)
l = ax.plot(bincenters, y, 'r--', linewidth=1)
plt.show()

MatPlotLib + GeoPandas: Plot Multiple Layers, Control Figsize

Given the shape file available here: I know can produce the basic map that I need with county labels and even some points on the map (see below). The issue I'm having is that I cannot seem to control the size of the figure with figsize.
Here's what I have:
import geopandas as gpd
import matplotlib.pyplot as plt
%matplotlib inline
figsize=5,5
fig = plt.figure(figsize=(figsize),dpi=300)
shpfileshpfile=r'Y:\HQ\TH\Groups\NR\PSPD\Input\US_Counties\cb_2015_us_county_20m.shp'
c=gpd.read_file(shpfile)
c=c.loc[c['GEOID'].isin(['26161','26093','26049','26091','26075','26125','26163','26099','26115','26065'])]
c['coords'] = c['geometry'].apply(lambda x: x.representative_point().coords[:])
c['coords'] = [coords[0] for coords in c['coords']]
ax=c.plot()
#Control some attributes regarding the axis (for the plot above)
ax.spines['top'].set_visible(False);ax.spines['bottom'].set_visible(False);ax.spines['left'].set_visible(False);ax.spines['right'].set_visible(False)
ax.tick_params(axis='y',which='both',left='off',right='off',color='none',labelcolor='none')
ax.tick_params(axis='x',which='both',top='off',bottom='off',color='none',labelcolor='none')
for idx, row in c.iterrows():
ax.annotate(s=row['NAME'], xy=row['coords'],
horizontalalignment='center')
lat2=[42.5,42.3]
lon2=[-84,-83.5]
#Add another plot...
ax.plot(lon2,lat2,alpha=1,marker='o',linestyle='none',markeredgecolor='none',markersize=15,color='white')
plt.show()
As you can see, I opted to call the plots by the axis name because I need to control attributes of the axis, such as tick_params. I'm not sure if there is a better approach. This seems like a "no-brainer" but I can't seem to figure out why I can't control the figure size.
Thanks in advance!
I just had to do the following:
Use fig, ax = plt.subplots(1, 1, figsize = (figsize))
2.use the ax=ax argument in c.plot()
import geopandas as gpd
import matplotlib.pyplot as plt
%matplotlib inline
figsize=5,5
#fig = plt.figure(figsize=(figsize),dpi=300)
#ax = fig.add_subplot(111)
fig, ax = plt.subplots(1, 1, figsize = (figsize))
shpfileshpfile=r'Y:\HQ\TH\Groups\NR\PSPD\Input\US_Counties\cb_2015_us_county_20m.shp'
c=gpd.read_file(shpfile)
c=c.loc[c['GEOID'].isin(['26161','26093','26049','26091','26075','26125','26163','26099','26115','26065'])]
c['coords'] = c['geometry'].apply(lambda x: x.representative_point().coords[:])
c['coords'] = [coords[0] for coords in c['coords']]
c.plot(ax=ax)
ax.spines['top'].set_visible(False);ax.spines['bottom'].set_visible(False);ax.spines['left'].set_visible(False);ax.spines['right'].set_visible(False)
ax.tick_params(axis='y',which='both',left='off',right='off',color='none',labelcolor='none')
ax.tick_params(axis='x',which='both',top='off',bottom='off',color='none',labelcolor='none')
for idx, row in c.iterrows():
ax.annotate(s=row['NAME'], xy=row['coords'],
horizontalalignment='center')
lat2=[42.5,42.3]
lon2=[-84,-83.5]
ax.plot(lon2,lat2,alpha=1,marker='o',linestyle='none',markeredgecolor='none',markersize=15,color='white')

Resources