Matplotlib histogram with errorbars - matplotlib

I have created a histogram with matplotlib using the pyplot.hist() function. I would like to add a Poison error square root of bin height (sqrt(binheight)) to the bars. How can I do this?
The return tuple of .hist() includes return[2] -> a list of 1 Patch objects. I could only find out that it is possible to add errors to bars created via pyplot.bar().

Indeed you need to use bar. You can use to output of hist and plot it as a bar:
import numpy as np
import pylab as plt
data = np.array(np.random.rand(1000))
y,binEdges = np.histogram(data,bins=10)
bincenters = 0.5*(binEdges[1:]+binEdges[:-1])
menStd = np.sqrt(y)
width = 0.05
plt.bar(bincenters, y, width=width, color='r', yerr=menStd)
plt.show()

Alternative Solution
You can also use a combination of pyplot.errorbar() and drawstyle keyword argument. The code below creates a plot of the histogram using a stepped line plot. There is a marker in the center of each bin and each bin has the requisite Poisson errorbar.
import numpy
import pyplot
x = numpy.random.rand(1000)
y, bin_edges = numpy.histogram(x, bins=10)
bin_centers = 0.5*(bin_edges[1:] + bin_edges[:-1])
pyplot.errorbar(
bin_centers,
y,
yerr = y**0.5,
marker = '.',
drawstyle = 'steps-mid-'
)
pyplot.show()
My personal opinion
When plotting the results of multiple histograms on the the same figure, line plots are easier to distinguish. In addition, they look nicer when plotting with a yscale='log'.

Related

Equivalent of Hist()'s Layout hyperparameter in Sns.Pairplot?

Am trying to find hist()'s figsize and layout parameter for sns.pairplot().
I have a pairplot that gives me nice scatterplots between the X's and y. However, it is oriented horizontally and there is no equivalent layout parameter to make them vertical to my knowledge. 4 plots per row would be great.
This is my current sns.pairplot():
sns.pairplot(X_train,
x_vars = X_train.select_dtypes(exclude=['object']).columns,
y_vars = ["SalePrice"])
This is what I would like it to look like: Source
num_mask = train_df.dtypes != object
num_cols = train_df.loc[:, num_mask[num_mask == True].keys()]
num_cols.hist(figsize = (30,15), layout = (4,10))
plt.show()
What you want to achieve isn't currently supported by sns.pairplot, but you can use one of the other figure-level functions (sns.displot, sns.catplot, ...). sns.lmplot creates a grid of scatter plots. For this to work, the dataframe needs to be in "long form".
Here is a simple example. sns.lmplot has parameters to leave out the regression line (fit_reg=False), to set the height of the individual subplots (height=...), to set its aspect ratio (aspect=..., where the subplot width will be height times aspect ratio), and many more. If all y ranges are similar, you can use the default sharey=True.
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np
# create some test data with different y-ranges
np.random.seed(20230209)
X_train = pd.DataFrame({"".join(np.random.choice([*'uvwxyz'], np.random.randint(3, 8))):
np.random.randn(100).cumsum() + np.random.randint(100, 1000) for _ in range(10)})
X_train['SalePrice'] = np.random.randint(10000, 100000, 100)
# convert the dataframe to long form
# 'SalePrice' will get excluded automatically via `melt`
compare_columns = X_train.select_dtypes(exclude=['object']).columns
long_df = X_train.melt(id_vars='SalePrice', value_vars=compare_columns)
# create a grid of scatter plots
g = sns.lmplot(data=long_df, x='SalePrice', y='value', col='variable', col_wrap=4, sharey=False)
g.set(ylabel='')
plt.show()
Here is another example, with histograms of the mpg dataset:
import matplotlib.pyplot as plt
import seaborn as sns
mpg = sns.load_dataset('mpg')
compare_columns = mpg.select_dtypes(exclude=['object']).columns
mpg_long = mpg.melt(value_vars=compare_columns)
g = sns.displot(data=mpg_long, kde=True, x='value', common_bins=False, col='variable', col_wrap=4, color='crimson',
facet_kws={'sharex': False, 'sharey': False})
g.set(xlabel='')
plt.show()

Cartopy non-zero central longitude distorted with contourf

