NumPy·Lesson 16·9 min·0/4 exercises

Running totals and differences

The reductions that don’t reduce — cumsum, diff, and their friends.

.cumsum.cumprodnp.diffnp.gradientnp.maximum.accumulateprepend=

Watch it happen

Play it through, or step back and forth yourself.

x
0
1
2
3
4
3
5
2
8
4
shape (5,)
x.sum()
22
one number — the path is lost

x.sum() gives one number: 22. Everything about the journey is gone — you can't tell whether it climbed steadily or spiked once.

The idea

sum collapses an axis to one number and throws away the journey. Cumulative operations keep the journey: same length out, showing the value at every point along the way.

x = np.array([3, 5, 2, 8, 4])
x.sum()        # 22
x.cumsum()     # [ 3  8 10 18 22]   <- same length, last value is the sum

Anywhere you'd otherwise write a loop with a running variable — a bank balance, a cumulative share of the total, a race position over time — this is the one-liner.

diff goes the other way

np.diff(x)         # [ 2 -3  6 -4]   <- gaps between neighbours

Note the length: one shorter. Five values have four gaps between them. That catches people out when they try to put the result back next to the original, and there's a fix built in — np.diff(x, prepend=0) keeps the length by assuming a zero in front.

diff and cumsum undo each other, which is a genuinely useful relationship: it's how you convert between "total so far" and "change this period", the two ways every dataset of running numbers gets stored.

Hand-drawn notes showing sum collapsing an array to one number while cumsum keeps every step of the journey, and diff going the other way and coming back one shorter.

The rest of the family

x.cumprod()                  # running product — compound growth
np.maximum.accumulate(x)     # running maximum — the peak so far
np.minimum.accumulate(x)     # running minimum
np.diff(x, n=2)              # differences of the differences
np.gradient(x)               # central differences — same length out

np.maximum.accumulate is worth knowing by name. Combined with cumsum, it's how you compute a drawdown — the gap between the running peak and the current value — in two lines and no loops:

balance  = x.cumsum()
peak     = np.maximum.accumulate(balance)
drawdown = peak - balance

That .accumulate suffix works on any ufunc that takes two arguments, so np.add.accumulate is just cumsum spelled the long way.

Along an axis

On 2-D data these take axis like any reduction — but since nothing collapses, the output shape matches the input:

cups.cumsum(axis=0)    # running total down the days, per stall
cups.cumsum(axis=1)    # running total across the stalls, per day
cups.cumsum()          # flattens first, then accumulates — rarely what you want

That last one is the trap: with no axis, the array is flattened before accumulating, so you get one long run across all the rows joined end to end. Always name the axis on 2-D data.

Practice

Write it yourself. The answer is there when you want it.

Putting the kettle on…

Starting up…

Write it yourself

not graded

Print x, its sum, its cumsum, then np.diff(x) and np.diff(x, prepend=0) so you can see which one keeps the length. Then treat the running total as a balance and compute the drawdown: the running peak with np.maximum.accumulate, minus the balance. Finish with cups.cumsum(axis=0).

Write something and press Run — the output appears here.

Your turn

4 exercises. Write the code yourself, then press Check — a nudge and the answer are there if you want them.

Return the running total of x.

your answer

Return the day-to-day changes in x — the gaps between consecutive values.

your answer

Return the running total of cups down the days — so each column accumulates independently, and the shape stays (4, 3).

your answer

Return the running maximum of x — the highest value seen so far at each position.

your answer