5.4.2 · D2Scientific Computing (Python)

Visual walkthrough — Array creation — np.zeros, np.ones, np.linspace, np.arange, np.random

2,635 words12 min readBack to topic
Shorthand check
= start, = stop, = step, = number of points.

Step 1 — A row of dots and the gaps between them

WHAT. Draw a straight line. Put down some evenly spaced dots. These dots are the numbers an array will hold; the line is the number line (a ruler), and the position of a dot is its value.

WHY. Before any formula, we need one physical fact clear in the eye: dots and gaps are different things, and you always have one more dot than gap. This single fact is the whole reason linspace divides by num-1.

PICTURE. Look at the figure. There are 5 orange dots but only 4 violet gaps between them. Count them yourself: put a finger on each dot, then on each space. Dots win by one — always.

Figure — Array creation — np.zeros, np.ones, np.linspace, np.arange, np.random
Fencepost check
If you see 5 posts, you have 4 rails between them.

Step 2 — What does "evenly spaced" force on us?

WHAT. We demand that every gap has the same width. Call that width (the Greek letter delta, which just means "a difference" — here, the difference between one dot and the next).

WHY. "Evenly spaced" is exactly the promise np.linspace makes. If gaps were allowed to differ, there would be no single formula — so we pin them all to one shared value .

PICTURE. Every violet gap in the figure is stamped with the same label . Walking from the left dot, each step to the right adds one more .

Figure — Array creation — np.zeros, np.ones, np.linspace, np.arange, np.random

Because each step adds one , the value of the -th dot is the starting value plus copies of :

  • — where dot number sits (the start). We count dots from , so the first dot has taken zero steps.
  • — the dot's index: how many gaps you crossed to reach it.
  • — the fixed gap width from Step 2.

At : . Good — the first dot is the start. ✔


Step 3 — Pinning the last dot gives linspace

WHAT. linspace promises that the last dot lands exactly on stop . We use that promise to solve for .

WHY. Up to now was unknown. The endpoint promise () is the extra fact that nails it down.

PICTURE. In the figure, the leftmost dot is glued to and the rightmost to . If there are num dots total, the last one is dot number (because we started counting at ).

Figure — Array creation — np.zeros, np.ones, np.linspace, np.arange, np.random

Set the last dot's value equal to using the rule from Step 2:

  • — the index of the last dot (Step 1: one fewer than the count). This is also the number of gaps.
  • Setting it equal to is the endpoint promise.

Now just solve for — subtract , divide by :


Step 4 — The endpoint=False twist

