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.

Frequently Asked Questions

What is the difference between plt.show and plt.savefig? show opens an interactive window; savefig writes an image file. In scripts and on servers use savefig, and set the Agg backend so no display is required.
Should I use matplotlib or seaborn? seaborn is built on matplotlib and produces better-looking statistical charts with less code. Learning matplotlib basics first is worthwhile, because seaborn hands you back a matplotlib figure to customise.
Why is my x-axis label cut off? The figure margins are too small. Call plt.tight_layout() before saving, and rotate long category labels with plt.xticks(rotation=45).
Should a bar chart always start at zero? Yes. The bar's length is what the reader compares, so truncating the axis exaggerates differences. Line charts can start elsewhere because slope rather than length carries the meaning.
How do I plot directly from a pandas DataFrame? Call .plot() on a Series or DataFrame, optionally with kind='bar' or kind='line'. It uses matplotlib underneath, so you can then apply matplotlib functions to label and save the result.