I am trying to plot the surface temperature from a NetCDF file using Cartopy and contourf. The domain of my plot is 30S to 60N and 90.044495E to 89.95552E (so all the way around the Earth centered on 90W). Here is a section of my code:
import numpy as np
import wrf as wrf
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
cart_proj = wrf.get_cartopy(skintemp)
lats, lons = wrf.latlon_coords(skintemp)
ax = plt.axes(projection=cart_proj)
ax.coastlines('50m', linewidth=0.8)
clevels = np.linspace(230,300,8)
cmap = plt.cm.YlOrRd
contours_fill = plt.contourf(wrf.to_np(lons), wrf.to_np(lats), skintemp, cmap=cmap, levels = clevels, transform=ccrs.PlateCarree(),extend="both")
cbar = plt.colorbar(contours_fill, shrink = .65, orientation='horizontal', pad=.05)
plt.show()
skintemp, lats and lons are all 2D arrays with dimensions (454, 1483), ordered (lat,lon), and cart_proj = wrf.projection.MercatorWithLatTS.
When I show the plot, it's distorted and incorrect:
I have determined that the issue has to do with the non-zero central longitude. The problem appears to be when the longitude changes from 179.90082 to -179.85632. lons.values[0,370]=179.90082, so I changed contourf to the following:
contours_fill = plt.contourf(wrf.to_np(lons[:,0:371]), wrf.to_np(lats[:,0:371]), skintemp[:,0:371], cmap=cmap, levels = clevels, transform=ccrs.PlateCarree(),extend="both")
which produces the following correct figure:
And when I change contourf to:
contours_fill = plt.contourf(wrf.to_np(lons[:,371:-1]), wrf.to_np(lats[:,371:-1]), skintemp[:,371:-1], cmap=cmap, levels = clevels, transform=ccrs.PlateCarree(),extend="both")
I get the other part of the map:
I cannot seem to get both parts of the map to display correctly together. I tried using contourf twice in the same plot, one for each section of the map, but only the last contourf line plots. Any help would be much appreciated!

Map a colorbar based on plot instead of imshow

I'm trying to get a colorbar for the following minimal example of my code.
g1 = gridspec.GridSpec(1, 1)
f, ((ax0)) = plt.subplots(1, 1)
ax0 = subplot(g1[0])
cmap = matplotlib.cm.get_cmap('viridis')
for i in linspace(0,1,11):
x = [-1,0,1]
y = [i,i,i]
rgba = cmap(i)
im = ax0.plot(x,y,color=rgba)
f.colorbar(im)
I also tried f.colorbar(cmap)
Probably pretty obvious, but I get errors such as
'ListedColormap' object has no attribute 'autoscale_None'
In reality, the value defining i is more complex, but I think this should do the trick. My data is plotted with plot and not with imshow (for which I know how to make the colormap).
The answers so far seem overly complicated. fig.colorbar() expects a ScalarMappable as its first argument. Often ScalarMappables are produced by imshow or contourplots and are readily avaible.
In this case you would need to define your custom ScalarMappable to provide to the colorbar.
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
cmap = plt.cm.get_cmap('viridis')
for i in np.linspace(0,1,11):
x = [-1,0,1]
y = [i,i,i]
rgba = cmap(i)
im = ax.plot(x,y,color=rgba)
sm = plt.cm.ScalarMappable(cmap=cmap)
sm.set_array([])
fig.colorbar(sm)
plt.show()
You should pass an Image or ContourSet when you call colorbar on a Figure.
You can make an image of the data points by calling plt.imshow with the data. You can start with this:
data = []
for i in np.linspace(0,1,11):
x = [-1,0,1]
y = [i,i,i]
rgba = cmap(i)
ax0.plot(x,y,color=rgba)
data.append([x, y])
image = plt.imshow(data)
figure.colorbar(image)
plt.show()
Reference:
https://matplotlib.org/api/figure_api.html#matplotlib.figure.Figure.colorbar
Oluwafemi Sule's solution almost works, but it plots the matrix into the same figure as the lines. Here a solution that opens a second figure, does the imshow call on that second figure, uses the result to draw the colorbar in the first figure, and then closes the second figure before calling plt.show():
import matplotlib
from matplotlib import pyplot as plt
from matplotlib import gridspec
import numpy as np
cmap = matplotlib.cm.get_cmap('viridis')
g1 = gridspec.GridSpec(1, 1)
f0, ((ax0)) = plt.subplots(1, 1)
f1, ((ax1)) = plt.subplots(1, 1)
for i in np.linspace(0,1,11):
x = [-1,0,1]
y = [i,i,i]
rgba = cmap(i)
ax0.plot(x,y,color=rgba)
data = np.linspace(0,1,100).reshape((10,10))
image = ax1.imshow(data)
f0.colorbar(image)
plt.close(f1)
plt.show()
The result looks like this:

Draw colorbar with twin scales

