NumPy for Numerical Computing

Reviewed & published by Brayan K

NumPy is the foundation of scientific Python. Its ndarray stores numbers in a fast, contiguous, typed block and runs vectorized operations in compiled C — turning slow Python loops into fast array math used by pandas, scikit-learn, and PyTorch.

Part of the free Python course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

What You'll Learn in This Lesson

🧱 1. The ndarray

import numpy as np

a = np.array([1, 2, 3, 4])
print(a)         # [1 2 3 4]
print(type(a))   # <class 'numpy.ndarray'>
print(a.shape)   # (4,)
print(a.dtype)   # int64

# ✅ Expected output:
# [1 2 3 4]
# <class 'numpy.ndarray'>
# (4,)
# int64

🏗️ 2. Creating Arrays

NumPy has dedicated constructors for common patterns:

import numpy as np

print(np.arange(0, 10, 2))    # [0 2 4 6 8]
print(np.zeros(3))            # [0. 0. 0.]
print(np.ones((2, 2)))        # 2x2 of ones
print(np.linspace(0, 1, 5))  # [0.   0.25 0.5  0.75 1.  ]

# ✅ Expected output:
# [0 2 4 6 8]
# [0. 0. 0.]
# [[1. 1.]
#  [1. 1.]]
# [0.   0.25 0.5  0.75 1.  ]
FunctionProduces
np.arange(a, b, step)Values from a up to b by step
np.zeros(shape) / np.ones(shape)Array filled with 0s or 1s
np.linspace(a, b, n)n evenly spaced points, a..b inclusive

📐 3. Shape, dtype, and reshape

Every array has a shape and a dtype. You can rearrange the layout with reshape:

import numpy as np

a = np.arange(6)           # [0 1 2 3 4 5]
print(a.shape)             # (6,)

b = a.reshape(2, 3)        # 2 rows, 3 cols
print(b)
# [[0 1 2]
#  [3 4 5]]
print(b.dtype)             # int64

# ✅ Expected output:
# (6,)
# [[0 1 2]
#  [3 4 5]]
# int64

The total number of elements must stay the same: a size-6 array can become 2x3 or 3x2, but not 2x4.

⚡ 4. Vectorized Operations

import numpy as np

a = np.array([1, 2, 3, 4])

print(a * 2)       # [2 4 6 8]
print(a + 10)      # [11 12 13 14]
print(a ** 2)      # [ 1  4  9 16]

b = np.array([10, 20, 30, 40])
print(a + b)       # [11 22 33 44]  (elementwise)

# ✅ Expected output:
# [2 4 6 8]
# [11 12 13 14]
# [ 1  4  9 16]
# [11 22 33 44]

📡 5. Broadcasting

Broadcasting lets arrays of different but compatible shapes combine. A dimension of size 1 (or a scalar) is virtually stretched to match:

import numpy as np

matrix = np.array([[1, 2, 3],
                   [4, 5, 6]])     # shape (2, 3)
row = np.array([10, 20, 30])      # shape (3,)

print(matrix + row)
# [[11 22 33]
#  [14 25 36]]
# 'row' was broadcast across both rows

# ✅ Expected output:
# [[11 22 33]
#  [14 25 36]]

No data was copied to a bigger array — NumPy applies the rule virtually, which keeps it fast and memory-light.

🎯 6. Indexing, Slicing, and Masks

Index like lists, but also filter with boolean masks:

import numpy as np

a = np.array([10, 20, 30, 40, 50])

print(a[0])        # 10
print(a[1:4])      # [20 30 40]
print(a[-1])       # 50

# Boolean mask: keep elements > 25
print(a[a > 25])   # [30 40 50]

# ✅ Expected output:
# 10
# [20 30 40]
# 50
# [30 40 50]

The expression a > 25 produces a boolean array; using it to index keeps only the True positions.

📊 7. Aggregations and axis

import numpy as np

a = np.array([[1, 2, 3],
              [4, 5, 6]])

print(a.sum())          # 21  (all elements)
print(a.mean())         # 3.5
print(a.sum(axis=0))    # [5 7 9]   (down columns)
print(a.sum(axis=1))    # [ 6 15]   (across rows)

# ✅ Expected output:
# 21
# 3.5
# [5 7 9]
# [ 6 15]

axis=0 collapses rows (one result per column); axis=1 collapses columns (one result per row).

