Python for Data Analytics: Phase 2 Training¶

Notebook 2.3: Data Visualization using Python -- Seaborn¶

Python Seaborn Pandas Jupyter License: MIT Author


Author: Prakash Ukhalkar
Role: Assistant Professor (MCA) | Researcher in Data Science and Machine Learning

Training Date: Monday, 06th July 2026
Notebook Scope: Seaborn for statistical visualization -- highlighting trends, distributions, and visual anomalies in production data.


Phase 2 -- Data Visualization using Python (Seaborn)¶

Learning Objectives

  • Understand how Seaborn relates to Matplotlib (built on top of it)
  • Use histplot, kdeplot, boxplot, violinplot, scatterplot, and heatmap
  • Map a third variable to colour using Seaborn's hue parameter
  • Highlight trends and detect visual anomalies in manufacturing data
  • Combine Seaborn and Matplotlib annotations on the same Axes

Training Date: Monday, 06th July 2026
Estimated Duration: 60 minutes
Prerequisites: Notebook 2.2 completed (Matplotlib Visualization)


Environment Setup¶

Concept and Code Explanation (Before Use)

Aspect Matplotlib Seaborn
Default style Plain white Polished themes (whitegrid, darkgrid)
Working with DataFrames Pass arrays manually Pass DataFrame + column names as strings
Statistical charts Manual calculation needed histplot with KDE, boxplot built in
Hue / group mapping Manual colour loops hue= -- one argument, automatic legend

sns.set_theme(style="whitegrid", palette="tab10") sets background style and colour palette for the entire session.

In [1]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

%matplotlib inline

sns.set_theme(style="whitegrid", palette="tab10")
plt.rcParams["figure.figsize"] = (10, 5)
plt.rcParams["figure.dpi"]     = 100
np.random.seed(42)

print("Seaborn version:", sns.__version__)
print("Visualization environment ready.")
Seaborn version: 0.13.2
Visualization environment ready.

Result Interpretation: Environment Setup¶

  • Seaborn version confirms installation is active.
  • sns.set_theme() affects all subsequent charts in this session -- call it once at the top.
  • If seaborn is not found, run pip install seaborn and restart the kernel.

Shared Dataset¶

Concept and Code Explanation (Before Use)

The dataset for this notebook extends the production simulation to 90 days across 4 machines.
An engineered anomaly (a period with elevated defects on machine M-103) is deliberately embedded.
Detecting this anomaly visually is the recurring challenge across all charts in this notebook.

In [2]:
n = 90
machines = ["M-101", "M-102", "M-103", "M-104"]
dates = pd.date_range(start="2026-07-01", periods=n, freq="B")

np.random.seed(42)
df = pd.DataFrame({
    "date"      : dates,
    "machine_id": np.random.choice(machines, size=n),
    "units"     : np.random.normal(500, 22, size=n).round(0).astype(int),
    "defects"   : np.random.poisson(lam=3, size=n),
    "temp_c"    : np.random.normal(72, 2.5, size=n).round(1),
})

# Engineered anomaly: M-103 rows in positions 5-20 get elevated defects
m103_idx = df[df["machine_id"] == "M-103"].index[5:20]
df.loc[m103_idx, "defects"] += np.random.randint(5, 12, size=len(m103_idx))

df["yield_pct"] = (
    (df["units"] - df["defects"]) / df["units"] * 100
).round(2)

print(f"Dataset shape: {df.shape}")
display(df.head(8))
Dataset shape: (90, 6)
date machine_id units defects temp_c yield_pct
0 2026-07-01 M-103 472 3 67.7 99.36
1 2026-07-02 M-104 524 3 75.4 99.43
2 2026-07-03 M-101 561 2 71.7 99.64
3 2026-07-06 M-103 526 3 75.1 99.43
4 2026-07-07 M-103 505 3 68.0 99.41
5 2026-07-08 M-104 519 1 70.5 99.81
6 2026-07-09 M-101 478 6 72.0 98.74
7 2026-07-10 M-101 465 1 72.1 99.78

Result Interpretation: Shared Dataset¶

  • 90 business days across 4 machines gives each machine approximately 22-23 observations.
  • The anomaly adds 5-12 extra defects to 15 M-103 observations. A good visualization should reveal this.
  • yield_pct is computed consistently with Notebook 2.1.

Chart 1 -- histplot with KDE: Defect Count Distribution¶

Concept and Code Explanation (Before Use)

sns.histplot() improvements over Matplotlib's hist():

  • kde=True overlays a Kernel Density Estimate -- a smooth probability shape curve.
  • hue= automatically plots separate histograms per group with distinct colours and a legend.

KDE shows whether the distribution has:

  • A single peak (unimodal) -- one dominant operating mode
  • Two peaks (bimodal) -- two distinct operating conditions
  • A long right tail -- occasional extreme defect events
In [3]:
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# Left: overall distribution
sns.histplot(data=df, x="defects", bins=18, kde=True, color="steelblue", ax=axes[0])
axes[0].set_title("Overall Defect Count Distribution", fontsize=12, fontweight="bold")
axes[0].set_xlabel("Defects per Day")
axes[0].set_ylabel("Frequency")

