Ridgeline Plots: Visualizing Data Distributions with Joy in Python

As artificial intelligence (AI) and machine learning (ML) experts, we often find ourselves swimming in a sea of complex, high-dimensional data. Gaining insight into the distributions and relationships within this data is crucial for developing effective models and making informed decisions. While traditional plotting techniques like histograms and scatter plots have their place, sometimes we need a visualization that can elegantly compare multiple distributions at once. Enter the ridgeline plot – a delightful way to stack distribution plots and reveal patterns in your data.

In this deep dive, we‘ll explore the history and mathematical foundations of ridgeline plots, walk through examples of creating them in Python, and showcase their application to real-world AI/ML scenarios. By the end, you‘ll appreciate the joy that ridgeline plots can bring to your data visualizations.

A Brief History of Joy

Ridgeline plots, also known as "joy plots," have a whimsical origin. The term "joy plot" was coined by the data scientist Claus O. Wilke in reference to the iconic cover art of the album "Unknown Pleasures" by the post-punk band Joy Division. The album cover features a series of stacked lines resembling a mountain range, which inspired the overlapping design of ridgeline plots.

Unknown Pleasures album cover

Despite their playful name, ridgeline plots have a serious purpose in data visualization. They excel at comparing multiple distributions in a compact and visually engaging format. This makes them invaluable for identifying shifts, outliers, and patterns across categories or groups.

Ridgeline plots build upon the concept of kernel density estimation (KDE), a non-parametric way to estimate the probability density function of a random variable. In a ridgeline plot, each "ridge" represents the KDE of a different category or group, stacked vertically with some overlap. This overlapping design allows for easy comparison of the distributions‘ shapes, central tendencies, and spreads.

Constructing Ridgeline Plots

To understand how ridgeline plots are constructed, let‘s briefly review the mathematics of KDE. Given a set of data points {x₁, x₂, …, xₙ}, the kernel density estimator for a point x is given by:

$$ \hat{f}h(x) = \frac{1}{n} \sum{i=1}^n K_h(x – xi) = \frac{1}{nh} \sum{i=1}^n K(\frac{x – x_i}{h}) $$

where K is the kernel function (e.g., Gaussian), and h is the bandwidth parameter controlling the smoothness of the estimation.

In a ridgeline plot, we compute the KDE for each category or group separately and plot them as overlapping curves. The vertical position of each curve is typically determined by the rank of the category, with the first category at the bottom and the last category at the top.

Here‘s a step-by-step process for constructing a ridgeline plot:

  1. Compute the KDE for each category or group in your data
  2. Determine the vertical position of each KDE curve based on the category rank
  3. Plot the KDE curves as filled polygons with some transparency (alpha)
  4. Customize the plot with appropriate labels, colors, and other styling options

Creating Ridgeline Plots in Python

Now that we understand the basic construction process, let‘s see how to create ridgeline plots in Python using the Seaborn and Joypy libraries. We‘ll use a real-world dataset of penguin measurements to demonstrate.

Dataset

We‘ll use the "penguins" dataset from the Seaborn library, which contains measurements of penguins from three different species: Adelie, Chinstrap, and Gentoo. Our goal is to visualize the distribution of flipper lengths for each species.

First, let‘s load the necessary libraries and the dataset:

import seaborn as sns
import joypy
import matplotlib.pyplot as plt

penguins = sns.load_dataset(‘penguins‘)

Using Seaborn

Seaborn provides a flexible interface for creating ridgeline plots using the sns.FacetGrid class and the kdeplot function. Here‘s an example:

g = sns.FacetGrid(penguins, 
                  row=‘species‘, 
                  hue=‘species‘, 
                  aspect=3, 
                  height=1.5)

g.map(sns.kdeplot, ‘flipper_length_mm‘, 
      bw_adjust=0.7,
      clip_on=False,
      fill=True, 
      alpha=0.6)

g.map(sns.kdeplot, ‘flipper_length_mm‘,
      bw_adjust=0.7,
      clip_on=False, 
      color=‘black‘)

