Draw a vertical line on seaborn jointplot - matplotlib

I'm trying to draw a vertical line on a Seaborn Joint Plot and either get two plots, or an error stating ax is not iterable. The logic is as follows:
a4_dims = (12, 4)
fig, ax = plt.subplots(figsize=a4_dims)
ax.set_xlim(-.75, 1.25)
ax.set_ylim(-.75,1.25)
plt.axvline(0)
sns.jointplot(x='1_3Movement',y='1_2Movement',data=dfm,kind='kde', xlim=(-.75, 1.25), ylim=(-.75,1.25))
and this is what I get.

Seaborn's jointplot creates its own figure and 3 axes. jointplot returns a JointGrid object. You can grab the individual axes via .ax_joint, .ax_marg_x and .ax_marg_y. To draw a line onto the contour plot part, use .ax_joint.
The jointplot is always a quadratic figure. The figsize can be set via height= (the width will be equal).
from matplotlib import pyplot as plt
import numpy as np
import seaborn as sns
kdeplot = sns.jointplot(x=np.random.normal(0.25, 0.5, 10), y=np.random.normal(0.25, 0.5, 10),
kind='kde', xlim=(-.75, 1.25), ylim=(-.75, 1.25), height=4)
# draw a vertical line on the joint plot, optionally also on the x margin plot
for ax in (kdeplot.ax_joint, kdeplot.ax_marg_x):
ax.axvline(0, color='crimson', ls='--', lw=3)
plt.show()

Related

Add xticks within margins

I am trying create two plots that should have the same width when displayed in a row-wise fashion. I have noticed that adding xticks followed by tight_layout makes the plot (pcolormesh) decrease in width from increasing the x-margins. I would like to move the ticks in such a way that the x-margins are eliminated and both pcolormesh have the same width.
I have the following example:
import numpy as np, matplotlib.pyplot as plt
def plot(ticks=True):
fig, ax = plt.subplots(figsize=(6,1))
np.random.seed(42)
a = np.random.randn(1,6)
ax.pcolormesh(a)
plt.gca().invert_yaxis()
ax.xaxis.tick_top()
ax.set(yticklabels=[])
ax.tick_params(left=False, length=5)
if ticks:
ax.set_xticks([0, 3, 6])
else:
plt.axis('off')
plt.tight_layout()
plt.savefig(f'plot-{ticks}.png', dpi=300, bbox_inches='tight', pad_inches=0.0)
I get the following plots when running with and without the ticks:
The x-margins are not the same, which is more noticeable when increasing the font-size. How do I move the 3 label to right and the 6 label to the left to make both images have the same x-margins (0 margin)?
EDIT
Using the suggestion from Align specific x labels differently to each other? we have
import numpy as np, matplotlib.pyplot as plt
plt.rcParams.update({'font.size': 17})
fig, ax = plt.subplots(figsize=(6,1))
np.random.seed(42)
a = np.random.randn(1,6)
ax.pcolormesh(a)
plt.gca().invert_yaxis()
ax.xaxis.tick_top()
ax.set(yticklabels=[])
ax.tick_params(left=False, length=5)
# get list of x tick objects
xtick_objects = ax.xaxis.get_major_ticks()
xtick_objects[0].label1.set_horizontalalignment('left') # left align first tick
xtick_objects[-1].label1.set_horizontalalignment('right') # right align last tick
ax.set_xticks([0, 3, 6])
plt.tight_layout()
# plt.savefig(f'plot.png', dpi=300, bbox_inches='tight', pad_inches=0.0
plt.show()
which does not seem to change the alignment.

How to append axes in Matplotlib but not inherit axis type (Cartopy type, to be specific)

