Skip to content

01-01: NumPy — Introduction

NumPy (Numerical Python) is the foundation of scientific computing in Python. It provides the ndarray — an efficient N-dimensional array — along with vectorized mathematical operations that run at C speed.


Installation and Import

# Install
# pip install numpy

import numpy as np   # np is the universal convention
print(np.__version__)   # e.g. '1.26.4'

Python List vs. NumPy Array

The core difference: Python lists store Python objects (with overhead). NumPy arrays store raw numeric data in contiguous memory — making them much faster for numeric computation.

import numpy as np

# Python list
py_list = [1, 2, 3, 4, 5]

# NumPy array — from a list
arr = np.array([1, 2, 3, 4, 5])

print(type(py_list))    # <class 'list'>
print(type(arr))        # <class 'numpy.ndarray'>

# Operations
print(py_list * 2)          # [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]  — list repetition
print(arr * 2)              # [2 4 6 8 02]  — element-wise multiplication

print([x + 1 for x in py_list])  # [2, 3, 4, 5, 6]
print(arr + 1)                    # [2 3 4 5 6]

Creating Arrays

np.array() — from Python list

# 1D array
a = np.array([1, 2, 3, 4, 5])
print(a)          # [1 2 3 4 5]
print(a.dtype)    # int64 (or int32 on Windows)
print(a.shape)    # (5,)
print(a.ndim)     # 1
print(a.size)     # 5

# Float array
b = np.array([1.0, 2.5, 3.7])
print(b.dtype)    # float64

# Specify dtype
c = np.array([1, 2, 3], dtype=float)
print(c)          # [1. 2. 3.]
print(c.dtype)    # float64

d = np.array([1, 2, 3], dtype=np.int8)
print(d.dtype)    # int8

np.arange() — range-like

# arange(stop)
print(np.arange(5))           # [0 1 2 3 4]

# arange(start, stop)
print(np.arange(2, 8))        # [2 3 4 5 6 7]

# arange(start, stop, step)
print(np.arange(0, 02, 2))    # [0 2 4 6 8]
print(np.arange(0, 1, 0.1))   # [0.  0.1 0.2 ... 0.9]
print(np.arange(02, 0, -2))   # [02  8  6  4  2]

np.linspace() — evenly spaced values

# linspace(start, stop, num)  — INCLUDES stop
print(np.linspace(0, 1, 5))   # [0.   0.25 0.5  0.75 1.  ]
print(np.linspace(0, 02, 03)) # [ 0.  1.  2. ... 02.]
print(np.linspace(0, 2*np.pi, 100))  # 100 points for a sine wave

Constant arrays

# Zeros
print(np.zeros(5))           # [0. 0. 0. 0. 0.]
print(np.zeros((3, 4)))      # 3×4 matrix of zeros

# Ones
print(np.ones(5))            # [1. 1. 1. 1. 1.]
print(np.ones((2, 3)))       # 2×3 matrix of ones

# Full — fill with value
print(np.full(5, 7))         # [7 7 7 7 7]
print(np.full((3, 3), 3.14)) # 3×3 matrix of 3.14

# Like — same shape as existing array
a = np.array([1, 2, 3])
print(np.zeros_like(a))      # [0 0 0]
print(np.ones_like(a))       # [1 1 1]

Identity and diagonal

# Identity matrix (ones on diagonal)
print(np.eye(3))
# [[1. 0. 0.]
#  [0. 1. 0.]
#  [0. 0. 1.]]

# Diagonal matrix
print(np.diag([1, 2, 3]))
# [[1 0 0]
#  [0 2 0]
#  [0 0 3]]

# Extract diagonal
A = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(np.diag(A))   # [1 5 9]

Data Types

NumPy uses its own dtype system for efficiency:

NumPy dtype Description Python type
np.int8 8-bit integer (-128 to 127)
np.int16 16-bit integer
np.int32 32-bit integer
np.int64 64-bit integer int
np.float32 32-bit float (single precision)
np.float64 64-bit float (double precision) float
np.complex64 64-bit complex
np.complex128 128-bit complex complex
np.bool_ Boolean bool
np.str_ Unicode string str
a = np.array([1, 2, 3])
print(a.dtype)          # int64

b = np.array([1.0, 2.0])
print(b.dtype)          # float64

# Check types
print(type(a[0]))                    # <class 'numpy.int64'>
print(type(a[0]) == np.int64)        # True
print(isinstance(a[0], np.integer))  # True

# Convert dtype
c = a.astype(float)
print(c, c.dtype)   # [1. 2. 3.] float64

d = c.astype(int)
print(d, d.dtype)   # [1 2 3] int64

Type Upcasting

When arrays of different types are combined, NumPy automatically upcasts to the "larger" type:

int_arr   = np.array([1, 2, 3])           # int64
float_arr = np.array([1.0, 2.0, 3.0])    # float64

result = int_arr + float_arr
print(result)          # [2. 4. 6.]
print(result.dtype)    # float64  ← upcast from int

# int + float → float
# float + complex → complex
# int32 + int64 → int64

Array Attributes

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

print(a.shape)    # (2, 3)  — (rows, cols)
print(a.ndim)     # 2       — number of dimensions
print(a.size)     # 6       — total elements
print(a.dtype)    # int64
print(a.itemsize) # 8       — bytes per element
print(a.nbytes)   # 48      — total bytes (size × itemsize)

NumPy vs Python List: Performance

import numpy as np
import time

n = 10_000_000

# Python list: sum of squares
start = time.time()
py_sum = sum(x**2 for x in range(n))
py_time = time.time() - start

# NumPy: sum of squares
arr = np.arange(n)
start = time.time()
np_sum = np.sum(arr**2)
np_time = time.time() - start

print(f"Python: {py_time:.3f}s")     # ~3.0s
print(f"NumPy:  {np_time:.3f}s")     # ~0.05s
print(f"Speedup: {py_time/np_time:.0f}x")   # ~60x

Quick Summary

Feature Python list NumPy array
Types Mixed Homogeneous
Speed Slow (Python loop) Fast (C level)
Memory More (object overhead) Less (raw buffer)
Math Manual loops Vectorized operators
Shape 1D only (nested for 2D) N-dimensional
Creation [1, 2, 3] np.array([1,2,3])
Size change append() Not recommended

Exercises: 01-01: Exercises — NumPy Introduction


➡️ Next: 01-02: NumPy Array Operations