Matplotlib Colorbar - Non-Linear - python

I have created a diverging colorbar with it's midpoint normalised at the median value of the data.
I would like to extend the midpoint color ('white') and apply it to the range (+- 15%) from the midpoint, and then have the diverging colorbar continue normally from that point.
My current colorbar is created using the following code:
#Initial ZValues contour plot
Colorbar_min = np.around(ZValues.min()*0.9,0)
Colorbar_max = np.around(ZValues.max()*1.1,0)
Colorbar_mid = np.median(ZValues)
#Colormap
cmap = plt.cm.seismic # define the colormap
cmaplist = [cmap(i) for i in range(cmap.N)] # extract all colors from the .seismic map
# create the new colourmap
cmap = mpl.colors.LinearSegmentedColormap.from_list('Custom cmap', cmaplist, cmap.N)
# define the bins and normalize
bounds = np.linspace(Colorbar_min, Colorbar_max, 30)
norm = mpl.colors.BoundaryNorm(bounds, cmap.N)
Chosen_CS = ax.tricontourf(Chosen_tri_refi, Chosen_Z_refi, cmap=cmap, levels=bounds,
norm=MidpointNormalize(midpoint=Colorbar_mid, vmin=Colorbar_min, vmax=Colorbar_max))
#Create a second axis for the colorbar
ax2 = fig.add_axes([0.87, 0.12, 0.04, 0.75]) #The numbers in the square brackets of add_axes refer to [left, bottom, width, height], where the coordinates are just fractions that go from 0 to 1 of the plotting area.
cb = mpl.colorbar.ColorbarBase(ax2, cmap=cmap, norm=MidpointNormalize(midpoint=Colorbar_mid, vmin=Colorbar_min, vmax=Colorbar_max),spacing='uniform', ticks=bounds, boundaries=bounds, format='%1i')
cb.set_label('ZValues', fontsize=7, weight="bold", rotation=270, labelpad=14)

You can create your custom colormap with a white midrange color at 50 ± 15 % by inserting a white section (i.e. 1. for all three colors) from .5 - .15 to .5 + .15 like so:
import matplotlib.pyplot as plt
import matplotlib as mpl
seismic_cdict = plt.cm.seismic._segmentdata
cdict = dict()
for c in seismic_cdict:
cdict[c] = [t for t in seismic_cdict[c] if t[0] < .35] + \
[(.35,1.,1.), (.65,1.,1.)] + \
[t for t in seismic_cdict[c] if t[0] > .65]
custom_cmap = mpl.colors.LinearSegmentedColormap('Custom cmap', cdict)
fig, ax = plt.subplots(figsize=(8, 1))
mpl.colorbar.ColorbarBase(ax, cmap=custom_cmap, orientation='horizontal')

Related

Scatter plot with different groups and marginal histograms for each group

I already have a scatter plot with different groups of elements and histograms on the margins, but they are linked to the whole data, not to the individual groups:
I'd like to have 2 histograms, one for each group of elements. How do I do that?
Here's my code:
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import csv
from matplotlib.colors import LinearSegmentedColormap
data= pd.read_csv("data.csv")
x=data['Fe']
y=data['V']
z=data['Discovery']
# Fixing random state for reproducibility
np.random.seed(19680801)
# definitions for the axes
left, width = 0.1, 0.7
bottom, height = 0.1, 0.7
spacing = 0.05
rect_scatter = [left, bottom, width, height]
rect_histx = [left, bottom + height + spacing, width, 0.2]
rect_histy = [left + width + spacing, bottom, 0.2, height]
# start with a rectangular Figure
fig=plt.figure(figsize=(7, 6))
ax_scatter = plt.axes(rect_scatter)
ax_scatter.tick_params(direction='in', top=True, right=True)
ax_histx = plt.axes(rect_histx)
ax_histx.tick_params(direction='in', labelbottom=True)
ax_histy = plt.axes(rect_histy)
ax_histy.tick_params(direction='in', labelleft=False)
# the function that separates the dots in different classes:
classes = np.zeros( len(x) )
classes[(z == 'Transit')] = 1
classes[(z == 'Radial Velocity')] = 2
# create color map:
colors = ['purple', 'orange']
cm = LinearSegmentedColormap.from_list('custom', colors, N=len(colors))
# the scatter plot:
scatter = ax_scatter.scatter(x, y, c=classes, s=10, cmap=cm, alpha=0.6)
lines, labels = scatter.legend_elements()
# legend with custom labels
labels = [r'Transit', r'Radial Velocity']
legend = ax_scatter.legend(lines, labels,
loc="upper left", title="Planetary Discovery Method")
ax_scatter.add_artist(legend)
# now determine nice limits by hand:
binwidth = 0.1
ax_scatter.set_xlim((-1, 0.7))
ax_scatter.set_ylim((-0.9, 0.9))
#histogram
weights = np.ones_like(x)/(len(x))
weights2 = np.ones_like(y)/(len(y))
ax_histx.hist(x, bins=bins, weights=weights, color='chartreuse')
ax_histy.hist(y, bins=bins, weights=weights, orientation='horizontal', color='darkmagenta')
ax_histx.set_xlim(ax_scatter.get_xlim())
ax_histy.set_ylim(ax_scatter.get_ylim())
#labeling
ax_scatter.set_xlabel('[Fe/H]')
ax_scatter.set_ylabel('[V/H]')
ax_histy.set_xlabel('Relative Dist.')
ax_histx.set_ylabel('Relative Dist.')
plt.show()
I'll add an example of a plot I'm trying to reach:

