I want to plot the Bull's Eye diagram of an image. I tried these codes
Shade 'cells' in polar plot with matplotlib
For Bull's Eye diagram I want to use different colors. Is there is any way to set this colors? In color = choice(['navy','maroon','lightgreen']) colors are repeating according as for loop iterates.
Does anyone know if in matplotlib there is a function correponding to matlab bullseye()?
To provide something similar to the matlab function you mentioned, I drafted some code (shared on git hub as well) for the creation of bullseyes with any number of sector for each radial subdivision. The colour is given by the colormap and by the values below the sector. Moreover, this allows for the modification the centering of the radial subdivision.
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import pandas as pd
import matplotlib.ticker as ticker
def bulls_eye(ax, data, cmap=None, norm=None, raidal_subdivisions=(2, 8, 8, 11),
centered=(True, False, False, True), add_nomenclatures=True, cell_resolution=128,
pfi_where_to_save=None, colors_bound='-k'):
"""
Clockwise, from smaller radius to bigger radius.
:param ax:
:param data:
:param cmap:
:param norm:
:param raidal_subdivisions:
:param centered:
:param add_nomenclatures:
:param cell_resolution:
:param pfi_where_to_save:
:return:
"""
line_width = 1.5
data = np.array(data).ravel()
if cmap is None:
cmap = plt.cm.viridis
if norm is None:
norm = mpl.colors.Normalize(vmin=data.min(), vmax=data.max())
theta = np.linspace(0, 2*np.pi, 768)
r = np.linspace(0, 1, len(raidal_subdivisions)+1)
nomenclatures = []
if isinstance(add_nomenclatures, bool):
if add_nomenclatures:
nomenclatures = range(1, sum(raidal_subdivisions)+1)
elif isinstance(add_nomenclatures, list) or isinstance(add_nomenclatures, tuple):
assert len(add_nomenclatures) == sum(raidal_subdivisions)
nomenclatures = add_nomenclatures[:]
add_nomenclatures = True
# Create the circular bounds
line_width_circular = line_width
for i in range(r.shape[0]):
if i == range(r.shape[0])[-1]:
line_width_circular = int(line_width / 2.)
ax.plot(theta, np.repeat(r[i], theta.shape), colors_bound, lw=line_width_circular)
# iterate over cells divided by radial subdivision
for rs_id, rs in enumerate(raidal_subdivisions):
for i in range(rs):
cell_id = sum(raidal_subdivisions[:rs_id]) + i
theta_i = - i * 2 * np.pi / rs + np.pi / 2
if not centered[rs_id]:
theta_i += (2 * np.pi / rs) / 2
theta_i_plus_one = theta_i - 2 * np.pi / rs # clockwise
# Create colour fillings for each cell:
theta_interval = np.linspace(theta_i, theta_i_plus_one, cell_resolution)
r_interval = np.array([r[rs_id], r[rs_id+1]])
angle = np.repeat(theta_interval[:, np.newaxis], 2, axis=1)
radius = np.repeat(r_interval[:, np.newaxis], cell_resolution, axis=1).T
z = np.ones((cell_resolution, 2)) * data[cell_id]
ax.pcolormesh(angle, radius, z, cmap=cmap, norm=norm)
# Create radial bounds
if rs > 1:
ax.plot([theta_i, theta_i], [r[rs_id], r[rs_id+1]], colors_bound, lw=line_width)
# Add centered nomenclatures if needed
if add_nomenclatures:
if rs == 1 and rs_id ==0:
cell_center = (0, 0)
else:
cell_center = ((theta_i + theta_i_plus_one) / 2., r[rs_id] + .5 * r[1] )
if isinstance(nomenclatures[0], (int, long, float, complex)):
ax.annotate(r"${:.3g}$".format(nomenclatures[cell_id]), xy=cell_center,
xytext=(cell_center[0], cell_center[1]),
horizontalalignment='center', verticalalignment='center', size=8)
else:
ax.annotate(nomenclatures[cell_id], xy=cell_center,
xytext=(cell_center[0], cell_center[1]),
horizontalalignment='center', verticalalignment='center', size=12)
ax.grid(False)
ax.set_ylim([0, 1])
ax.set_yticklabels([])
ax.set_xticklabels([])
if pfi_where_to_save is not None:
plt.savefig(pfi_where_to_save, format='pdf', dpi=200)
def multi_bull_eyes(multi_data, cbar=None, cmaps=None, normalisations=None,
global_title=None, canvas_title='title', titles=None, units=None, raidal_subdivisions=(2, 8, 8, 11),
centered=(True, False, False, True), add_nomenclatures=(True, True, True, True),
pfi_where_to_save=None, show=True):
plt.clf()
n_fig = len(multi_data)
if cbar is None:
cbar = [True] * n_fig
if cmaps is None:
cmaps = [mpl.cm.viridis] * n_fig
if normalisations is None:
normalisations = [mpl.colors.Normalize(vmin=np.min(multi_data[i]), vmax=np.max(multi_data[i]))
for i in range(n_fig)]
if titles is None:
titles = ['Title {}'.format(i) for i in range(n_fig)]
h_space = 0.15 / n_fig
h_dim_fig = .8
w_dim_fig = .8 / n_fig
def fmt(x, pos):
# a, b = '{:.2e}'.format(x).split('e')
# b = int(b)
# return r'${} \times 10^{{{}}}$'.format(a, b)
return r"${:.4g}$".format(x)
# Make a figure and axes with dimensions as desired.
fig = plt.figure(figsize=(3 * n_fig, 4))
fig.canvas.set_window_title(canvas_title)
if global_title is not None:
plt.suptitle(global_title)
for n in range(n_fig):
origin_fig = (h_space * (n + 1) + w_dim_fig * n, 0.15)
ax = fig.add_axes([origin_fig[0], origin_fig[1], w_dim_fig, h_dim_fig], polar=True)
bulls_eye(ax, multi_data[n], cmap=cmaps[n], norm=normalisations[n], raidal_subdivisions=raidal_subdivisions,
centered=centered, add_nomenclatures=add_nomenclatures[n])
ax.set_title(titles[n], size=10)
if cbar[n]:
origin_cbar = (h_space * (n + 1) + w_dim_fig * n, .15)
axl = fig.add_axes([origin_cbar[0], origin_cbar[1], w_dim_fig, .05])
cb1 = mpl.colorbar.ColorbarBase(axl, cmap=cmaps[n], norm=normalisations[n], orientation='horizontal',
format=ticker.FuncFormatter(fmt))
cb1.ax.tick_params(labelsize=8)
if units is not None:
cb1.set_label(units[n])
if pfi_where_to_save is not None:
plt.savefig(pfi_where_to_save, format='pdf', dpi=330)
if show:
plt.show()
if __name__ == '__main__':
# Very dummy data:
data = np.array(range(29)) + 1
# TEST bull-eye three-fold
if True:
fig, ax = plt.subplots(figsize=(12, 8), nrows=1, ncols=3,
subplot_kw=dict(projection='polar'))
fig.canvas.set_window_title('Left Ventricle Bulls Eyes')
# First one:
cmap = mpl.cm.viridis
norm = mpl.colors.Normalize(vmin=1, vmax=29)
bulls_eye(ax[0], data, cmap=cmap, norm=norm)
ax[0].set_title('Bulls Eye ')
axl = fig.add_axes([0.14, 0.15, 0.2, 0.05])
cb1 = mpl.colorbar.ColorbarBase(axl, cmap=cmap, norm=norm, orientation='horizontal')
cb1.set_label('Some Units')
# Second one
cmap2 = mpl.cm.cool
norm2 = mpl.colors.Normalize(vmin=1, vmax=29)
bulls_eye(ax[1], data, cmap=cmap2, norm=norm2)
ax[1].set_title('Bulls Eye ')
axl2 = fig.add_axes([0.41, 0.15, 0.2, 0.05])
cb2 = mpl.colorbar.ColorbarBase(axl2, cmap=cmap2, norm=norm2, orientation='horizontal')
cb2.set_label('Some other units')
# Third one
cmap3 = mpl.cm.winter
norm3 = mpl.colors.Normalize(vmin=1, vmax=29)
bulls_eye(ax[2], data, cmap=cmap3, norm=norm3)
ax[2].set_title('Bulls Eye third')
axl3 = fig.add_axes([0.69, 0.15, 0.2, 0.05])
cb3 = mpl.colorbar.ColorbarBase(axl3, cmap=cmap3, norm=norm3, orientation='horizontal')
cb3.set_label('Some more units')
plt.show()
if True:
fig = plt.figure(figsize=(5, 7))
fig.canvas.set_window_title('Bulls Eyes - segmentation assessment')
# First and only:
cmap = mpl.cm.viridis
norm = mpl.colors.Normalize(vmin=1, vmax=29)
ax = fig.add_axes([0.1, 0.2, 0.8, 0.7], polar=True)
bulls_eye(ax, data, cmap=cmap, norm=norm)
ax.set_title('Bulls Eye')
axl = fig.add_axes([0.1, 0.15, 0.8, 0.05])
cb1 = mpl.colorbar.ColorbarBase(axl, cmap=cmap, norm=norm, orientation='horizontal')
cb1.set_label('Some Units')
plt.show()
if True:
multi_data = [range(1,17), list( 0.000000001 * np.array(range(1,17))), list( 0.001 * np.array(range(1,17)))]
print multi_data
multi_bull_eyes(multi_data, raidal_subdivisions=(3,3,4,6),
centered=(True, True, True, True), add_nomenclatures=[True]*3)
plt.show(block=True)
It is proposed within my code LabelsManager on github, that you are welcome to use.
Here is an example, for raidal_subdivisions=(2, 8, 8, 11), and centered=(True, False, False, True):
I don't think that there is a specific bullseye method in matplotlib, but comparing what the matlab function does and the right plot in this answer, I think that you should be able to get what you want playing a bit with the radius coordinates. E.g. (borrowing from the aforesaid answer)
import numpy as np
import matplotlib.pyplot as plt
theta, r = np.mgrid[0:2*np.pi:20j, 0.2:1:10j]
z = np.random.random(theta.size).reshape(theta.shape)
fig, ax = plt.subplots(ncols=1, subplot_kw=dict(projection='polar'))
ax.pcolormesh(theta, r, z)
ax.set_yticklabels([])
ax.set_ylim([0, 1])
plt.show()
plots
I don't understand exactly what you mean with the question about the colors. ax.pcolormesh accept either a colormap or a matplotlib color. If you want specific colors you can play with those two parameters and/or create your own colormap.
Related
I've created a plot of normal distribution like this:
fig, ax = plt.subplots()
ax.set_title('Плотнось распределения вероятности')
ax.set_xlabel('x')
ax.set_ylabel('f(x)')
x = np.linspace(148, 200, 100) # X от 148 до 200
y = (1 / (5 * math.sqrt(2*math.pi))) * np.exp((-(x-178)**2) / (2*5**2))
ax.plot(x, y)
plt.show()
But I also need to add vertical lines inside the graph area, color inner segments and add marks like in picture on axis = 0.
How can I do it in python using matplotlib?
I've tried to use plt.axvline, but the vertical lines go outside of my main plot:
plt.axvline(x = 178, color = 'g', label = 'axvline - full height')
plt.axvline(x = 178+5, color = 'b', label = 'axvline - full height')
plt.axvline(x = 178-5, color = 'b', label = 'axvline - full height')
plt.axvline(x = 178+5*2, color = 'r', label = 'axvline - full height')
plt.axvline(x = 178-5*2, color = 'r', label = 'axvline - full height')
The line version can be implemented using vlines, but note that your reference figure can be better reproduced using fill_between.
Line version
Instead of axvline, use vlines which supports ymin and ymax bounds.
Change your y into a lambda f(x, mu, sd) and use that to define the ymax bounds:
# define y as a lambda f(x, mu, sd)
f = lambda x, mu, sd: (1 / (sd * (2*np.pi)**0.5)) * np.exp((-(x-mu)**2) / (2*sd**2))
fig, ax = plt.subplots(figsize=(8, 3))
x = np.linspace(148, 200, 200)
mu = 178
sd = 5
ax.plot(x, f(x, mu, sd))
# define 68/95/99 locations and colors
xs = mu + sd*np.arange(-3, 4)
colors = [*'yrbgbry']
# draw lines at 68/95/99 points from 0 to the curve
ax.vlines(xs, ymin=0, ymax=[f(x, mu, sd) for x in xs], color=colors)
# relabel x ticks
plt.xticks(xs, [f'${n}\sigma$' if n else '0' for n in range(-3, 4)])
Shaded version
Use fill_between to better recreate the sample figure. Define the shaded bounds using the where parameter:
fig, ax = plt.subplots(figsize=(8, 3))
x = np.linspace(148, 200, 200)
mu = 178
sd = 5
y = (1 / (sd * (2*np.pi)**0.5)) * np.exp((-(x-mu)**2) / (2*sd**2))
ax.plot(x, y)
# use `where` condition to shade bounded regions
bounds = mu + sd*np.array([-np.inf] + list(range(-3, 4)) + [np.inf])
alphas = [0.1, 0.2, 0.5, 0.8, 0.8, 0.5, 0.2, 0.1]
for left, right, alpha in zip(bounds, bounds[1:], alphas):
ax.fill_between(x, y, where=(x >= left) & (x < right), color='b', alpha=alpha)
# relabel x ticks
plt.xticks(bounds[1:-1], [f'${n}\sigma$' if n else '0' for n in range(-3, 4)])
To label the region percentages, add text objects at the midpoints of the bounded regions:
midpoints = mu + sd*np.arange(-3.5, 4)
percents = [0.1, 2.1, 13.6, 34.1, 34.1, 13.6, 2.1, 0.1]
colors = [*'kkwwwwkk']
for m, p, c in zip(
midpoints, # midpoints of bounded regions
percents, # percents captured by bounded regions
colors, # colors of text labels
):
ax.text(m, 0.01, f'{p}%', color=c, ha='center', va='bottom')
Having the following line in my plot code
ax.plot(x, pdf_individual, '--k', label = "single Gaussians")
, with pdf_individual being a list of lists, results in this picture:
Is there a way to just have "single Gaussians" once in the labels, instead of 6 times, which is the amount of single Gaussians for the Gaussian Mixture Model?
This is the whole post with the suggested solution
import matplotlib as mpl
import matplotlib.ticker as mtick
from matplotlib.lines import Line2D
mpl.rcParams['figure.dpi'] = 600
test_input = input_list # THIS IS A 1D LIST with a few hundred items
X = np.asarray(test_input).reshape(-1,1)
N = np.arange(1, 11)
models = [None for i in range(len(N))]
for i in range(len(N)):
models[i] = GaussianMixture(N[i]).fit(X)
# compute the AIC and the BIC
AIC = [m.aic(X) for m in models]
BIC = [m.bic(X) for m in models]
fig = plt.figure(figsize=(12, 4))
fig.subplots_adjust(left=0.1, right=0.9,
bottom=0.21, top=0.9, wspace=0.3)
ax = fig.add_subplot(131)
M_best = models[np.argmin(AIC)]
comp_count = str(M_best)
x = np.linspace(0, 0.1, 100)
logprob = M_best.score_samples(x.reshape(-1, 1))
responsibilities = M_best.predict_proba(x.reshape(-1, 1))
pdf = np.exp(logprob)
pdf_individual = responsibilities * pdf[:, np.newaxis]
left, width = .245, .5
bottom, height = .4, .5
right = left + width
top = bottom + height
plt.setp( ax.xaxis.get_majorticklabels(), rotation=-45, ha="left" )
ax.yaxis.set_major_formatter(mtick.PercentFormatter())
ax.hist(X, 30, density=True, histtype='stepfilled', alpha=0.4, label="Data")
ax.plot(x, pdf, '-k', color = "red", label='GMM')
for i, pdf_individual in enumerate(pdf_individual):
ax.plot(x, pdf_individual, '--k', label = "single Gaussians" if i == 0 else "")
#for pdf in pdf_individual[1:]: ax.plot(x, pdf, '--k')
ax.text(right, top, "Anzahl Komponenten: " + comp_count[-2],
horizontalalignment='center',
verticalalignment='bottom',
transform=ax.transAxes)
ax.set_xlabel('$x$')
ax.set_ylabel('$p(x)$')
plt.legend()
plt.show()
It results in this error:
ValueError: x and y must have same first dimension, but have shapes (100,) and (6,)
EDIT:
Putting
pdf_individual = np.transpose(pdf_individual)
makes the code above work
I am using "plt.subplots(2, 2, sharex=True, sharey=True)" to draw a 2*2 subplots. Each subplot has two Y axis and contains normal distribution curve over a histogram. Noting I particularly set "sharex=True, sharey=True" here in order to make all subplots share the same X axis and Y axis.
After running my code, everything is fine except the second, three, and fourth subplots where the normal distribution curve doesn't fit the histogram very well (please see the figure here)
I did googling but failed to get this issue solved. However, if I set "sharex=True, sharey=False" in my code, then the figure looks correct, but all subplots use their own Y axix which isn't what I want. Please see the figure here
Hope this issue can be fixed by experts in StackOverflow. Many thanks in advance!
Below is my code:
import matplotlib.pyplot as plt
from scipy.stats import norm
def align_yaxis(ax1, v1, ax2, v2):
#adjust ax2 ylimit so that v2 in ax2 is aligned to v1 in ax1
_, y1 = ax1.transData.transform((0, v1))
_, y2 = ax2.transData.transform((0, v2))
inv = ax2.transData.inverted()
_, dy = inv.transform((0, 0)) - inv.transform((0, y1-y2))
miny, maxy = ax2.get_ylim()
ax2.set_ylim(miny+dy, maxy+dy)
def drawSingle(myax, mydf , title, offset):
num_bins = 200
xs = mydf["gap"]
x = np.linspace(-1,1,1000)
mu =np.mean(x)
sigma =np.std(xs)
n, bins, patche = myax.hist(xs, num_bins, alpha=0.8, facecolor='blue', density=False)
myax.set_ylabel('frequency',color="black",fontsize=12, weight = "bold")
myax.set_xlabel('X', fontsize=12, weight = "bold",horizontalalignment='center')
ax_twin = myax.twinx()
y_normcurve = norm.pdf(bins, mu, sigma)
ax_twin.plot(bins, y_normcurve, 'r--')
align_yaxis(myax,0,ax_twin,0)
peakpoint = norm.pdf(mu,loc=mu,scale=sigma)
plt.vlines(mu, 0, peakpoint, 'y', '--', label='example')
ax_twin.set_ylabel("probablility dense",color="black",fontsize=12, weight = "bold")
def drawSubplots(mydf1,mydf2,mydf3,mydf4, pos1,pos2,pos3,pos4, title, filename):
plt.rcParams['figure.figsize'] = (18,15 )
my_x_ticks = np.arange(-0.8, 0.8,0.1)
rows, cols = 2, 2
fig, ax = plt.subplots(2, 2, sharex=True, sharey=True)
drawSingle(ax[0][0], mydf1, "Subplot1", pos1)
drawSingle(ax[0][1], mydf2, "Subplot2", pos2)
drawSingle(ax[1][0], mydf3, "Subplot3", pos3)
drawSingle(ax[1][1], mydf4, "Subplot4", pos4)
plt.text(-1, -1, title, horizontalalignment='center', fontsize=18)
plt.show()
drawSubplots(df1, df2,df3,df4,3.2,3.1,2.7,2.85,"test9", "test9")
Here is an attempt to:
have the left y-axes being "frequency" (which is very uninformative in the case of the current bin widths) and shared among the 4 subplots
have the right y-axes be a "probability density"; note how the top of all gaussians is around y=0.02 (the twin axes can only be set at the end because the shared y axes can be updated via later subplots)
have the histogram and the normal curve aligned
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
from scipy.stats import norm
def drawSingle(myax, mydf, title):
num_bins = 200
xs = mydf["gap"]
x = np.linspace(-1, 1, 1000)
mu = np.mean(x)
sigma = np.std(xs)
n, bins, patches = myax.hist(xs, num_bins, alpha=0.8, facecolor='blue', density=False)
myax.set_ylabel('frequency', color="black", fontsize=12, weight="bold")
myax.set_xlabel('X', fontsize=12, weight="bold", horizontalalignment='center')
normalization_factor = len(xs) * (bins[1] - bins[0])
y_normcurve = norm.pdf(x, mu, sigma) * normalization_factor
myax.plot(x, y_normcurve, 'r--')
myax.vlines(mu, 0, y_normcurve.max(), 'y', '--', color='lime', label='example')
return normalization_factor
def drawSubplots(mydf1, mydf2, mydf3, mydf4, title):
plt.rcParams['figure.figsize'] = (18, 15)
fig, ax = plt.subplots(nrows=2, ncols=2, sharex=True, sharey=True)
dfs = [mydf1, mydf2, mydf3, mydf4]
norm_factors = [drawSingle(ax_i, df, title)
for ax_i, df, title in zip(ax.ravel(), dfs, ["Subplot1", "Subplot2", "Subplot3", "Subplot4"])]
for ax_i, norm_factor in zip(ax.ravel(), norm_factors):
ax_twin = ax_i.twinx()
ymax = ax_i.get_ylim()[1]
ax_twin.set_ylim(0, ymax / norm_factor)
plt.suptitle(title, fontsize=18)
plt.tight_layout()
plt.show()
df1, df2, df3, df4 = [pd.DataFrame({"gap": np.random.normal(0, 0.2, n)}) for n in [6000, 4000, 1800, 1200]]
drawSubplots(df1, df2, df3, df4, "Title")
Many thanks JohanC, you are amazing.
Based on your code, I just added a few lines of code within drawSubplots function in order to make 95% of the Gaussian curve area shaded between the lower bound and upper bound for each subplot. The following is my try. It seems that ax_twin.fill_between doesn't work normally here. As you could see from the figure that the shaded area is out of the Gaussian curve enter image description here. What I want is only to shade the area under the Gaussian curve between the lower bound and upper bound. If you don't mind, would you please check it out my mistake? Thank you very much!
import matplotlib.pyplot as plt
import math
from scipy.stats import norm
def align_yaxis(ax1, v1, ax2, v2):
#adjust ax2 ylimit so that v2 in ax2 is aligned to v1 in ax1
_, y1 = ax1.transData.transform((0, v1))
_, y2 = ax2.transData.transform((0, v2))
inv = ax2.transData.inverted()
_, dy = inv.transform((0, 0)) - inv.transform((0, y1-y2))
miny, maxy = ax2.get_ylim()
ax2.set_ylim(miny+dy, maxy+dy)
def drawSingle(myax, mydf , title):
num_bins = 200
xs = mydf["gap"]
x = np.linspace(-1,1,1000)
mu =np.mean(xs)
sigma =np.std(xs)
n, bins, patches = myax.hist(xs, num_bins, alpha=0.8, facecolor='blue', density=False)
myax.set_ylabel('Frequency', color="black", fontsize=12, weight="bold")
myax.set_xlabel(title, fontsize=12, weight="bold", horizontalalignment='center')
normalization_factor = len(xs) * (bins[1] - bins[0])
y_normcurve = norm.pdf(x, mu, sigma) * normalization_factor
myax.plot(x, y_normcurve, 'r--')
myax.vlines(mu, 0, y_normcurve.max(), 'y', '--', color='lime', label='example')
plt.xlim(-0.8,0.8)
my_x_ticks = np.arange(-0.8, 0.8,0.1)
plt.xticks(my_x_ticks)
return normalization_factor, mu, sigma
def drawSubplots(mydf1,mydf2,mydf3,mydf4, title):
plt.rcParams['figure.figsize'] = (18,15 )
norm_factors = []
mus = []
sigmas = []
my_x_ticks = np.arange(-0.8, 0.8,0.1)
rows, cols = 2, 2
fig, ax = plt.subplots(nrows=rows, ncols=cols, sharex=True, sharey=True)
dfs = [mydf1, mydf2, mydf3, mydf4]
#norm_factors = [drawSingle(ax_i, df, title)
#for ax_i, df, title in zip(ax.ravel(), dfs, ["Subplot1", "Subplot2", "Subplot3", "Subplot4"])]
for ax_i, df, title in zip(ax.ravel(), dfs, ["Subplot1", "Subplot2", "Subplot3", "Subplot4"]):
norm_factor, mu, sigma = drawSingle(ax_i, df, title)
norm_factors.append(norm_factor)
mus.append(mu)
sigmas.append(sigma)
for ax_i, norm_factor, mu, sigma in zip(ax.ravel(), norm_factors, mus, sigmas ):
ax_twin = ax_i.twinx()
xmax = ax_i.get_xlim()[1]
ax_twin.set_ylim(0, xmax / norm_factor)
ax_twin.set_ylabel("probablility dense",color="black",fontsize=12, weight = "bold")
CI_95_lower = mu - (1.96*sigma)
CI_95_upper = mu + (1.96*sigma)
px_shaded = np.arange(CI_95_lower,CI_95_upper,0.1)
ax_twin.fill_between(px_shaded,norm.pdf(px_shaded,loc=mu,scale=sigma) * norm_factor,alpha=0.75, color='pink')
area_shaded_95_CI = norm.cdf(x=CI_95_upper, loc=mu, scale=sigma)-norm.cdf(x=CI_95_lower, loc=mu, scale=sigma)
ax_twin.text(-0.06,0.01,str(round(area_shaded_95_CI*100,1))+"%", fontsize=20)
ax_twin.annotate(s=f'lower bound= {CI_95_lower:.3f}',xy=(CI_95_lower,norm.pdf(CI_95_lower,loc=mu,scale=sigma)),xytext=(-0.75,0.01),weight='bold',color='blue',\
arrowprops=dict(arrowstyle='-|>',connectionstyle='arc3',color='green'),\
fontsize=12
)
ax_twin.annotate(s=f'upper bound= {CI_95_upper:.3f}',xy=(CI_95_upper,norm.pdf(CI_95_upper,loc=mu,scale=sigma)),xytext=(0.28,0.01),weight='bold',color='blue',\
arrowprops=dict(arrowstyle='-|>',connectionstyle='arc3',color='green'),\
fontsize=12
)
ax_twin.text(0.05, 0.03, r"$\mu=" + f'{mu:.6f}' + ", \sigma=" + f'{sigma:.6f}' + "$" + ", confidence interval=95%" ,
horizontalalignment='center', fontsize=15)
plt.suptitle(title, fontsize=18)
plt.tight_layout()
plt.show()
df1, df2, df3, df4 = [pd.DataFrame({"gap": np.random.normal(0, 0.2, n)}) for n in [6000, 4000, 1800, 1200]]
drawSubplots(df1, df2, df3, df4, "Title")
I'm trying to plot multiple images with Matplotlib's imshow() method, and have them share a single y axis. Although the images have the same number of y pixels, the images don't end up the same height.
Demonstration code;
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import poisson
def ibp_oneparam(alpha, N):
"""One-parameter IBP"""
# First customer
Z = np.array([np.ones(poisson(alpha).rvs(1))], dtype=int)
# ith customer
for i in range(2, N+1):
# Customer walks along previously sampled dishes
z_i = []
for previously_sampled_dish in Z.T:
m_k = np.sum(previously_sampled_dish)
if np.random.rand() >= m_k / i:
# Customer decides to sample this dish
z_i.append(1.0)
else:
# Customer decides to skip this dish
z_i.append(0.0)
# Customer decides to try some new dishes
z_i.extend(np.ones(poisson(alpha / i).rvs(1)))
z_i = np.array(z_i)
# Add this customer to Z
Z_new = np.zeros((
Z.shape[0] + 1,
max(Z.shape[1], len(z_i))
))
Z_new[0:Z.shape[0], 0:Z.shape[1]] = Z
Z = Z_new
Z[i-1, :] = z_i
return Z
np.random.seed(3)
N = 10
alpha = 2.0
#plt.figure(dpi=100)
fig, (ax1, ax2, ax3) = plt.subplots(
1,
3,
dpi=100,
sharey=True
)
Z = ibp_oneparam(alpha, N)
plt.sca(ax1)
plt.imshow(
Z,
extent=(0.5, Z.shape[1] + 0.5, len(Z) + 0.5, 0.5),
cmap='Greys_r'
)
plt.ylabel("Customers")
plt.xlabel("Dishes")
plt.xticks(range(1, Z.shape[1] + 1))
plt.yticks(range(1, Z.shape[0] + 1))
Z = ibp_oneparam(alpha, N)
plt.sca(ax2)
plt.imshow(
Z,
extent=(0.5, Z.shape[1] + 0.5, len(Z) + 0.5, 0.5),
cmap='Greys_r'
)
plt.xlabel("Dishes")
plt.xticks(range(1, Z.shape[1] + 1))
Z = ibp_oneparam(alpha, N)
plt.sca(ax3)
plt.imshow(
Z,
extent=(0.5, Z.shape[1] + 0.5, len(Z) + 0.5, 0.5),
cmap='Greys_r'
)
plt.xlabel("Dishes")
plt.xticks(range(1, Z.shape[1] + 1))
plt.show()
Output;
I expect these images to each have the same height, and have varying widths. How can I achieve this?
Aside: The code above is demonstrating the Indian Buffet Process. For the purposes of this post, consider the three images to be random binary matrices with the same number of rows, but variable numbers of columns.
Thank you,
I got a decent result with grid-spec width_ratios.
"""fig, (ax1, ax2, ax3) = plt.subplots(
1,
3,
dpi=100,
sharey=True,
constrained_layout=True
)"""
# I commented the above code and replaced with below.
import matplotlib.gridspec as gridspec
fig = plt.figure(constrained_layout=True)
gs = gridspec.GridSpec(ncols=3, nrows=1, figure=fig, width_ratios=[7./4.,1,6./4.])
ax1 = fig.add_subplot(gs[0,0])
ax2 = fig.add_subplot(gs[0,1])
ax3 = fig.add_subplot(gs[0,2])
It's some what counter intuitive that you need to use width ratios to adjust the heights but in the context of a grid with multiple rows it makes sense that you can only scale columns independently by width. and rows independently by height.
https://matplotlib.org/tutorials/intermediate/gridspec.html
Are there any colormaps or is there a simple way to transform a matplotlib colormap to provide a much bigger color range near 0.5 and a smaller one at the extremes? I am creating a bunch of subplots, one of which has color values of about 10 times the others, so it’s values dominate and the rest of the plots all look the same. For a simple example say we have:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(1,10,10)
y = np.linspace(1,10,10)
t1 = np.random.normal(2,0.3,10)
t2 = np.random.normal(9,0.01,10)
t2_max = max(t2)
plt.figure(figsize=(22.0, 15.50))
p = plt.subplot(1,2,1)
colors = plt.cm.Accent(t1/t2_max)
p.scatter(x, y, edgecolors=colors, s=15, linewidths=4)
p = plt.subplot(1,2,2)
colors = plt.cm.Accent(t2/t2_max)
p.scatter(x, y, edgecolors=colors, s=15, linewidths=4)
plt.subplots_adjust(left=0.2)
cbar_ax = plt.axes([0.10, 0.15, 0.05, 0.7])
sm = plt.cm.ScalarMappable(cmap=plt.cm.Accent, norm=plt.Normalize(vmin=0, vmax=t2_max))
sm._A = []
cbar = plt.colorbar(sm,cax=cbar_ax)
plt.show()
There is much more variation in t1 than in t2, however the variation can not be seen because of the high values of t2. What I want is a map the will provide a larger color gradient around the mean of t1 without transforming the data itself. I have found one solution here http://protracted-matter.blogspot.co.nz/2012/08/nonlinear-colormap-in-matplotlib.html but cant get it to work for my scatter plots.
EDIT:
From answer below the class can be modified to take negative numbers, and fixed boundaries.
import numpy as np
import matplotlib.pyplot as plt
x = y = np.linspace(1, 10, 10)
t1mean, t2mean = -6, 9
sigma1, sigma2 = .3, .01
t1 = np.random.normal(t1mean, sigma1, 10)
t2 = np.random.normal(t2mean, sigma2, 10)
class nlcmap(object):
def __init__(self, cmap, levels):
self.cmap = cmap
self.N = cmap.N
self.monochrome = self.cmap.monochrome
self.levels = np.asarray(levels, dtype='float64')
self._x = self.levels
self.levmax = self.levels.max()
self.levmin = self.levels.min()
self.transformed_levels = np.linspace(self.levmin, self.levmax,
len(self.levels))
def __call__(self, xi, alpha=1.0, **kw):
yi = np.interp(xi, self._x, self.transformed_levels)
return self.cmap(yi / (self.levmax-self.levmin)+0.5, alpha)
tmax = 10
tmin = -10
#the choice of the levels depends on the data:
levels = np.concatenate((
[tmin, tmax],
np.linspace(t1mean - 2 * sigma1, t1mean + 2 * sigma1, 5),
np.linspace(t2mean - 2 * sigma2, t2mean + 2 * sigma2, 5),
))
levels = levels[levels <= tmax]
levels.sort()
print levels
cmap_nonlin = nlcmap(plt.cm.jet, levels)
fig, (ax1, ax2) = plt.subplots(1, 2)
ax1.scatter(x, y, edgecolors=cmap_nonlin(t1), s=15, linewidths=4)
ax2.scatter(x, y, edgecolors=cmap_nonlin(t2), s=15, linewidths=4)
fig.subplots_adjust(left=.25)
cbar_ax = fig.add_axes([0.10, 0.15, 0.05, 0.7])
#for the colorbar we map the original colormap, not the nonlinear one:
sm = plt.cm.ScalarMappable(cmap=plt.cm.jet,
norm=plt.Normalize(vmin=tmin, vmax=tmax))
sm._A = []
cbar = fig.colorbar(sm, cax=cbar_ax)
#here we are relabel the linear colorbar ticks to match the nonlinear ticks
cbar.set_ticks(cmap_nonlin.transformed_levels)
cbar.set_ticklabels(["%.2f" % lev for lev in levels])
plt.show()
Your link provides quite a good solution for the colormap. I edited a bit, but it contained al the necessary. You need to pick some sensible levels for your nonlinear colormap. I used two ranges centered around the mean values, between +- 4 the standard deviation of your sample. by changing that to another number you obtain a different local gradient in the color around the two mean values.
For the colorbar, you
either leave the colors nonlinearly spaced with linearly spaced labels
you have linearly spaced colors with nonlinearly spaced labels.
The second allows greater resolution when looking at the data, looks nicer and is implemented below:
import numpy as np
import matplotlib.pyplot as plt
x = y = np.linspace(1, 10, 10)
t1mean, t2mean = 2, 9
sigma1, sigma2 = .3, .01
t1 = np.random.normal(t1mean, sigma1, 10)
t2 = np.random.normal(t2mean, sigma2, 10)
class nlcmap(object):
def __init__(self, cmap, levels):
self.cmap = cmap
self.N = cmap.N
self.monochrome = self.cmap.monochrome
self.levels = np.asarray(levels, dtype='float64')
self._x = self.levels
self.levmax = self.levels.max()
self.transformed_levels = np.linspace(0.0, self.levmax,
len(self.levels))
def __call__(self, xi, alpha=1.0, **kw):
yi = np.interp(xi, self._x, self.transformed_levels)
return self.cmap(yi / self.levmax, alpha)
tmax = max(t1.max(), t2.max())
#the choice of the levels depends on the data:
levels = np.concatenate((
[0, tmax],
np.linspace(t1mean - 4 * sigma1, t1mean + 4 * sigma1, 5),
np.linspace(t2mean - 4 * sigma2, t2mean + 4 * sigma2, 5),
))
levels = levels[levels <= tmax]
levels.sort()
cmap_nonlin = nlcmap(plt.cm.jet, levels)
fig, (ax1, ax2) = plt.subplots(1, 2)
ax1.scatter(x, y, edgecolors=cmap_nonlin(t1), s=15, linewidths=4)
ax2.scatter(x, y, edgecolors=cmap_nonlin(t2), s=15, linewidths=4)
fig.subplots_adjust(left=.25)
cbar_ax = fig.add_axes([0.10, 0.15, 0.05, 0.7])
#for the colorbar we map the original colormap, not the nonlinear one:
sm = plt.cm.ScalarMappable(cmap=plt.cm.jet,
norm=plt.Normalize(vmin=0, vmax=tmax))
sm._A = []
cbar = fig.colorbar(sm, cax=cbar_ax)
#here we are relabel the linear colorbar ticks to match the nonlinear ticks
cbar.set_ticks(cmap_nonlin.transformed_levels)
cbar.set_ticklabels(["%.2f" % lev for lev in levels])
plt.show()
In the result, notice that the ticks of the colorbar are NOT equispaced:
You could use LinearSegmentedColormap:
With this, you need to set up a color lookup table within a dictionary e.g. 'cdict' below.
cdict = {'red': [(0.0, 0.0, 0.0),
(0.15, 0.01, 0.01),
(0.35, 1.0, 1.0),
(1.0, 1.0, 1.0)],
'green': [(0.0, 0.0, 0.0),
(1.0, 0.0, 1.0)],
'blue': [(0.0, 0.0, 1.0),
(0.9, 0.01, 0.01),
(1.0, 0.0, 1.0)]}
This shows the transistions between values. I have set red to vary a lot around the values of t1/t2_max (0.15 to 0.35) and blue to vary a lot around the values of t2/t2_max (0.9 to 1.0). Green does nothing. I'd recommend reading the docs to see how this works. (Note this could be automated to automatically vary around your values). I then tweaked your code to show the graph:
import matplotlib.colors as col
my_cmap = col.LinearSegmentedColormap('my_colormap', cdict)
plt.figure(figsize=(22.0, 15.50))
p = plt.subplot(1,2,1)
colors = my_cmap(t1/t2_max)
p.scatter(x, y, edgecolors=colors, s=15, linewidths=4)
p = plt.subplot(1,2,2)
colors = my_cmap(t2/t2_max)
p.scatter(x, y, edgecolors=colors, s=15, linewidths=4)
plt.subplots_adjust(left=0.2)
cbar_ax = plt.axes([0.10, 0.15, 0.05, 0.7])
sm = plt.cm.ScalarMappable(cmap=my_cmap, norm=plt.Normalize(vmin=0, vmax=t2_max))
sm._A = []
cbar = plt.colorbar(sm,cax=cbar_ax)
plt.show()