I use Basemap to plot, which doesn't introduce its own axis type and rather does some extreme math to make its geographic plotting. That let this work:
import matplotlib.pyplot as plt
fig, ax = plt.subplots(); #use instead of fig because it inits an axis too
divider = make_axes_locatable(ax); #prep to add an axis
cax = divider.append_axes('right', size='2.0%', pad=0.15); #make a color bar axis
I preferred this because it kept the new axis within the original axis bounds, which made lining up other subplots (by using an identical divider.append_axes(...) call with them as well) easy and making sure the new colorbar axis didn't clip off the plot easy as well.
But since Basemap is depreciated, I'm trying to move to Cartopy. Unfortunately similar code with Cartopy:
import matplotlib.pyplot as plt
import cartopy as cartopy
fig, ax = plt.subplots(); #use instead of fig because it inits an axis too
ax = plt.axes(projection=cartopy.crs.PlateCarree()); #redefine the axis to be a geographical axis
divider = make_axes_locatable(ax); #prep to add an axis
cax = divider.append_axes('right', size='2.0%', pad=0.15); #make a color bar axis
Gives the error:
KeyError: 'map_projection'
Since under Cartopy the axis ax is changed to a Cartopy axis, the appended axis cax is also a Cartopy axis. If I provide a map_projection=cartopy.crs.PlateCarree() the code runs, but I don't want it to be a Cartopy axis, I want it to be a colorbar (and the Cartopy axis errors if I use it as a colorbar).
How can I append_axes but not inherit the Cartopy axis type?
Additionally, I found not-quite-good-enough work-around using:
import matplotlib.pyplot as plt
import cartopy as cartopy
fig, ax = plt.subplots(); #use instead of fig because it inits an axis too
ax = plt.axes(projection=cartopy.crs.PlateCarree()); #redefine the axis to be a geographical axis
cax = ax.inset_axes((1.02, 0, 0.02, 1)); #make a color bar axis
But that cax is not part of the original axis bounds, which makes it useless for lining up other subplots that use the divider.append_axes() method and the colorbar can clip off the edge of the plot since it is floating in "white space".

Align multi-line ticks in Seaborn plot