🧪 Worked Example: A Whole Sensor Report

Each idea above is small on its own. This is what they look like working together: two temperature sensors, three readings each, and a report built without writing a single loop. Read the comments, run it, then change a number and predict what moves.

import numpy as np

# Two sensors (rows), three readings each (columns), in degrees Celsius.
temps = np.array([[21.5, 22.0, 23.5],
                  [19.0, 18.5, 20.0]])

print("shape :", temps.shape)   # (2, 3)  -> 2 rows, 3 columns
print("dtype :", temps.dtype)   # float64 -> every element is a 64-bit float
print("size  :", temps.size)    # 6       -> total number of elements

# Vectorised maths: this ONE expression touches all six numbers.
# No Python loop is written, and none runs - the work happens in compiled C.
fahrenheit = temps * 9 / 5 + 32
print("fahrenheit:")
print(fahrenheit)

# axis=1 collapses the columns -> one average per SENSOR (per row).
print("mean per sensor:", temps.mean(axis=1))

# axis=0 collapses the rows -> one average per READING SLOT (per column).
print("mean per slot  :", temps.mean(axis=0))

# A comparison does not give one True/False - it gives an ARRAY of them.
warm_mask = temps > 21
print("mask:")
print(warm_mask)

# Indexing with that mask keeps only the True positions, flattened to 1-D.
print("warm readings:", temps[warm_mask])

# True counts as 1, so summing a mask counts the matches.
print("how many warm?", warm_mask.sum())

# ✅ Expected output:
# shape : (2, 3)
# dtype : float64
# size  : 6
# fahrenheit:
# [[70.7 71.6 74.3]
#  [66.2 65.3 68. ]]
# mean per sensor: [22.33333333 19.16666667]
# mean per slot  : [20.25 20.25 21.75]
# mask:
# [[ True  True  True]
#  [False False False]]
# warm readings: [21.5 22.  23.5]
# how many warm? 3

🎯 Your Turn: Reshape, Aggregate, Filter

Three blanks, one for each of the ideas you have just met. Fill them in, run it, and check your output against the expected block at the bottom of the snippet.

import numpy as np

# 🎯 YOUR TURN — replace each ___ and run it.

scores = np.arange(1, 13)      # the numbers 1 to 12 in a flat array
print(scores)

grid = scores.___(3, 4)        # 👉 rearrange the 12 values into 3 rows of 4
print(grid)

# 👉 which axis collapses the ROWS and leaves one total per column? 0 or 1?
print("column totals:", grid.sum(axis=___))

# 👉 finish the comparison so only values greater than 8 survive the mask
print("big values   :", grid[grid ___ 8])

# ✅ Expected output:
# [ 1  2  3  4  5  6  7  8  9 10 11 12]
# [[ 1  2  3  4]
#  [ 5  6  7  8]
#  [ 9 10 11 12]]
# column totals: [15 18 21 24]
# big values   : [ 9 10 11 12]

🚀 8. Why NumPy Is Faster Than Lists

AspectPython listNumPy ndarray
Memory layoutScattered pointersContiguous block
Element typeMixed, boxed objectsSingle fixed dtype
Loop executionPython bytecodeCompiled C

For heavy numeric work, NumPy is often tens to hundreds of times faster than equivalent pure-Python loops.

🎯 Mini-Challenge: The Exam Board Report

Nothing is pre-written this time. You have a table of marks: three students down the rows, three exam papers across the columns. Produce the report described in the brief using array operations only — if you find yourself writing a for loop, there is a NumPy way to do it instead.

# 🎯 MINI-CHALLENGE: exam board report
#
# 1. import numpy as np
# 2. Build the marks table (3 students down, 3 papers across):
#        scores = np.array([[58, 71, 90],
#                           [42, 65, 77],
#                           [88, 95, 61]])
# 3. Print "student means:" followed by each STUDENT's average, rounded to
#    1 decimal place  (np.round(values, 1) does the rounding)
# 4. Print "paper highs  :" followed by the highest mark on each PAPER
# 5. Print "marks under 60:" followed by how many marks in the whole table
#    are below 60  (a mask plus .sum())
# 6. Add 5 bonus marks to every mark but never go above 100, then print it
#    under the heading "after bonus:"  (np.minimum(array, 100) caps values)
#
# ✅ Expected output:
# student means: [73.  61.3 81.3]
# paper highs  : [88 95 90]
# marks under 60: 2
# after bonus:
# [[ 63  76  95]
#  [ 47  70  82]
#  [ 93 100  66]]

