FacetGrid with contour plots

To find the best hyperparameters for a Support Vector Regression i did a grid search with a DataFrame as result like:

svr__kernel svr__C  svr__epsilon   mae  
rbf         0.01    0.1            19.80    
linear      0.01    0.1            19.00    
poly2       0.01    0.1            19.72    
rbf         0.01    0.2            19.76
..          ..      ..             ..

To visualize the results i created a contour plot for one kernel.

fig, ax = plt.subplots(figsize=(15,7))

plot_df = df[df.svr__kernel == "poly2"].copy()
C = plot_df["svr__C"]
epsilon = plot_df["svr__epsilon"]
score = plot_df["mae"]

# Plotting all evaluations:
ax.plot(C, epsilon, "ko", ms=1)
# Create a contour plot
cntr = ax.tricontourf(C, epsilon, score, levels=12, cmap="RdBu_r")
# Adjusting the colorbar
fig.colorbar(cntr, ax=ax, label="MAE")
# Adjusting the axis limits
ax.set(
    xlim=(min(C), max(C)),
    ylim=(min(epsilon), max(epsilon)),
    xlabel="C",
    ylabel="Epsilon",
)
ax.set_title("SVR performance landscape")

Contour Plor for SVR Hyperparametertuning

Now i would like to have a FacetGrid with contour plots from every kernel and the same colorbar for the mae value. Unfortunately i have serious problems understanding the proceeding of FacetGrids.

Answer

Answer

If you have a dataframe like this:

kernels = ['rbf', 'linear', 'poly2']
c_size = 10
eps_size = 10
df = pd.DataFrame({'svr__kernel': np.repeat(kernels, c_size*eps_size),
                   'svr__C': len(kernels)*eps_size*list(np.linspace(0, 5, c_size)),
                   'svr__epsilon': len(kernels)*list(np.repeat(np.linspace(0.1, 1, eps_size), c_size))})
df['mae'] = 15 + 10*np.random.random(len(df))
    svr__kernel    svr__C  svr__epsilon        mae
0           rbf  0.000000           0.1  18.745401
1           rbf  0.555556           0.1  24.507143
2           rbf  1.111111           0.1  22.319939
3           rbf  1.666667           0.1  20.986585
4           rbf  2.222222           0.1  16.560186
..          ...       ...           ...        ...
295       poly2  2.777778           1.0  20.222433
296       poly2  3.333333           1.0  22.699936
297       poly2  3.888889           1.0  17.158210
298       poly2  4.444444           1.0  21.228905
299       poly2  5.000000           1.0  15.853475

You can set up the seaborn.FacetGrid with:

overall_min = df['mae'].min()
overall_max = df['mae'].max()
cmap = RdBu_r
levels = 12

g = sns.FacetGrid(df, col = 'svr__kernel')
g.map(plt.tricontourf, 'svr__C', 'svr__epsilon', 'mae', levels = levels, cmap = cmap, vmin = overall_min, vmax = overall_max)

Then you can move to the left the last plot in order to make some space for the colorbar, add an axis and draw the colormap on it:

g.fig.subplots_adjust(right = 0.88)
cbar_ax = g.fig.add_axes([0.9, 0.1, 0.03, 0.8])
norm = BoundaryNorm(np.linspace(overall_min, overall_max, levels), cmap.N)
plt.colorbar(ScalarMappable(norm = norm, cmap = cmap), cax = cbar_ax)

Complete Code

In this example I added 3 to 'mae' column with add_3_to_poly2 function only for 'poly2' kernel, with the aim of doing a check on colorbar values with respect to contourf levels.

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
from matplotlib.cm import ScalarMappable, RdBu_r
from matplotlib.colors import BoundaryNorm


def add_3_to_poly2(df):
    if df['svr__kernel'] == 'poly2':
        return df['mae'] + 3
    else:
        return df['mae']


kernels = ['rbf', 'linear', 'poly2']
c_size = 10
eps_size = 10
df = pd.DataFrame({'svr__kernel': np.repeat(kernels, c_size*eps_size),
                   'svr__C': len(kernels)*eps_size*list(np.linspace(0, 5, c_size)),
                   'svr__epsilon': len(kernels)*list(np.repeat(np.linspace(0.1, 1, eps_size), c_size))})
df['mae'] = 15 + 10*np.random.random(len(df))
df['mae'] = df.apply(add_3_to_poly2, axis = 1)


overall_min = df['mae'].min()
overall_max = df['mae'].max()
cmap = RdBu_r
levels = 12

g = sns.FacetGrid(df, col = 'svr__kernel')
g.map(plt.tricontourf, 'svr__C', 'svr__epsilon', 'mae', levels = levels, cmap = cmap, vmin = overall_min, vmax = overall_max)

g.fig.subplots_adjust(right = 0.88)
cbar_ax = g.fig.add_axes([0.9, 0.1, 0.03, 0.8])
norm = BoundaryNorm(np.linspace(overall_min, overall_max, levels), cmap.N)
plt.colorbar(ScalarMappable(norm = norm, cmap = cmap), cax = cbar_ax)

plt.show()

Plot

enter image description here

Notes

As you can see, 'poly2' contourf values are actually greater than other ones and colorbar correctly keeps track of all values, from overall minimum to overall maximum.