PYTHON / MATPLOTLIB
Line plots and plotting from data
Draw line plots from lists and records with ax.plot, control ordering and sampling, and understand why a line looks the way it does.
What you will learn
- Pass two parallel sequences to ax.plot: x positions and y values
- Recognise that plot joins points in list order, not sorted order
- Use the y-only form and know x becomes 0, 1, 2, ... automatically
- Split records into x and y lists before plotting, and skip gaps with nan
Understanding Line plots and plotting from data
ax.plot does one thing: it takes a sequence of (x, y) points and draws straight segments from each point to the next. There is no curve fitting and no smoothing. A sine wave looks smooth only because you handed it enough closely spaced samples that each segment is too short to see; with five samples you get four visible straight lines. The mental model to keep is a polyline, a connected path, not a function being graphed.
Because the path follows list order, ordering is part of your data, not a display detail. If your x values arrive out of order, the line walks backwards wherever the next x is smaller than the previous one, producing a folded shape that hides the trend. Sorting the pairs together, so each y stays attached to its own x, fixes this. Sorting the two lists separately silently destroys the pairing and gives a plausible-looking but wrong plot.
Plotting from real data is therefore mostly reshaping. Records usually arrive as rows or dictionaries, and your job is to produce two parallel sequences of equal length whose i-th elements belong together. Two shorthands help: ax.plot(y) alone substitutes 0, 1, 2, ... for x, and a format string like "o--" sets marker and line style in one argument. A float nan in y breaks the polyline at that point, which is how you show missing readings instead of drawing a straight lie across the gap.
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
months = [1, 2, 3, 4, 5, 6]
rainfall = [58.0, 43.5, 47.2, 51.0, 62.4, 48.8]
fig, ax = plt.subplots()
line, = ax.plot(months, rainfall)
ax.set_xlabel("month")
ax.set_ylabel("rainfall (mm)")
fig.savefig("rainfall.png")
print(type(line).__name__)
print(line.get_xdata())
print(line.get_linestyle(), line.get_marker())
print(ax.get_xlim())ax.plot draws a polyline through your points in the order you supply them, so the shape on screen depends on both the values and their ordering.
Worked examples
Order decides the shape
The same four points drawn as given and after sorting the pairs together.
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
pairs = [(3, 9), (1, 1), (4, 16), (2, 4)]
x = [p[0] for p in pairs]
y = [p[1] for p in pairs]
fig, ax = plt.subplots()
ax.plot(x, y, marker="o")
order = sorted(range(len(x)), key=lambda i: x[i])
xs = [x[i] for i in order]
ys = [y[i] for i in order]
ax.plot(xs, ys, marker="o")
fig.savefig("order.png")
print("as given:", x)
print("sorted: ", xs)
print("backtracks as given:", sum(1 for a, b in zip(x, x[1:]) if b < a))
print("backtracks sorted: ", sum(1 for a, b in zip(xs, xs[1:]) if b < a))Example explained
Line 1sorted(range(len(x)), key=...) produces an index order, so x and y are reordered with the same permutation and stay paired.
Line 2Each backtrack, a step where the next x is smaller, is a segment that runs right to left and folds the line over itself.
Line 3Both plot calls use identical data; only the traversal order differs, which is enough to change the picture entirely.
One argument means y only
Passing a single sequence makes matplotlib invent integer x positions.
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
temps = [12.1, 13.4, 15.9, 15.2]
fig, ax = plt.subplots()
line, = ax.plot(temps, "o--")
fig.savefig("single.png")
print(line.get_xdata())
print(line.get_marker(), line.get_linestyle())Example explained
Line 1With one data argument the values are treated as y and x becomes a float range 0..len(y)-1.
Line 2"o--" is a format string, not a keyword: the letter sets the marker and the dashes set the line style.
Line 3If your real x values are years or timestamps, the index x is wrong and the axis will be misleading.
nan breaks the line
Missing readings stored as nan leave a visible gap instead of a fake straight segment.
import math
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
hours = list(range(8))
readings = [1.0, 1.4, float("nan"), float("nan"), 2.2, 2.5, 2.9, 3.1]
fig, ax = plt.subplots()
ax.plot(hours, readings, marker="o")
fig.savefig("gaps.png")
segments = 0
prev_ok = False
for v in readings:
ok = not math.isnan(v)
if ok and not prev_ok:
segments += 1
prev_ok = ok
print("drawn segments:", segments)
print("missing:", sum(1 for v in readings if math.isnan(v)))Example explained
Line 1nan values are excluded from drawing, so the polyline is cut into two runs of connected points.
Line 2The counting loop mirrors what matplotlib does visually: a new run starts wherever a valid value follows a nan.
Line 3Dropping the nan entries instead would connect hour 1 straight to hour 4 and imply data you never measured.
Important notes
Two points per segment means a line plot implies the values in between are meaningful; for unrelated categories the connecting line asserts something false.
None inside a y list works like nan for breaking a line, but nan survives numeric conversion and arithmetic, so prefer float("nan") when the data passes through numpy.
Common mistakes
Calling sorted() on the x list and the y list separately: both look tidy, the lengths match, but every y is now attached to the wrong x and the plotted trend is fabricated.
Passing ax.plot(y) when x is really a year or a date column: the axis is labelled 0, 1, 2, ... and unevenly spaced observations appear evenly spaced.
Plotting numbers still stored as strings from a CSV: matplotlib treats them as category labels, so "10" sits next to "9" in file order instead of at the right numeric height.
Try it yourself
Change, predict, then run
Build lists of the twelve monthly values [5, 6, 9, 13, 17, 21, 23, 22, 19, 14, 9, 6] against months 1 to 12, plot them with round markers, then shuffle the pairs with random.shuffle and plot again to see how the line folds.
Open the Python workspaceCheck your understanding
You plot np.sin over x = np.linspace(0, 2*np.pi, 5). The result is a jagged zig-zag rather than a smooth wave. What explains it?
- plot only draws straight segments between the supplied points, and 5 points give just 4 segments
- The line style defaults to a rough dashed style that must be set to solid
- The figure resolution is too low, so raising dpi will smooth the curve
- np.sin expects degrees, so the sampled values are wrong
Show answer
A line plot is a polyline through the points you provide, so smoothness comes from sampling density; linspace with 200 points fixes it. Raising dpi is tempting because the plot looks coarse, but dpi only changes pixel count, and the same four straight segments would simply be rendered more sharply.