I want to create a violin plot, with either matplotlib or searborn, in which the plot is colored according to a colormap.
This is what I get:
This is what I would like to get (I used Photoshop here):
How can I obtain the desired plot?
I thought there would be a better was to do this, but, based on #ImportanceOfBeingErnest's comment, I guess this is actually the way to go:
from matplotlib.path import Path
from matplotlib.patches import PathPatch
from mpl_toolkits.axes_grid1.axes_divider import make_axes_locatable
x = [np.random.normal(loc=i, scale=1, size=(100,)) for i in range(5)]
fig, ax = plt.subplots()
violins = ax.violinplot(x)
ymin, ymax = ax.get_ylim()
xmin, xmax = ax.get_xlim()
# create a numpy image to use as a gradient
Nx,Ny=1,1000
imgArr = np.tile(np.linspace(0,1,Ny), (Nx,1)).T
cmap = 'hsv'
for violin in violins['bodies']:
path = Path(violin.get_paths()[0].vertices)
patch = PathPatch(path, facecolor='none', edgecolor='none')
ax.add_patch(patch)
img = ax.imshow(imgArr, origin="lower", extent=[xmin,xmax,ymin,ymax], aspect="auto",
cmap=cmap,
clip_path=patch)
# colorbar
ax_divider = make_axes_locatable(ax)
cax = ax_divider.append_axes("right", size="5%", pad="2%")
norm = matplotlib.colors.Normalize(vmin=ymin, vmax=ymax)
cb = matplotlib.colorbar.ColorbarBase(cax, cmap=matplotlib.cm.get_cmap(cmap),
norm=norm,
orientation='vertical')
Related
I've made a colormap from a matrix (matrix300.txt). I would like to normalize my colormap, but I don't know how to do it. The axes should be from 0 to 3.
import numpy as np
import matplotlib.pyplot as plt
import math
from matplotlib import colors
import matplotlib.colors as colors
p = np.loadtxt('matrix300.txt')
cmap = colors.ListedColormap(['yellow', 'white'], name='Gamma=1')
bounds = [-1, 0, 1]
norm = colors.BoundaryNorm(bounds, cmap.N)
img = plt.imshow(p, interpolation='nearest', origin='lower', cmap=cmap, norm=norm)
plt.colorbar(img, cmap=cmap, norm=norm, boundaries=bounds)
plt.xlabel("h_0")
plt.ylabel("h")
plt.show()
Your usage of ListedColormap is not right here. It is for creating a user-defined colormap. So it may be a bit too much of elaborating if you need a colormap just for plotting. I post an example using ListedColormap anyway.
import numpy as np
import matplotlib.pyplot as plt
import math
from matplotlib import colors
import matplotlib.colors as colors
# dummy data
yy, xx = np.mgrid[0:1:10j, 0:1:10j]
p = np.exp(-10*(xx-0.5)**2-10*(yy-0.5)**2) # gaussian function
# create your own colormap
pmax, pmin = np.max(p), np.min(p) # get data range
bounds = np.linspace(pmin, pmax, 100) # set data range for colormap
num = 100 # how many colors do you need?
norm = colors.BoundaryNorm(bounds, ncolors=num) # an object for cmap normalization
carray = np.array([np.linspace(0,1,num), np.linspace(0.0,0.25,num), [0.5]*num, [1]*num]).T # list of RGBA values
cmap = colors.ListedColormap(carray, N=100, name='mycolormap') # create your own color map from carray
# plot data
img = plt.imshow(p, interpolation='nearest', origin='lower', cmap=cmap, norm=norm)
plt.colorbar(img, cmap=cmap, norm=norm, boundaries=bounds)
plt.show()
Mush easier way is to use pcolormesh.
fig, ax = plt.subplots(1,1)
ax.set_aspect("equal")
im = ax.pcolormesh(xx, yy, p, vmin=pmin, vmax=pmax)
cbar = fig.colorbar(mappable=im)
plt.show()
I am trying to make an upper triangle correlation matrix which ideally I would like to superpose to another picture of a lower triangle matrix. Therefore, I would like the mask color to be setup to none or transparent (otherwise if it's white I will not be able to superpose)...any idea about how to do this in seaborn?
EDIT
Here is what I would like to do: using a set of columns from dataframe, I would like to plot the pairplot (lower triangle) and the correlation map (upper triangle) of these columns
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
rs = np.random.RandomState(112358)
d1 = pd.DataFrame(data=rs.normal(size=(100, 10)), columns=[*'abcdefghij' ])
corr1 = d1.corr()
mask1 = np.tril(np.ones_like(corr1, dtype=bool))
fig, ax = plt.subplots(figsize=(11, 9))
sns.heatmap(corr1, mask=mask1, cmap='PRGn', vmax=.3, vmin=-.3,
square=True, linewidths=.5, cbar_kws={"shrink": .85, "pad":-.01}, ax=ax)
def hide_current_axis(*args, **kwds):
plt.gca().set_visible(False)
e = sns.pairplot(d1)
e.map_upper(hide_current_axis)
plt.show()
This code of course works, but it plots the two figures separately.
The normal way to create a triangular heatmap is to mask away the not-needed part. Nothing will be drawn there, the original background color will stay visible. If you draw a second heatmap, it also will only draw where it isn't masked away.
Here is some code to demonstrate the idea.
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
sns.set_theme(style="white")
rs = np.random.RandomState(112358)
d1 = pd.DataFrame(data=rs.normal(size=(100, 10)), columns=[*'abcdefghij' ])
d2 = pd.DataFrame(data=rs.normal(size=(100, 10)), columns=[*'abcdefghij' ])
corr1 = d1.corr()
corr2 = d2.corr()
mask1 = np.tril(np.ones_like(corr1, dtype=bool))
mask2 = np.triu(np.ones_like(corr2, dtype=bool))
fig, ax = plt.subplots(figsize=(11, 9))
sns.heatmap(corr1, mask=mask1, cmap='PRGn', vmax=.3, vmin=-.3,
square=True, linewidths=.5, cbar_kws={"shrink": .85, "pad":-.01}, ax=ax)
sns.heatmap(corr1, mask=mask2, cmap='RdYlBu', vmax=.3, vmin=-.3,
square=True, linewidths=.5, cbar_kws={"shrink": .85}, ax=ax)
# the following lines color and hatch the axes background, only the diagonals are visible
ax.patch.set_facecolor('grey')
ax.patch.set_edgecolor('yellow')
ax.patch.set_hatch('xx')
plt.show()
About the new question, to combine a pairplot with a triangular heatmap. As a pairplot is a figure-level function, it creates its own figure with subplots. It should be created first.
As a second step, a special ax for the heatmap can be created, using the positions of the pairplot's subplots. Setting its facecolor to 'none' makes it fully transparent (the default would be white, hiding everything behind).
Adding a colorbar can be more cumbersome, as the pairplot doesn't leave a good spot to position it.
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
def hide_current_axis(*args, **kwds):
plt.gca().set_visible(False)
rs = np.random.RandomState(112358)
d1 = pd.DataFrame(data=rs.normal(size=(20, 5)), columns=[*'abcde'])
e = sns.pairplot(d1)
e.map_upper(hide_current_axis)
(xmin, _), (_, ymax) = e.axes[0, 0].get_position().get_points()
(_, ymin), (xmax, _) = e.axes[-1, -1].get_position().get_points()
ax = e.fig.add_axes([xmin, ymin, xmax - xmin, ymax - ymin], facecolor='none')
corr1 = d1.corr()
mask1 = np.tril(np.ones_like(corr1, dtype=bool))
sns.heatmap(corr1, mask=mask1, cmap='seismic', vmax=.5, vmin=-.5,
linewidths=.5, cbar=False, annot=True, annot_kws={'size': 22}, ax=ax)
ax.set_xticks([])
ax.set_yticks([])
# ax.xaxis.tick_top()
# ax.yaxis.tick_right()
plt.show()
As mentioned in the comments, an approach more faithful to seaborn's philosophy would be to color the axes of the upper right subplots according to the correlation together with a numeric display. I couldn't find example code, here is my attempt:
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from scipy.stats import pearsonr
def corrfunc(x, y, **kwds):
cmap = kwds['cmap']
norm = kwds['norm']
ax = plt.gca()
ax.tick_params(bottom=False, top=False, left=False, right=False)
sns.despine(ax=ax, bottom=True, top=True, left=True, right=True)
r, _ = pearsonr(x, y)
facecolor = cmap(norm(r))
ax.set_facecolor(facecolor)
lightness = (max(facecolor[:3]) + min(facecolor[:3]) ) / 2
ax.annotate(f"r={r:.2f}", xy=(.5, .5), xycoords=ax.transAxes,
color='white' if lightness < 0.7 else 'black', size=26, ha='center', va='center')
rs = np.random.RandomState(112358)
d1 = pd.DataFrame(data=rs.normal(size=(20, 5)), columns=[*'abcde'])
g = sns.PairGrid(d1)
g.map_lower(plt.scatter, s=10)
g.map_diag(sns.histplot, kde=False)
g.map_upper(corrfunc, cmap=plt.get_cmap('seismic'), norm=plt.Normalize(vmin=-.5, vmax=.5))
g.fig.subplots_adjust(wspace=0.06, hspace=0.06) # equal spacing in both directions
plt.show()
Sometimes, it is useful to change the size of the correlation value based on the abs(r). Higher values -> larger numbers.
def corrfunc(x, y, **kwds):
cmap = kwds['cmap']
norm = kwds['norm']
ax = plt.gca()
ax.tick_params(bottom=False, top=False, left=False, right=False)
sns.despine(ax=ax, bottom=True, top=True, left=True, right=True)
r, _ = pearsonr(x, y)
facecolor = cmap(norm(r))
ax.set_facecolor(facecolor)
lightness = (max(facecolor[:3]) + min(facecolor[:3]) ) / 2
tam = int(70*abs(r))
if tam < 10:
tam = 10
ax.annotate(f"{r:.2f}", xy=(.5, .5), xycoords=ax.transAxes,
color='white' if lightness < 0.7 else 'black', size=tam, ha='center', va='center')
I'm trying to figure out how to append an axes to my polar projection. The newly added axes is supposed to wrap around the original polar axes like a ring.
For that purpose I tried to use append_axes from a divider created via make_axes_locatable on a polar matplotlib projection ax.
However, there's no option for "outer" or anything that would resemble polar projections with the append_axes arguments.
Instead of a ring around the axes, I get a new axes just below the original one (see picture).
Is there any alternative that would allow creating an axes of ring shape around an existing polar axes?
Note, I don't want to add them on the same ax because the scales may be different.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
plt.style.use("seaborn-white")
def synthesize(polar=False):
fig = plt.figure()
ax = fig.add_subplot(111, polar=polar)
t = np.linspace(0,2*np.pi)
r_sin = np.sin(t)
r_cos = np.cos(t)
for r in [r_sin, r_cos]:
ax.scatter(t, r, c=r, s=t*100, edgecolor="white", cmap=plt.cm.magma_r)
ax.scatter(t, -r, c=r, s=t*100, edgecolor="white", cmap=plt.cm.magma_r)
ax.set_title("polar={}".format(polar),fontsize=15)
ax.set_xticklabels([])
return fig, ax, t
# Rectilinear
fig, ax, t = synthesize(polar=False)
# Here are the plot dimensions in response to the answer below
xlim = ax.get_xlim()
ylim = ax.get_ylim()
rlim = (ax.get_rmin(), ax.get_rmax())
print(xlim, ylim, rlim)
(0.0, 6.283185307179586) (0.0, 2.2437621373846617) (0.0, 2.2437621373846617)
# Encircling axis
divider = make_axes_locatable(ax)
ax_below = divider.append_axes("bottom", size="32.8%", pad=0.1)
ax_below.scatter(t, np.tan(t), c="black", edgecolor="white")
# Polar
fig, ax, t = synthesize(polar=True)
divider = make_axes_locatable(ax)
ax_below = divider.append_axes("bottom", size="32.8%", pad=0.1)
ax_below.scatter(t, np.tan(t), c="black", edgecolor="white")
You probably can do something by tunning set_rmax and set_rorigin like this:
import numpy as np
import matplotlib.pyplot as plt
plt.style.use("seaborn-white")
def synthesize(polar=False):
fig = plt.figure()
ax = fig.add_subplot(111, polar=polar)
ax.set_rmax(30)
t = np.linspace(0,2*np.pi)
r_sin = np.sin(t)
r_cos = np.cos(t)
for r in [r_sin, r_cos]:
ax.scatter(t, r, c=r, s=t*100, edgecolor="white", cmap=plt.cm.magma_r)
ax.scatter(t, -r, c=r, s=t*100, edgecolor="white", cmap=plt.cm.magma_r)
ax.set_title("polar={}".format(polar),fontsize=15)
ax.set_xticklabels([])
return fig, ax, t
# Polar
fig, ax, t = synthesize(polar=True)
ax_below = fig.add_subplot(111, polar=True, frameon=True)
# ax_below = divider.append_axes("bottom", size="32.8%", pad=0.1)
ax_below.scatter(t, np.tan(t), c="black", edgecolor="white")
ax_below.set_rorigin(-75)
ax_below.set_rmin(-25)
plt.show()
Fairly simple: I'm wondering if there is an easy way to rescale the colormap to the visible area which is set in this case by ax.set_xlim. The resulting plot I am looking for would look identical in terms of color to the one where ax.set_xlim([-2,2]) is commented out in the code below.
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-10,10,100)
y = np.linspace(-10,10,100)
X,Y = np.meshgrid(x,y)
Z = X
fig = plt.figure()
ax = fig.add_subplot(111)
cmap = plt.get_cmap('RdYlBu')
cax = ax.contourf(X,Y,Z,256,cmap=cmap, vmin = np.min(Z), vmax=np.max(Z))
cbar = fig.colorbar(cax, cmap=cmap)
ax.set_xlim([-2,2])
plt.show()
This might not be the most elegant solution, but it will "rescale" your colorbar to the range of the display. Note, if you don't set a ylim as well, you' need to remove get_ylim.
fig = plt.figure()
ax = fig.add_subplot(111)
cmap = plt.get_cmap('RdYlBu')
# Note - if you don't set ylim as well, ax.get_ylim() returns (0,1) & you'd need to leave that part out
ax.set_xlim(-2,2)
xl = ax.get_xlim()
ax.set_ylim(-1,1)
yl = ax.get_ylim()
XL = np.where(((x>xl[0]))&(x<xl[1]))[0]
YL = np.where(((y>yl[0])&(y<yl[1])))[0].repeat(XL.shape[0]).reshape(-1,XL.shape[0])
XL = XL.repeat(YL.shape[0]).reshape(YL.shape[0],-1)
values = np.linspace(Z[YL,XL].min(),Z[YL,XL].max(),256)
cax = ax.contourf(X,Y,Z,values,cmap=cmap, vmin = values.min(), vmax=values.max())
cbar = fig.colorbar(cax, cmap=cmap)
plt.show()
I am trying to use a colorbar to label discrete, coded values plotted using imshow. I can achieve the colorbar that I want using the boundaries and values keywords, which makes the maximum value of the colorbar effectively 1 greater than the maximum value of the data being plotted.
Now I want ticks to be in the middle of each color range in the colorbar, but cannot specify a tick position for the largest color block in the colorbar, seemingly because it is outside of the data value limits.
Here's a quick block of code to demonstrate the problem:
data = np.tile(np.arange(4), 2)
fig = plt.figure()
ax = fig.add_subplot(121)
ax.imshow(data[None], aspect='auto')
cax = fig.add_subplot(122)
cbar = fig.colorbar(ax.images[0], cax=cax, boundaries=[0,1,2,3,4], values=[0,1,2,3])
cbar.set_ticks([.5, 1.5, 2.5, 3.5])
cbar.set_ticklabels(['one', 'two', 'three', 'four'])
Note the missing tick where 'four' should be. What's the right way to do this?
To summarize, this works for me:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import cm
from matplotlib import colors
data = np.tile(np.arange(4), 2)
fig = plt.figure()
ax = fig.add_subplot(121)
cmap = cm.get_cmap('jet', 4)
bounds = np.arange(5)
vals = bounds[:-1]
norm = colors.BoundaryNorm(bounds, cmap.N)
ax.imshow(data[None], aspect='auto', interpolation='nearest', cmap=cmap, norm=norm)
cax = fig.add_subplot(122)
cbar = fig.colorbar(ax.images[0], cax=cax, boundaries=bounds, values=vals)
cbar.set_ticks(vals + .5)
cbar.set_ticklabels(['one', 'two', 'three', 'four'])
The solution was to specify the colormap explicitly for the image using get_cmap and bounded by BoundaryNorm. Then specifying the tick positions just works. The resulting plot is:
You are not using the same colormap in imshow and cbar. As your data and cbar is defined in the same way (same limits etc.) so you do not realize the inconsistency in the above example. You should define the colormap first.
Let's say you want to divide your data into 4-discrete colors, then you can use
import numpy as np
import pylab as plt
from matplotlib import colors, cm
data = np.tile(np.arange(4), 2)
fig = plt.figure()
ax = fig.add_subplot(121)
cax = fig.add_subplot(122)
cmap = cm.get_cmap('jet', 4) # 4 discrete color
im=ax.imshow(data[None], aspect='auto',cmap=cmap)
cbar = fig.colorbar(ax.images[0], cax=cax, cmap=cmap)
plt.show()
You can now put the ticks according to your need.
In case you want to define the bounds as well as the colors in these bounds then you can use ListedColormap as follows:
data = np.tile(np.arange(4), 2)
fig = plt.figure()
ax = fig.add_subplot(121)
cax = fig.add_subplot(122)
cmap = colors.ListedColormap(['b','g','y','r'])
bounds=[0,1,2,3,4]
norm = colors.BoundaryNorm(bounds, cmap.N)
im=ax.imshow(data[None], aspect='auto',cmap=cmap, norm=norm)
cbar = fig.colorbar(im, cax=cax, cmap=cmap, norm=norm, boundaries=bounds, ticks=[0.5,1.5,2.5,3.5],)
plt.show()