What you'll learn
Quick Answer
Use plt.plot for trends over time, plt.bar for comparing categories, plt.scatter for relationships between two numbers. Always label both axes and give the chart a title. Start bar chart y-axes at zero, or the chart lies.
Your first chart
import matplotlib.pyplot as plt
subjects = ["Physics", "Chem", "Maths", "CS"]
marks = [78, 85, 92, 88]
plt.figure(figsize=(6, 4))
plt.bar(subjects, marks, color="#6366f1")
plt.title("Marks by subject")
plt.xlabel("Subject")
plt.ylabel("Marks")
plt.ylim(0, 100)
plt.tight_layout()
plt.savefig("chart.png", dpi=100)
Six of those lines are the chart and four are making it comprehensible. That ratio is roughly right — a chart without axis labels forces the reader to guess, and a guess is worse than no chart.
tight_layout() stops long labels being cut off at the edges, which is the most common complaint about matplotlib output. savefig writes a file; plt.show() opens a window instead, which is what you want when working interactively.
Choosing the right chart
Three cover most needs, and choosing wrongly is a more serious error than any styling mistake:
- Line — something changing over an ordered axis, usually time. Sales per month, temperature per hour.
- Bar — comparing separate categories. Marks per subject, population per state.
- Scatter — the relationship between two numeric variables. Study hours against marks.
The frequent mistake is a line chart across categories. Connecting Physics to Chemistry with a line implies something continuous exists between them, which is meaningless. If the x-axis has no natural order, use bars.
Pie charts deserve their poor reputation. People compare angles badly, and anything beyond three or four slices becomes unreadable. A bar chart is almost always clearer.
Multiple series and legends
months = [1, 2, 3, 4, 5, 6]
plt.figure(figsize=(6, 4))
plt.plot(months, [10, 12, 15, 14, 18, 21], marker="o", label="2025")
plt.plot(months, [12, 15, 17, 19, 22, 26], marker="s", label="2026")
plt.legend()
plt.grid(True, alpha=0.3)
plt.savefig("line.png", dpi=100)
Call plot once per series, give each a label, then call legend(). Without the legend call the labels are stored but never displayed, which catches people out.
The marker argument matters more than it looks: distinct markers keep the series distinguishable when the figure is printed in black and white, or read by someone who cannot easily separate the colours. A faint grid via alpha=0.3 helps read values without competing with the data.
Axis choices that make a chart lie
The most common way a chart misleads is a truncated y-axis on a bar chart. Values of 78, 85, 92 and 88 plotted from 75 to 95 make Maths look several times larger than Physics. Plotted from 0, the real difference — modest — is visible.
Bar charts must start at zero, because the bar's length is the visual encoding and a truncated bar encodes a false length. Line charts may start elsewhere, since the reader is comparing slope rather than length, but say so clearly.
Two more worth watching: keep the y-axis scale identical when placing two charts side by side, or the comparison is meaningless; and label units explicitly. "Marks" is fine, "Value" is not.
Fitting it into real work
In practice you will plot straight from a DataFrame, and pandas has matplotlib built in:
df.groupby("stream")["marks"].mean().plot(kind="bar")
plt.ylabel("Average marks")
plt.tight_layout()
plt.savefig("by_stream.png")
That is the common path: aggregate with pandas, then plot the result.
If you are producing charts inside a script rather than a notebook, set the non-interactive backend at the top with matplotlib.use("Agg") before importing pyplot. Without it, a script on a machine with no display can fail or hang trying to open a window.
Also call plt.close() after saving in a loop. Figures accumulate in memory otherwise, and a script generating hundreds of charts will slow down and eventually warn you about it.
