Skip to content

NumPy Arrays

NumPy's array is the foundation of scientific computing in Python. Where Python lists are flexible but slow, NumPy arrays are typed, contiguous in memory, and vectorised — operations run in compiled C, not Python loops. This speed difference is the reason data scientists use NumPy.

Learning Objectives

  • Create 1D and 2D NumPy arrays
  • Perform vectorised arithmetic (no loops needed)
  • Index and slice arrays
  • Use universal functions (ufuncs)
  • Apply statistical operations on arrays

Creating Arrays

import numpy as np

# From a Python list
revenues = np.array([42000, 38500, 51200, 49800, 55100])
print(revenues)          # [42000 38500 51200 49800 55100]
print(revenues.dtype)    # int64
print(revenues.shape)    # (5,)

# Float array
prices = np.array([149.0, 35.99, 389.00, 49.99, 89.99])
print(prices.dtype)      # float64

# Zeros and ones — initialise arrays
zeros = np.zeros(5)          # [0. 0. 0. 0. 0.]
ones = np.ones((3, 4))       # 3×4 array of 1s
empty = np.empty((2, 3))     # uninitialized (fast, values are garbage)

# Ranges
months = np.arange(1, 13)    # [1 2 3 4 5 6 7 8 9 10 11 12]
evenly = np.linspace(0, 1, 5) # [0.   0.25 0.5  0.75 1.  ]

# 2D array (matrix)
sales_matrix = np.array([
    [100, 150, 200],    # Q1 sales: 3 regions
    [120, 130, 180],    # Q2 sales: 3 regions
    [90,  160, 210],    # Q3 sales: 3 regions
])
print(sales_matrix.shape)   # (3, 3) — 3 rows, 3 columns

Vectorised Operations — No Loops Needed

The key advantage of NumPy: operations apply element-wise without writing a loop.

revenues = np.array([42000, 38500, 51200, 49800, 55100])

# Scalar operations apply to every element
print(revenues * 1.1)       # 10% increase: [46200. 42350. 56320. 54780. 60610.]
print(revenues / 1000)      # in thousands: [42.  38.5 51.2 49.8 55.1]
print(revenues - 40000)     # distance from baseline

# Element-wise between two arrays
costs = np.array([28000, 24000, 33000, 31000, 36000])
profits = revenues - costs
margins = profits / revenues * 100
print(margins.round(1))     # [33.3 37.7 35.5 37.7 34.7]

Speed comparison:

import time

data = list(range(1_000_000))
arr = np.array(data)

# Python loop
start = time.time()
result = [x * 2 for x in data]
print(f"Loop: {time.time() - start:.3f}s")   # ~0.08s

# NumPy vectorised
start = time.time()
result = arr * 2
print(f"NumPy: {time.time() - start:.3f}s")  # ~0.001s — 80x faster


Indexing and Slicing

revenues = np.array([42000, 38500, 51200, 49800, 55100])

# Single element
print(revenues[0])     # 42000
print(revenues[-1])    # 55100

# Slice [start:stop:step]
print(revenues[1:4])   # [38500 51200 49800]
print(revenues[::2])   # [42000 51200 55100]

# 2D indexing [row, column]
matrix = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(matrix[1, 2])    # 6  (row 1, col 2)
print(matrix[0, :])    # [1 2 3]  (entire first row)
print(matrix[:, 1])    # [2 5 8]  (entire second column)
print(matrix[1:, 1:])  # bottom-right 2×2 submatrix

Boolean indexing

revenues = np.array([42000, 38500, 51200, 49800, 55100])

# Create boolean mask
above_50k = revenues >= 50000
print(above_50k)   # [False False  True False  True]

# Use mask to filter
print(revenues[above_50k])   # [51200 55100]

# Combine conditions
print(revenues[(revenues >= 45000) & (revenues <= 55000)])  # [49800 55100]

Use & and | (not and and or) for NumPy boolean operations

revenues >= 45000 and revenues <= 55000 raises a ValueError on arrays. Always use & (AND) and | (OR), and wrap each condition in parentheses.


Statistical Functions

revenues = np.array([42000, 38500, 51200, 49800, 55100])

