Skip to content
wannacode

Summaries and missing values

In short: Aggregations in NumPy (sum, mean, max, argmax) make one number from many, axis says which dimension disappears, and NaN marks a missing value.

Problem

“What do we sell the most?”

The till sends a report: how many units of four products we sold each week, for the last six weeks. Each row is a week, each column is a product:

Pythondata: the shop tables

The manager asks three things: how much we sold in total, which product does best, and which week was the strongest. Then they also send the takings by day, with one day missing.

Each answer is one number (or one per product), computed from many numbers. This is called an aggregation.

The idea

One number from many: sum, mean, min, max

An array has methods that summarise all its values: sum() for the total, mean() for the mean, min() and max() for the smallest and the largest. Here they are on the prices of the 25 products. to_numpy() takes the column as a NumPy array:

Pythondata: the shop tables

The sum of the prices comes out as 175.89999999999998, not 175.9. The computer stores decimals in binary and 0.1 cannot be written exactly, so tiny errors add up. They are not errors in the data.

For a report you round: round(x, 2) gives 2 decimal places. The mean price becomes €7.04, as in describe() from the first look.

More precisely, if you are curious

round is Python’s function and works on one number. An array has the method .round(2) (or np.round(arr, 2)), which rounds every number in it. Round at the end, for display: if you round halfway through, the rounding error goes into the next steps.

The idea

Which, not how much: argmax and argmin

max() tells you how much the most expensive product costs. But the manager asks which one it is. argmax() gives the position of the largest value, and argmin() that of the smallest. With the position you take the name from the array of names:

Pythondata: the shop tables

The most expensive is the knife set at position 22 (€39), and the cheapest is the butter roll at position 2 (€0.80). Positions start at 0, as with lists.

1. prices.max() returns 39.0 and prices.argmax() returns 22. What is 22?
More precisely, if you are curious

If the largest value appears several times, argmax gives its first position. In the till report, weeks 1 and 4 have 103 units each, and argmin over the weeks gives only week 1. If ties matter, check them with a mask: week_totals == week_totals.min().

See it

Along which dimension: axis

sales.sum() adds up all 24 cells: 654 units in six weeks. For “which product” you need a total for each column, and for “which week” a total for each row. The axis parameter says which:

Pythondata: the shop tables
  • axis=0 is the dimension of the rows (the weeks). The weeks merge and one number per product remains: bread is 263 units, coffee 39.
  • axis=1 is the dimension of the columns (the products). The products merge and one number per week remains: from 103 to 120.

Everyone gets confused here, because “axis=0” sounds like “by rows”, yet it gives a result by column. The rule that does not lie: axis is the dimension that disappears. The shape (6, 4) with axis=0 loses the 6 and becomes (4,), and with axis=1 it loses the 4 and becomes (6,).

The same goes for mean, min, max, argmax and the rest. The mean per week for each product, rounded to one decimal place:

Pythondata: the shop tables

Try it

The strongest week

Which week sold the most units in total? Put the total of each week in week_totals, and its number (1 to 6) in best_week.

Pythondata: the shop tables

The idea

How much so far: cumsum

Sometimes the manager asks not “how much this week” but “how much from the start up to this week”. That is the running total: cumsum() adds the numbers one by one and keeps every partial sum:

Pythondata: the shop tables

After the third week we have sold 319 units, and the last number, 654, is the grand total from sales.sum(). The running total is the line that grows on “sales year to date” charts.

The idea

One missing day: NaN

Now the takings by day for the last week. On Wednesday the till did not send a report, so Wednesday’s place holds np.nan: the value for “missing” you saw in the first look. Here is what happens to the mean:

Pythondata: the shop tables

The ordinary mean is nan. Any arithmetic with NaN gives NaN: what is 412.5 plus “I don’t know”? I don’t know. NumPy raises no error; it quietly returns nan, and in a long report it goes unnoticed.

np.isnan(revenue) is a mask that says where values are missing, and .sum() on it counts them: 1 day. np.nanmean and np.nansum skip NaN: the mean takings of the six known days are €494.70, and their sum is €2,968.20. That sum is “what we know so far”, not the takings for the whole week: Wednesday is not in it.

See it

Why not 0

The temptation is to replace the missing value with 0 and move on. np.nan_to_num does exactly that:

Pythondata: the shop tables

The mean drops from €494.70 to €424.03. The zero says “we sold nothing on Wednesday”, and that is not true: the shop was open, only the report is missing. 0 is not “missing”. Zero is a value, and NaN is the absence of a value.

0 is the right replacement only when it really means zero. If the till sends no line for a product nobody bought, then the gap is zero sales. The meaning of the data decides this, not the code.

Try it

A week without a count

The till did not send how many packs of coffee we sold in the third week. Put in avg_coffee how many packs we sell per week on average.

Pythondata: the shop tables

Exercise

The report for the manager

Answer the manager from the till report. Put in product_totals the units of each product over the six weeks, in best_product the name of the best-selling product, and in avg_per_week how many units each product sells per week on average, rounded to one decimal place.

Pythondata: the shop tables

Test

Check yourself

1. sales has shape (6, 4): 6 weeks and 4 products. What is the shape of sales.mean(axis=0)?
2. In an array of takings by day, one value is np.nan. What does revenue.sum() give?
3. Why is it wrong to replace the missing takings with 0 and then take the mean?
4. The units by week are [103, 104, 112]. What does .cumsum() give?

Made by Ivo Gergov as part of Evo Tech's campaign for accessible programming education ·