Course topics

By WebNest Studio

Python Tutorial

Introduction to NumPy

NumPy (Numerical Python) is the foundation of data science in Python. pandas, matplotlib, scikit-learn, SciPy, PyTorch and TensorFlow are all built on — or interoperate with — its central object, the ndarray: a fast, fixed-type, n-dimensional array of numbers.

Why not just use lists? A Python list stores pointers to separate Python objects, so maths on a million numbers means a million slow Python-level operations. A NumPy array stores raw numbers in one contiguous block of memory and runs operations in optimised C code, typically 10–100× faster and using far less memory. This lesson covers installing NumPy, creating arrays, their key attributes, data types, and the idea of vectorised operations.

Installing and Importing

Install with pip install numpy (it is also included in Anaconda). By universal convention it is imported as import numpy as np. Everything in this module can be run in a Jupyter notebook, VS Code, or as a normal script.

Creating Arrays

  • np.array([1, 2, 3]) — from a list (nested lists give 2-D arrays).
  • np.zeros((2, 3)), np.ones(5), np.full((2, 2), 7), np.eye(3) — filled arrays and the identity matrix.
  • np.arange(start, stop, step) — like range but returns an array; np.linspace(start, stop, num) — evenly spaced values including the end point.
  • np.random.default_rng(seed) — the modern random generator: .integers(), .random(), .normal(), .choice().

Attributes and Data Types

Every array has a shape (size of each dimension, e.g. (3, 4) = 3 rows, 4 columns), ndim (number of dimensions), size (total elements), dtype (element type) and itemsize/nbytes (memory). All elements share one dtype: int64, float64, bool, complex128, fixed-width strings and more. Mixing ints and floats upcasts to float; convert explicitly with astype(). Choosing smaller types such as float32 or int8 can save a lot of memory on big data.

Vectorisation

Arithmetic on arrays applies element by element without a Python loop: prices * 1.18 adds 18% tax to every price, a + b adds two arrays position by position, and functions like np.sqrt, np.exp and np.round ("universal functions" or ufuncs) work on whole arrays. Writing code this way — vectorised — is the single most important NumPy habit.

Examples

Creating arrays and inspecting their attributes

Python
import numpy as np

marks = np.array([78, 92, 65, 88])
matrix = np.array([[1, 2, 3], [4, 5, 6]])
print(marks, marks.dtype, marks.shape, marks.ndim)
print(matrix)
print("shape:", matrix.shape, "size:", matrix.size, "bytes:", matrix.nbytes)

print(np.zeros((2, 3)))
print(np.ones(4, dtype=int), np.full(3, 7.5))
print(np.eye(3, dtype=int))
print(np.arange(0, 20, 5), np.linspace(0, 1, 5))

rng = np.random.default_rng(seed=42)          # same seed -> same "random" numbers
print(rng.integers(1, 7, size=5))              # five dice rolls
print(rng.normal(loc=170, scale=10, size=3).round(1))
Output
[78 92 65 88] int64 (4,) 1
[[1 2 3]
 [4 5 6]]
shape: (2, 3) size: 6 bytes: 48
[[0. 0. 0.]
 [0. 0. 0.]]
[1 1 1 1] [7.5 7.5 7.5]
[[1 0 0]
 [0 1 0]
 [0 0 1]]
[ 0  5 10 15] [0.   0.25 0.5  0.75 1.  ]
[1 5 4 3 3]
[179.4 150.5 157. ]

Data types, upcasting and astype()

Python
import numpy as np

print(np.array([1, 2, 3]).dtype, np.array([1, 2.5]).dtype, np.array([True, False]).dtype)
print(np.array([1, 2, 3.7]))                   # ints upcast to float

prices = np.array(["199.5", "45", "1200"])
as_numbers = prices.astype(float)
print(prices.dtype, "->", as_numbers.dtype, as_numbers.sum())

big = np.arange(1_000_000, dtype=np.float64)
print(big.nbytes // 1_000_000, "MB as float64,", big.astype(np.float32).nbytes // 1_000_000, "MB as float32")
print(np.array([3.9, -3.9]).astype(int))       # astype(int) truncates toward zero
Output
int64 float64 bool
[1.  2.  3.7]
<U5 -> float64 1444.5
8 MB as float64, 4 MB as float32
[ 3 -3]

Vectorised operations versus Python loops

Python
import time
import numpy as np

prices = np.array([120.0, 250.0, 99.0, 560.0])
quantities = np.array([3, 1, 10, 2])
print(prices * 1.18)                           # tax on every price
print(prices * quantities)                     # element-wise multiplication
print((prices * quantities).sum(), np.sqrt([16, 25, 81]))

numbers = list(range(2_000_000))
array = np.arange(2_000_000)

start = time.perf_counter()
squares_list = [n * n for n in numbers]
loop_time = time.perf_counter() - start

start = time.perf_counter()
squares_array = array * array
numpy_time = time.perf_counter() - start

print("same result:", squares_list[-1] == squares_array[-1])
print("NumPy faster:", numpy_time < loop_time)
Output
[141.6  295.   116.82 660.8 ]
[ 360.  250.  990. 1120.]
2720.0 [4. 5. 9.]
same result: True
NumPy faster: True

Common Mistakes

  • Looping over array elements in Python instead of using vectorised operations.
  • Forgetting that all elements share one dtype, so np.array([1, "a"]) turns everything into strings.
  • Confusing np.arange (step size, end excluded) with np.linspace (number of points, end included).
  • Using the legacy np.random.seed/np.random.rand instead of the recommended np.random.default_rng().
  • Expecting astype(int) to round — it truncates; use np.round first.

Key Points to Remember

  • NumPy arrays are fast, compact, single-type, n-dimensional containers of numbers.
  • Create arrays with array, zeros, ones, full, eye, arange, linspace and default_rng.
  • shape, ndim, size and dtype describe an array; astype converts types.
  • Vectorised operations apply to every element at once and are much faster than loops.
  • NumPy is the base of pandas, matplotlib and scikit-learn.

Practice the examples

Change an input, predict the result, then compare it with the output. Explain why the result changes.

Use your local project environment for these examples. Codelab currently runs Python and HTML/CSS/JavaScript; framework examples may need project dependencies.