---
title: "HCL-Based Color Scales for seaborn"
---

This article shows a series of different plots and figures using the
[`seaborn`](https://seaborn.pydata.org/) graphics library using
color maps based on the _colorspace_ package.


## Scatterplot

The following example shows the number of motorbikes recorded by an automatic
traffic counting station in the Harz Mountains (Germany), an region very
popular with bikers. The number of bikes (y-axis) is plotted against the
daily mean temperature (x-axis) and colored by season using custom colors from a
modified version of the HCL-based qualitative color palette "Dark 3".

```{python seaborn_example_1}
#| warning: true
#| error: true

from colorspace import qualitative_hcl, dataset 
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns

# Loading HarzTraffic data set
df = dataset("HarzTraffic")       

# Palette (modified "Dark 3" sequential palette)
pal = qualitative_hcl("Dark 3", h1 = -150, h2 = 120)

# Creating plot
fig, ax = plt.subplots(1, 1, figsize = (8, 4))
sns.scatterplot(data = df, x = "temp", y = "bikes",
                hue="season", ax = ax,
                palette = pal.colors(4))
plt.title("Number of bikes given daily mean temperature")
plt.xlabel("temperature [deg C]")
plt.show() 
```


## Multi-group histogram

The next example shows a multi-group distribution plot (`kind = "kde"`) based
on the 'HarzTraffic' data set where the distribution of the daily maximum temperature
is shown given the prevailing season. The colors are based on a modified
sequential HCL-based palette using "Dark 3" as template (adjusting hues).

```{python seaborn_example_2}
#| warning: true
#| error: true
#| layout-ncol: 1

from colorspace import qualitative_hcl, dataset
from matplotlib import pyplot as plt
import seaborn as sns 

# Loading penguins data set
df = dataset("HarzTraffic")

# Palette to be used
pal = qualitative_hcl("Dark 3", h1 = -180, h2 = 100)

# Creating plot
g = sns.displot(data = df, x = "tempmax", hue = "season", fill = "season",   
                kind = "kde", rug = True, height = 4, aspect = 1.5,
                palette = pal.colors(4))
g.set_axis_labels("temperature [deg C]")              
g.set(title = "Distribution of daily maximum temperature given season")
plt.tight_layout()
plt.show()
```

## Heatmap

HCL-based color maps (`LinearSegmentedColormap` objects) can be used directly
for seaborn heatmaps. The plot below shows the joint density of 
the daily maximum temperature (y-axis) and daily minimum temperature (x-axis)
based on the 'HarzTraffic' data set.

The left subplot employs one of the pre-defined sequential HCL-based
color palette "YlOrRd" (yellow-orange-red) in reverse order, while the
right subplot shows the same information but using a custom sequential
HCL-based palette. Instead of reversing the palette via `rev = True`,
the `.reversed()` method on the `LinearSegmentedColormap` (cmap) is
used to get the same effect.


```{python seaborn_example_3}
#| warning: true
#| error: true

from colorspace import sequential_hcl, dataset
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np

# Loading penguins data set
df = dataset("HarzTraffic")

# New figure with 2 subplots
fig, (ax1, ax2) = plt.subplots(1, 2, figsize = (12, 6))

# Draw heatmap (left)
g1 = sns.histplot(x = "tempmin", y = "tempmax", data = df,
            bins = 25, pthresh = .1, ax = ax1,
            cmap = sequential_hcl("YlOrRd", rev = True).cmap()) 

g1.plot([-50, 50], [-50, 50], color = "black") # Adding diagonal
g1.set(xlim = [-8, 18], ylim = [-5, 32]) # Re-adjusting limits
g1.set_xlabel("daily minimum temperature [deg C]")    
g1.set_ylabel("daily maximum temperature [deg C]")                   
g1.set_title("Joint density of daily minimum and maximum\n" +
            "temperature. Sonnenberg, Harz, Germany")

# Create fully customized HCL-based sequential palette
custom_cmap = sequential_hcl(h = [330, 170], c = [45, 70, 10],
                             l = [25, 95], power = [0.5, 1.5]).cmap().reversed()

# Draw heatmap (right) with customized color map
g2 = sns.histplot(x = "tempmin", y = "tempmax", data = df,
            bins = 25, pthresh = .1, ax = ax2,
            cmap = custom_cmap)

g2.plot([-50, 50], [-50, 50], color = "black") # Adding diagonal
g2.set(xlim = [-8, 18], ylim = [-5, 32]) # Re-adjusting limits
g2.set_xlabel("daily minimum temperature [deg C]")    
g2.set_ylabel("daily maximum temperature [deg C]")                   
g2.set_title("Joint density of daily minimum and maximum\n" +
            "temperature. Sonnenberg, Harz, Germany")

plt.show()
```

## Lineplot with multiple facets

This example shows the number of bikes and cars for the
different years by day of year (`yday`; Julian day) using
a diverging HCL-based color palette.

```{python}
#| warning: true
#| error: true
#| layout-ncol: 1

from colorspace import diverging_hcl
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd

# Loading penguins data set
df = dataset("HarzTraffic")
df["year"] = pd.DatetimeIndex(df.date).year

# Reorganizing the data
a = df.loc[:, ["year", "yday", "bikes"]].rename(columns = {"bikes": "count"})
a.insert(2, "type", "bikes")
b = df.loc[:, ["year", "yday", "cars"]].rename(columns = {"cars": "count"})
b.insert(2, "type", "cars")

# Concatenate (rowwise)
df = pd.concat([a, b], axis = 0)
del a, b

# Number of colors required
N  = len(np.unique(df.year))

# Plot the lines on two facets
sns.relplot(data = df, x = "yday", y = "count",
            hue = "year", row = "type",
            kind = "line", size_order = ["cars", "bikes"],
            height = 4, aspect = 3, facet_kws = dict(sharey = False),
            palette = diverging_hcl("Green-Orange").colors(N))
plt.show()
```


## Barplot

The Harz region in Germany is well known amongst bikers which
often use their time on the weekend to go for a ride. To visualize
this, the following barplot shows the average number of bikes (with error bars)
during the summer seasons (June-August) 2021-2023 based on the "HarzTraffic" data set.
The qualitative HCL-based color palette "Harmonic" is used to colorize the different
days of week.

```{python seaborn_minimal}
#| warning: true
#| error: true
#| layout-ncol: 1

from colorspace import qualitative_hcl, dataset
import matplotlib.pyplot as plt
import seaborn as sns

# Loading data set
df = dataset("HarzTraffic")
df = df.loc[(df.season == "summer"), :]

sns.barplot(x = "dayofweek", y = "bikes", hue = "dow", data = df,
            order = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
            legend = False,
            palette = qualitative_hcl("Harmonic").colors(7))
plt.title("Average number of bikes per day of week\nsummer season 2021-2023")
plt.show()
```

As a slightly more complex example, the visualization below shows grouped
barplots with the total number of bikes, cars, trucks, and other vehicles
registered on a monthly basis between January 2021 and November 2023, using the
example data set The different vehicle types are colorized using the sequential
HCL-based color palette "Plasma". The upper plot shows original colors (no
visual constraints), the lower simulates the same colors seen by people with
deuteranope vision (green-yellow-red weakness).

```{python seaborn_barplot}
#| error: true
#| warning: true

from colorspace import sequential_hcl, dataset, deutan
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
 
# Loading penguins data set
df = dataset("MonthlyHarzTraffic")

# Reshaping the pandas DataFrame
res = []
for t in ["bikes", "cars", "trucks", "others"]:
    tmp = df.loc[:, ["year", "month", t]].rename(columns = {t: "count"})
    tmp.insert(3, "type", t)
    res.append(tmp)
df = pd.concat(res, axis = 0)
del res, tmp

# New figure
fig, (ax1, ax2) = plt.subplots(2, 1)#, figsize = (8, 6))
    
# Plotting (unconstraint and deuteranope vision)
sns.barplot(x = "month", y = "count", hue = "type", data = df, ax = ax1,
            palette = sequential_hcl("Plasma")(4))
sns.barplot(x = "month", y = "count", hue = "type", data = df, ax = ax2,
            palette = deutan(sequential_hcl("Plasma")(4)))

# Setting title, adjusting legend
ax1.set_title("Total number of vehicles, 2021-2023")
ax1.set_xlabel(None)
sns.move_legend(ax1, "upper left", ncol = 4, title = None, frameon = False)
sns.move_legend(ax2, "upper left", ncol = 4, title = None, frameon = False)
plt.show()
```