g.map(plt.axhline, y=0, 
      lw=2, 
      clip_on=False)

g.fig.subplots_adjust(hspace=-0.6)
g.set_titles(row_template=‘{row_name}‘)
g.set(yticks=[], xlabel=‘Flipper Length (mm)‘)
g.despine(bottom=True, left=True)
plt.show()

Seaborn ridgeline plot of penguin flipper lengths

In this code, we create a FacetGrid with the ‘species‘ variable as the row parameter. We then map the kdeplot function to each subset of data to plot the KDE curves. The bw_adjust parameter controls the bandwidth, fill fills the area under the curve, and alpha sets the transparency.

To achieve the overlapping effect, we adjust the vertical spacing between subplots using subplots_adjust. We also add horizontal lines at y=0 for visual separation. Finally, we customize the plot with appropriate titles, labels, and despine the unnecessary axes.

Using JoyPy

The JoyPy library provides a high-level interface specifically designed for creating ridgeline plots. It offers a range of customization options and a simpler syntax compared to Seaborn.

joypy.joyplot(penguins, 
              by=‘species‘, 
              column=‘flipper_length_mm‘, 
              figsize=(6, 4),
              linewidth=0.5,
              colormap=plt.cm.viridis,
              alpha=0.8,
              legend=True,
              overlap=1.5,
              fade=True)

plt.title(‘Penguin Flipper Length Distribution by Species‘)
plt.show()

JoyPy ridgeline plot of penguin flipper lengths

With JoyPy, we simply pass the DataFrame, specify the categorical variable with by, and the numeric variable with column. We can easily customize the plot using parameters like figsize, linewidth, colormap, alpha, legend, overlap, and fade.

Applications in AI and Machine Learning

Ridgeline plots are not just a pretty face – they have practical applications in various AI and ML scenarios. Here are a few examples:

Comparing Model Performance Metrics

When training ML models, we often need to compare performance metrics like accuracy, precision, recall, or F1 score across different hyperparameter settings, feature sets, or algorithms. Ridgeline plots provide a compact way to visualize these comparisons.

For example, let‘s say we trained a series of Support Vector Machines (SVMs) with different kernel functions and regularization parameters on the iris flower dataset. We can use a ridgeline plot to compare the distribution of F1 scores:

import numpy as np
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.svm import SVC
from sklearn.metrics import f1_score

iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target, random_state=42)

param_grid = {‘C‘: [0.1, 1, 10], 
              ‘kernel‘: [‘linear‘, ‘poly‘, ‘rbf‘, ‘sigmoid‘]}

grid_search = GridSearchCV(SVC(), param_grid, cv=5, scoring=‘f1_macro‘)
grid_search.fit(X_train, y_train)

results_df = pd.DataFrame(grid_search.cv_results_)

plt.figure(figsize=(8, 5))
joypy.joyplot(results_df, 
              by=‘param_kernel‘, 
              column=‘mean_test_score‘, 
              colormap=plt.cm.plasma,
              figsize=(8, 5),
              linewidth=0.8,
              legend=True,
              overlap=2,
              fade=True)

plt.title(‘Distribution of F1 Scores by SVM Kernel‘)
plt.show()

Ridgeline plot comparing SVM F1 scores

From this plot, we can easily see that the ‘rbf‘ and ‘poly‘ kernels tend to yield higher and more consistent F1 scores compared to ‘linear‘ and ‘sigmoid‘ kernels.

Visualizing Neural Network Activations

In deep learning, understanding the distribution of activations in different layers of a neural network can provide insights into the learning process and help identify issues like vanishing or exploding gradients.

Here‘s an example of using ridgeline plots to visualize the activations of a simple feedforward neural network on the MNIST handwritten digits dataset:

import tensorflow as tf
from tensorflow import keras

(X_train, y_train), (X_test, y_test) = keras.datasets.mnist.load_data()
X_train = X_train.reshape((60000, 28 * 28)) / 255.0
X_test = X_test.reshape((10000, 28 * 28)) / 255.0