Different shading under Seaborn Distplot

I'm trying to create plot with shadings which are based on this MIC(1) line.
Different shading above than beneath.
from scipy import stats
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
def createSkewDist(mean, sd, skew, size):
# calculate the degrees of freedom 1 required to obtain the specific skewness statistic, derived from simulations
loglog_slope=-2.211897875506251
loglog_intercept=1.002555437670879
df2=500
df1 = 10**(loglog_slope*np.log10(abs(skew)) + loglog_intercept)
# sample from F distribution
fsample = np.sort(stats.f(df1, df2).rvs(size=size))
# adjust the variance by scaling the distance from each point to the distribution mean by a constant, derived from simulations
k1_slope = 0.5670830069364579
k1_intercept = -0.09239985798819927
k2_slope = 0.5823114978219056
k2_intercept = -0.11748300123471256
scaling_slope = abs(skew)*k1_slope + k1_intercept
scaling_intercept = abs(skew)*k2_slope + k2_intercept
scale_factor = (sd - scaling_intercept)/scaling_slope
new_dist = (fsample - np.mean(fsample))*scale_factor + fsample
# flip the distribution if specified skew is negative
if skew < 0:
new_dist = np.mean(new_dist) - new_dist
# adjust the distribution mean to the specified value
final_dist = new_dist + (mean - np.mean(new_dist))
return final_dist
desired_mean = 30
desired_skew = 1.5
desired_sd = 20
final_dist = createSkewDist(mean=desired_mean, sd=desired_sd, skew=desired_skew, size=1000000)
# inspect the plots & moments, try random sample
fig, ax = plt.subplots(figsize=(12,7))
sns.distplot(final_dist,
hist=False,
ax=ax,
color='darkred',
kde_kws=dict(linewidth=4))
l1 = ax.lines[0]
# Get the xy data from the lines so that we can shade
x1 = l1.get_xydata()[:,0]
x1[0] = 0
y1 = l1.get_xydata()[:,1]
y1[0] = 0
ax.fill_between(x1,y1, color="lemonchiffon", alpha=0.3)
ax.set_ylim(0.0001,0.03)
ax.axhline(0.002, ls="--")
ax.set_xlim(1.5, 200)
ax.set_yticklabels([])
ax.set_xticklabels([])
trans = transforms.blended_transform_factory(
ax.get_yticklabels()[0].get_transform(), ax.transData)
ax.text(0,0.0025, "{}".format("MIC(1) = 1"), color="blue", transform=trans,
ha="right", va="top", fontsize = 12)
trans_2 = transforms.blended_transform_factory(
ax.get_xticklabels()[0].get_transform(), ax.transData)
ax.text(84,0, "{}".format("\n84"), color="darkred", transform=trans_2,
ha="center", va="top", fontsize = 12)
ax.text(1.5,0, "{}".format("\n0"), color="darkred", transform=trans_2,
ha="center", va="top", fontsize = 12)
ax.axvline(x = 84, ymin = 0, ymax = 0.03, ls = '--', color = 'darkred' )
ax.set_yticks([])
ax.set_xticks([])
ax.spines['top'].set_color(None)
ax.spines['right'].set_color(None)
ax.spines['left'].set_linewidth(2)
ax.spines['bottom'].set_linewidth(2)
ax.set_ylabel("Concentration [mg/L]", labelpad = 80, fontsize = 15)
ax.set_xlabel("Time [h]", labelpad = 80, fontsize = 15)
ax.set_title("AUC/MIC", fontsize = 20, pad = 30)
plt.annotate("AUC/MIC",
xy=(18, 0.02),
xytext=(18, 0.03),
arrowprops=dict(arrowstyle="->"), fontsize = 12);
;
That's what I have:
And that's what I'd like to have (it's done in paint, so forgive me :) ):
I was experimenting with fill_between and fill_betweenx. However, without any satisfying results. Definitely, run out of ideas. I'd really appreciate any help on this. Best wishes!
Your fill_between works as expected. The problem is that color="lemonchiffon" with alpha=0.3 is barely visible. Try to use a brighter color and/or a higher value for alpha.
So, this colors the part of the graph between zero and the kde curve.
Now, to create a different coloring above and below the horizontal line, where= and np.minimum can be used in fill_between:
pos_hline = 0.002
ax.fill_between(x1, pos_hline, y1, color="yellow", alpha=0.3, where=y1 > pos_hline)
ax.fill_between(x1, 0, np.minimum(y1, pos_hline), color="blue", alpha=0.3)
Without where=y1 > pos_hline, fill_between would also color the region above the curve where the curve falls below that horizontal line.
PS: Note that sns.histplot has been deprecated since Seaborn version 0.11. To only plot the kde curve, you can use sns.kdeplot:
sns.kdeplot(final_dist, ax=ax, color='darkred', linewidth=4)

Loop to create subplot /Python

i have a little problem to create a subplot loop.
The following code show my result for one plot.... So it starts with a dayloop than with a hour loop (8 timesteps).
If i run the code i get a nice QUiver plot with the colorbar.
for dd in range(1,15):
day=str(dd)
readfile=fns[files_indizes[dd]]
if dd < 10:
nc_u_comp = NetCDFFile(ROOT+u_comp1+'0'+day+comp)
nc_v_comp = NetCDFFile(ROOT+v_comp1+'0'+day+comp)
else:
nc_u_comp = NetCDFFile(ROOT+u_comp1+day+comp)
nc_v_comp = NetCDFFile(ROOT+v_comp1+day+comp)
time = nc_u_comp.variables['time'][:]
index=readfile.find(comp)
index=index+len(comp)
date=readfile[index-14:index-6]
plt.clf()
for tt in range(0,len(time)):
if tt < 10:
h =str(0)+str(tt)
else:
h=str(tt)
varU=nc_u_comp.variables['u10'][tt,:,:]
varV=nc_v_comp.variables['v10'][tt,:,:]
lat = nc_u_comp.variables['latitude'][:]
lon = nc_u_comp.variables['longitude'][:]
plt.rcParams["figure.figsize"] = [10,10]
#plane projection of the world
#map with box size (defintion on the top)
box = sgeom.box(minx=llcrnrlon, maxx=urcrnrlon, miny=llcrnrlat, maxy=urcrnrlat)
x0, y0, x1, y1 = box.bounds
#Map plot. The middel of the map is central_longitude
#proj = ccrs.PlateCarree(central_longitude=0)
proj=ccrs.PlateCarree()
#Change middelpoint of the map
box_proj = ccrs.PlateCarree(central_longitude=0)
ax2 = plt.axes(projection=proj)
ax2.set_extent([x0, x1, y0, y1], box_proj)
ax2.add_feature(cartopy.feature.BORDERS, linestyle='-', alpha=.5)
ax2.coastlines(resolution='50m')
#Definition of the scale_bar
gl = ax2.gridlines(ccrs.PlateCarree(), \
linestyle='--', alpha=1, linewidth=0.5, draw_labels=True)
gl.xlabels_top = False
gl.ylabels_right = False
gl.xformatter = LONGITUDE_FORMATTER
gl.yformatter = LATITUDE_FORMATTER
magnitude = (varU ** 2 + varV ** 2) ** 0.5
strm =plt.streamplot(lon , lat , varU, varV, linewidth=2, density=2, color=magnitude)
cbar= plt.colorbar()
cbar.set_label('$m/s$')
name='Wind in 10 m '+ date + h+' UTC'
ax2.set_aspect('auto')
plt.title(name, y=1)
Now i want to create an 2x4 Subplot array with a colorbar allocate to the complete Subplot array.
I find some infromation in the internet, but it doesn't run with my code. Maybe someone can help me?
This shows how to plot an array of simple Cartopy maps in 4 rows 2 columns. Also shows how to plot a colorbar to accompany the maps array. Hope it helps.
import numpy as np
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import matplotlib as mpl
# create figure with figsize big enough to accomodate all maps, labels, etc.
fig = plt.figure(figsize=(8, 10), tight_layout=False)
# define plot array's arrangement
columns = 2
rows = 4
# set projection to use
projex = ccrs.PlateCarree()
# set the colormap and norm for
# the colorbar to use
cmap1 = mpl.cm.magma
norm1 = mpl.colors.Normalize(vmin=0, vmax=100)
def plotmymap(axs):
# your plot specs of each map should replace this
img = np.random.randint(100, size=(15, 30)) # 2d array of random values (1-100)
# render image on current axis
plims = plt.imshow(img, extent=[-180,180,-90,90], alpha=0.5, cmap=cmap1, norm=norm1)
axs.set_global()
axs.coastlines()
# add title to the map
axs.set_title("Map_"+str(i))
return plims # for use by colorbar
for i in range(1, columns*rows +1):
# add a subplot into the array of plots
ax = fig.add_subplot(rows, columns, i, projection=projex)
plims = plotmymap(ax) # a simple maps is created on subplot
# add a subplot for vertical colorbar
bottom, top = 0.1, 0.9
left, right = 0.1, 0.8
fig.subplots_adjust(top=top, bottom=bottom, left=left, right=right, hspace=0.15, wspace=0.25)
cbar_ax = fig.add_axes([0.85, bottom, 0.05, top-bottom])
fig.colorbar(plims, cax=cbar_ax) # plot colorbar
plt.show() # this plot all the maps
The resulting plots:

How can I create custom break points in a matplotlib colorbar?

I'm borrowing an example from the matplotlib custom cmap examples page:
https://matplotlib.org/examples/pylab_examples/custom_cmap.html
This produces the same image with different numbers of shading contours, as specified in the number of bins: n_bins:
https://matplotlib.org/_images/custom_cmap_00.png
However, I'm interested not only in the number of bins, but the specific break points between the color values. For example, when nbins=6 in the top right subplot, how can I specify the ranges of the bins to such that the shading is filled in these custom areas:
n_bins_ranges = ([-10,-5],[-5,-2],[-2,-0.5],[-0.5,2.5],[2.5,7.5],[7.5,10])
Is it also possible to specify the inclusivity of the break points? For example, I'd like to specify in the range between -2 and 0.5 whether it's -2 < x <= -0.5 or -2 <= x < -0.5.
EDIT WITH ANSWER BELOW:
Using the accepted answer below, here is code that plots each step including finally adding custom colorbar ticks at the midpoint. Note I can't post an image since I'm a new user.
Set up data and 6 color bins:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
# Make some illustrative fake data:
x = np.arange(0, np.pi, 0.1)
y = np.arange(0, 2*np.pi, 0.1)
X, Y = np.meshgrid(x, y)
Z = np.cos(X) * np.sin(Y) * 10
# Create colormap with 6 discrete bins
colors = [(1, 0, 0), (0, 1, 0), (0, 0, 1)] # R -> G -> B
n_bin = 6
cmap_name = 'my_list'
cm = matplotlib.colors.LinearSegmentedColormap.from_list(
cmap_name, colors, N=n_bin)
Plot different options:
# Set up 4 subplots
fig, axs = plt.subplots(2, 2, figsize=(6, 9))
fig.subplots_adjust(left=0.02, bottom=0.06, right=0.95, top=0.94, wspace=0.05)
# Plot 6 bin figure
im = axs[0,0].imshow(Z, interpolation='nearest', origin='lower', cmap=cm)
axs[0,0].set_title("Original 6 Bin")
fig.colorbar(im, ax=axs[0,0])
# Change the break points
n_bins_ranges = [-10,-5,-2,-0.5,2.5,7.5,10]
norm = matplotlib.colors.BoundaryNorm(n_bins_ranges, len(n_bins_ranges))
im = axs[0,1].imshow(Z, interpolation='nearest', origin='lower', cmap=cm, norm=norm)
axs[0,1].set_title("Custom Break Points")
fig.colorbar(im, ax=axs[0,1])
# Arrange color labels by data interval (not colors)
im = axs[1,0].imshow(Z, interpolation='nearest', origin='lower', cmap=cm, norm=norm)
axs[1,0].set_title("Linear Color Distribution")
fig.colorbar(im, ax=axs[1,0], spacing="proportional")
# Provide custom labels at color midpoints
# And change inclusive equality by adding arbitrary small value
n_bins_ranges_arr = np.asarray(n_bins_ranges)+1e-9
norm = matplotlib.colors.BoundaryNorm(n_bins_ranges, len(n_bins_ranges))
n_bins_ranges_midpoints = (n_bins_ranges_arr[1:] + n_bins_ranges_arr[:-1])/2.0
im = axs[1,1].imshow(Z, interpolation='nearest', origin='lower', cmap=cm ,norm=norm)
axs[1,1].set_title("Midpoint Labels\n Switched Equal Sign")
cbar=fig.colorbar(im, ax=axs[1,1], spacing="proportional",
ticks=n_bins_ranges_midpoints.tolist())
cbar.ax.set_yticklabels(['Red', 'Brown', 'Green 1','Green 2','Gray Blue','Blue'])
plt.show()
You can use a BoundaryNorm as follows:
import matplotlib.pyplot as plt
import matplotlib.colors
import numpy as np
x = np.arange(0, np.pi, 0.1)
y = np.arange(0, 2*np.pi, 0.1)
X, Y = np.meshgrid(x, y)
Z = np.cos(X) * np.sin(Y) * 10
colors = [(1, 0, 0), (0, 1, 0), (0, 0, 1)] # R -> G -> B
n_bin = 6 # Discretizes the interpolation into bins
n_bins_ranges = [-10,-5,-2,-0.5,2.5,7.5,10]
cmap_name = 'my_list'
fig, ax = plt.subplots()
# Create the colormap
cm = matplotlib.colors.LinearSegmentedColormap.from_list(
cmap_name, colors, N=n_bin)
norm = matplotlib.colors.BoundaryNorm(n_bins_ranges, len(n_bins_ranges))
# Fewer bins will result in "coarser" colomap interpolation
im = ax.imshow(Z, interpolation='nearest', origin='lower', cmap=cm, norm=norm)
ax.set_title("N bins: %s" % n_bin)
fig.colorbar(im, ax=ax)
plt.show()
Or, if you want proportional spacing, i.e. the distance between colors according to their values,
fig.colorbar(im, ax=ax, spacing="proportional")
As the boundary norm documentation states
If b[i] <= v < b[i+1]
then v is mapped to color j; as i varies from 0 to len(boundaries)-2, j goes from 0 to ncolors-1.
So the colors are always chosen as -2 <= x < -0.5, in order to obtain the equal sign on the other side you would need to supply
something like n_bins_ranges = np.array([-10,-5,-2,-0.5,2.5,7.5,10])-1e-9

Matplotlib Colorbar Display Digtis

How do I exactly specify the colorbar labels in matplotlib? Frequently, I need to create very specific color scales, but the colorbar labels display so poorly you can't tell what the scale is. I would like to manually define the text next to the colorbar tick marks, or at least have them display in scientific notation.
Here is an example plot where you can't tell what the bottom four color bins represent:
And here is a working example of how that plot was created:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import colors
# mock up some data
x = np.random.random(50)
y = np.random.random(50)
c = np.arange(0, 1, 1.0/50.0) # color of points
c[0] = 0.00001
c[1] = 0.0001
c[2] = 0.001
c[3] = 0.01
s = 500 * np.random.random(50) + 25 # size of points
# set up some custom color scaling
lcmap = colors.ListedColormap(['#FFFFFF', '#FF99FF', '#8000FF',
'#0000FF', '#0080FF', '#58FAF4',
'#00FF00', '#FFFF00', '#FF8000',
'#FF0000'])
bounds = [0.0, 0.000001, 0.00001, 0.0001,
0.001, 0.01, 0.1, 0.25, 0.5, 0.75, 1.0]
norm = colors.BoundaryNorm(bounds, lcmap.N)
# create some plot
fig, ax = plt.subplots()
im = ax.scatter(x, y, c=c, s=s, cmap=lcmap, norm=norm)
# add the colorbar
fig.colorbar(im, ax=ax)
fig.savefig('temp.jpg')
cbar = fig.colorbar(cax, ticks=[-1, 0, 1])
cbar.ax.set_xticklabels(['Low', 'Medium', 'High'])
and use whatever iterable you want instead of ['Low', 'Medium', 'High']
see: http://matplotlib.org/examples/pylab_examples/colorbar_tick_labelling_demo.html

Categories