seaborn
This guide defines best practices for using Seaborn in Python for data visualization, emphasizing modern, reproducible, and performant approaches in AI/ML pipelines.
The skill
# seaborn Best Practices
Seaborn is the definitive library for statistical data visualization in Python. This guide outlines our team's mandatory best practices to ensure consistent, reproducible, and high-quality plots in all AI/ML projects.
## 1. Code Organization & Project-Wide Styling
Always configure Seaborn's global style settings once per project. Centralize this in a dedicated `styles.py` module and import it. This guarantees a uniform visual language across all outputs.
❌ BAD: Ad-hoc styling in every script/notebook
```python
# my_script.py
import seaborn as sns
import matplotlib.pyplot as plt
sns.set_theme(style="darkgrid", palette="viridis") # In every file!
# ... plotting code
```
✅ GOOD: Centralized and imported styling
```python
# styles.py
import seaborn as sns
import matplotlib.pyplot as plt
def apply_seaborn_defaults():
"""Applies project-wide Seaborn and Matplotlib styling."""
sns.set_theme(
context="talk", # Readable text for presentations/reports
style="whitegrid", # Clean background with light grid
palette="deep" # Perceptually uniform, colorblind-friendly
)
plt.rcParams["figure.figsize"] = (8, 5) # Consistent figure size
plt.rcParams["figure.dpi"] = 150 # On-screen clarity
# my_script.py
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd
from .styles import apply_seaborn_defaults # Adjust import path as needed
apply_seaborn_defaults()
# ... plotting code
```
## 2. Prefer Figure-Level Functions
For complex layouts, especially with faceting, always use Seaborn's figure-level functions (`relplot`, `catplot`, `pairplot`, `lmplot`). They handle figure creation, axis management, and legends automatically, ensuring consistency.
❌ BAD: Manual `subplots` with axes-level functions for faceting
```python
# Hard to manage multiple subplots, shared axes, and legends manually
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
sns.scatterplot(data=df[df['category'] == 'A'], x='x', y='y', ax=axes[0])
sns.scatterplot(data=df[df['category'] == 'B'], x='x', y='y', ax=axes[1])
plt.tight_layout()
plt.show()
```
✅ GOOD: `relplot` for automatic faceting and consistent sizing
```python
import pandas as pd
import numpy as np
# Example data
data = {
'x': np.random.rand(100),
'y': np.random.rand(100) + np.random.rand(100),
'category': np.random.choice(['A', 'B'], 100),
'group': np.random.choice(['X', 'Y'], 100)
}
df = pd.DataFrame(data)
g = sns.relplot(
data=df, x='x', y='y',
col='category', # Facet by category
hue='group', # Color by group
kind='scatter',
height=4, aspect=1.2
)
g.set_axis_labels("Feature X", "Feature Y")
g.set_titles("Category: {col_name}")
plt.tight_layout()
plt.show()
```
## 3. Data Format: Long-Form is King
Seaborn thrives on "tidy" or "long-form" data. Ensure your Pandas DataFrames are structured with one observation per row and variables as columns. This allows Seaborn to infer groupings automatically, reducing boilerplate.
❌ BAD: Wide-form data requiring `melt` or manual iteration
```python
# Data where 'value_A' and 'value_B' are separate columns for the same metric
wide_df = pd.DataFrame({
'id': [1, 2], 'value_A': [10, 20], 'value_B': [15, 25]
})
# To plot, you'd need:
melted_df = wide_df.melt(id_vars=['id'], var_name='metric', value_name='value')
sns.barplot(data=melted_df, x='metric', y='value')
plt.show() # Added plt.show()
```
✅ GOOD: Data already in long-form
```python
long_df = pd.DataFrame({
'id': [1, 2, 1, 2],
'metric': ['value_A', 'value_A', 'value_B', 'value_B'],
'value': [10, 20, 15, 25]
})
sns.barplot(data=long_df, x='metric', y='value')
plt.show()
```
## 4. Performance for Large Datasets
For datasets with millions of rows, plotting every point can be slow and create over-plotting. Optimize for clarity and speed.
* **Down-sample**: Plot a representative subset for initial exploration.
* **Binning/Aggregation**: Summarize data before plotting (e.g., `histplot` with `bins`, `kdeplot`, `lineplot` on aggregated data).
* **Limit `hue`**: Excessive `hue` categories lead to cluttered legends and slow rendering.
* **Efficient Data Loading**: Use `polars` or `duckdb` for faster initial data processing if `pandas` is a bottleneck.
```python
# Assume large_df is a very large DataFrame
# ✅ GOOD: Down-sampling for quick exploration
sampled_df = large_df.sample(n=100_000, random_state=42)
sns.scatterplot(data=sampled_df, x='feature1', y='feature2', alpha=0.5)
plt.show()
# ✅ GOOD: Aggregating for trends
daily_avg = large_df.groupby('date')['value'].mean().reset_index()
sns.lineplot(data=daily_avg, x='date', y='value')
plt.show()
```
## 5. Type Hints for Clarity
Always use explicit type hints for functions that generate plots. This improves code readability, maintainability, and enables static analysis.
```python
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from typing import Optional, Union
def plot_feature_distribution(
df: pd.DataFrame,
feature_col: str,
hue_col: Optional[str] = None,
title: str = "Feature Distribution"
) -> plt.Axes:
"""
Generates a histogram and KDE plot for a given feature.
Args:
df: Input DataFrame.
feature_col: Name of the column to plot.
hue_col: Optional column for grouping.
title: Plot title.
Returns:
The Matplotlib Axes object.
"""
fig, ax = plt.subplots(figsize=(10, 6))
sns.histplot(data=df, x=feature_col, hue=hue_col, kde=True, ax=ax)
ax.set_title(title)
plt.tight_layout()
return ax
# Example usage
tips = sns.load_dataset("tips")
plot_feature_distribution(tips, "total_bill", hue_col="sex", title="Total Bill Distribution by Sex")
plt.show()
```
## 6. Reproducibility: Virtual Environments & Packaging
Ensure full reproducibility of your plotting environment.
* **Virtual Environments**: Always use `venv` or `conda` environments.
* **`requirements.txt`**: Pin *all* dependencies (e.g., `seaborn`, `matplotlib`, `pandas`, `numpy`) to exact versions using `pip freeze > requirements.txt`.
```bash
# In your project root
python -m venv .venv
source .venv/bin/activate
pip install seaborn==0.13.2 matplotlib==3.8.2 pandas==2.1.4 numpy==1.26.2
pip freeze > requirements.txt
```
## 7. Testing: Visual Regression
For critical visualizations in production pipelines, implement visual regression tests. Compare hashes of generated PNGs or use a dedicated library (e.g., `pytest-mpl`) to detect unintended aesthetic changes caused by library updates or code modifications.
```python
# Example (conceptual)
# test_plots.py
import hashlib
import os
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd
from your_project.styles import apply_seaborn_defaults # Adjust import
def generate_plot_image(filename: str):
apply_seaborn_defaults()
df = sns.load_dataset("iris")
sns.scatterplot(data=df, x="sepal_length", y="sepal_width", hue="species")
plt.savefig(filename, bbox_inches="tight")
plt.close() # Crucial to close figures after saving
def test_iris_scatter_plot_regression():
expected_image_path = "tests/baseline_iris_scatter.png"
generated_image_path = "tests/temp_iris_scatter.png"
generate_plot_image(generated_image_path)
with open(expected_image_path, "rb") as f_expected:
expected_hash = hashlib.md5(f_expected.read()).hexdigest()
with open(generated_image_path, "rb") as f_generated:
generated_hash = hashlib.md5(f_generated.read()).hexdigest()
assert expected_hash == generated_hash, "Iris scatter plot has changed visually!"
os.remove(generated_image_path) # Clean up
```
## 8. Documentation: Clear Docstrings
Document every plotting function with a clear docstring. Describe the data source, plot type, purpose, statistical assumptions, parameters, return values, and any side effects (e.g., `plt.show()`). This makes code audit-friendly and maintainable.
```python
# See example in Section 5 for a well-documented plotting function.
```
How to install it
1. Open the tool and start a new conversation, project or template. 2. Paste the text below wherever the tool keeps standing instructions — usually called system prompt, instructions, or persona. 3. Save it so it applies to every run rather than a single message.