Course index · Previous · Next

NumPy Arrays, Vectorization, and Monetary Arithmetic

Module 1: Foundations and Reproducibility · Lecture 3 of 32

Before manipulating a DataFrame, learn the array model underneath many numerical operations. Use a tiny basket calculation to understand shapes, broadcasting, masks, and why money should retain a declared unit.

What you will learn

Understand the problem

Arrays describe a collection with a shape

A one-dimensional array of three prices has shape (3,). Multiplying it by a three-element quantity array pairs values by position. A column-shaped array with shape (3, 1) multiplied by a row-shaped array with shape (3,) produces a (3, 3) matrix through broadcasting. That operation can be mathematically valid while completely wrong for an order-level calculation. Inspect shapes before trusting a total.

Vectorization is a model, not a magic word

Array expressions apply an operation across a collection without a Python loop around each element. They usually make numerical intent clearer and can reduce interpreter overhead. They do not guarantee speed for every workload, and creating a huge intermediate matrix can cost more memory than a careful loop. Measure the relevant workload rather than judging code only by whether it contains a for statement.

Minor units prevent a class of rounding surprises

Represent 199.00 INR as 19900 paise. Multiplying price by integer units and subtracting whole-paise discounts remains exact within the chosen integer range. Division for an average produces a fractional result; round only for display, with a documented rule when amounts must be posted back to a ledger. Large totals can overflow fixed-width integers, so check magnitude assumptions when scaling beyond this small dataset.

Masks encode a rule

A boolean mask has one truth value per observation. In this example a cancellation changes recognized merchandise revenue to zero without deleting the observation. That distinction preserves the ability to calculate a cancellation rate later. A mask with the wrong shape can select or broadcast along an unintended axis, so validate shape and expected totals on a small example first.

Run the worked example

Download and extract the course kit linked below. Open a terminal in the extracted folder, complete the setup in Lecture 2, and run python run_course.py --lesson 3 --examples-only. Each script starts from the shared dataset; it does not depend on a notebook cell from another lecture. All retail records are fictional and amounts are integer paise unless a chart explicitly says INR.

from pathlib import Path
import numpy as np
import pandas as pd
from course_data import load_orders, load_raw, load_customers, clean_orders, ROOT, OUT

prices = np.array([19900, 29900, 49900], dtype=np.int64)
units = np.array([1, 2, 3], dtype=np.int64)
discounts = np.array([0, 1000, 2000], dtype=np.int64)
cancelled = np.array([False, True, False])
gross = prices * units
net = np.where(cancelled, 0, gross - discounts)
print('Shape:', net.shape)
print('Gross paise:', gross.tolist())
print('Net paise:', net.tolist())
print('Total INR:', int(net.sum()) / 100)
assert net.shape == (3,)
assert net.tolist() == [19900, 0, 147700]
assert int(net.sum()) == 167600

Verified output

Shape: (3,)
Gross paise: [19900, 59800, 149700]
Net paise: [19900, 0, 147700]
Total INR: 1676.0

Walk through the result

Each position represents one order. The second order has a gross amount but contributes zero recognized revenue because its cancellation mask is true. np.where returns a new array, leaving the original gross values available for reconciliation. The total 167600 is still paise; the displayed currency total is 1676.00 INR. Assertions on the entire small vector catch a swapped discount or status much more precisely than an assertion on only the grand total.

Common mistakes and diagnosis

Do not combine arrays of different grains merely because their lengths happen to match. NumPy aligns by position, whereas pandas often aligns by labels. Do not round every intermediate percentage discount independently without specifying the business rounding rule. np.vectorize is primarily a convenience wrapper, not a promise of compiled numerical speed. For missing integers, pandas nullable integer columns are often more suitable than inserting a floating NaN into a NumPy integer array.

Practice assignment

Set a full refund on the third order. Recalculate net revenue while retaining the cancellation rule. Assert that revenue cannot fall below zero for this example, then demonstrate the shape produced by prices[:, None] * units and explain why it is not an order total.

Worked solution and explanation

Add the following code below the worked example, or run the matching file in the solutions folder. Compare the result with the business definition before trusting a passing assertion.

refunds = np.array([0, 0, 147700], dtype=np.int64)
after_refunds = np.where(cancelled, 0, gross - discounts - refunds)
assert after_refunds.tolist() == [19900, 0, 0]
assert np.all(after_refunds >= 0)
matrix = prices[:, None] * units
assert matrix.shape == (3, 3)

Check your understanding

When can a valid broadcasting operation still be a data-analysis error?

Answer guide

When dimensions represent incompatible entities or the expanded shape is not the intended grain. A price-by-quantity matrix considers every combination; an order-level table needs matched price and quantity pairs. NumPy checks shape compatibility, not business meaning. Validate both the mathematical shape and the interpretation of each axis.

Reference and next step

Download the complete course kit: datasets, all examples, solutions, and capstone.

Return to the full Data Analysis with Python and pandas curriculum.

Course index · Previous · Next