I am learning to make color bars, and thus learning to make good use of plt.Normalize , I succeeded to make it work with scipy.stats.norm, but when tryin to use plt.norm, I found out that I have to do two things to make it work well :
defining vmin and vmax to -1.96 and 1.96 respectively,I guess that it's because they are the z value for 95% confidence interval, but I still don't precisely know why they have we have to set vmin and vmax to those values
dividing the standard deviation by sqrt( number of elements )
I don't understand why are those two points important for using the Norm. Any help is welcome ! thank you in advance
# Use the following data for this assignment:
%matplotlib notebook
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import scipy.stats as st
df = pd.DataFrame([np.random.normal(33500,150000,3650),
np.random.normal(41000,90000,3650),
np.random.normal(41000,120000,3650),
np.random.normal(48000,55000,3650)],
index=[1992,1993,1994,1995])
new_df = pd.DataFrame()
new_df['mean'] = df.mean(axis =1)
new_df['std'] = df.std(axis =1)
new_df['se'] = df.sem(axis= 1)
new_df['C_low'] = new_df['mean'] - 1.96 * new_df['se']
new_df['C_high'] = new_df['mean'] + 1.96 * new_df['se']
from scipy.stats import norm
import numpy as np
# First, Define a figure
fig = plt.figure()
# next define its the axis and create a plot
ax = fig.add_subplot(1,1,1)
# change the ticks
xticks = np.array(new_df.index,dtype= 'str')
# remove the top and right borders
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
# draw the bars in the axis
bars = ax.bar(xticks,new_df['mean'].values,
yerr = (1.96*new_df['se'],1.96*new_df['se']),
capsize= 10)
# define labels
plt.xlabel('YEARS',size = 14)
plt.ylabel('FREQUENCY',size = 14)
# Define color map
cmap = plt.cm.get_cmap('coolwarm')
# define scalar mappable
sm = plt.cm.ScalarMappable(cmap = cmap)
# draw the color bar
cbar = plt.colorbar(cmap = cmap, mappable =sm)
# define norm (will be used later to turn y to a value from 0 to 1 )
# define the events
class Cursor(object):
def __init__(self,ax):
self.ax = ax
self.lx = ax.axhline(color = 'c')
self.txt = ax.text(1,50000,'')
def mouse_movemnt(self,event):
#behaviour outside of the plot
if not event.inaxes:
return
#behavior inside the plot
y = event.ydata
self.lx.set_ydata(y)
for idx,bar in zip(new_df.index, bars):
norm = plt.Normalize(vmin =-1.96,vmax = 1.96)
mean = new_df.loc[idx,'mean']
err = new_df.loc[idx, 'se']
std = new_df.loc[idx,'std']/ np.sqrt(df.shape[1]) # not sure why we re dividing by np.sqrt(df.shape[1])
self.txt.set_text(f'Y = {round(y,2)} \n')
color_prob = norm( (mean - y)/std)
#color_prob = norm.cdf(y,loc = mean, scale = err) # you can also use this
bar.set_color( cmap(color_prob))
# connect the events to the plot
cursor = Cursor(ax)
plt.connect('motion_notify_event', cursor.mouse_movemnt)
None
After few hours of thinking, an explanation barged into my head and I was able to answer all of my inquiries,
first before answering the first point, I will answer the second one, the standard deviation was divided by the sqrt(nbr of element) because the resulting value is the standard error.
I will now move on to answering the first part:
(I can't embed images for now and I can't use latex either so I have to put links of the image instead). But here is the conclusion in advance, for all values within that confidence interval, the function (y-mean)/se will spit out a value within the range [−1.96,1.96]
answer of first part
Please, if I left something out or you have a better answer, share it with me.
Related
I am trying to label the intersection of two lines in a plot I have made. The code/MWE is:
import matplotlib.pyplot as plt
import numpy as np
#ignore my gross code, first time ever using Python :-)
#parameters
d = 0.02
s = 0.50 #absurd, but dynamics robust to 1>s>0
A = 0.90
u = 0.90
#variables
kt = np.arange(0, 50, 1)
invest = (1 - np.exp(-d*kt))*kt
output = A*u*kt
saving = s*output
#plot
plt.plot(kt, invest, 'r', label='Investment')
plt.plot(kt, output, 'b', label='Output')
plt.plot(kt, saving, label='Saving')
plt.xlabel('$K_t$')
plt.ylabel('$Y_t$, $S_t$, $I_t$')
plt.legend(loc="upper left")
#Steady State; changes with parameters
Kbar = np.log(1-s*A*u)/-d
x, y = [Kbar, Kbar], [0, s*A*u*Kbar]
plt.plot(x, y, 'k--')
#custom axes (no top and right)
ax = plt.gca()
right_side = ax.spines["right"]
right_side.set_visible(False)
top_side = ax.spines["top"]
top_side.set_visible(False)
#ax.grid(True) #uncomment for gridlines
plt.xlim(xmin=0) #no margins; preference
plt.ylim(ymin=0)
plt.show()
which creates:
I am trying to create a little label at the bottom of the dotted black line that says "$K^*$". I want it to coincide with Kbar so that, like the black line, it moves along with the parameters. Any tips or suggestions here?
I don't quite understand what you mean by "under the black dotted line", but you can already use the coordinate data of the dotted line to annotate it. I put it above the intersection point, but if you want to put it near the x-axis, you can set y=0.
plt.text(max(x), max(y)+1.5, '$K^*$', transform=ax.transData)
baseTicks=list(plt.xticks()[0]) #for better control, replace with a range or arange
ax.set_xticks(baseTicks+[np.log(1-A*u*s)/(-d)])
ax.set_xticklabels(baseTicks+['$K^*$'])
I am acquiring data from an external device and I am plotting it in real-time using matplotlib.animation.FuncAnimation. For this measurement, it is important to keep the bottom y-axis limit at 0, but keeping the upper limit free. The device itself returns two sets of data for this measurement so I am animating both sets at the same time, hence the subplots.
Searching online suggests using axes.set_ylim(bottom=0), both in this question and this one. However their solutions do not work for me.
The autoscalling that's already part of the code is using axes.relim() and axes.autoscale_view(True, True, True), from the answer to another question which I have since forgotten. Messing with these lines of code seems to fix the viewing window but it no longer scales with the data. The data could then animate itself 'off-screen'.
I've recreated below the essence of what the acquisition (ideally) looks like since it's easier then using multiple files.
I am assuming that the problem lies in animate_corr(i) with the scaling. The rest is of the code is simply getting and plotting the data.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
#used to generate test data
import random
pi = np.pi
factor = 1
random.seed(123)
#procedural data generation for left plot
def get_data(data):
global mu
global b
global index
global factor
b=b*0.99
factor=factor*1.01
new_data = [factor*(((1-((1/(2*b))*np.exp(-1*(abs(i-mu))/b)))*random.random())+10) for i in index]
return new_data
#procedural data generation for right plot
def get_data_norm(data):
global mu
global b
global index
new_data = [((1-((1/(2*b))*np.exp(-1*(abs(i-mu))/b)))+10) for i in index]
return new_data
#animation function, assuming problem is here
def animate_corr(i):
global dat
global dat_norm
dat = get_data(dat)
dat_norm = get_data_norm(dat_norm)
#these two not working as expected
axs_corr[0].set_ylim((0, None), auto=True)
axs_corr[1].set_ylim(bottom=0, top=None, auto=True)
line_corr.set_ydata(dat)
line_corr_norm.set_ydata(dat_norm)
#rescales axes automatically
axs_corr[0].relim()
axs_corr[0].autoscale_view(True,True,True)
axs_corr[1].relim()
axs_corr[1].autoscale_view(True,True,True)
return line_corr, line_corr_norm,
#plots definitions
fig_corr, axs_corr = plt.subplots(1,2, sharex=True, figsize=(10,5))
fig_corr.suptitle('Animated Correlation')
#x is fixed
length = 1001
index = np.linspace(-10,10,length)
#laplacian distribution parameters
mu = 0
b = 2
#data
dat = [(1-((1/(2*b))*np.exp(-1*(abs(i-mu))/b)))+10 for i in index]
dat_norm = [(1-(1/(2*b))*np.exp(-(abs(i-mu))/b))+10 for i in index]
#initial plots
line_corr, = axs_corr[0].plot(index, dat)
line_corr_norm, = axs_corr[1].plot(index, dat_norm)
#titles
axs_corr[0].set_title('Random')
axs_corr[1].set_title('No Random')
#axes labels
fig_corr.text(0.51, 0.04, 'Time (ns)', ha='center')
fig_corr.text(0.04, 0.5, 'Coincidinces', va='center', rotation='vertical')
#animation call
ani_corr = animation.FuncAnimation(fig_corr, animate_corr, interval=10, blit=False, save_count=50)
plt.show()
I would like to have both plots have the y-axis limit fixed at 0. So the left one would keep increasing its max value and seeing this reflected in its scale. The right plot would have its dip get sharper and sharper but once its smaller than 0, the plot wouldn't change its scale anymore (since this plot doesn't have its values get larger).
#ivallesp almost had it. Removing axs_corr[0].set_ylim((0, None), auto=True) and axs_corr[1].set_ylim((0, None), auto=True) from before the set_ydata method and placing them after the autoscale_view call, for both plots, made it work as I wanted it too.
The following code should work :D.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
#used to generate test data
import random
pi = np.pi
factor = 1
random.seed(123)
#procedural data generation for left plot
def get_data(data):
global mu
global b
global index
global factor
b=b*0.99
factor=factor*1.01
new_data = [factor*(((1-((1/(2*b))*np.exp(-1*(abs(i-mu))/b)))*random.random())+10) for i in index]
return new_data
#procedural data generation for right plot
def get_data_norm(data):
global mu
global b
global index
new_data = [((1-((1/(2*b))*np.exp(-1*(abs(i-mu))/b)))+10) for i in index]
return new_data
#animation function, assuming problem is here
def animate_corr(i):
global dat
global dat_norm
dat = get_data(dat)
dat_norm = get_data_norm(dat_norm)
#these two not working as expected
axs_corr[0].set_ylim((0, None), auto=True)
axs_corr[1].set_ylim(bottom=0, top=None, auto=True)
line_corr.set_ydata(dat)
line_corr_norm.set_ydata(dat_norm)
#rescales axes automatically
axs_corr[0].relim()
axs_corr[0].autoscale_view(True,True,True)
axs_corr[0].set_ylim(0, None)
axs_corr[1].relim()
axs_corr[1].autoscale_view(True,True,True)
axs_corr[1].set_ylim(0, None)
return line_corr, line_corr_norm,
#plots definitions
fig_corr, axs_corr = plt.subplots(1,2, sharex=True, figsize=(10,5))
fig_corr.suptitle('Animated Correlation')
#x is fixed
length = 1001
index = np.linspace(-10,10,length)
#laplacian distribution parameters
mu = 0
b = 2
#data
dat = [(1-((1/(2*b))*np.exp(-1*(abs(i-mu))/b)))+10 for i in index]
dat_norm = [(1-(1/(2*b))*np.exp(-(abs(i-mu))/b))+10 for i in index]
#initial plots
line_corr, = axs_corr[0].plot(index, dat)
line_corr_norm, = axs_corr[1].plot(index, dat_norm)
#titles
axs_corr[0].set_title('Random')
axs_corr[1].set_title('No Random')
#axes labels
fig_corr.text(0.51, 0.04, 'Time (ns)', ha='center')
fig_corr.text(0.04, 0.5, 'Coincidinces', va='center', rotation='vertical')
#animation call
ani_corr = animation.FuncAnimation(fig_corr, animate_corr, interval=10, blit=False, save_count=50)
plt.show()
I currently work with an instrument that provides data in Wavenumber, but most of my community works in wavelength. Because of this I would like to create plots that display Wavenumber in cm^-1 along the bottom x-axis and wavelength in µm along the top. However the spacing doesn't quite match up between the two units of measurement to display a single spectrum. How do I create a different spacing for wavelength?
Here is an example of how a portion of one spectrum looks when plotted as a function of wavenumber against when it's plotted as a function of wavelength. Below is the code I'm currently implementing.
wn = wn_tot[425:3175] #range of 250 to 3000 cm-1
wl = 10000/wn #wavelength in microns
fig = plt.figure(1)
ax1 = plt.subplot(1,1,1)
ax2 = ax1.twiny()
ax1.plot(wn, spc[45], 'c', label='Wavenumber')
ax2.plot(wl, spc[45], 'm', label='Wavelength')
ax1.set_xlabel('Wavenumber (cm$^{-1}$)')
ax2.set_xlabel('Wavelength ($\mu$m)')
ax1.set_ylabel('Relative Intensity')
ax2.invert_xaxis()
fig.legend(loc=2, bbox_to_anchor=(0,1), bbox_transform=ax1.transAxes)
As said in the comment on the OP, both scales cannot be simultaneously linear, since one cannot be obtained from the other via a linear transformation. You must hence accept that one (or both) have ticks at non-regular intervals.
The correct way to do it
Apply a transformation to the scale, which causes matplotlib to have a non-homogeneous scale.
The doc for Axes.set_yscale leads to that example which demonstrate the syntax ax1.set_xscale('function', functions=(forward, inverse)). Here in that case, the transformation functions are simply
def forward(wn):
# cm^{-1} to μm
return 1.0e4 / wn
def reverse(lam):
# μm to cm^{-1}
return 1.0e4 / lam
However, my matplotlib is stuck on version 2.2.2 which does not have that feature, so I cannot give a working example.
The hacky way that works with older versions
Give tick positions and labels by hand, performing the calculations yourself.
# -*- coding: utf-8 -*-
import numpy as np
import matplotlib.pyplot as plt
def lambda_to_wave(lam):
# μm to cm^{-1}
return 1.0e4 / lam
x_wave = np.linspace(2000.0, 3000.0)
y_arb = np.linspace(0.0, 1.0e6)
ticks_wavelength_values = np.linspace(3.5, 5.5, num=5)
ticks_labels = [str(lam) for lam in ticks_wavelength_values]
ticks_wavenumber_positions = lambda_to_wave(ticks_wavelength_values)
print ticks_wavelength_values
print ticks_wavenumber_positions
fig = plt.figure(1)
ax1 = plt.subplot(1,1,1) # wavenumber
ax2 = ax1.twiny() # wavelength
ax2.get_shared_x_axes().join(ax1, ax2) # https://stackoverflow.com/questions/42973223/how-share-x-axis-of-two-subplots-after-they-are-created
ax1.plot(x_wave, y_arb, 'c', label='Data')
ax1.set_xlabel('Wavenumber (cm$^{-1}$)')
ax1.set_ylabel('Relative Intensity')
ax2.set_xticks(ticks_wavenumber_positions)
ax2.set_xticklabels(ticks_labels)
ax2.set_xlabel('Wavelength ($\mu$m)')
ax1.set_xlim(left=1800.0, right=3000.0)
fig.legend(loc=2, bbox_to_anchor=(0,1), bbox_transform=ax1.transAxes)
plt.show()
You can do without the second call to plot if you prefer: https://matplotlib.org/gallery/subplots_axes_and_figures/secondary_axis.html#sphx-glr-gallery-subplots-axes-and-figures-secondary-axis-py
wn = wn_tot[425:3175] #range of 250 to 3000 cm-1
fig = plt.figure(1)
ax1 = plt.subplot(1,1,1)
ax1.plot(wn, spc[45], 'c', label='Wavenumber')
def forward(x):
return 10000 / x
def inverse(x):
return 10000 / x
secax = ax.secondary_xaxis('top', functions=(forward, inverse))
ax1.set_xlabel('Wavenumber (cm$^{-1}$)')
secax.set_xlabel('Wavelength ($\mu$m)')
ax1.set_ylabel('Relative Intensity')
My aim is to show a bar chart with 3-dim data, x, categorical and y1, y2 as continuous series; the bars should have heights from y1 and color to indicate y2.
This does not seem to be particularly obscure to me, but I didn't find a simple / built-in way to use a bar chart to visualise three dimensions -- I'm thinking mostly for exploratory purposes, before investigating relationships more formally.
Am I missing a type of plot in the libraries? Is there a good alternative to showing 3d data?
Anyway here are some things that I've tried that aren't particularly satisfying:
Some data for these attempts
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# Example data with explicit (-ve) correlation in the two series
n = 10; sd = 2.5
fruits = [ 'Lemon', 'Cantaloupe', 'Redcurrant', 'Raspberry', 'Papaya',
'Apricot', 'Cherry', 'Durian', 'Guava', 'Jujube']
np.random.seed(101)
cost = np.random.uniform(3, 15, n)
harvest = 50 - (np.random.randn(n) * sd + cost)
df = pd.DataFrame(data={'fruit':fruits, 'cost':cost, 'harvest':harvest})
df.sort_values(by="cost", inplace=True) # preferrable to sort during plot only
# set up several subplots to show progress.
n_colors = 5; cmap_base = "coolwarm" # a diverging map
fig, axs = plt.subplots(3,2)
ax = axs.flat
Attempt 1 uses hue for the 3rd dim data in barplot. However, this produces a single color for each value in the series, and also seems to do odd things with the bar width & spacing.
import seaborn as sns
sns.barplot(ax=ax[0], x='fruit', y='cost', hue='harvest',
data=df, palette=cmap_base)
# fix the sns barplot label orientation
ax[0].set_xticklabels(ax[0].get_xticklabels(), rotation=90)
Attempt 2 uses the pandas DataFrame.plot.bar, with a continuous color range, then adds a colorbar (need scalar mappable). I borrowed some techniques from medium post among others.
import matplotlib as mpl
norm = mpl.colors.Normalize(vmin=min(df.harvest), vmax=max(df.harvest), clip=True)
mapper1 = mpl.cm.ScalarMappable(norm=norm, cmap=cmap_base)
colors1 = [mapper1.to_rgba(x) for x in df.harvest]
df.plot.bar(ax=ax[1], x='fruit', y='cost', color=colors1, legend=False)
mapper1._A = []
plt.colorbar(mapper1, ax=ax[1], label='havest')
Attempt 3 builds on this, borrowing from https://gist.github.com/jakevdp/91077b0cae40f8f8244a to facilitate a discrete colormap.
def discrete_cmap(N, base_cmap=None):
"""Create an N-bin discrete colormap from the specified input map"""
# from https://gist.github.com/jakevdp/91077b0cae40f8f8244a
base = plt.cm.get_cmap(base_cmap)
color_list = base(np.linspace(0, 1, N))
cmap_name = base.name + str(N)
return base.from_list(cmap_name, color_list, N)
cmap_disc = discrete_cmap(n_colors, cmap_base)
mapper2 = mpl.cm.ScalarMappable(norm=norm, cmap=cmap_disc)
colors2 = [mapper2.to_rgba(x) for x in df.harvest]
df.plot.bar(ax=ax[2], x='fruit', y='cost', color=colors2, legend=False)
mapper2._A = []
cb = plt.colorbar(mapper2, ax=ax[2], label='havest')
cb.set_ticks(np.linspace(*cb.get_clim(), num=n_colors+1)) # indicate color boundaries
cb.set_ticklabels(["{:.0f}".format(t) for t in cb.get_ticks()]) # without too much precision
Finally, attempt 4 gives in to trying 3d in one plot and present in 2 parts.
sns.barplot(ax=ax[4], x='fruit', y='cost', data=df, color='C0')
ax[4].set_xticklabels(ax[4].get_xticklabels(), rotation=90)
sns.regplot(x='harvest', y='cost', data=df, ax=ax[5])
(1) is unusable - I'm clearly not using as intended. (2) is ok with 10 series but with more series is harder to tell whether a given sample is above/below average, for instance. (3) is quite nice and scales to 50 bars ok, but it is far from "out-of-the-box", too involved for a quick analysis. Moreover, the sm._A = [] seems like a hack but the code fails without it. Perhaps the solution in a couple of lines in (4) is a better way to go.
To come back to the question again: Is it possible easily produce a bar chart that displays 3d data? I've focused on using a small number of colors for the 3rd dimension for easier identification of trends, but I'm open to other suggestions.
I've posted a solution as well, which uses a lot of custom code to achieve what I can't really believe is not built in some graphing library of python.
edit:
the following code, using R's ggplot gives a reasonable approximation to (2) with built-in commands.
ggplot(data = df, aes(x =reorder(fruit, +cost), y = cost, fill=harvest)) +
geom_bar(data=df, aes(fill=harvest), stat='identity') +
scale_fill_gradientn(colours=rev(brewer.pal(7,"RdBu")))
The first 2 lines are more or less the minimal code for barplot, and the third changes the color palette.
So if this ease were available in python I'd love to know about it!
I'm posting an answer that does solve my aims of being simple at the point of use, still being useful with ~100 bars, and by leveraging the Fisher-Jenks 1d classifier from PySAL ends up handling outliers quite well (post about d3 coloring)
-- but overall is quite involved (50+ lines in the BinnedColorScaler class, posted at the bottom).
# set up the color binner
quantizer = BinnedColorScaler(df.harvest, k=5, cmap='coolwarm' )
# and plot dataframe with it.
df.plot.bar(ax=ax, x='fruit', y='cost',
color=df.harvest.map(quantizer.map_by_class))
quantizer.add_legend(ax, title='harvest') # show meaning of bins in legend
Using the following class that uses a nice 1d classifier from PySAL and borrows ideas from geoplot/geopandas libraries.
from pysal.esda.mapclassify import Fisher_Jenks
class BinnedColorScaler(object):
'''
give this an array-like data set, a bin count, and a colormap name, and it
- quantizes the data
- provides a bin lookup and a color mapper that can be used by pandas for selecting artist colors
- provides a method for a legend to display the colors and bin ranges
'''
def __init__(self, values, k=5, cmap='coolwarm'):
self.base_cmap = plt.cm.get_cmap(cmap) # can be None, text, or a cmap instane
self.bin_colors = self.base_cmap(np.linspace(0, 1, k)) # evenly-spaced colors
# produce bins - see _discrete_colorize in geoplot.geoplot.py:2372
self.binning = Fisher_Jenks(np.array(values), k)
self.bin_edges = np.array([self.binning.yb.min()] + self.binning.bins.tolist())
# some text for the legend (as per geopandas approx)
self.categories = [
'{0:.2f} - {1:.2f}'.format(self.bin_edges[i], self.bin_edges[i + 1])
for i in xrange(len(self.bin_edges) - 1)]
def map_by_class(self, val):
''' return a color for a given data value '''
#bin_id = self.binning.find_bin(val)
bin_id = self.find_bin(val)
return self.bin_colors[bin_id]
def find_bin(self, x):
''' unfortunately the pysal implementation seems to fail on bin edge
cases :(. So reimplement with the way we expect here.
'''
# wow, subtle. just <= instead of < in the uptos
x = np.asarray(x).flatten()
uptos = [np.where(value <= self.binning.bins)[0] for value in x]
bins = [v.min() if v.size > 0 else len(self.bins)-1 for v in uptos] #bail upwards
bins = np.asarray(bins)
if len(bins) == 1:
return bins[0]
else:
return bins
def add_legend(self, ax, title=None, **kwargs):
''' add legend showing the discrete colors and the corresponding data range '''
# following the geoplot._paint_hue_legend functionality, approx.
# generate a patch for each color in the set
artists, labels = [], []
for i in xrange(len(self.bin_colors)):
labels.append(self.categories[i])
artists.append(mpl.lines.Line2D(
(0,0), (1,0), mfc='none', marker='None', ls='-', lw=10,
color=self.bin_colors[i]))
return ax.legend(artists, labels, fancybox=True, title=title, **kwargs)
I want to do something with plt.hist2d and plt.colorbar and I'm having real trouble working out how to do it. To explain, I've written the following example:
import numpy as np
from matplotlib import pyplot as plt
x = np.random.random(1e6)
y = np.random.random(1e6)
plt.hist2d(x, y)
plt.colorbar()
plt.show()
This code generates a plot that looks something like the image below.
If I generate a histogram, ideally I would like the colour bar to extend beyond the maximum and minimum range of the data to the next step beyond the maximum and minimum. In the example in this question, this would set the colour bar extent from 9660 to 10260 in increments of 60.
How can I force either plt.hist2d or plt.colorbar to set the colour bar such that ticks are assigned to the start and end of the plotted colour bar?
I think this is what you're looking for:
h = plt.hist2d(x, y)
mn, mx = h[-1].get_clim()
mn = 60 * np.floor(mn / 60.)
mx = 60 * np.ceil(mx / 60.)
h[-1].set_clim(mn, mx)
cbar = plt.colorbar(h[-1], ticks=np.arange(mn, mx + 1, 60), )
This gives something like,
It's also often convenient to use tickers from the matplotlib.ticker, and use the tick_values method of tickers, but for this purpose I think the above is most convenient.
Good luck!
With huge thanks to farenorth, who got me thinking about this in the right way, I came up with a function, get_colour_bar_ticks:
def get_colour_bar_ticks(colourbar):
import numpy as np
# Get the limits and the extent of the colour bar.
limits = colourbar.get_clim()
extent = limits[1] - limits[0]
# Get the yticks of the colour bar as values (ax.get_yticks() returns them as fractions).
fractions = colourbar.ax.get_yticks()
yticks = (fractions * extent) + limits[0]
increment = yticks[1] - yticks[0]
# Generate the expanded ticks.
if (fractions[0] == 0) & (fractions[-1] == 1):
return yticks
else:
start = yticks[0] - increment
end = yticks[-1] + increment
if fractions[0] == 0:
newticks = np.concatenate((yticks, [end]))
elif fractions[1] == 1:
newticks = np.concatenate(([start], yticks))
else:
newticks = np.concatenate(([start], yticks, [end]))
return newticks
With this function I can then do this:
from matplotlib import pyplot as plt
x = np.random.random(1e6)
y = np.random.random(1e6)
h = plt.hist2d(x, y)
cbar = plt.colorbar()
ticks = get_colour_bar_ticks(cbar)
h[3].set_clim(ticks[0], ticks[-1])
cbar.set_clim(ticks[0], ticks[-1])
cbar.set_ticks(ticks)
plt.show()
Which results in this, which is what I really wanted: