Plot alignment and formatting help in Matplotlib and Seaborn - python

I have a dataframe with 15 rows, which I plot using a seaborn heatmap. I have three plots, each with different scale for the heatmap. The first two plots are the first two rows, which are not aligned on the plot.
I have created a grid with 15 rows, I give each of the first two rows 1/15th of the grid so I don't know why it is not aligned.
Another problem with the first two rows of the heatmap is that the text formatting doesn't work either.
So I want to do two things:
Stretch the top two rows of the table to align it with the bottom one and;
To make the formatting work for the top two rows as well.
Maybe also add titles to my white xaxes (l1 and l2) that separate the the subgroups in the bottom plot (standard methods like ax.set_title does not work).
My code:
import seaborn as sns
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.gridspec as gs
gs = gs.GridSpec(15, 1) # nrows, ncols
f = plt.figure(figsize=(10, 15))
cmap = sns.diverging_palette(220, 10, as_cmap=True)
ax1 = f.add_subplot(gs[0:1, :])
ax2 = f.add_subplot(gs[1:2, :])
ax3 = f.add_subplot(gs[2:15, :])
ticksx = plt.xticks(fontsize = 18, fontweight='bold')
ticksy = plt.yticks(fontsize = 18, fontweight='bold')
wageplot = sns.heatmap(df[0:1], vmin=3000, vmax=10000, annot=False, square=True, cmap=cmap, ax=ax1, yticklabels=True, cbar=False, xticklabels=False)
tenureplot = sns.heatmap(df[1:2], vmin=45, vmax=100, annot=True, square=True, cmap=cmap, ax=ax2, yticklabels=True, cbar=False, xticklabels=False)
heatmap = sns.heatmap(df[2:15], vmin=0, vmax=1, annot=False, square=True, cmap=cmap, ax=ax3, yticklabels=True, cbar=True, xticklabels=True)
heatmap.set_xticklabels(cols, rotation=45, ha='right')
l1 = plt.axhline(y=1, linewidth=14, color='w', label='Female')
l2 = plt.axhline(y=5, linewidth=14, color='w', label='Education')
f.tight_layout()
I would appreciate if I can pointed to where can I get some information about how to program the needed grid. An example image:

Related

Combine Binned barplot with lineplot

