Python for Data Analytics: Phase 2 Training¶

Notebook 2.2: Data Visualization using Python -- Matplotlib¶

Python Matplotlib 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: Build publication-quality Histograms, Scatter Plots, and Line Charts with Matplotlib including datetime axis handling.


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

Learning Objectives

  • Understand the Matplotlib figure/axes object model
  • Create and annotate Histograms, Scatter Plots, and Line Charts
  • Handle datetime axes correctly for time-series production data
  • Apply consistent styling and labelling for professional chart output
  • Revise concepts through two industry-style case studies

Training Date: Monday, 06th July 2026
Estimated Duration: 60 minutes
Prerequisites: Notebook 2.1 completed (NumPy and Pandas Foundations)


Environment Setup¶

Concept and Code Explanation (Before Use)

Matplotlib's core module is pyplot, imported as plt by convention.

The figure/axes object model:

  • A Figure is the overall canvas (the blank page).
  • Axes is the individual chart area inside the figure.
  • Every element (title, labels, lines, bars) is set on the Axes object with .set_* methods.

matplotlib.dates provides formatters and locators for datetime axes.
%matplotlib inline is a Jupyter magic that embeds charts directly in the notebook output.

In [1]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.dates as mdates

%matplotlib inline

# Set consistent figure size and resolution for all charts in this notebook.
plt.rcParams["figure.figsize"] = (10, 5)
plt.rcParams["figure.dpi"]     = 100
plt.rcParams["axes.grid"]      = True
plt.rcParams["grid.alpha"]     = 0.4

np.random.seed(42)
print("Matplotlib version:", plt.matplotlib.__version__)
print("Visualization environment ready.")
Matplotlib version: 3.10.8
Visualization environment ready.

Result Interpretation: Environment Setup¶

  • plt.rcParams is a global settings dictionary -- set it once so every chart inherits the same size, resolution, and grid style.
  • %matplotlib inline is Jupyter-specific.
  • A clean import with no errors confirms all packages are available.

Case Study Data¶

Concept and Code Explanation (Before Use)

Both case studies use a shared 60-day production simulation.

The simulation models:

  • Daily units produced -- normally distributed around 500 with standard deviation 25.
  • Daily defect count -- Poisson-distributed with mean 4.
  • Machine assignment -- randomly assigned from 4 machines.
  • Date range index from 01 July to end of September 2026.
In [2]:
# ── Simulate 60 days of production data ───────────────────────────────────────
n = 60
dates = pd.date_range(start="2026-07-01", periods=n, freq="B")

production_data = pd.DataFrame({
    "date"          : dates,
    "machine_id"    : np.random.choice(["M-101","M-102","M-103","M-104"], size=n),
    "units_produced": np.random.normal(500, 25, size=n).round(0).astype(int),
    "defects"       : np.random.poisson(lam=4, size=n),
})
production_data["yield_pct"] = (
    (production_data["units_produced"] - production_data["defects"])
    / production_data["units_produced"] * 100
).round(2)
production_data = production_data.set_index("date")

print(f"Dataset shape: {production_data.shape}")
display(production_data.head())
Dataset shape: (60, 4)
machine_id units_produced defects yield_pct
date
2026-07-01 M-103 471 10 97.88
2026-07-02 M-104 509 2 99.61
2026-07-03 M-101 485 2 99.59
2026-07-06 M-103 493 3 99.39
2026-07-07 M-103 485 5 98.97

Result Interpretation: Case Study Data¶

  • 60 business days spans approximately three calendar months.
  • np.random.poisson(lam=4) is the correct distribution for defect counts.
  • Setting date as the index enables Matplotlib's automatic datetime tick placement.

Chart 1 -- Histogram: Distribution of Daily Units Produced¶

Concept and Code Explanation (Before Use)

A histogram answers: How often does each value range occur?
It divides the data range into equal-width bins and counts how many observations fall into each bin.

In manufacturing, histograms reveal:

  • Whether production is centred near the target
  • The spread -- wide spread indicates high variation
  • Skewness -- is the process shifted left or right of target?

Key Matplotlib arguments:

  • bins= -- number of bins; start with 10-15 for moderate datasets
  • edgecolor= -- outline colour for each bar
  • axvline() -- draw a vertical reference line
In [3]:
fig, ax = plt.subplots()

ax.hist(
    production_data["units_produced"],
    bins=15, color="steelblue", edgecolor="white", linewidth=0.6,
)

target   = 500
std_band = production_data["units_produced"].std()

ax.axvline(target,            color="red",    linestyle="--", linewidth=1.5,
           label=f"Target ({target})")