model = keras.Sequential([
    keras.layers.Dense(128, activation=‘relu‘, input_shape=(28 * 28,)),
    keras.layers.Dense(64, activation=‘relu‘),
    keras.layers.Dense(10, activation=‘softmax‘)
])

model.compile(optimizer=‘adam‘,
              loss=‘sparse_categorical_crossentropy‘,
              metrics=[‘accuracy‘])

history = model.fit(X_train, y_train, 
                    validation_data=(X_test, y_test),
                    batch_size=128, 
                    epochs=5)

activations = []
for layer in model.layers:
    if ‘dense‘ in layer.name:
        activations.append(keras.Model(inputs=model.input, outputs=layer.output)(X_test))

activation_dfs = [pd.DataFrame(a, columns=[f‘neuron_{i}‘ for i in range(a.shape[1])]) 
                  for a in activations]

plt.figure(figsize=(10, 8))
for i, df in enumerate(activation_dfs, start=1):
    joypy.joyplot(df, 
                  figsize=(10, 2),
                  title=f‘Layer {i} Activations‘,
                  colormap=plt.cm.hot,
                  linewidth=0.5,
                  overlap=1,
                  fade=True)
    plt.xlim(-0.1, 1.1)
    plt.show()

Ridgeline plots of neural network activations

These plots reveal the distribution of activations in each dense layer of the network. We can see that the activations become more sparse and localized as we move deeper into the network, indicating that the network is learning to represent the input digits with more specialized neurons.

Limitations and Best Practices

While ridgeline plots are a powerful tool, they do have some limitations and best practices to keep in mind:

  1. Overplotting: When dealing with a large number of categories or groups, ridgeline plots can become cluttered and difficult to interpret. In these cases, consider grouping or filtering the categories, or using alternative visualization techniques like heatmaps or small multiples.

  2. Normalizing Distributions: If the distributions being compared have very different scales or ranges, it can be helpful to normalize them before plotting. This can be done by scaling each distribution to have zero mean and unit variance, or by using a logarithmic transformation.

  3. Choosing Appropriate Bandwidths: The bandwidth parameter in KDE controls the smoothness of the estimated density curves. Choosing an appropriate bandwidth is important for accurately representing the underlying distribution. Various methods exist for selecting bandwidths, such as Silverman‘s rule of thumb or cross-validation.

  4. Considering Alternative Plots: While ridgeline plots excel at comparing multiple distributions, they may not always be the best choice for a given data set or question. Other techniques like violin plots, box plots, or stacked histograms may be more appropriate depending on the nature of the data and the desired insights.

Conclusion

Ridgeline plots offer a visually engaging and informative way to compare multiple distributions in a single figure. By stacking KDE curves with some overlap, they enable us to identify patterns, shifts, and outliers across categories or groups.

As AI and ML experts, we can leverage ridgeline plots to gain insights into model performance, neural network activations, hyperparameter comparisons, and more. The Python data visualization ecosystem, with libraries like Seaborn and JoyPy, makes it easy to create customized ridgeline plots that bring joy and clarity to our data.

However, it‘s important to use ridgeline plots judiciously and to consider their limitations and best practices. With the right approach, ridgeline plots can be a valuable addition to any data scientist‘s visualization toolbox.

So go forth and bring some joy to your data! Your distributions will thank you.

References

  • Wilke, C.O. (2018). Fundamentals of Data Visualization. O‘Reilly Media.
  • Waskom, M. (2021). seaborn: statistical data visualization. Journal of Open Source Software, 6(60), 3021.
  • Holtz, Y. (2018). Joypy: Visualize Distributions with Ridgeline Plots. https://github.com/sbebo/joypy
  • Joy Division. (1979). Unknown Pleasures [Album cover]. Factory Records.

How useful was this post?

Click on a star to rate it!

Average rating 0 / 5. Vote count: 0

No votes so far! Be the first to rate this post.

Similar Posts