I'd like to represent two datasets on the same plot, one as a line as one as a binned barplot. I can do each individually:
tobar = pd.melt(pd.DataFrame(np.random.randn(1000).cumsum()))
tobar["bins"] = pd.qcut(tobar.index, 20)
bp = sns.barplot(data=tobar, x="bins", y="value")
toline = pd.melt(pd.DataFrame(np.random.randn(1000).cumsum()))
lp = sns.lineplot(data=toline, x=toline.index, y="value")
But when I try to combine them, of course the x axis gets messed up:
fig, ax = plt.subplots()
ax2 = ax.twinx()
bp = sns.barplot(data=tobar, x="bins", y="value", ax=ax)
lp = sns.lineplot(data=toline, x=toline.index, y="value", ax=ax2)
bp.set(xlabel=None)
I also can't seem to get rid of the bin labels.
How can I get these two informations on the one plot?
This answer explains why it's better to plot the bars with matplotlib.axes.Axes.bar instead of sns.barplot or pandas.DataFrame.bar.
In short, the xtick locations correspond to the actual numeric value of the label, whereas the xticks for seaborn and pandas are 0 indexed, and don't correspond to the numeric value.
This answer shows how to add bar labels.
ax2 = ax.twinx() can be used for the line plot if needed
Works the same if the line plot is different data.
Tested in python 3.11, pandas 1.5.2, matplotlib 3.6.2, seaborn 0.12.1
Imports and DataFrame
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
# test data
np.random.seed(2022)
df = pd.melt(pd.DataFrame(np.random.randn(1000).cumsum()))
# create the bins
df["bins"] = pd.qcut(df.index, 20)
# add a column for the mid point of the interval
df['mid'] = df.bins.apply(lambda row: row.mid.round().astype(int))
# pivot the dataframe to calculate the mean of each interval
pt = df.pivot_table(index='mid', values='value', aggfunc='mean').reset_index()
Plot 1
# create the figure
fig, ax = plt.subplots(figsize=(30, 7))
# add a horizontal line at y=0
ax.axhline(0, color='black')
# add the bar plot
ax.bar(data=pt, x='mid', height='value', width=4, alpha=0.5)
# set the labels on the xticks - if desired
ax.set_xticks(ticks=pt.mid, labels=pt.mid)
# add the intervals as labels on the bars - if desired
ax.bar_label(ax.containers[0], labels=df.bins.unique(), weight='bold')
# add the line plot
_ = sns.lineplot(data=df, x=df.index, y="value", ax=ax, color='tab:orange')
Plot 2
fig, ax = plt.subplots(figsize=(30, 7))
ax.axhline(0, color='black')
ax.bar(data=pt, x='mid', height='value', width=4, alpha=0.5)
ax.set_xticks(ticks=pt.mid, labels=df.bins.unique(), rotation=45)
ax.bar_label(ax.containers[0], weight='bold')
_ = sns.lineplot(data=df, x=df.index, y="value", ax=ax, color='tab:orange')
Plot 3
The bar width is the width of the interval
fig, ax = plt.subplots(figsize=(30, 7))
ax.axhline(0, color='black')
ax.bar(data=pt, x='mid', height='value', width=50, alpha=0.5, ec='k')
ax.set_xticks(ticks=pt.mid, labels=df.bins.unique(), rotation=45)
ax.bar_label(ax.containers[0], weight='bold')
_ = sns.lineplot(data=df, x=df.index, y="value", ax=ax, color='tab:orange')

How to set space between plot and colormap table

I am using secondary y-axis and cmap color but when I plot together the color bar cross to my plot
here is my code
fig,ax1=plt.subplots()
ax1 = df_Combine.plot.scatter('Parameter2', 'NPV (MM €)', marker='s', s=500, ylim=(-10,60), c='Lifetime1 (a)', colormap='jet_r', vmin=0, vmax=25, ax=ax1)
graph.axhline(0, color='k')
plt.xticks(rotation=90)
ax2 = ax1.twinx()
ax2.plot(df_Combine_min_select1["CumEnergy1 (kWH)"])
plt.show()
and here is my plotting
anyone can help how to solve this issue?
Thank you
When you let pandas automatically create a colorbar, you don't have positioning options. Therefore, you can create the colorbar in a separate step and provide the pad= parameter to set a wider gap. Default, pad is 0.05, meaning 5% of the width of the subplot.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
fig, ax1 = plt.subplots()
df_Combine = pd.DataFrame({'Parameter2': np.random.rand(10) * 10,
'NPV (MM €)': np.random.rand(10),
'Lifetime1 (a)': np.random.rand(10) * 25,
})
ax1 = df_Combine.plot.scatter('Parameter2', 'NPV (MM €)', marker='s', s=500, ylim=(-10, 60), c='Lifetime1 (a)',
colormap='jet_r', vmin=0, vmax=25, ax=ax1, colorbar=False)
plt.colorbar(ax1.collections[0], ax=ax1, pad=0.1)
ax2 = ax1.twinx()
ax2.plot(np.random.rand(10))
plt.show()

How to plot a paired histogram using seaborn