ax.axvline(target - std_band, color="orange", linestyle=":",  linewidth=1.2,
           label=f"-1 SD ({target - std_band:.0f})")
ax.axvline(target + std_band, color="green",  linestyle=":",  linewidth=1.2,
           label=f"+1 SD ({target + std_band:.0f})")

ax.set_title("Distribution of Daily Units Produced (60 Business Days)",
             fontsize=13, fontweight="bold", pad=12)
ax.set_xlabel("Units Produced per Day", fontsize=11)
ax.set_ylabel("Frequency (days)", fontsize=11)
ax.legend(fontsize=9)

plt.tight_layout()
plt.savefig("chart1_histogram.png", dpi=150, bbox_inches="tight")
plt.show()

print(f"Mean  : {production_data['units_produced'].mean():.1f} units")
print(f"StdDev: {production_data['units_produced'].std():.1f} units")
No description has been provided for this image
Mean  : 497.4 units
StdDev: 23.9 units

Result Interpretation: Histogram¶

  • The histogram shows a roughly bell-shaped distribution centred near 500.
  • The red dashed line marks the target. If the bulk of bars sit to the left, average output is below target.
  • Approximately 68% of bars should fall between the orange and green ±1 SD lines for a normal process.
  • plt.savefig() exports the chart as a PNG for reports or presentations.

Chart 2 -- Scatter Plot: Units Produced vs Defect Count¶

Concept and Code Explanation (Before Use)

A scatter plot answers: Is there a relationship between two numeric variables?
Each point represents one day.

In manufacturing:

  • Strong negative correlation (as units increase, defects decrease) may indicate a speed-quality trade-off.
  • Colouring by a third variable (machine ID) adds a third dimension without extra chart complexity.

Key Matplotlib arguments:

  • c= -- point colour mapped from a categorical column
  • s= -- point size
  • alpha= -- transparency
In [4]:
machine_codes = production_data["machine_id"].astype("category").cat.codes
colour_map    = plt.cm.tab10

fig, ax = plt.subplots()
ax.scatter(
    production_data["units_produced"],
    production_data["defects"],
    c=machine_codes, cmap=colour_map, s=60, alpha=0.75,
    edgecolors="white", linewidths=0.5,
)

machines = production_data["machine_id"].astype("category").cat.categories.tolist()
handles  = [
    plt.Line2D([0],[0], marker="o", color="w",
               markerfacecolor=colour_map(i / len(machines)), markersize=8)
    for i in range(len(machines))
]
ax.legend(handles, machines, title="Machine", fontsize=9, title_fontsize=9)

ax.set_title("Daily Units Produced vs Defect Count by Machine",
             fontsize=13, fontweight="bold", pad=12)
ax.set_xlabel("Units Produced", fontsize=11)
ax.set_ylabel("Defects", fontsize=11)

plt.tight_layout()
plt.savefig("chart2_scatter.png", dpi=150, bbox_inches="tight")
plt.show()

r = production_data["units_produced"].corr(production_data["defects"])
print(f"Pearson correlation (units vs defects): {r:.3f}")
No description has been provided for this image
Pearson correlation (units vs defects): 0.061

Result Interpretation: Scatter Plot¶

  • A Pearson correlation near 0 means no linear relationship -- defects are driven by factors other than output volume.
  • Points coloured by machine allow you to spot whether one machine systematically produces more defects.
  • Outlier points in the top-right (high units AND high defects) are the most concerning -- investigate those dates.

Chart 3 -- Line Chart with Datetime Axis¶

Concept and Code Explanation (Before Use)

A line chart answers: How does a metric change over time?

Datetime axis handling:

  • mdates.DateFormatter("%b %Y") formats tick labels as "Jul 2026", "Aug 2026".
  • mdates.WeekdayLocator(byweekday=0) places minor ticks on every Monday.
  • fig.autofmt_xdate() rotates tick labels to prevent overlap.

A 5-day rolling average (df.rolling(window=5).mean()) smooths noise and reveals the underlying trend.

In [5]:
fig, ax = plt.subplots()

# Raw daily yield -- thin, semi-transparent
ax.plot(
    production_data.index, production_data["yield_pct"],
    color="steelblue", linewidth=1.0, alpha=0.5, label="Daily yield %",
)

# 5-business-day rolling average
rolling_avg = production_data["yield_pct"].rolling(window=5).mean()
ax.plot(
    production_data.index, rolling_avg,
    color="navy", linewidth=2.0, label="5-day rolling avg",
)

ax.axhline(99.0, color="red", linestyle="--", linewidth=1.2, label="Target 99%")