# Right: distribution per machine
sns.histplot(data=df, x="defects", hue="machine_id", bins=16, kde=True, alpha=0.4, ax=axes[1])
axes[1].set_title("Defect Distribution per Machine", fontsize=12, fontweight="bold")
axes[1].set_xlabel("Defects per Day")
axes[1].set_ylabel("Frequency")

plt.suptitle("Seaborn histplot -- Defect Count Analysis", fontsize=13, y=1.02)
plt.tight_layout()
plt.savefig("sns_chart1_histplot.png", dpi=150, bbox_inches="tight")
plt.show()
No description has been provided for this image

Result Interpretation: histplot with KDE¶

  • The overall distribution should show a right-skewed profile -- M-103's anomaly creates a long right tail.
  • The per-machine distribution should show M-103's histogram shifted to the right.
  • KDE curves make shape comparison across machines easier than reading raw bar heights.

Chart 2 -- boxplot and violinplot: Group Comparison¶

Concept and Code Explanation (Before Use)

Box plot -- shows five summary statistics:

  • Horizontal line inside = median
  • Box edges = 25th and 75th percentiles (IQR)
  • Whiskers = 1.5 x IQR
  • Dots beyond whiskers = outliers

Violin plot -- extends the box plot by adding the full KDE shape on both sides.

Use box plots for quick comparison in presentations.
Use violin plots for deeper distributional insights during analysis.

In [5]:
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# Box plot
sns.boxplot(data=df, x="machine_id", y="yield_pct",
            hue="machine_id", palette="tab10", legend=False, linewidth=1.2, ax=axes[0])
axes[0].axhline(99.0, color="red", linestyle="--", linewidth=1.2, label="Target 99%")
axes[0].set_title("Yield Rate by Machine -- Box Plot", fontsize=12, fontweight="bold")
axes[0].set_xlabel("Machine")
axes[0].set_ylabel("Yield Rate (%)")
axes[0].legend(fontsize=9)

# Violin plot
sns.violinplot(data=df, x="machine_id", y="yield_pct",
               hue="machine_id", palette="tab10", legend=False, inner="quartile", ax=axes[1])
axes[1].axhline(99.0, color="red", linestyle="--", linewidth=1.2, label="Target 99%")
axes[1].set_title("Yield Rate by Machine -- Violin Plot", fontsize=12, fontweight="bold")
axes[1].set_xlabel("Machine")
axes[1].set_ylabel("Yield Rate (%)")
axes[1].legend(fontsize=9)

plt.suptitle("Box Plot vs Violin Plot -- Machine Yield Comparison", fontsize=13, y=1.02)
plt.tight_layout()
plt.savefig("sns_chart2_box_violin.png", dpi=150, bbox_inches="tight")
plt.show()
No description has been provided for this image

Result Interpretation: Box Plot vs Violin Plot¶

  • M-103's box should appear lower on the y-axis (lower yield) with a longer lower whisker.
  • The violin plot reveals whether M-103's distribution is bimodal (two bumps: one normal, one anomaly).
  • Box plot outlier dots identify specific dates worth investigating.

Chart 3 -- Scatter Plot with Anomaly Highlighting¶

Concept and Code Explanation (Before Use)

sns.scatterplot() with hue= maps colours to a categorical variable automatically.
sns.regplot() overlays a linear regression line with a confidence interval.

Highlighting anomalies:
Overlay anomalous points in a contrasting colour to draw attention without hiding the rest of the data.
This visual annotation is more effective than a data table for operational reviewers.

In [6]:
anomaly_threshold = int(df["defects"].quantile(0.90))
df["is_anomaly"]  = df["defects"] > anomaly_threshold

fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# Left: scatter coloured by machine, anomalies as red X
ax = axes[0]
sns.scatterplot(data=df[~df["is_anomaly"]], x="units", y="defects",
                hue="machine_id", alpha=0.6, s=50, ax=ax)
sns.scatterplot(data=df[df["is_anomaly"]], x="units", y="defects",
                color="red", s=100, marker="X", label="Anomaly", ax=ax)
ax.set_title("Units vs Defects -- Anomalies Highlighted", fontsize=12, fontweight="bold")
ax.set_xlabel("Units Produced")
ax.set_ylabel("Defects")

# Right: regression plot for M-103 only
ax2 = axes[1]
m103 = df[df["machine_id"] == "M-103"]
sns.regplot(data=m103, x="units", y="defects",
            scatter_kws={"alpha": 0.6, "color": "darkorange"},
            line_kws={"color": "red", "linewidth": 2}, ax=ax2)
ax2.set_title("M-103: Units vs Defects with Regression", fontsize=12, fontweight="bold")
ax2.set_xlabel("Units Produced")
ax2.set_ylabel("Defects")
r_m103 = m103["units"].corr(m103["defects"])
ax2.annotate(f"r = {r_m103:.3f}", xy=(0.05, 0.92), xycoords="axes fraction",
             fontsize=10, color="red")