I would like to make a paired histogram like the one shown here using the seaborn distplot.
This kind of plot can also be referred to as the back-to-back histogram shown here, or a bihistogram inverted/mirrored along the x-axis as discussed here.
Here is my code:
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
green = np.random.normal(20,10,1000)
blue = np.random.poisson(60,1000)
fig, ax = plt.subplots(figsize=(8,6))
sns.distplot(blue, hist=True, kde=True, hist_kws={'edgecolor':'black'}, kde_kws={'linewidth':2}, bins=10, color='blue')
sns.distplot(green, hist=True, kde=True, hist_kws={'edgecolor':'black'}, kde_kws={'linewidth':2}, bins=10, color='green')
ax.set_xticks(np.arange(-20,121,20))
ax.set_yticks(np.arange(0.0,0.07,0.01))
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
plt.show()
Here is the output:
When I use the method discussed here (plt.barh), I get the bar plot shown just below, which is not what I am looking for.
Or maybe I haven't understood the workaround well enough...
A simple/short implementation of python-seaborn-distplot similar to these kinds of plots would be perfect. I edited the figure of my first plot above to show the kind of plot I hope to achieve (though y-axis not upside down):
Any leads would be greatly appreciated.
You could use two subplots and invert the y-axis of the lower one and plot with the same bins.
df = pd.DataFrame({'a': np.random.normal(0,5,1000), 'b': np.random.normal(20,5,1000)})
fig =plt.figure(figsize=(5,5))
ax = fig.add_subplot(211)
ax2 = fig.add_subplot(212)
bins = np.arange(-20,40)
ax.hist(df['a'], bins=bins)
ax2.hist(df['b'],color='orange', bins=bins)
ax2.invert_yaxis()
edit:
improvements suggested by #mwaskom
fig, axes = plt.subplots(nrows=2, ncols=1, sharex=True, figsize=(5,5))
bins = np.arange(-20,40)
for ax, column, color, invert in zip(axes.ravel(), df.columns, ['teal', 'orange'], [False,True]):
ax.hist(df[column], bins=bins, color=color)
if invert:
ax.invert_yaxis()
plt.subplots_adjust(hspace=0)
Here is a possible approach using seaborn's displots.
Seaborn doesn't return the created graphical elements, but the ax can be interrogated. To make sure the ax only contains the elements you want upside down, those elements can be drawn first. Then, all the patches (the rectangular bars) and the lines (the curve for the kde) can be given their height in negative. Optionally the x-axis can be set at y == 0 using ax.spines['bottom'].set_position('zero').
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
green = np.random.normal(20, 10, 1000)
blue = np.random.poisson(60, 1000)
fig, ax = plt.subplots(figsize=(8, 6))
sns.distplot(green, hist=True, kde=True, hist_kws={'edgecolor': 'black'}, kde_kws={'linewidth': 2}, bins=10,
color='green')
for p in ax.patches: # turn the histogram upside down
p.set_height(-p.get_height())
for l in ax.lines: # turn the kde curve upside down
l.set_ydata(-l.get_ydata())
sns.distplot(blue, hist=True, kde=True, hist_kws={'edgecolor': 'black'}, kde_kws={'linewidth': 2}, bins=10,
color='blue')
ax.set_xticks(np.arange(-20, 121, 20))
ax.set_yticks(np.arange(0.0, 0.07, 0.01))
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
pos_ticks = np.array([t for t in ax.get_yticks() if t > 0])
ticks = np.concatenate([-pos_ticks[::-1], [0], pos_ticks])
ax.set_yticks(ticks)
ax.set_yticklabels([f'{abs(t):.2f}' for t in ticks])
ax.spines['bottom'].set_position('zero')
plt.show()

Python: dynamically produce seaborn plots then display results side-by-side?

