Python Quest

Track 14 · Lesson 1 of 12

NumPy arrays

Store numbers in NumPy arrays, read their dtype and shape, and pick out values with indexes, slices and boolean masks.

About 40 minIntermediate3 exercisesFree

You will be able to

  • Explain how a NumPy array differs from a Python list
  • Create arrays from lists and with np.arange, np.zeros and np.linspace
  • Read an array's dtype, shape and ndim
  • Index and slice one- and two-dimensional arrays
  • Select values with a boolean mask, combining conditions with & and |

A warehouse logs its temperature every hour: 24 numbers a day, 8,760 a year, for every room. A Python list can hold those numbers, but every question you ask — "which hours were above 25 °C?", "what was the average?" — needs a loop. Data analysis runs on a library built for exactly this job: NumPy (Numerical Python).

NumPy's central object is the array: a grid of values that all have the same type, stored side by side in memory. Because every value has the same type, NumPy can work on the whole grid at once in fast compiled code. pandas, which you'll use for the rest of this track, is built on top of NumPy arrays, so the ideas in this lesson come back everywhere.

A list and an array of the same readings
import numpy as np

readings = [21.5, 22.0, 23.4, 25.1, 26.8]
temps = np.array(readings)

print(readings)
print(temps)
print(type(temps))
print(temps.dtype)
print(temps.shape)

Try it: Change one reading to the integer 30 and run it again. The dtype stays float64, because an array holds one type and NumPy converts 30 to 30.0.

import numpy as np is the standard way to import NumPy; every tutorial and colleague will write np.. Three attributes describe any array:

AttributeMeaningExample above
dtypethe single type every element hasfloat64 (64-bit floating-point numbers, which have a decimal point)
shapethe size along each dimension, as a tuple(5,) — five values in one dimension
ndimthe number of dimensions1

The common dtypes are int64 (whole numbers), float64 (decimals), bool (True/False) and strings. If you mix ints and floats, NumPy upcasts everything to float. If you mix numbers and text, everything becomes text — a sign your data needs cleaning before you can do maths with it.

Making arrays without typing every value

Array constructors
import numpy as np

hours = np.arange(0, 24, 6)       # like range(): start, stop (excluded), step
print(hours)

empty_bins = np.zeros(4)           # four 0.0 values
print(empty_bins)

prices = np.linspace(10, 20, 5)    # 5 evenly spaced values from 10 to 20 inclusive
print(prices)

grid = np.array([[3, 0, 5],
                 [1, 4, 2]])       # a list of rows makes a 2-D array
print(grid.shape, grid.ndim, grid.dtype)

Try it: Change np.linspace(10, 20, 5) to np.linspace(10, 20, 3). Notice that, unlike np.arange, the stop value is included.

Indexing and slicing

One-dimensional arrays index and slice exactly like lists: temps[0] is the first value, temps[-1] the last, temps[1:4] the values at positions 1, 2 and 3.

A two-dimensional array has rows and columns. You index it with one pair of brackets and a comma: grid[row, column]. A colon on its own means "all of them", so grid[:, 2] is the whole third column and grid[1, :] (or grid[1]) is the second row.

Units in stock: 3 shelves (rows) by 4 slots (columns)
import numpy as np

stock = np.array([[12,  0,  7,  3],
                  [ 5,  9,  0, 11],
                  [ 8,  2,  6,  0]])

print(stock[0, 2])     # shelf 0, slot 2
print(stock[2])        # the whole of shelf 2
print(stock[:, 1])     # slot 1 on every shelf
print(stock[0:2, 2:])  # shelves 0-1, slots 2 onwards

Try it: Print stock[-1, -1]. Negative positions count from the end in both directions.

Slices are views, not copies

There is one important difference from lists. Slicing a list builds a new list. Slicing a NumPy array does not copy the data: it gives a view, a new array object that looks at the same memory. Changing a value through the view changes the original array too. When you want an independent piece, ask for one with .copy().

Predict the output

import numpy as np

week = np.array([12, 15, 11, 14, 13])
middle = week[1:4]       # a view
middle[0] = 0
print(week)

safe = week[1:4].copy()  # an independent copy
safe[0] = 99
print(week)

Boolean masks

Comparing an array with a value compares every element and gives back an array of True/False values of the same shape. That True/False array is called a boolean mask. Put a mask inside the brackets and NumPy keeps only the positions where the mask is True.

