Introduction to NumPy — Arrays & Operations
np.array, np.zeros, np.arange and np.linspace, how to inspect an array's shape and dtype, element-wise operations, and basic statistics with np.sum, np.mean and np.std.
Why NumPy?
NumPy ("Numerical Python") provides the ndarray — a fixed-type, fixed-size array — plus a huge library of fast operations that work on entire arrays at once. It's the numerical foundation that Pandas, Matplotlib, and Scikit-Learn are all built on top of.
The problem with plain Python loops
A Python list can hold any mix of types, and every arithmetic operation on it goes through the Python interpreter one element at a time. NumPy arrays fix both of these: every element has the same data type, stored in one contiguous block of memory, and operations run as tight, pre-compiled loops instead of interpreted Python bytecode.
# The "manual loop" way — pure Python prices = [19.99, 24.50, 7.25, 42.00] discounted = [] for p in prices: discounted.append(p * 0.9) # 10% off, one item at a time # The NumPy "vectorized" way — no explicit loop needed import numpy as np prices_arr = np.array(prices) discounted_arr = prices_arr * 0.9 # applies to every element at once print(discounted_arr)
prices_arr * 0.9 instead of a for loop is called vectorization. The loop still happens — but inside NumPy's pre-compiled C code instead of Python's interpreter, which is why vectorized NumPy code is consistently faster than the equivalent hand-written Python loop, especially as arrays grow large.Creating Arrays
There are several standard ways to build a NumPy array, depending on whether you already have the data or need to generate it.
import numpy as np # From an existing Python list a = np.array([10, 20, 30, 40]) # A 2D array from a list of lists grid = np.array([[1, 2, 3], [4, 5, 6]]) # Arrays of a given shape, filled with zeros or ones zeros = np.zeros(5) # [0. 0. 0. 0. 0.] zeros_2d = np.zeros((2, 3)) # 2 rows, 3 columns, all zeros ones = np.ones((3, 3)) # 3x3 grid of ones # A range of values — like Python's range(), but returns an array r = np.arange(0, 10, 2) # [0 2 4 6 8] — start, stop (exclusive), step # N evenly spaced values between two endpoints (inclusive) lin = np.linspace(0, 1, 5) # [0. 0.25 0.5 0.75 1. ] print(a, grid, zeros_2d, r, lin, sep="\n")
np.arange(start, stop, step) works like Python's range() — you specify the step size, and the count of values is whatever falls out. np.linspace(start, stop, num) works the other way — you specify how many values you want, and NumPy figures out the spacing, including both endpoints.Array Shape, dtype, and Size
Every array carries metadata describing its dimensions and the type of data it stores. Checking these is one of the first things to do when something isn't behaving as expected.
import numpy as np grid = np.array([[1, 2, 3], [4, 5, 6]]) print(grid.shape) # (2, 3) — 2 rows, 3 columns print(grid.ndim) # 2 — number of dimensions print(grid.size) # 6 — total number of elements print(grid.dtype) # int64 (or int32 on some systems) — every element shares one type # Explicitly choosing a dtype floats = np.array([1, 2, 3], dtype="float64") print(floats.dtype) # float64 print(floats) # [1. 2. 3.] # Reshaping — same data, different shape flat = np.arange(6) # [0 1 2 3 4 5] reshaped = flat.reshape(2, 3) # [[0 1 2] [3 4 5]]
np.array([1, 2, "three"]) doesn't raise an error — NumPy silently upcasts everything to a single common type (here, strings), which can quietly break arithmetic later. When something behaves oddly, checking .dtype is often the fastest way to find out why.Element-Wise Operations
Arithmetic and comparison operators on NumPy arrays apply to every element automatically — no loop required. This is vectorization in action for everyday math.
import numpy as np a = np.array([1, 2, 3, 4]) b = np.array([10, 20, 30, 40]) # Array with a single number (a "scalar") print(a + 5) # [6 7 8 9] print(a * 2) # [2 4 6 8] # Array with another array of the same shape print(a + b) # [11 22 33 44] — element-by-element, NOT concatenation print(a * b) # [10 40 90 160] # Comparisons produce a boolean array print(a > 2) # [False False True True]
[1,2,3] + [10,20,30] concatenates into a 6-item list, and [1,2,3] * 2 repeats the list. NumPy arrays redefine these operators to mean element-wise math instead — a common source of confusion the first time you switch between the two.Basic Math Functions
NumPy provides fast, built-in functions for the summary statistics you'll compute constantly while exploring a dataset.
import numpy as np scores = np.array([88, 92, 75, 61, 99, 70]) print(np.sum(scores)) # 485 — total print(np.mean(scores)) # 80.833... — average print(np.std(scores)) # standard deviation — spread around the mean print(np.min(scores)) # 61 print(np.max(scores)) # 99 print(np.median(scores)) # 81.5 — middle value when sorted # These are also available as array methods — both forms are equivalent print(scores.mean()) print(scores.sum())
Lesson Summary
Let's recap everything you learned in this lesson:
np.array(), np.zeros(), np.ones(), np.arange(), and np.linspace()..shape, .ndim, .size, and .dtype.np.sum(), np.mean(), np.std(), np.min()/np.max() compute fast summary statistics.np.arange(0, 10, 2) produce?.shape return?np.array([1, 2, 3]) + np.array([10, 20, 30]) produce?Put arrays and vectorized operations to work on a small "dataset."
You're given a week of daily high temperatures in Celsius:
[22, 25, 19, 30, 28, 24, 21]. Write a script that: (1) creates a NumPy array from this list, (2) converts every value to Fahrenheit using the vectorized formula F = C * 9/5 + 32 (no loop), and (3) prints the mean, minimum, and maximum Fahrenheit temperature, each formatted to 1 decimal place with an f-string.
Rules: No Python
for loop for the conversion — use array arithmetic directly. Use np.mean(), np.min(), and np.max() for the statistics.
💡 Show hints if you're stuck
- Build the array:
celsius = np.array([22, 25, 19, 30, 28, 24, 21]) - Convert all at once:
fahrenheit = celsius * 9 / 5 + 32 - Format with an f-string:
f"Mean: {np.mean(fahrenheit):.1f}°F"