⚙️ Setup¶
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
INK="#1a2138"; CYAN="#0891b2"; PURPLE="#7c3aed"; AMBER="#d97706"; GREEN="#059669"; PINK="#db2777"; BLUE="#2563eb"; GRID="#e6e9f2"
plt.rcParams.update({"figure.facecolor":"white","axes.facecolor":"white","figure.dpi":110,"font.size":11,
"axes.edgecolor":GRID,"axes.grid":True,"grid.color":GRID,"axes.axisbelow":True,"axes.spines.top":False,
"axes.spines.right":False,"axes.titlesize":12,"axes.titleweight":"bold","legend.frameon":False})
print("Ready.")
Ready.
pets = pd.Series({"Dog":48,"Cat":39,"Fish":12,"Rabbit":7,"Reptile":5,"Bird":15})
pets = pets.sort_values()
fig, ax = plt.subplots(figsize=(7,4))
ax.barh(pets.index, pets.values, color=CYAN, edgecolor="white", height=0.7)
ax.set_xlabel("count"); ax.set_title("Pets owned"); ax.grid(axis="y", visible=False)
for i,v in enumerate(pets.values): ax.text(v+0.5, i, str(v), va="center", fontweight="bold")
plt.tight_layout(); plt.show()
Answer: Sorting makes the ranking instant, and horizontal bars give each label room without rotating text. These are nominal categories (no natural order), so reordering by value is perfectly fair, that would NOT be allowed for ordinal categories like S/M/L/XL.
labels=["Team A","Team B"]; vals=[82, 86]
fig,(a1,a2)=plt.subplots(1,2,figsize=(10,4))
a1.bar(labels, vals, color=PINK, edgecolor="white", width=0.6); a1.set_ylim(80,88)
a1.set_title("Truncated (starts at 80)")
a2.bar(labels, vals, color=GREEN, edgecolor="white", width=0.6); a2.set_ylim(0,100)
a2.set_title("Zero baseline (honest)")
plt.tight_layout(); plt.show()
looks=(vals[1]-80)/(vals[0]-80); real=vals[1]/vals[0]
print(f"truncated bar heights ratio: {looks:.1f}x | real ratio: {real:.2f}x")
truncated bar heights ratio: 3.0x | real ratio: 1.05x
Answer: Starting at 80 makes Team B's bar look 3x Team A's, when the scores really differ by under 5%. Because a bar encodes length from zero, cutting the baseline breaks that proportionality. The honest fix is a zero baseline (or, if zero is impractical, switch to a dot plot, which does not require one).
print("(a) favorite flavor -> BAR CHART : distinct categories, bars have GAPS, reorderable")
print("(b) ages of 500 people -> HISTOGRAM : continuous values binned, bars TOUCH, fixed order")
(a) favorite flavor -> BAR CHART : distinct categories, bars have GAPS, reorderable (b) ages of 500 people -> HISTOGRAM : continuous values binned, bars TOUCH, fixed order
Answer: (a) Flavor is categorical, so a bar chart with gaps between bars; you may sort them. (b) Age is continuous, so a histogram of binned ages with touching bars along a number line, and the bins stay in numeric order. The touching-vs-gapped bars are the quickest tell.
genre = pd.Series({"Pop":21,"Rock":18,"Hip-Hop":17,"Country":12,"Jazz":11,"EDM":9,"Classical":7,"Other":5})
g = genre.sort_values()
fig, ax = plt.subplots(figsize=(7.5,4.2))
ax.barh(g.index, g.values, color=PURPLE, edgecolor="white", height=0.7)
ax.set_xlabel("share (%)"); ax.set_title("Favorite genre (sorted)"); ax.grid(axis="y", visible=False)
for i,v in enumerate(g.values): ax.text(v+0.3, i, f"{v}%", va="center", fontweight="bold")
plt.tight_layout(); plt.show()
print(f"slices sum to {genre.sum()}%")
slices sum to 100%
Answer: Eight slices is far too many for a pie, several are near the same size and the eye cannot rank angles that close. On a sorted bar chart every value sits on a common scale, so the ranking and the near-ties (Rock 18 vs Hip-Hop 17) are obvious. As a rule, keep pies to about 5 slices at most; otherwise use a bar.
regions=["North","South","West"]; line1=np.array([40,55,30]); line2=np.array([25,20,45])
x=np.arange(3); w=0.38
fig,(a1,a2)=plt.subplots(1,2,figsize=(12,4.2))
a1.bar(x-w/2,line1,w,label="Line 1",color=CYAN,edgecolor="white")
a1.bar(x+w/2,line2,w,label="Line 2",color=AMBER,edgecolor="white")
a1.set_title("Grouped"); a1.set_xticks(x); a1.set_xticklabels(regions); a1.legend()
a2.bar(x,line1,label="Line 1",color=CYAN,edgecolor="white")
a2.bar(x,line2,bottom=line1,label="Line 2",color=AMBER,edgecolor="white")
a2.set_title("Stacked"); a2.set_xticks(x); a2.set_xticklabels(regions); a2.legend()
plt.tight_layout(); plt.show()
Answer: The grouped chart answers "which line sells more in each region?" because both bars share the zero baseline and compare directly. The stacked chart answers "what is each region's total?" because the segments add up to the full height, but the upper (Line 2) segments float, so comparing Line 2 across regions on the stacked chart is hard. Match the layout to the question.