Python for Data Analytics: Phase 2 Training¶
Notebook 2.2: Data Visualization using Python -- Matplotlib¶
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.
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.rcParamsis a global settings dictionary -- set it once so every chart inherits the same size, resolution, and grid style.%matplotlib inlineis 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.
# ── 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
dateas 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 datasetsedgecolor=-- outline colour for each baraxvline()-- draw a vertical reference line
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")
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 columns=-- point sizealpha=-- transparency
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}")
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.
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}%")
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:
- Units produced over the period (line chart)
- Defects by machine (bar chart)
- Yield distribution (histogram)
- Units vs defects correlation (scatter)
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.")
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 useax.set_title().plt.tight_layout()adjusts spacing between panels -- always call it beforesavefig().- 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.