Index Ai Academy
Phase 0 · Tools · Working with data

NumPy arrays

Do math on whole columns at once, the foundation of every ML library.

About 30 minutes · Free lesson

Start this lesson

Why it matters

Every machine learning library is built on arrays of numbers. A dataset is a 2D array. An image is a 3D array. A model's weights are arrays. And an embedding, the way models capture the meaning of text, is a 1D array of a few thousand decimals. NumPy is where this way of working comes from. PyTorch, which you'll use for deep learning, copies its style almost exactly. Learn to think in whole arrays here, and the math in Phase 2 becomes code you can run.

What you'll be able to do

  • Create arrays and read their shape and dtype
  • Do math on whole arrays at once instead of looping
  • Select data with indexes, slices and boolean masks
  • Summarize along an axis, and predict how broadcasting combines shapes

What it covers

  1. Lists can't do math on themselves
  2. Shape and dtype
  3. Indexing, slicing and masks
  4. Summaries and axes
  5. Broadcasting
  6. Worked example: a week of campaign data
  7. Two slips that NumPy makes easy
  8. Shapes must line up

Lists can't do math on themselves

Multiply a Python list by 2 and you don't get doubled numbers. You get the list twice:

clicks = [5470, 2050, 3800]
print(clicks * 2)
print([c * 2 for c in clicks])
It runs right here in your browser, with nothing to install.

To do math on every item of a list, you need a loop or a comprehension. NumPy's array does math on every element directly. Think of a spreadsheet column: type one formula, and it applies to every row at once.

import numpy as np

clicks = np.array([5470, 2050, 3800])
impressions = np.array([182340, 410200, 950000])

print(clicks * 2)
print(clicks / impressions)

How it works:

  • import numpy as np is the standard way to load it. Every tutorial and AI assistant writes it.
  • np.array(...) turns a list into an array.
  • clicks * 2 doubles each element.
  • clicks / impressions divides element by element: first with first, second with second.

This is called vectorized math. You write the operation once, and NumPy applies it to every element in fast, pre-built code. On large data, that's often tens to hundreds of times faster than a Python loop.

Shape and dtype

import numpy as np

spend = np.array([3120.5, 1890, 6400, 120])
table = np.array([
    [5470, 301],
    [2050, 88],
    [3800, 41],
])

print(spend.dtype, spend.shape)
print(table.dtype, table.shape, table.ndim)

Two attributes describe every array:

  • dtype is the type of every element. Unlike a list, an array holds one type throughout. spend became float64 (decimals), even though 1890 was written as a whole number. table holds whole numbers: int32 here in the browser, int64 on most computers.
  • shape gives the size along each axis (direction). (4,) is a 1D array of 4 values. (3, 2) is 3 rows by 2 columns. Shapes are written as tuples: fixed-size groups of values in parentheses. ndim is the number of axes.

Most bugs in ML code are shape bugs. So printing .shape is the first thing to do when something looks wrong.

NumPy can also create arrays for you:

import numpy as np

print(np.zeros(3))
print(np.arange(0, 10, 2))
print(np.linspace(0, 1, 5))

rng = np.random.default_rng(seed=42)
print(rng.integers(1, 7, size=5))
  • arange works like range.
  • linspace gives evenly spaced values between two ends.
  • The random generator with a seed gives the same "random" numbers every run, so experiments can be repeated.
Try it now

Units sold (tee, hoodie, mug) on two days, times each product's price. Every cell is combined with the cell in the same position. No loop needed.

Indexing, slicing and masks

A 1D array indexes and slices like a list. A 2D array takes a row and a column, separated by a comma. : means "all of them":

import numpy as np

table = np.array([
    [5470, 301],   # clicks, conversions for campaign 0
    [2050, 88],    # campaign 1
    [3800, 41],    # campaign 2
])
print(table[0])        # first row
print(table[0, 1])     # row 0, column 1
print(table[:, 0])     # every row, column 0: all the clicks
print(table[1:, 1])    # rows 1 onward, column 1

The most useful trick is the boolean mask. It works like a stencil laid over the data. A comparison on an array gives an array of True and False. Indexing with that array keeps only the True positions:

import numpy as np

spend = np.array([3120.5, 1890, 6400, 120, 0, 2390.75])
revenue = np.array([15840, 5236, 2870, 6600, 0, 3290])

profitable = revenue > spend
print(profitable)
print(spend[profitable])
print("Spend on profitable campaigns:", spend[profitable].sum())

big_and_profitable = (revenue > spend) & (spend > 2000)
print(big_and_profitable)

Combine conditions with & (and) and | (or), with each condition in parentheses. Python's and and or don't work on arrays, because they expect a single True or False.

Quick check
What does this print?
import numpy as np
a = np.array([4, 9, 1, 7])
print(a[a > 5])
  1. [9, 7]
  2. 2
  3. [False True False True]
  4. [9 7]
Show the answer

[9 7]

a > 5 is a mask; indexing with it keeps 9 and 7. Arrays print without commas, which tells you it's an array and not a list.

Summaries and axes

Arrays have methods for summaries: sum, mean, min, max, std, and argmax (the position of the largest value). On a 2D array, axis says which direction to sum up:

import numpy as np

