NumPy: Getting Started with NumPy
NumPy is the foundation of Python scientific computing. Whether you're doing machine learning, data analysis, or engineering simulation, almost every numerical library builds on NumPy. This chapter starts with the performance pain points of Python lists and reveals how NumPy achieves 160x speedup — your first step into numerical computing.
1. What You'll Learn
- ❶ Why Python lists struggle with numerical computing
- ❷ NumPy's core advantages (speed / memory / broadcasting)
- ❸ Key differences between ndarray and Python list
- ❹ NumPy ecosystem overview
- ❺ Installing NumPy and running your first program
2. Speed-Up Journey with Million-Level Data
(1) The Problem: Alice's List Loop
Alice is a data analyst who needs to square 1 million random numbers. She writes the most "natural" Python code — a for loop processing list elements one by one.
▶ Example
> Output: Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
: Squaring a list (Difficulty ⭐)
import time
import random
size = 1_000_000 # 1 million numbers
data = [random.random() for _ in range(size)]
start = time.time()
result = [x * x for x in data]
elapsed = time.time() - start
print(f"List comprehension: {elapsed:.4f} seconds")
# Typical output: List comprehension: 0.8000 seconds
> Output: Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
Alice's code runs in about 0.8 seconds. That doesn't seem bad — until she needs to do the same operation on hundreds of millions of data points, where the wait becomes unbearable.
(2) The Solution: Bob's NumPy Vectorization
Bob changes only two lines: replacing the list with np.array and the loop with a single vectorized multiplication.
▶ Example
> Output: Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
: NumPy squaring (Difficulty ⭐⭐)
import time
import numpy as np
size = 1_000_000 # 1 million numbers
data = np.random.random(size)
start = time.time()
result = data * data # vectorized operation
elapsed = time.time() - start
print(f"NumPy vectorized: {elapsed:.4f} seconds")
# Typical output: NumPy vectorized: 0.0050 seconds
> Output: Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
The same 1 million numbers take only about 5 milliseconds with NumPy — roughly 160x faster.
(3) The Payoff: What Makes It 160x Faster
NumPy's speed isn't about "Python being faster" — it's about offloading computation to C and using contiguous memory:
- Under the hood is C: NumPy's core operations are implemented in C/Fortran, bypassing the Python interpreter's per-line overhead
- Contiguous memory: ndarray elements are packed tightly in memory, achieving high CPU cache hit rates
- Vectorization: A single instruction processes the entire array at once, no Python-level loop needed
This is NumPy's core philosophy — Python's convenience with C's speed.
3. Why Python Lists Struggle with Numbers
(1) Loop Overhead: Interpreted Execution
Python is dynamically typed. Each loop iteration goes through: type check → method lookup → execute operation → box the result. For numerical work, these extra steps are pure waste.
# Each iteration: type check + method lookup + boxing
result = []
for x in data: # Python loop overhead per iteration
result.append(x * x) # type check, method dispatch, result boxing
> Output: Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
Compared to compiled C machine code, a single Python loop iteration can be tens of times more expensive than the actual multiplication.
(2) Type Overhead: Every Element Is a Full Object
A Python list doesn't store raw numbers — it stores PyObject pointers. The integer 42 takes 28 bytes in Python, but only 4 bytes in C.
4. NumPy's Core Advantages
(1) What Is ndarray
ndarray (N-dimensional array) is NumPy's core data structure. It is:
- Homogeneous: all elements share the same type (e.g., all
float64) - Fixed-size: element size is fixed, no boxing needed
- Contiguous: elements are packed tightly in memory, no pointer indirection
- Multi-dimensional: supports 1D, 2D, and even N-dimensional data
(2) ndarray vs Python List
| Feature | Python list | NumPy ndarray |
|---|---|---|
| Element type | Any mix | Homogeneous (single dtype) |
| Memory layout | Pointer array, scattered elements | Contiguous memory block |
| Single int memory | 28 bytes (PyObject) | 8 bytes (int64) |
| Bulk operations | List comprehension / for loop | Vectorized, single statement |
| Broadcasting | Not supported | Automatic dimension expansion |
| Slice view | Returns a copy | Returns a view by default (zero-copy) |
| Multi-dimensional | Nested lists (irregular) | Native N-dimensions, shape attribute |
| Under the hood | CPython interpreter | C / Fortran compiled code |
▶ Example
> Output: Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
: Memory comparison (Difficulty ⭐)
import sys
import numpy as np
size = 1_000_000 # 1 million integers
# Python list
py_list = list(range(size))
list_bytes = sys.getsizeof(py_list) + sum(sys.getsizeof(x) for x in py_list[:1000]) * size // 1000
print(f"Python list: ~{list_bytes / 1024 / 1024:.1f} MB")
# NumPy ndarray
np_array = np.arange(size, dtype=np.int64)
array_bytes = np_array.nbytes
print(f"NumPy ndarray: {array_bytes / 1024 / 1024:.1f} MB")
print(f"Ratio: {list_bytes / array_bytes:.1f}x more memory for list")
# Typical output:
# Python list: ~44.7 MB
# NumPy ndarray: 7.6 MB
# Ratio: 5.9x more memory for list
> Output: Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
A simple int64 ndarray uses about 6x less memory than a Python list of the same size — because ndarray stores raw values, not full Python objects.
(3) A First Look at Broadcasting
"Broadcasting" lets arrays of different shapes operate directly together, without manually expanding dimensions.
▶ Example
> Output: Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
: Array operations vs list comprehension (Difficulty ⭐⭐)
import numpy as np
# Task: add 100 to every element and multiply by 2
data = [1, 2, 3, 4, 5]
# --- Python list ---
result_list = [(x + 100) * 2 for x in data]
print(result_list)
# [202, 204, 206, 208, 210]
# --- NumPy ndarray ---
arr = np.array(data)
result_np = (arr + 100) * 2 # broadcasting + vectorization
print(result_np)
# [202 204 206 208 210]
# With a 2D array and 1D array
matrix = np.array([[1, 2, 3],
[4, 5, 6]])
row = np.array([10, 20, 30])
print(matrix + row)
# [[11 22 33]
# [14 25 36]]
> Output: Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
A list comprehension needs to spell out the loop logic; NumPy just needs one mathematical expression — code is the formula.
5. NumPy Ecosystem Overview
(1) The NumPy Ecosystem
NumPy is the foundation of the entire Python scientific computing ecosystem. Almost every major library depends on it:
mindmap
root((NumPy Ecosystem))
Data Science
Pandas
Polars
Machine Learning
Scikit-learn
TensorFlow
PyTorch
Scientific Computing
SciPy
Matplotlib
SymPy
Deep Learning Frameworks
Keras
JAX
MXNet
Specialized Domains
scikit-image
NetworkX
AstroPy
> Output: Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
(2) Ecosystem Reference Table
| Library | Domain | Why It Depends on NumPy |
|---|---|---|
| Pandas | Data analysis | DataFrame stores column data as ndarray under the hood |
| Scikit-learn | Machine learning | Model inputs and outputs are ndarray |
| SciPy | Scientific computing | Provides linear algebra / optimization / integration on top of ndarray |
| Matplotlib | Visualization | Plot data sources are ndarray |
| TensorFlow | Deep learning | Tensor concept originates from ndarray, convertible |
| PyTorch | Deep learning | Tensor and ndarray are interchangeable |
| Polars | Data analysis | Columnar storage, still NumPy-compatible |
(3) When to Use NumPy vs Pure Python
| Scenario | Pure Python | NumPy | Recommendation |
|---|---|---|---|
| A few dozen simple operations | Good enough | Slightly faster | Pure Python |
| 10K+ bulk operations | Slow | 10~100x faster | NumPy |
| Matrix multiplication | Nested loops, very slow | BLAS-backed, very fast | NumPy |
| Solving linear equations | Impractical to write manually | np.linalg.solve |
NumPy |
| String processing | Flexible | Not its strength | Pure Python |
| Mixed-type data | Lists handle it naturally | Requires structured arrays | Depends |
6. Installation and Your First Program
(1) Installation Methods
| Method | Command | When to Use |
|---|---|---|
| pip | pip install numpy |
General, simplest |
| conda | conda install numpy |
Anaconda / Miniconda environments |
| System package | apt install python3-numpy |
Linux system-wide install |
| From source | python setup.py build |
Custom BLAS backend needed |
Beginners should use
pip install numpy. Anaconda users can useconda install numpy.
▶ Example
> Output: Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
: Verify installation (Difficulty ⭐)
# Install
pip install numpy
# Verify
python -c "import numpy as np; print(np.__version__)"
# Output (example): 2.1.0
> Output: Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
(2) Creating Your First ndarray
▶ Example
> Output: Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
: Creating arrays with np.array (Difficulty ⭐)
import numpy as np
# From a Python list
a = np.array([1, 2, 3, 4, 5])
print(type(a)) # <class 'numpy.ndarray'>
print(a.dtype) # int64
print(a.shape) # (5,)
# From nested list (2D)
b = np.array([[1, 2, 3],
[4, 5, 6]])
print(b.shape) # (2, 3)
print(b.ndim) # 2
# Specify dtype explicitly
c = np.array([1, 2, 3], dtype=np.float32)
print(c.dtype) # float32
> Output: Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
(3) Checking Version and Configuration
▶ Example
> Output: Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
: Checking version and config (Difficulty ⭐)
import numpy as np
print(f"NumPy version: {np.__version__}")
# Show build configuration (BLAS, LAPACK, etc.)
np.show_config()
# Output includes: BLAS, LAPACK backend info,
# which determines computation performance
> Output: Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
The BLAS/LAPACK backend shown by np.show_config() determines how fast your NumPy matrix operations run — performance differences between OpenBLAS, MKL, and other backends can be several times.
7. Hands-On: Mean and Standard Deviation of 10M Numbers
Computing the mean and standard deviation of 10 million floating-point numbers — one of the most common data analysis tasks. We'll implement it in both pure Python and NumPy, comparing code size, speed, and memory.
▶ Example
> Output: Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
: Comprehensive comparison — list vs NumPy (Difficulty ⭐⭐⭐)
import time
import math
import random
import numpy as np
import sys
size = 10_000_000 # 10 million
# ========== Pure Python ==========
py_data = [random.random() for _ in range(size)]
t0 = time.time()
mean_py = sum(py_data) / len(py_data)
var_py = sum((x - mean_py) ** 2 for x in py_data) / len(py_data)
std_py = math.sqrt(var_py)
elapsed_py = time.time() - t0
# Memory estimate for list
mem_py = sys.getsizeof(py_data) + size * 24 # ~24 bytes per float object
# ========== NumPy ==========
np_data = np.random.random(size)
t0 = time.time()
mean_np = np.mean(np_data)
std_np = np.std(np_data)
elapsed_np = time.time() - t0
# Memory for ndarray
mem_np = np_data.nbytes
# ========== Results ==========
print(f"--- Pure Python ---")
print(f" Mean: {mean_py:.6f} Std: {std_py:.6f}")
print(f" Time: {elapsed_py:.3f}s")
print(f" Memory: ~{mem_py / 1024 / 1024:.0f} MB")
print(f"--- NumPy ---")
print(f" Mean: {mean_np:.6f} Std: {std_np:.6f}")
print(f" Time: {elapsed_np:.3f}s")
print(f" Memory: {mem_np / 1024 / 1024:.1f} MB")
print(f"--- Comparison ---")
print(f" Speedup: {elapsed_py / elapsed_np:.1f}x")
print(f" Memory saving: {mem_py / mem_np:.1f}x")
# Typical output:
# --- Pure Python ---
# Mean: 0.500032 Std: 0.288675
# Time: 3.200s
# Memory: ~305 MB
# --- NumPy ---
# Mean: 0.500032 Std: 0.288675
# Time: 0.030s
# Memory: 76.3 MB
# --- Comparison ---
# Speedup: 106.7x
# Memory saving: 4.0x
> Output: Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
| Metric | Pure Python | NumPy | Comparison |
|---|---|---|---|
| Lines of code (core logic) | 4 lines | 2 lines | NumPy uses 50% fewer |
| Execution time | ~3.2s | ~0.03s | ~100x faster |
| Memory usage | ~305 MB | ~76 MB | 4x less |
Bottom line: The larger your data, the more NumPy shines. At 10 million data points, NumPy crushes pure Python in both speed and memory.
❓ FAQ
array module only supports 1D homogeneous arrays with no broadcasting, linear algebra, or FFT capabilities. NumPy's ndarray supports multi-dimensional data, broadcasting, and a rich set of mathematical functions — they're in completely different leagues.📖 Summary
- Python lists have two pain points for numerical computing: high loop interpretation overhead and large memory per element (full objects)
- NumPy achieves ~160x speedup on 1 million data points through C-level implementation + contiguous memory + vectorization
- ndarray is a homogeneous, fixed-size, contiguous multi-dimensional array, using about 6x less memory than Python lists
- Broadcasting lets arrays of different shapes operate together directly — code is the formula
- NumPy is the foundation of the Python scientific computing ecosystem: Pandas, Scikit-learn, SciPy, TensorFlow, and many more all depend on it
- Install with
pip install numpy, create arrays withnp.array(), check config withnp.show_config()
📝 Exercises
-
Beginner (Difficulty ⭐): Install NumPy and run
import numpy as np; print(np.__version__). Make sure the version is ≥ 1.24. Record the output. -
Intermediate (Difficulty ⭐⭐): Create a list and an ndarray, each containing 5 million random numbers. Use list comprehension and NumPy vectorization to compute the square of each number plus 10. Time both approaches with
time.time()and compare. Record the speed ratio. -
Advanced (Difficulty ⭐⭐⭐): Run
np.show_config()and identify the BLAS backend your NumPy uses (OpenBLAS / MKL / other). Research how this backend affects matrix operation performance and write a brief explanation.