print(np.sum(revenues))         # 236600
print(np.mean(revenues))        # 47320.0
print(np.median(revenues))      # 49800.0
print(np.std(revenues))         # 5977.9  (population std)
print(np.std(revenues, ddof=1)) # 6685.0  (sample std)
print(np.var(revenues))         # 35734560.0
print(np.min(revenues))         # 38500
print(np.max(revenues))         # 55100
print(np.percentile(revenues, [25, 50, 75]))  # [42000. 49800. 51200.]

# Index of min/max
print(np.argmin(revenues))   # 1  (February)
print(np.argmax(revenues))   # 4  (May)

# Cumulative functions
print(np.cumsum(revenues))   # [42000 80500 131700 181500 236600]

2D aggregation — axis parameter

sales = np.array([
    [100, 150, 200],   # Q1
    [120, 130, 180],   # Q2
    [90,  160, 210],   # Q3
])

print(np.sum(sales))           # 1340  — total of all elements
print(np.sum(sales, axis=0))   # [310 440 590]  — sum each column (region totals)
print(np.sum(sales, axis=1))   # [450 430 460]  — sum each row (quarterly totals)
print(np.mean(sales, axis=0))  # regional averages

Universal Functions (ufuncs)

ufuncs apply element-wise mathematical operations:

x = np.array([1, 4, 9, 16, 25])
print(np.sqrt(x))    # [1. 2. 3. 4. 5.]
print(np.log(x))     # natural log
print(np.log10(x))   # base-10 log
print(np.exp(x))     # e^x
print(np.abs(np.array([-3, 2, -1, 4])))  # [3 2 1 4]
print(np.round(np.array([1.234, 5.678, 9.012]), 2))  # [1.23 5.68 9.01]

Random Numbers — Essential for Simulation

rng = np.random.default_rng(seed=42)   # seeded for reproducibility

# Simulate 1000 orders from a normal distribution
orders = rng.normal(loc=250, scale=80, size=1000)   # mean=£250, std=£80
orders = np.clip(orders, 10, 1000)  # clip to realistic range

print(f"Simulated: mean=£{orders.mean():.2f}, std=£{orders.std():.2f}")

# Random integers — simulate customer IDs
customer_ids = rng.integers(1, 101, size=1000)  # 100 customers

# Uniform distribution — discount rates
discounts = rng.uniform(0, 0.3, size=1000)  # 0% to 30%

Practice Exercises

# Dataset for exercises
np.random.seed(42)
daily_revenue = np.array([
    12500, 13200, 11800, 14500, 16200, 8900, 9100,
    15300, 14800, 13100, 12900, 17500, 15800, 8200,
    16100, 14200, 13800, 15500, 18200, 9500, 10200,
    17100, 16500, 14900, 15200, 19800, 16400, 8800,
])

Warm-up 1. Find the mean, median, and std of daily_revenue. 2. Find all days where revenue was above £15,000. 3. What percentage of days had revenue above £15,000?

Main 4. Calculate the 7-day moving average. (Hint: use a loop with np.mean(daily_revenue[i:i+7]) for each window, or look up np.convolve.) 5. Find the top 5 highest-revenue days and their indices (day numbers). Use np.argsort. 6. Reshape daily_revenue into a 4×7 matrix (4 weeks of 7 days). Calculate total revenue per week.

Stretch 7. Standardise daily_revenue to a z-score array (subtract mean, divide by std). Identify which days are "outliers" (|z| > 2). 8. Simulate 365 days of revenue using np.random.normal(mean, std) where mean and std are computed from daily_revenue. Calculate the 10th and 90th percentiles of the simulated distribution.


Interview Questions

[Beginner] What is the difference between a Python list and a NumPy array?

[Beginner] What does "vectorised operation" mean in NumPy?

[Mid-level] What does the axis parameter do in NumPy aggregation functions like np.sum?

[Mid-level] Why do you need to use & and | instead of and and or when combining NumPy boolean conditions?

[Senior] Explain the concept of broadcasting in NumPy. Give an example where it's useful.


← Agenda · Next: Pandas DataFrames →