I'd like to draw a (vertical) colorbar, which has two different scales (corresponding to two different units for the same quantity) on each side. Think Fahrenheit on one side and Celsius on the other side. Obviously, I'd need to specify the ticks for each side individually.
Any idea how I can do this?
That should get you started:
import matplotlib.pyplot as plt
import numpy as np
# generate random data
x = np.random.randint(0,200,(10,10))
plt.pcolormesh(x)
# create the colorbar
# the aspect of the colorbar is set to 'equal', we have to set it to 'auto',
# otherwise twinx() will do weird stuff.
cbar = plt.colorbar()
pos = cbar.ax.get_position()
cbar.ax.set_aspect('auto')
# create a second axes instance and set the limits you need
ax2 = cbar.ax.twinx()
ax2.set_ylim([-2,1])
# resize the colorbar (otherwise it overlays the plot)
pos.x0 +=0.05
cbar.ax.set_position(pos)
ax2.set_position(pos)
plt.show()
If you create a subplot for the colorbar, you can create a twin axes for that subplot and manipulate it like a normal axes.
import matplotlib.colors as mcolors
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(-1,2.7)
X,Y = np.meshgrid(x,x)
Z = np.exp(-X**2-Y**2)*.9+0.1
fig, (ax, cax) = plt.subplots(ncols=2, gridspec_kw={"width_ratios":[15,1]})
im =ax.imshow(Z, vmin=0.1, vmax=1)
cbar = plt.colorbar(im, cax=cax)
cax2 = cax.twinx()
ticks=np.arange(0.1,1.1,0.1)
iticks=1./np.array([10,3,2,1.5,1])
cbar.set_ticks(ticks)
cbar.set_label("z")
cbar.ax.yaxis.set_label_position("left")
cax2.set_ylim(0.1,1)
cax2.set_yticks(iticks)
cax2.set_yticklabels(1./iticks)
cax2.set_ylabel("1/z")
plt.show()
Note that in newer version of matplotlib, the above answers no long work (as #Ryan Skene pointed out). I'm using v3.3.2. The secondary_yaxis function works for the colorbars in the same way as for regular plot axes and gives one colorbar with two scales: https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.secondary_yaxis.html#matplotlib.axes.Axes.secondary_yaxis
import matplotlib.pyplot as plt
import numpy as np
# generate random data
x = np.random.randint(0,200,(10,10)) #let's assume these are temperatures in Fahrenheit
im = plt.imshow(x)
# create the colorbar
cbar = plt.colorbar(im,pad=0.1) #you may need to adjust this padding for the secondary colorbar label[enter image description here][1]
cbar.set_label('Temperature ($^\circ$F)')
# define functions that relate the two colorbar scales
# e.g., Celcius to Fahrenheit and vice versa
def F_to_C(x):
return (x-32)*5/9
def C_to_F(x):
return (x*9/5)+32
# create a second axes
cbar2 = cbar.ax.secondary_yaxis('left',functions=(F_to_C,C_to_F))
cbar2.set_ylabel('Temperatrue ($\circ$C)')
plt.show()
I am using an inset axis for my colorbar and, for some reason, I found the above to answers no longer worked as of v3.4.2. The twinx took up the entire original subplot.
So I just replicated the inset axis (instead of using twinx) and increased the zorder on the original inset.
axkws = dict(zorder=2)
cax = inset_axes(
ax, width="100%", height="100%", bbox_to_anchor=bbox,
bbox_transform=ax.transAxes, axes_kwargs=axkws
)
cbar = self.fig.colorbar(mpl.cm.ScalarMappable(cmap=cmap), cax=cax)
cbar.ax.yaxis.set_ticks_position('left')
caxx = inset_axes(
ax, width="100%", height="100%",
bbox_to_anchor=bbox, bbox_transform=ax.transAxes
)
caxx.yaxis.set_ticks_position('right')

matplotlib: Using append_axes multiple times

I'm new to matplotlib, so I do not have strong enough command of the language to know if I'm going about this the right way, but I've been searching for the answer for a while now, and I just cannot find anything one way or the other on this.
I know how to use matplotlib's append_axes locator function to append histograms alongside 2D plots, e.g.:
axMain= fig1.add_subplot(111)
cax = plt.contourf(xl,y1,z1)
divider = make_axes_locatable(axMain)
axHisty = divider.append_axes("right", 1.2, pad=0.1, sharey=axMain)
axHisty.plot(x,y)
and I also know how to append a colorbar in a similar manner:
divider = make_axes_locatable(axMain)
ax_cb = divider.new_horizontal(size='5%', pad=0.3)
fig1.add_axes(ax_cb)
fig1.colorbar(cax, cax=ax_cb)
What I am not clear on is how to do both in the same subplot without the two appended figures overlapping. To be clear, I want the histogram to have the same yaxis ticks and height as the axContour, and I want the colorbar to have the same height as axContour. ImageGrid doesn't seem to be quite what I want because I do not want to fix the size of my plot. It would better for me if I could add/remove these figure "embellishments" interactively, but maybe that is not possible...Let me know!
You are already fixing the size of your plot with divider.append_axes("right", 1.2, pad=0.1, sharey=axMain). 1.2 is the size of the new axis. Below is a way of plotting three axes using gridspec.
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.gridspec as grd
from numpy.random import rand
# add axes
fig1 = plt.figure(1)
gs = grd.GridSpec(1, 3, width_ratios=[5,1, 1], wspace=0.3)
axMain = plt.subplot(gs[0])
axHisty = plt.subplot(gs[1])
ax_cb = plt.subplot(gs[2])
# some things to plot
x = [1,2,3,4]
y = [1,2,3,4]
x1 = [1,2,3,4]
y1 = [1,2,3,4]
z1 = rand(4,4)
# make plots
h = axMain.contourf(x1,y1,z1)
axHisty.plot(x,y)
cb = plt.colorbar(h, cax = ax_cb)
plt.show()