# Datetime axis formatting
ax.xaxis.set_major_locator(mdates.MonthLocator())
ax.xaxis.set_minor_locator(mdates.WeekdayLocator(byweekday=0))
ax.xaxis.set_major_formatter(mdates.DateFormatter("%b %Y"))
fig.autofmt_xdate(rotation=30)

ax.set_title("Daily Yield Rate with 5-Day Rolling Average",
             fontsize=13, fontweight="bold", pad=12)
ax.set_xlabel("Date", fontsize=11)
ax.set_ylabel("Yield Rate (%)", fontsize=11)
ax.legend(fontsize=9)
ax.set_ylim(96, 101)

plt.tight_layout()
plt.savefig("chart3_line_datetime.png", dpi=150, bbox_inches="tight")
plt.show()

print(f"Average daily yield : {production_data['yield_pct'].mean():.2f}%")
print(f"Minimum daily yield : {production_data['yield_pct'].min():.2f}%")
No description has been provided for this image
Average daily yield : 99.21%
Minimum daily yield : 97.88%

Result Interpretation: Line Chart with Datetime Axis¶

  • Month labels on the x-axis make the chart readable to non-technical audiences.
  • The 5-day rolling average removes day-to-day noise -- a downward trend is visible even when individual days look acceptable.
  • set_ylim(96, 101) zooms the y-axis to the relevant range -- a 0-100% chart makes variation invisible.

Case Study Revision -- Multi-Chart Production Report¶

Concept and Code Explanation (Before Use)

Real management reports combine multiple charts on a single figure using subplots.
plt.subplots(rows, cols) returns a Figure and a 2-D array of Axes objects.

This case study mimics a one-page weekly production summary with four panels:

  1. Units produced over the period (line chart)
  2. Defects by machine (bar chart)
  3. Yield distribution (histogram)
  4. Units vs defects correlation (scatter)
In [10]:
fig, axes = plt.subplots(2, 2, figsize=(14, 9))
fig.suptitle("60-Day Production Summary Dashboard",
             fontsize=15, fontweight="bold", y=1.01)

# Panel [0,0] -- Units over time
axes[0,0].plot(production_data.index, production_data["units_produced"],
               color="steelblue", linewidth=1.2)
axes[0,0].axhline(500, color="red", linestyle="--", linewidth=1, label="Target 500")
axes[0,0].xaxis.set_major_locator(mdates.MonthLocator())
axes[0,0].xaxis.set_major_formatter(mdates.DateFormatter("%b"))
axes[0,0].set_title("Daily Units Produced", fontsize=11)
axes[0,0].set_ylabel("Units")
axes[0,0].legend(fontsize=8)
plt.setp(axes[0,0].get_xticklabels(), rotation=20, ha="right")

# Panel [0,1] -- Avg defects by machine
machine_defects = production_data.groupby("machine_id")["defects"].mean()
axes[0,1].bar(machine_defects.index, machine_defects.values,
              color=["#4C72B0","#DD8452","#55A868","#C44E52"], edgecolor="white")
axes[0,1].set_title("Avg Defects by Machine", fontsize=11)
axes[0,1].set_ylabel("Avg Defects / Day")

# Panel [1,0] -- Yield histogram
axes[1,0].hist(production_data["yield_pct"], bins=12,
               color="teal", edgecolor="white", linewidth=0.6)
axes[1,0].axvline(99.0, color="red", linestyle="--", linewidth=1.2, label="Target 99%")
axes[1,0].set_title("Yield Rate Distribution", fontsize=11)
axes[1,0].set_xlabel("Yield %")
axes[1,0].set_ylabel("Frequency")
axes[1,0].legend(fontsize=8)

# Panel [1,1] -- Units vs defects scatter
axes[1,1].scatter(production_data["units_produced"], production_data["defects"],
                  color="coral", alpha=0.7, edgecolors="white", s=50)
axes[1,1].set_title("Units Produced vs Defects", fontsize=11)
axes[1,1].set_xlabel("Units Produced")
axes[1,1].set_ylabel("Defects")

plt.tight_layout()
plt.savefig("chart4_dashboard.png", dpi=150, bbox_inches="tight")
plt.show()
print("Dashboard saved.")
No description has been provided for this image
Dashboard saved.

Result Interpretation: Multi-Chart Dashboard¶

  • The 2x2 layout packs four charts into a single shareable image.
  • fig.suptitle() sets a title above all panels; individual panel titles use ax.set_title().
  • plt.tight_layout() adjusts spacing between panels -- always call it before savefig().
  • This dashboard pattern is reusable: swap the data source and all four panels update automatically.

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