Normalizing height / mode of kdeplot to be 1

587 Views Asked by At

I am using the FacetGrid example from seaborn [Overlapping densities (‘ridge plot’)]. However, instead of normalizing the integral of the kdeplot, I want to normalize the heights. Does anyone have an idea, how to realize it?

import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
sns.set_theme(style="white", rc={"axes.facecolor": (0, 0, 0, 0)})

# Create the data
rs = np.random.RandomState(1979)
x = rs.randn(500)
g = np.tile(list("ABCDEFGHIJ"), 50)
df = pd.DataFrame(dict(x=x, g=g))
m = df.g.map(ord)
df["x"] += m

# Initialize the FacetGrid object
pal = sns.cubehelix_palette(10, rot=-.25, light=.7)
g = sns.FacetGrid(df, row="g", hue="g", aspect=15, height=.5, palette=pal)

# Draw the densities in a few steps
g.map(sns.kdeplot, "x",
      bw_adjust=.5, clip_on=False,
      fill=True, alpha=1, linewidth=1.5)
g.map(sns.kdeplot, "x", clip_on=False, color="w", lw=2, bw_adjust=.5)

# passing color=None to refline() uses the hue mapping
g.refline(y=0, linewidth=2, linestyle="-", color=None, clip_on=False)


# Define and use a simple function to label the plot in axes coordinates
def label(x, color, label):
    ax = plt.gca()
    ax.text(0, .2, label, fontweight="bold", color=color,
            ha="left", va="center", transform=ax.transAxes)


g.map(label, "x")

# Set the subplots to overlap
g.figure.subplots_adjust(hspace=-.25)

# Remove axes details that don't play well with overlap
g.set_titles("")
g.set(yticks=[], ylabel="")
g.despine(bottom=True, left=True)

So far, I have done some search engine requests where I tried to find something comparable that has been performed for histplot from matplotlib. However, I have found only solutions for the normalization of the integral.

1

There are 1 best solutions below

0
On

For just one kdeplot -

A method normalize() to normalize the values -

def normalize(arr, t_min, t_max):
    norm_arr = []
    diff = t_max - t_min
    diff_arr = max(arr) - min(arr)   
    for i in arr:
        temp = (((i - min(arr))*diff)/diff_arr) + t_min
        norm_arr.append(temp)
    return norm_arr

If fill=False

tips = sns.load_dataset("tips")
ax = sns.kdeplot(data=tips, x="total_bill")
line = ax.lines[0]
line.set_ydata(normalize(line.get_ydata(),0,1))
ax.set_ylim(0,1.05)        
ax.autoscale_view()

enter image description here

If fill=True

tips = sns.load_dataset("tips")
ax = sns.kdeplot(data=tips, x="total_bill",fill=True)
path = ax.collections[0].get_paths()
ys = normalize(path[0].vertices[:, 1],0,1)
path[0].vertices[:, 1] = ys
ax.set_ylim(0,1.05)        
ax.autoscale_view()

enter image description here

Now if you want to use a FacetGrid then, probably all your problems can be solved just by using sharey=True like -

g = sns.FacetGrid(df, row="g", hue="g", aspect=15, height=.5, palette=pal, sharey=True)

But still if you need to normalize then-

define a wrapper function -

def kdeplot(data, **kwargs):
    ax = sns.kdeplot(data, **kwargs)
    if 'fill' in kwargs.keys() and kwargs['fill']==True:
        path = ax.collections[0].get_paths()
        ys = normalize(path[0].vertices[:, 1],0,1)
        path[0].vertices[:, 1] = ys
    else:
        line = ax.lines[0]
        line.set_ydata(normalize(line.get_ydata(),0,1))
    ax.set_ylim(0,1.05)       
    ax.autoscale_view()

then -

tips = sns.load_dataset("tips")
ax = kdeplot(data=tips, x="total_bill",fill=True)
ax = kdeplot(data=tips, x="total_bill",fill=False, lw=4)

enter image description here

Now you can just use kdeplot instead of sns.kdeplot -

g.map(kdeplot, "x",bw_adjust=.5, clip_on=False,
      fill=True, alpha=1, linewidth=1.5)
g.map(kdeplot, "x", clip_on=False, color="w", lw=2, bw_adjust=.5)