# your code here

🎉 Conclusion

With NumPy you can:

✔ Store numbers in fast, typed ndarrays

✔ Create arrays with arange, zeros, ones, linspace

✔ Run vectorized math and broadcasting

✔ Filter data with slicing and boolean masks

✔ Aggregate along any axis

Think in arrays, not loops — that is the NumPy mindset.

📋 Quick Reference — NumPy

SyntaxWhat it does
np.array([1, 2, 3])Build an ndarray from a list
np.linspace(0, 1, 5)Evenly spaced values
a.reshape(2, 3)Reorganize into a new shape
a[a > 25]Boolean mask filtering
a.sum(axis=0)Aggregate along an axis

🎉 Great work! You've completed this lesson.

You now think in arrays and can run fast vectorized numeric code with NumPy.

Practice quiz

What is the core data structure provided by NumPy?

  • The ndarray
  • The Series
  • The matrix list
  • The DataFrame

Answer: The ndarray. NumPy's central object is the ndarray (n-dimensional array), a fixed-type, contiguous block of numbers.

Which function creates evenly spaced values like 0, 2, 4, 6, 8?

  • np.linspace(0, 10)
  • np.ones(5)
  • np.arange(0, 10, 2)
  • np.zeros(5)

Answer: np.arange(0, 10, 2). np.arange(start, stop, step) works like range() but returns an ndarray, here 0,2,4,6,8.

What does np.linspace(0, 1, 5) return?

  • Five integers from 0 to 5
  • Five evenly spaced values from 0 to 1 inclusive
  • An error
  • Five random numbers

Answer: Five evenly spaced values from 0 to 1 inclusive. linspace(start, stop, num) returns num evenly spaced points including both endpoints: 0, 0.25, 0.5, 0.75, 1.

What does the .dtype attribute of an array tell you?

  • The number of dimensions
  • The total number of elements
  • The shape
  • The element data type (e.g. int64, float64)

Answer: The element data type (e.g. int64, float64). dtype reports the data type of the array's elements; all elements share one dtype.

If a has shape (2, 3), what does a.reshape(3, 2) produce?

  • A 3x2 array with the same 6 elements
  • A transposed copy with swapped values
  • A 2x3 array unchanged
  • An error, sizes differ

Answer: A 3x2 array with the same 6 elements. reshape returns a new view with the same data and total size (6 elements) arranged as 3 rows of 2.

What does arr * 2 do on a NumPy array arr?

  • Doubles only the first element
  • Doubles every element (vectorized)
  • Concatenates arr to itself
  • Raises a TypeError

Answer: Doubles every element (vectorized). Arithmetic is elementwise and vectorized: each element is multiplied by 2, with no Python loop.

What is broadcasting in NumPy?

  • Sorting arrays in place
  • Sending arrays over a network
  • Printing arrays to the console
  • Automatically stretching shapes so arrays of different sizes can combine elementwise

Answer: Automatically stretching shapes so arrays of different sizes can combine elementwise. Broadcasting lets NumPy combine arrays of compatible (not identical) shapes by virtually expanding dimensions of size 1.

Given a = np.array([1, 2, 3, 4]), what is a[a > 2]?

  • array([3, 4])
  • array([True, True])
  • array([1, 2])
  • array([2, 3, 4])

Answer: array([3, 4]). Boolean masking keeps elements where the condition is True, so a[a > 2] is array([3, 4]).

For a 2D array a, what does a.sum(axis=0) compute?

  • The sum across each row
  • The number of rows
  • The sum down each column
  • The sum of every element

Answer: The sum down each column. axis=0 collapses the rows, producing one sum per column; axis=1 would sum across each row.

Why is a NumPy vectorized operation usually faster than a Python for-loop?

  • It skips some elements
  • It runs as optimized C on contiguous typed data instead of per-element Python bytecode
  • It uses more memory
  • It always uses the GPU

Answer: It runs as optimized C on contiguous typed data instead of per-element Python bytecode. NumPy stores homogeneous data contiguously and runs loops in compiled C, avoiding per-element Python interpreter overhead.

Continue this course