plt.suptitle("Scatter Analysis -- Trend and Anomaly Detection", fontsize=13, y=1.02)
plt.tight_layout()
plt.savefig("sns_chart3_scatter_anomaly.png", dpi=150, bbox_inches="tight")
plt.show()

print(f"Anomaly threshold (90th pct): {anomaly_threshold} defects")
print(f"Anomalous rows: {df['is_anomaly'].sum()} of {len(df)}")
No description has been provided for this image
Anomaly threshold (90th pct): 10 defects
Anomalous rows: 7 of 90

Result Interpretation: Scatter with Anomaly Highlights¶

  • Red X markers immediately direct attention to the anomalous records.
  • A flat regression line on M-103 (r near 0) means the anomaly is not volume-driven.
  • annotate() places the correlation coefficient directly on the chart.

Chart 4 -- heatmap: Correlation and Cross-Tabulation¶

Concept and Code Explanation (Before Use)

A heatmap encodes numeric values as colour intensity.

Two common uses:

  1. Correlation matrix -- which variables move together? Use df.corr() as input.
  2. Pivot table heatmap -- how does a metric vary across two categorical dimensions?

Key arguments:

  • annot=True -- print the numeric value inside each cell
  • fmt=".2f" -- number format for annotations
  • cmap="coolwarm" -- blue for negative, red for positive correlation
In [7]:
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# Correlation matrix
numeric_cols = ["units", "defects", "temp_c", "yield_pct"]
corr_matrix  = df[numeric_cols].corr()

sns.heatmap(corr_matrix, annot=True, fmt=".2f", cmap="coolwarm",
            vmin=-1, vmax=1, linewidths=0.5, ax=axes[0])
axes[0].set_title("Variable Correlation Matrix", fontsize=12, fontweight="bold")

# Pivot table: avg defects by machine x shift
df["shift"] = np.tile(["Morning","Afternoon","Night"], n)[:n]
pivot = df.pivot_table(values="defects", index="machine_id", columns="shift", aggfunc="mean")

sns.heatmap(pivot, annot=True, fmt=".1f", cmap="YlOrRd",
            linewidths=0.5, ax=axes[1])
axes[1].set_title("Avg Defects: Machine x Shift", fontsize=12, fontweight="bold")
axes[1].set_xlabel("Shift")
axes[1].set_ylabel("Machine")

plt.suptitle("Heatmaps -- Correlation and Cross-Tabulation", fontsize=13, y=1.02)
plt.tight_layout()
plt.savefig("sns_chart4_heatmap.png", dpi=150, bbox_inches="tight")
plt.show()
No description has been provided for this image

Result Interpretation: Heatmap¶

  • defects and yield_pct should show a strong negative correlation (close to -1).
  • Temperature correlation near 0 means process temperature is not driving defects.
  • The pivot table heatmap identifies the worst machine-shift combination in seconds.

Chart 5 -- lineplot with Confidence Band: Trend and Uncertainty¶

Concept and Code Explanation (Before Use)

sns.lineplot() with estimator="mean" and errorbar="sd" automatically:

  • Plots the mean value per x-axis point per group
  • Shades the area +-1 standard deviation as a confidence band

This shows both the central trend and the process variability at each point.
Overlapping confidence bands between two machines means their means are statistically indistinguishable.

In [8]:
df_line = df.copy()
df_line["week"] = df_line["date"].dt.isocalendar().week.astype(int)

fig, ax = plt.subplots(figsize=(12, 5))

sns.lineplot(
    data=df_line, x="week", y="defects",
    hue="machine_id", estimator="mean", errorbar="sd",
    linewidth=2, alpha=0.9, ax=ax
)

ax.axvspan(28, 31, color="red", alpha=0.08, label="Anomaly period")

ax.set_title("Weekly Mean Defects by Machine (with +/-1 SD band)",
             fontsize=12, fontweight="bold")
ax.set_xlabel("ISO Week Number")
ax.set_ylabel("Average Daily Defects")
ax.legend(title="Machine", fontsize=9, title_fontsize=9)

plt.tight_layout()
plt.savefig("sns_chart5_lineplot_ci.png", dpi=150, bbox_inches="tight")
plt.show()
print("All charts complete.")
No description has been provided for this image
All charts complete.

Result Interpretation: lineplot with Confidence Band¶

  • Wide confidence bands indicate high week-to-week variation.
  • M-103's line should rise noticeably during the anomaly weeks (highlighted in red).
  • axvspan() adds a semi-transparent shaded region -- effective for annotating known event periods.

About This Material¶

Author Prakash Ukhalkar
Role Assistant Professor (MCA) · Researcher in Data Science and Machine Learning
GitHub @prakash-ukhalkar
Repository mfg-python-sql-training
License MIT

Provided for educational use in Python Data Analytics Phase 2 training programmes.
For questions, corrections, or contributions, open an issue or pull request on GitHub.


© 2026 Prakash Ukhalkar · Python for Data Analytics · MIT License