# rows: 3 campaigns. columns: clicks on Mon, Tue, Wed
daily = np.array([
    [120, 135, 150],
    [80, 60, 70],
    [45, 50, 40],
])
print(daily.sum())            # everything
print(daily.sum(axis=0))      # down the rows: one total per day
print(daily.sum(axis=1))      # across the columns: one total per campaign
print(daily.mean(axis=1).round(1))
print(daily.sum(axis=1).argmax())

The axis you name is the one that disappears:

  • axis=0 collapses the rows, leaving one value per column.
  • axis=1 collapses the columns, leaving one value per row.

The last line prints 0: campaign 0 had the most clicks, 405 in total.

Quick check
What does this print?
import numpy as np
t = np.array([[1, 2], [3, 4]])
print(t.sum(axis=0))
  1. [4 6]
  2. 10
  3. [1 2 3 4]
  4. [3 7]
Show the answer

[4 6]

axis=0 collapses the rows, leaving one total per column: 1 + 3 = 4 and 2 + 4 = 6.

Broadcasting

NumPy can combine arrays of different shapes when it can stretch the smaller one to fit. This is called broadcasting. A single number stretches to every element:

import numpy as np

prices = np.array([28, 64, 18])      # shape (3,)
print(prices * 1.2)                  # 1.2 is applied to every element

units = np.array([
    [3, 1, 4],                       # day 1: tee, hoodie, mug
    [2, 5, 0],                       # day 2
])                                   # shape (2, 3)
print(units * prices)                # prices is stretched down both rows

The rule: line the shapes up from the right. Each pair of sizes must be equal, or one of them must be 1. A missing size counts as 1.

  • (2, 3) and (3,) line up as 3 with 3, so prices is reused for every row.
  • (2, 3) and (2,) line up as 3 with 2, which fails:
import numpy as np

units = np.array([[3, 1, 4], [2, 5, 0]])
day_discount = np.array([0.9, 0.8])
print(units * day_discount)

To apply one discount per day (per row), make the discounts a column with shape (2, 1). Now 3 lines up with 1, and the column is stretched across:

import numpy as np

units = np.array([[3, 1, 4], [2, 5, 0]])
day_discount = np.array([0.9, 0.8])
print(units * day_discount.reshape(2, 1))
Quick check
units has shape (2, 3). Which of these can you multiply it by without an error?
  1. An array of shape (3, 2)
  2. An array of shape (4,)
  3. An array of shape (3,): one value per column
  4. An array of shape (2,): one value per row
Show the answer

An array of shape (3,): one value per column

Line the shapes up from the right: 3 with 3 works. 3 with 2 or 4 fails. For one value per row, reshape to (2, 1) first.

Worked example

Worked example: a week of campaign data

Three campaigns, seven days of spend and revenue. Find each campaign's weekly ROAS, the best day overall, and how many days Search beat a ROAS of 5, all without a single loop over the numbers.

import numpy as np

campaigns = ["Search", "Display", "Email"]
# rows: campaigns. columns: Mon to Sun
spend = np.array([
    [450, 470, 430, 460, 480, 300, 280],
    [270, 265, 280, 270, 275, 260, 270],
    [20, 15, 25, 20, 20, 10, 10],
])
revenue = np.array([
    [2300, 2500, 2100, 2400, 2600, 1500, 1300],
    [740, 700, 800, 760, 750, 720, 766],
    [1000, 900, 1200, 950, 1100, 700, 750],
])

weekly_roas = revenue.sum(axis=1) / spend.sum(axis=1)
daily_revenue = revenue.sum(axis=0)
search_daily_roas = revenue[0] / spend[0]

for name, roas in zip(campaigns, weekly_roas.round(2)):
    print(name, roas)
print("Best day (0 = Monday):", daily_revenue.argmax())
print("Days Search beat ROAS 5:", (search_daily_roas > 5).sum())
In the lesson you can change this and run it.

It prints a weekly ROAS of 5.12 for Search, 2.77 for Display and 55.0 for Email. Then the best day is 4 (Friday), and Search beat a ROAS of 5 on 4 days.

  • sum(axis=1) totals each campaign's week. Dividing two arrays of 3 gives three ROAS figures at once.
  • sum(axis=0) totals each day across campaigns, and argmax finds the best one.
  • revenue[0] / spend[0] takes the Search row from both arrays and divides day by day.
  • (search_daily_roas > 5).sum() counts the True values, because True counts as 1 and False as 0.

The for loop only formats the output. All the math is vectorized.

Two slips that NumPy makes easy

To combine conditions on arrays, use & and | with brackets: (a > 1) & (a < 5). The words and and or expect one true-or-false answer, not a whole array, so they raise an error.

For axes, remember that the axis you name is the one that disappears. On a table of rows and columns, axis=0 collapses the rows, which gives one answer per column.

Shapes must line up

Broadcasting only works when the shapes are compatible, so check .shape whenever you get an error here.

Practice what you just read

  • 11 code examples you can edit and run
  • a practice exercise that checks your code
  • a 5-question quiz
  • 5 review cards for spaced repetition

Start this lesson

Try a quiz question

You multiply an array of shape (4, 3) by an array of shape (3,). What's the shape of the result?

  1. (4, 3)
  2. (3,)
  3. An error: the shapes don't match
  4. (4,)

Answer it at the end of the lesson to see if you're right, and why.

Before this lesson