I have the following heatmap:
I've broken up the category names by each capital letter and then capitalised them. This achieves a centering effect across the labels on my x-axis by default which I'd like to replicate across my y-axis.
yticks = [re.sub("(?<=.{1})(.?)(?=[A-Z]+)", "\\1\n", label, 0, re.DOTALL).upper() for label in corr.index]
xticks = [re.sub("(?<=.{1})(.?)(?=[A-Z]+)", "\\1\n", label, 0, re.DOTALL).upper() for label in corr.columns]
fig, ax = plt.subplots(figsize=(20,15))
sns.heatmap(corr, ax=ax, annot=True, fmt="d",
cmap="Blues", annot_kws=annot_kws,
mask=mask, vmin=0, vmax=5000,
cbar_kws={"shrink": .8}, square=True,
linewidths=5)
for p in ax.texts:
myTrans = p.get_transform()
offset = mpl.transforms.ScaledTranslation(-12, 5, mpl.transforms.IdentityTransform())
p.set_transform(myTrans + offset)
plt.yticks(plt.yticks()[0], labels=yticks, rotation=0, linespacing=0.4)
plt.xticks(plt.xticks()[0], labels=xticks, rotation=0, linespacing=0.4)
where corr represents a pre-defined pandas dataframe.
I couldn't seem to find an align parameter for setting the ticks and was wondering if and how this centering could be achieved in seaborn/matplotlib?
I've adapted the seaborn correlation plot example below.
from string import ascii_letters
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
sns.set_theme(style="white")
# Generate a large random dataset
rs = np.random.RandomState(33)
d = pd.DataFrame(data=rs.normal(size=(100, 7)),
columns=['Donald\nDuck','Mickey\nMouse','Han\nSolo',
'Luke\nSkywalker','Yoda','Santa\nClause','Ronald\nMcDonald'])
# Compute the correlation matrix
corr = d.corr()
# Generate a mask for the upper triangle
mask = np.triu(np.ones_like(corr, dtype=bool))
# Set up the matplotlib figure
f, ax = plt.subplots(figsize=(11, 9))
# Generate a custom diverging colormap
cmap = sns.diverging_palette(230, 20, as_cmap=True)
# Draw the heatmap with the mask and correct aspect ratio
sns.heatmap(corr, mask=mask, cmap=cmap, vmax=.3, center=0,
square=True, linewidths=.5, cbar_kws={"shrink": .5})
for i in ax.get_yticklabels():
i.set_ha('right')
i.set_rotation(0)
for i in ax.get_xticklabels():
i.set_ha('center')
Note the two for sequences above. These get the label and then set the horizontal alignment (You can also change the vertical alignment (set_va()).
The code above produces this:

Plotting masked numpy array leads to incorrect colorbar

I'm trying to create a custom color bar for a matplotlib PolyCollection. Everything seems ok until I attempt to plot a masked array. The color bar no longer shows the correct colors even though the plot does. Is there a different procedure for plotting masked arrays?
I'm using matplotlib 1.4.0 and numpy 1.8.
Here's my plotting code:
import numpy
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib.collections import PolyCollection
vertices = numpy.load('vertices.npy')
array = numpy.load('array.npy')
# Take 2d slice out of 3D array
slice_ = array[:, :, 0:1].flatten(order='F')
fig, ax = plt.subplots()
poly = PolyCollection(vertices, array=slice_, edgecolors='black', linewidth=.25)
cm = mpl.colors.ListedColormap([(1.0, 0.0, 0.0), (.2, .5, .2)])
poly.set_cmap(cm)
bounds = [.1, .4, .6]
norm = mpl.colors.BoundaryNorm(bounds, cm.N)
fig.colorbar(poly, ax=ax, orientation='vertical', boundaries=bounds, norm=norm)
ax.add_collection(poly, autolim=True)
ax.autoscale_view()
plt.show()
Here's what the plot looks like:
However, when I plot a masked array with the following change before the slicing:
array = numpy.ma.array(array, mask=array > .5)
I get a color bar that now shows only a single color. Even though both colors are (correctly) still shown in the plot.
Is there some trick to keeping a colobar consistent when plotting a masked array? I know I can use cm.set_bad to change the color of masked values, but that's not quite what I'm looking for. I want the color bar to show up the same between these two plots since both colors and the color bar itself should remain unchanged.
Pass the BoundaryNorm to the PolyCollection, poly. Otherwise, poly.norm gets set to a matplotlib.colors.Normalize instance by default:
In [119]: poly.norm
Out[119]: <matplotlib.colors.Normalize at 0x7faac4dc8210>
I have not stepped through the source code sufficiently to explain exactly what is happening in the code you posted, but I speculate that the interaction of this Normalize instance and the BoundaryNorm make the range of values seen by the fig.colorbar different than what you expected.
In any case, if you pass norm=norm to PolyCollection, then the result looks correct:
import numpy
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.collections as mcoll
import matplotlib.colors as mcolors
numpy.random.seed(4)
N, M = 3, 3
vertices = numpy.random.random((N, M, 2))
array = numpy.random.random((1, N, 2))
# vertices = numpy.load('vertices.npy')
# array = numpy.load('array.npy')
array = numpy.ma.array(array, mask=array > .5)
# Take 2d slice out of 3D array
slice_ = array[:, :, 0:1].flatten(order='F')
fig, ax = plt.subplots()
bounds = [.1, .4, .6]
cm = mpl.colors.ListedColormap([(1.0, 0.0, 0.0), (.2, .5, .2)])
norm = mpl.colors.BoundaryNorm(bounds, cm.N)
poly = mcoll.PolyCollection(
vertices,
array=slice_,
edgecolors='black', linewidth=.25, norm=norm)
poly.set_cmap(cm)
fig.colorbar(poly, ax=ax, orientation='vertical')
ax.add_collection(poly, autolim=True)
ax.autoscale_view()
plt.show()

"panel barchart" in matplotlib

I would like to produce a figure like this one using matplotlib:
(source: peltiertech.com)
My data are in a pandas DataFrame, and I've gotten as far as a regular stacked barchart, but I can't figure out how to do the part where each category is given its own y-axis baseline.
Ideally I would like the vertical scale to be exactly the same for all the subplots and move the panel labels off to the side so there can be no gaps between the rows.
I haven't exactly replicated what you want but this should get you pretty close.
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
#create dummy data
cols = ['col'+str(i) for i in range(10)]
ind = ['ind'+str(i) for i in range(10)]
df = pd.DataFrame(np.random.normal(loc=10, scale=5, size=(10, 10)), index=ind, columns=cols)
#create plot
sns.set_style("whitegrid")
axs = df.plot(kind='bar', subplots=True, sharey=True,
figsize=(6, 5), legend=False, yticks=[],
grid=False, ylim=(0, 14), edgecolor='none',
fontsize=14, color=[sns.xkcd_rgb["brownish red"]])
plt.text(-1, 100, "The y-axis label", fontsize=14, rotation=90) # add a y-label with custom positioning
sns.despine(left=True) # get rid of the axes
for ax in axs: # set the names beside the axes
ax.lines[0].set_visible(False) # remove ugly dashed line
ax.set_title('')
sername = ax.get_legend_handles_labels()[1][0]
ax.text(9.8, 5, sername, fontsize=14)
plt.suptitle("My panel chart", fontsize=18)