WHAT. Sometimes you want the last dot removednp.linspace(a, b, num, endpoint=False). This is common when tiling a repeating pattern (like angles around a circle, where and are the same point and you don't want both).

WHY. If we drop the final post but keep the same number of dots, the spacing must change. Draw it and see what stays fixed.

PICTURE. The figure overlays two rulers on with . Top (endpoint=True): dots at , last dot on . Bottom (endpoint=False): the same 5 dots now stop before ; the phantom 6th dot (hollow) would have been on .

Figure — Array creation — np.zeros, np.ones, np.linspace, np.arange, np.random

The trick: with endpoint=False, NumPy imagines one extra dot at (the hollow one), spaces all of them evenly, then throws the last one away. So now there are real gaps, not :

  • Denominator is now, because the phantom dot adds one more gap.
  • We keep the first dots and discard the phantom.

Step 5 — A different question: fixed step, not fixed count (arange)

WHAT. Now flip the priority. You do not care how many dots you get; you care that each step is exactly step wide, and you walk from until you would reach or pass .

WHY. This is the mindset of Python's built-in range: "start here, keep stepping by , stop before ." The count is now an output, whatever falls out.

PICTURE. In the figure, we plant then march right in equal jumps of . A magenta wall sits at . We keep a dot only if it lands strictly left of the wall. The wall itself is never a dot — stop is excluded.

Figure — Array creation — np.zeros, np.ones, np.linspace, np.arange, np.random
Does arange include stop?
No — stop is excluded, exactly like Python range.

Step 6 — Counting the dots: where the ceiling comes from

WHAT. How many dots survive the wall? We derive the count .

WHY. We want a formula, not a march-and-count each time.

PICTURE. The figure zooms on the last stretch before the wall. Dot number sits at (same rule as Step 2). We keep it while , i.e. while .

Figure — Array creation — np.zeros, np.ones, np.linspace, np.arange, np.random

So the allowed indices are up to (but not including) .

Why exactly of them? Let us count carefully. The kept indices are all whole numbers with . Reading off the number line: they are up to the largest whole number below . Call that largest index . Then the kept indices run , which is numbers.

  • If is a whole number (say ): the largest whole number strictly below is , giving dots. (The dot at would land on the wall and is rejected.)
  • If is fractional (say ): the largest whole number below is , giving dots.

In both cases the answer is : the ceiling rounds up, which is exactly "count every integer slot sitting strictly short of the wall." The step figure draws these slots as filled dots and the rejected slot on the wall as hollow.


Step 7 — The degenerate & dangerous cases

WHAT. Every function has "what if the input is weird?" corners. We cover them so nothing surprises you: a zero step, a wrong-sign step, num=1, and float drift.

WHY. Contract rule: the reader must never meet a case we skipped. The arange API is "fixed step" — so the two things that can break a step (it being , or pointing the wrong way) deserve their own picture.

PICTURE. The figure stacks four mini-rulers:

  • (a) step = 0: np.arange(0, 1, 0). The march never moves — each "next" dot equals the last, so you'd loop forever trying to reach the wall. NumPy refuses: it raises ZeroDivisionError (the formula divides by zero). Never pass step .
  • (b) wrong-sign step: np.arange(0, 1, -0.25) (going left while the wall is to the right), or np.arange(1, 0, 0.25) (going right while the wall is to the left). You march away from the wall, so no dot ever satisfies the keep-condition. Result: an empty array []. In the formula, comes out negative, and means zero dots.
  • (c) num=1: np.linspace(0, 1, 1) → just . One dot means zero gaps, so would divide by — NumPy special-cases it and returns only the start.
  • (d) float drift: np.arange(0, 1, 0.1) — because binary can't store exactly (see Floating-point representation and rounding errors), the marching sum accumulates a tiny error, so the final dot may sneak in at or be dropped, making jump between and .
Figure — Array creation — np.zeros, np.ones, np.linspace, np.arange, np.random
Recall Edge-case quick table

np.linspace(a, b, 1) returns? ::: [a] — one dot, no gaps, no division by zero. np.linspace(a, b, 0) returns? ::: an empty array []. np.arange(0, 1, 0) does what? ::: raises ZeroDivisionError (step 0 is illegal). np.arange(0, 1, -0.25) returns? ::: empty array [] — the step points away from the wall. Safer choice for "N points on an interval"? ::: np.linspace (avoids float drift).


The one-picture summary

This final figure puts both derivations side by side so the shape of each formula is visible at a glance: linspace fixes the two ends and asks "how wide is each of the gaps?"; arange fixes the step width and asks "how many dots fit before the wall?"

Figure — Array creation — np.zeros, np.ones, np.linspace, np.arange, np.random
  • Top ruler (linspace): both ends nailed (filled orange), gaps , so .
  • Bottom ruler (arange): first dot nailed, magenta wall at excluded (hollow), count (for a correctly-signed, non-zero step).
Recall Feynman: the whole walkthrough in plain words

Picture a fence. linspace says: "I want exactly 5 posts, one at each end of my yard, spread the fence evenly." Since 5 posts make only 4 rails, you cut each rail as (yard length) ÷ 4 — that's the mysterious "minus one." If you tell it "no post on the far end," it pretends there's a 6th post at the end, spaces all six, then yanks that last post — now it's ÷ 5 instead. arange thinks the opposite way: "I'll take fixed 0.25-metre steps from the start and stop the instant I'd hit or cross the far wall." You never plant a post on the wall, and to count how many posts you got you round the division up, because you fill every step-slot sitting strictly short of the wall. Two traps: a step of zero means you never move, so NumPy throws an error; a step pointing the wrong way marches away from the wall, so you get nothing back — an empty array. And if your step is a messy decimal like 0.1, the computer can't store it exactly, so the marching adds up tiny errors and the last post might wobble in or out — that's the day you switch back to linspace.


Connections

  • Parent: Array creation (Hinglish)
  • Floating-point representation and rounding errors — why float arange drifts (Step 7).
  • Plotting functions with Matplotliblinspace feeds a smooth x-axis.
  • NumPy arrays — shape, dtype, ndim — what these functions actually return.
  • Vectorization vs Python loops — why we build filled arrays instead of looping.

Concept Map

count of points

step width

use instead

What do you fix first

linspace

arange

gaps = num minus 1

delta = length over num minus 1

stop is a wall excluded

count = ceil of length over step

step zero errors

wrong sign gives empty

float step drifts