Which hours were too warm?
import numpy as np

temps = np.array([21.5, 22.0, 23.4, 25.1, 26.8, 24.9, 22.7])
too_warm = temps > 25
print(too_warm)
print(temps[too_warm])
print(too_warm.sum(), "readings above 25")

# Two conditions: & means "and", | means "or". Each condition needs its own parentheses.
comfortable = temps[(temps >= 22) & (temps <= 24)]
print(comfortable)

Try it: Change the last condition to (temps < 22) | (temps > 26) to find readings outside the comfortable range.

too_warm.sum() counts the True values, because True counts as 1 and False as 0. That trick — summing a mask to count matches — is used constantly in analysis.

Why & and not and? Python's and asks "is this whole object true?", and for an array of many values that question has no single answer, so NumPy raises ValueError: The truth value of an array with more than one element is ambiguous. The & operator works element by element. The parentheses matter because & binds more tightly than >=.

Predict the output

import numpy as np

orders = np.array([3, 12, 7, 15, 1, 9])
big = orders >= 9
print(big)
print(orders[big])
print(big.sum())

Guided practice · ●●difficulty 2 of 5 · about 5 min

Split a week of meter readings

A smart meter records one electricity reading (kWh) per day, Sunday first, so a week is an array of 7 values: Sunday–Thursday are working days and Friday–Saturday are the weekend.

Write split_week(usage) that takes a NumPy array of 7 readings and returns a tuple (workdays, weekend) where both parts are NumPy arrays made by slicing:

split_week(np.array([12.1, 11.8, 12.4, 13.0, 12.7, 18.2, 17.5]))
# (array([12.1, 11.8, 12.4, 13. , 12.7]), array([18.2, 17.5]))

Python code editor. Tab indents. Press Escape then Tab to leave the editor. Control or Command plus Enter runs the code; Shift plus Enter checks your answer.

Ctrl+Enter run · Shift+Enter check · your code is saved in this browser as you type

Output

Run your code to see its output, or Check to test it against the requirements.

The solution becomes available after you pass, or after all three hints and two checks.

Challenge · ●●●difficulty 3 of 5 · about 8 min

Readings above a safety limit

A cold-storage room must stay at or below a temperature limit. Write over_limit(temps, limit) that returns a tuple (count, values):

  • count — how many readings are strictly above limit (an int)
  • values — a NumPy array of those readings, in their original order

Use a boolean mask, not a loop.

over_limit(np.array([3.5, 4.2, 5.1, 3.9, 6.0]), 4.0)
# (3, array([4.2, 5.1, 6. ]))

If no reading is above the limit, return (0, <an empty array>).

Python code editor. Tab indents. Press Escape then Tab to leave the editor. Control or Command plus Enter runs the code; Shift plus Enter checks your answer.

Ctrl+Enter run · Shift+Enter check · your code is saved in this browser as you type

Output

Run your code to see its output, or Check to test it against the requirements.

The solution becomes available after you pass, or after all three hints and two checks.

Debugging · ●●●difficulty 3 of 5 · about 6 min

Fix the price band filter

A pricing analyst wants every product price between low and high, inclusive at both ends. Their function crashes with:

ValueError: The truth value of an array with more than one element is ambiguous.

Fix in_band(prices, low, high) so it returns a NumPy array of the prices with low <= price <= high, in their original order.

in_band(np.array([15.0, 49.5, 50.0, 99.0, 120.0]), 50, 100)   # array([50., 99.])

Python code editor. Tab indents. Press Escape then Tab to leave the editor. Control or Command plus Enter runs the code; Shift plus Enter checks your answer.

Ctrl+Enter run · Shift+Enter check · your code is saved in this browser as you type

Output

Run your code to see its output, or Check to test it against the requirements.

The solution becomes available after you pass, or after all three hints and two checks.

Summary

  • A NumPy array holds values of one dtype; dtype, shape and ndim describe it.
  • np.array, np.arange, np.zeros and np.linspace create arrays.
  • Index 2-D arrays with grid[row, column]; a colon means all rows or all columns.
  • A comparison gives a boolean mask; array[mask] keeps the True positions and mask.sum() counts them.
  • Combine masks with & and |, with parentheses around each condition.