I'm trying to produce multiple seaborn kernel density plots for the numeric variables of my Pandas DataFrame. I have the names of all of my numeric columns in a list, numberCol. Presently, I can make a kdeplot for each variable that I explicitly name, like so:
import seaborn as sbn
sbn.set_style('whitegrid')
sbn.kdeplot(np.array(df.v2), bw=0.5) # for pandas.core.frame.DataFrame input
Is there a better way to iterate through the numberCol list, produce an sbn.kdeplot for each variable in numberCol, then display them side-by-side with something smarter than something like:
import matplotlib.pyplot as plt
import seaborn as sns
# Here we create a figure instance, and two subplots
fig = plt.figure(figsize = (20,20)) # width x height
ax1 = fig.add_subplot(3, 3, 1) # row, column, position
ax2 = fig.add_subplot(3, 3, 2)
ax3 = fig.add_subplot(3, 3, 3)
# We use ax parameter to tell seaborn which subplot to use for this plot
sns.heatmap(data=subset1.corr(), ax=ax1, cmap = cmap, square=True, cbar_kws={'shrink': .3}, annot=True, annot_kws={'fontsize': 12})
sns.heatmap(data=subset2.corr(), ax=ax2, cmap = cmap, square=True, cbar_kws={'shrink': .3}, annot=True, annot_kws={'fontsize': 12})
sns.heatmap(data=subset3.corr(), ax=ax3, cmap = cmap, square=True, cbar_kws={'shrink': .3}, annot=True, annot_kws={'fontsize': 12})
If I understand your question, this should do the trick
Ncols = 9
cols = ['col_{:d}'.format(i) for i in range(Ncols)]
df = pd.DataFrame(np.random.random(size=(1000,Ncols)),columns=cols)
fig, axs = plt.subplots(3,3) # adjust the geometry based on your number of columns to plot
for ax,col in zip(axs.flatten(), cols):
sns.kdeplot(df[col], ax=ax)

Arrange matplotlib subplots in skewed grid

Using matplotlib, I'd like to display multiple subplots on a grid that has a different number of columns per row, where each subplot has roughly the same size, and the subplots are arranged such that they are more or less centered, like this:
It's a fairly simple matter to create a grid that has the 2, 3, 2 pattern with gridspec, but the problem there is that gridspec, unsurprisingly, aligns them to a grid, so the plots in the rows with 2 plots in them are wider:
Here's the code to generate that:
from matplotlib import gridspec
from matplotlib import pyplot as plt
fig = plt.figure()
arrangement = (2, 3, 2)
nrows = len(arrangement)
gs = gridspec.GridSpec(nrows, 1)
ax_specs = []
for r, ncols in enumerate(arrangement):
gs_row = gridspec.GridSpecFromSubplotSpec(1, ncols, subplot_spec=gs[r])
for col in range(ncols):
ax = plt.Subplot(fig, gs_row[col])
fig.add_subplot(ax)
for i, ax in enumerate(fig.axes):
ax.text(0.5, 0.5, "Axis: {}".format(i), fontweight='bold',
va="center", ha="center")
ax.tick_params(axis='both', bottom='off', top='off', left='off',
right='off', labelbottom='off', labelleft='off')
plt.tight_layout()
I know that I can set up a bunch of subplots and tweak their arrangement by working out the geometry of it, but I think it could get a bit complicated, so I was hoping that there might be a simpler method available.
I should note that even though I'm using a (2, 3, 2) arrangement as my example, I'd like to do this for arbitrary collections, not just this one.
The idea is usually to find the common denominator between the subplots, i.e. the largest subplot that the desired grid can be composed of, and span all subplots over several of those such that the desired layout is achieved.
Here you have 3 rows and 6 columns and each subplot spans 1 row and two columns, just that the subplots in the first row span subplot positions 1/2 and 3/4, while in the second row they span positions 0/1, 2/3, 4/5.
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
gs = gridspec.GridSpec(3, 6)
ax1a = plt.subplot(gs[0, 1:3])
ax1b = plt.subplot(gs[0, 3:5])
ax2a = plt.subplot(gs[1, :2])
ax2b = plt.subplot(gs[1, 2:4])
ax2c = plt.subplot(gs[1, 4:])
ax3a = plt.subplot(gs[2, 1:3])
ax3b = plt.subplot(gs[2, 3:5])
for i, ax in enumerate(plt.gcf().axes):
ax.text(0.5, 0.5, "Axis: {}".format(i), fontweight='bold',
va="center", ha="center")
ax.tick_params(axis='both', bottom='off', top='off', left='off',
right='off', labelbottom='off', labelleft='off')
plt.tight_layout()
plt.show()

Categories