Level: Intermediate / Advanced
Versions: Python 3.12 · NumPy 1.26 · Google Colab
Table of Contents
- Course Overview
- Exploring Array Copy and View in NumPy
- Working with Matrices Using the NumPy Matrix Library
- Performing Complex Calculations Using NumPy Linear Algebra
- Conceptual Diagrams
- Summary and Resources
1. Course Overview
This course covers advanced array operations with NumPy, going beyond simple element manipulation. Three major topics are addressed:
| Module | Topic | Duration |
|---|---|---|
| 1 | Exploring Array Copy and View | 13m 23s |
| 2 | Working with Matrices (matrix library) | 18m 45s |
| 3 | Linear Algebra with numpy.linalg | 14m 43s |
Running scenario: A fictional company called Globomantics wants to build financial portfolios, compute risk/return metrics, and apply optimization techniques — all in NumPy.
2. Exploring Array Copy and View in NumPy
2.1 Business Context and Prerequisites
In data science projects, memory management is critical. Understanding the difference between copy and view allows you to:
- Preserve the integrity of original data
- Optimize memory consumption
- Avoid subtle bugs caused by unintended data mutation
Prerequisites: Python basics (lists, numpy.array), basic memory management concepts.
2.2 Concept: Copy vs View
Analogy: You are preparing a certification with a friend.
Option A — Array COPY (deep copy)
┌─────────────────────────────────────────────────────┐
│ You photocopy the relevant pages of the book │
│ → Your friend receives an INDEPENDENT copy │
│ → Their annotations do NOT affect your original │
└─────────────────────────────────────────────────────┘
Option B — Array VIEW (shallow copy)
┌─────────────────────────────────────────────────────┐
│ You lend the book itself │
│ → You SHARE the same content │
│ → Their annotations appear in your book too │
└─────────────────────────────────────────────────────┘
When to use COPY:
- Preserving original data (guaranteed integrity)
- Data augmentation in machine learning / computer vision
- Parallel processing in distributed environments
- Debugging and state snapshots
When to use VIEW:
- Resource-sensitive applications (mobile devices, embedded systems)
- Real-time streaming (always working on the most recent data)
- Slicing large arrays without duplicating memory
Memory visualization:
COPY:
original_array ──→ [Memory Block A: 1, 2, 3, 4, 5]
copied_array ──→ [Memory Block B: 1, 2, 3, 4, 5] ← independent copy
VIEW:
original_array ──→ [Memory Block A: 1, 2, 3, 4, 5]
▲
view_array ─────────────┘ ← same memory
2.3 Demo: Array Copy (deep copy)
import numpy as np
# Creating the original array
source_array = np.array([10, 20, 30, 40, 50])
# Deep copy — numpy.copy()
cloned_array = source_array.copy()
print("Original:", source_array) # [10 20 30 40 50]
print("Clone: ", cloned_array) # [10 20 30 40 50]
# Modifying the original
source_array[0] = 99
print("After modification:")
print("Original:", source_array) # [99 20 30 40 50]
print("Clone: ", cloned_array) # [10 20 30 40 50] ← unchanged
# .base attribute — who owns the memory?
print("source_array.base:", source_array.base) # None (owns its memory)
print("cloned_array.base:", cloned_array.base) # None (owns its memory)
Key result:
cloned_array.basereturnsNone→ the copy owns its own memory block.
2.4 Demo: Array View (shallow copy)
import numpy as np
source_array = np.array([10, 20, 30, 40, 50])
# Shallow copy — numpy.view()
view_array = source_array.view()
print("Original:", source_array) # [10 20 30 40 50]
print("View: ", view_array) # [10 20 30 40 50]
# Modifying the original
source_array[0] = 99
print("After modification:")
print("Original:", source_array) # [99 20 30 40 50]
print("View: ", view_array) # [99 20 30 40 50] ← reflects the change!
# .base attribute
print("source_array.base:", source_array.base) # None
print("view_array.base: ", view_array.base) # [99 20 30 40 50] → shared memory
Key result:
view_array.basereturns the original array → they share the same memory block.
2.5 Copy vs View Reference Table
| Criterion | array.copy() | array.view() |
|---|---|---|
| Type | Deep copy | Shallow copy |
| Memory | New block allocated | Same memory block |
| Changes propagated | No | Yes |
array.base | None | Reference to original |
| Performance | Slower (allocation) | Faster |
| Typical use | Data preservation | Efficient slicing |
| ML use case | Data augmentation | Feature slicing |
3. Working with Matrices Using the NumPy Matrix Library
3.1 Data Science Use Cases
Matrices are fundamental in data science because they natively represent tabular data:
Dataset (features × samples):
feature_1 feature_2 feature_3
sample_1 [ 0.5 1.2 0.8 ]
sample_2 [ 1.1 0.3 2.1 ]
sample_3 [ 0.9 1.8 1.5 ]
Key applications:
- Feature engineering: matrix multiplication, inversion, eigenvalues, eigenvectors
- Dimensionality reduction: SVD (Singular Value Decomposition)
- Deep learning: neuron weights and biases = matrices; forward/backward propagation = matrix multiplications
- NLP: numerical text representations (TF-IDF matrices, word embeddings)
3.2 Matrix Creation Functions
numpy.matlib — function categories:
┌─────────────────────────────────────────────────────────┐
│ CREATION │
│ ├── mat(data) → matrix from array-like │
│ └── asmatrix(data) → convert to matrix │
├─────────────────────────────────────────────────────────┤
│ INITIALIZATION │
│ ├── zeros(shape) → matrix of zeros │
│ ├── ones(shape) → matrix of ones │
│ ├── eye(n) → n×n identity matrix │
│ └── empty(shape) → uninitialized matrix (random) │
└─────────────────────────────────────────────────────────┘
Important:
empty()does not produce zeros — it returns random values (memory residuals). Always usezeros()when zero initialization is required.
3.3 Demo: Creating Matrices
import numpy.matlib
import numpy as np
# 3×3 zero matrix of type int
zeros_mat = numpy.matlib.zeros((3, 3), dtype=int)
print(zeros_mat)
# [[0 0 0]
# [0 0 0]
# [0 0 0]]
# 3×3 ones matrix of type int
ones_mat = numpy.matlib.ones((3, 3), dtype=int)
print(ones_mat)
# [[1 1 1]
# [1 1 1]
# [1 1 1]]
# 2×2 empty matrix of type string
empty_str = numpy.matlib.empty((2, 2), dtype=str)
print(empty_str)
# [[''] ['']]
# [[''] ['']]
# 2×2 empty matrix of type int — WARNING: unpredictable values!
empty_int = numpy.matlib.empty((2, 2), dtype=int)
print(empty_int)
# Unpredictable values (e.g.: [[140234 0] [0 140234]])
# 3×3 identity matrix
identity = numpy.matlib.eye(3, dtype=int)
print(identity)
# [[1 0 0]
# [0 1 0]
# [0 0 1]]
3.4 Demo: Matrix Manipulation (stack, split, insert, delete)
import numpy as np
# Two base matrices
even_matrix = np.matrix([[2, 4, 6],
[8, 10, 12]]) # shape: (2, 3)
odd_matrix = np.matrix([[1, 3, 5],
[7, 9, 11]]) # shape: (2, 3)
# --- STACKING ---
# hstack: horizontal concatenation (axis 1)
h_stacked = np.hstack((even_matrix, odd_matrix))
print("hstack:", h_stacked.shape) # (2, 6)
# [[ 2 4 6 1 3 5]
# [ 8 10 12 7 9 11]]
# vstack: vertical concatenation (axis 0)
v_stacked = np.vstack((even_matrix, odd_matrix))
print("vstack:", v_stacked.shape) # (4, 3)
# [[ 2 4 6]
# [ 8 10 12]
# [ 1 3 5]
# [ 7 9 11]]
# --- SPLITTING ---
# hsplit: horizontal split (back to original matrices)
h_split = np.hsplit(h_stacked, 2)
print("hsplit[0]:", h_split[0]) # even_matrix
print("hsplit[1]:", h_split[1]) # odd_matrix
# vsplit: vertical split
v_split = np.vsplit(v_stacked, 2)
print("vsplit[0]:", v_split[0]) # even_matrix
print("vsplit[1]:", v_split[1]) # odd_matrix
hstack vs vstack visualization:
even_matrix odd_matrix
[2 4 6] [1 3 5]
[8 10 12] [7 9 11]
hstack → [2 4 6 | 1 3 5 ] ← axis 1 (columns)
[8 10 12 | 7 9 11 ]
vstack → [2 4 6 ]
[8 10 12 ] ← axis 0 (rows)
[1 3 5 ]
[7 9 11 ]
3.5 Demo: Arithmetic Operations on Matrices
import numpy as np
A = np.matrix([[2, 3, 4],
[5, 6, 7]])
B = np.matrix([[8, 9, 10],
[11, 12, 13]])
# Element-wise addition
print(np.add(A, B))
# [[10 12 14]
# [16 18 20]]
# Element-wise subtraction
print(np.subtract(A, B))
# [[-6 -6 -6]
# [-6 -6 -6]]
# Element-wise multiplication (Hadamard product)
print(np.multiply(A, B))
# [[16 27 40]
# [55 72 91]]
# Matrix multiplication (dot product)
# Requires compatible dimensions: (m×n) · (n×p) = (m×p)
A_sq = np.matrix([[1, 2],
[3, 4]]) # (2×2)
B_sq = np.matrix([[5, 6],
[7, 8]]) # (2×2)
print(np.dot(A_sq, B_sq))
# [[19 22]
# [43 50]]
# Manual verification:
# [0,0] = 1*5 + 2*7 = 5 + 14 = 19 ✓
# [0,1] = 1*6 + 2*8 = 6 + 16 = 22 ✓
Dot product — visualization:
A B A·B
[1 2] × [5 6] = [19 22]
[3 4] [7 8] [43 50]
Calculation [0,0]: row 0 of A · column 0 of B
[1, 2] · [5, 7] = 1×5 + 2×7 = 19
3.6 Matrix Operations Reference Table
| Operation | NumPy Function | Description |
|---|---|---|
| Addition | np.add(A, B) | Element-wise, identical shapes required |
| Subtraction | np.subtract(A, B) | Element-wise |
| Element-wise multiply | np.multiply(A, B) | Hadamard product |
| Dot product | np.dot(A, B) | Classic matrix multiplication |
| Horizontal stack | np.hstack((A, B)) | Concatenate on axis 1 |
| Vertical stack | np.vstack((A, B)) | Concatenate on axis 0 |
| Horizontal split | np.hsplit(M, n) | Split into n equal parts on axis 1 |
| Vertical split | np.vsplit(M, n) | Split into n equal parts on axis 0 |
| Zeros | np.matlib.zeros((r,c)) | Initialize with zeros |
| Ones | np.matlib.ones((r,c)) | Initialize with ones |
| Identity | np.matlib.eye(n) | Diagonal = 1, rest = 0 |
| Empty | np.matlib.empty((r,c)) | Uninitialized — random values |
4. Performing Complex Calculations Using NumPy Linear Algebra
4.1 Finance Use Cases
numpy.linalg is at the core of modern financial applications:
| Domain | Application | linalg Functions |
|---|---|---|
| Portfolio optimization | Mean-variance (Markowitz), CAPM | solve(), inv(), eig() |
| Risk management | VaR, CVaR | Matrix operations |
| Monte Carlo | Scenario simulation | cholesky(), svd() |
| Option pricing | Black-Scholes, stochastic models | solve(), norm() |
| Credit scoring | Logistic regression, PCA | eig(), svd() |
| Algo trading | Time series analysis | lstsq() |
4.2 Categories of linalg Functions
numpy.linalg — organization:
┌────────────────────────────────────────────────────────┐
│ 1. MATRIX & VECTOR PRODUCTS │
│ dot(), multi_dot(), vdot(), inner(), outer() │
├────────────────────────────────────────────────────────┤
│ 2. DECOMPOSITIONS │
│ svd() → Singular Value Decomposition │
│ cholesky() → Cholesky decomposition │
│ qr() → QR decomposition │
├────────────────────────────────────────────────────────┤
│ 3. EIGENVALUES │
│ eig() → eigenvalues + eigenvectors │
│ eigvals() → eigenvalues only │
├────────────────────────────────────────────────────────┤
│ 4. NORMS & METRICS │
│ matrix_rank() → matrix rank │
│ trace() → diagonal sum │
│ det() → determinant │
│ norm() → vector/matrix norm │
├────────────────────────────────────────────────────────┤
│ 5. SOLVING EQUATIONS │
│ solve() → solves Ax = b │
│ lstsq() → least squares │
│ inv() → matrix inverse │
└────────────────────────────────────────────────────────┘
4.3 Demo: Rank, Trace, Determinant and Eigenvalues
import numpy as np
# 3×3 demo matrix
# Row 3 = sum of first two rows → rank = 2
M = np.array([[1, 2, 3],
[4, 5, 6],
[5, 7, 9]]) # row 3 = row 1 + row 2
# --- RANK ---
rank = np.linalg.matrix_rank(M)
print("Rank:", rank) # 2
# Visual explanation:
# Row 1: [1, 2, 3]
# Row 2: [4, 5, 6]
# Row 3: [5, 7, 9] = [1+4, 2+5, 3+6] → linearly dependent
# → only 2 linearly independent rows → rank = 2
# --- TRACE ---
# Matrix with identifiable diagonal
M2 = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 10]])
# Diagonal: 1 + 5 + 10 = 16
trace_val = np.trace(M2)
print("Trace:", trace_val) # 16
# --- DETERMINANT ---
det_val = np.linalg.det(M2)
print("Determinant:", round(det_val, 4)) # -3.0 (approx.)
# --- EIGENVALUES & EIGENVECTORS ---
eigenvalues, eigenvectors = np.linalg.eig(M2)
print("Eigenvalues:", eigenvalues)
print("Eigenvectors:\n", eigenvectors)
Visualization — Diagonal and Trace:
M2 = [[ 1 2 3 ]
[ 4 [5] 6 ] ← diagonal
[ 7 8 [10]]
Trace = 1 + 5 + 10 = 16
Eigenvalues:
┌──────────────────────────────────────────────────┐
│ For each eigenvalue λ: M·v = λ·v │
│ v = corresponding eigenvector │
│ │
│ Utility in Data Science: │
│ • PCA: eigenvalues → explained variance │
│ • Stability of dynamic systems │
│ • Spectral analysis of graphs │
└──────────────────────────────────────────────────┘
4.4 Demo: Solving Linear Equations
Problem: Solve Y = m·X to find m, where X and Y are known matrices.
import numpy as np
# Known data
matrix_X = np.array([[3, 1],
[2, 4]]) # coefficients
matrix_Y = np.array([7, 12]) # results
# ─── APPROACH 1: Manual (2 steps) ───────────────────────────────────────────
# Step 1: Compute the inverse of X
X_inverse = np.linalg.inv(matrix_X)
print("Inverse of X:\n", X_inverse)
# Step 2: Dot product of X⁻¹ and Y → solves for m
solution_manual = np.dot(X_inverse, matrix_Y)
print("Solution (manual):", solution_manual)
# ─── APPROACH 2: numpy.linalg.solve() — recommended ─────────────────────────
solution_solve = np.linalg.solve(matrix_X, matrix_Y)
print("Solution (solve):", solution_solve)
# Both approaches produce the same result
# Verification: X · solution ≈ Y
print("Verification X·m:", np.dot(matrix_X, solution_solve)) # should ≈ Y
Mathematical resolution — visualization:
Y = m · X
[ 7] = m · [3 1]
[12] [2 4]
→ m = X⁻¹ · Y
X⁻¹ = 1/det(X) · adj(X)
det(X) = 3×4 - 1×2 = 10
X⁻¹ = (1/10) · [[ 4 -1]
[-2 3]]
m = X⁻¹ · Y = ...
4.5 linalg Reference Table
| Function | Syntax | Returns | Usage |
|---|---|---|---|
matrix_rank | np.linalg.matrix_rank(M) | Integer | Number of linearly independent rows/columns |
trace | np.trace(M) | Scalar | Diagonal sum |
det | np.linalg.det(M) | Scalar | Determinant (0 = singular matrix) |
inv | np.linalg.inv(M) | Matrix | Inverse of M (requires det ≠ 0) |
eig | np.linalg.eig(M) | (values, vectors) | Eigenvalues and eigenvectors |
eigvals | np.linalg.eigvals(M) | Array | Eigenvalues only |
solve | np.linalg.solve(A, b) | Array | Solves Ax = b (recommended) |
lstsq | np.linalg.lstsq(A, b) | Tuple | Least squares (overdetermined system) |
svd | np.linalg.svd(M) | (U, s, Vh) | Singular Value Decomposition |
cholesky | np.linalg.cholesky(M) | Matrix | Cholesky decomposition |
norm | np.linalg.norm(M) | Scalar | Norm (Frobenius by default) |
dot | np.dot(A, B) | Array/Matrix | Matrix product |
5. Conceptual Diagrams
5.1 Copy vs View — Memory Flow
flowchart TD
A[source_array\nMemory Block A] -->|array.copy| B[cloned_array\nMemory Block B\nnew allocation]
A -->|array.view| C[view_array\nPoints to Memory A]
A2[Modify source_array] --> D{Type?}
D -->|copy| E["cloned_array\nNOT modified\n.base → None"]
D -->|view| F["view_array\nMODIFIED\n.base → source_array"]
style B fill:#4CAF50,color:#fff
style C fill:#FF9800,color:#fff
style E fill:#4CAF50,color:#fff
style F fill:#FF9800,color:#fff
5.2 Matrix Stacking Operations
flowchart LR
A["Matrix A\n2×3"] --> H["hstack\nnp.hstack(A,B)"]
B["Matrix B\n2×3"] --> H
H --> HR["Result\n2×6\naxis=1"]
A2["Matrix A\n2×3"] --> V["vstack\nnp.vstack(A,B)"]
B2["Matrix B\n2×3"] --> V
V --> VR["Result\n4×3\naxis=0"]
style HR fill:#2196F3,color:#fff
style VR fill:#9C27B0,color:#fff
5.3 Linear Algebra Pipeline — System Solving
flowchart TD
A["System of equations\nAx = b"] --> B{Method?}
B -->|"2 steps"| C["1. X_inv = np.linalg.inv(A)\n2. x = np.dot(X_inv, b)"]
B -->|"1 step (recommended)"| D["x = np.linalg.solve(A, b)"]
C --> E["Solution x"]
D --> E
E --> F["Verification\nnp.dot(A, x) ≈ b"]
style D fill:#4CAF50,color:#fff
style E fill:#2196F3,color:#fff
5.4 numpy.linalg Operations Hierarchy
mindmap
root((numpy.linalg))
Products
dot
multi_dot
inner
outer
Decompositions
svd
cholesky
qr
Eigenvalues
eig
eigvals
Metrics
matrix_rank
trace
det
norm
Equations
solve
lstsq
inv
6. Summary and Resources
What You Learned
| Module | Key Concepts | Functions Used |
|---|---|---|
| Copy & View | Deep copy vs shallow copy, base attribute, memory management | array.copy(), array.view(), .base |
| Matrix Library | Creation, initialization, stack, split, arithmetic operations | zeros(), ones(), eye(), hstack(), vstack(), hsplit(), vsplit(), add(), subtract(), multiply(), dot() |
| Linear Algebra | Rank, trace, determinant, eigenvalues, equation solving | matrix_rank(), trace(), det(), eig(), inv(), solve() |
Key Takeaways
array.copy()→ deep copy, new memory block,.base=Nonearray.view()→ shallow copy, shared memory,.base= original arraynp.matlib.empty()does not produce zeros — usezeros()when needed- Dot product ≠ element-wise multiply:
np.dot(A, B)vsnp.multiply(A, B) np.linalg.solve(A, b)is preferred overinv(A) · b(numerically more stable)- Rank of a matrix = number of linearly independent rows/columns
- Eigenvalues are fundamental in PCA and dynamic systems analysis
Resources
| Resource | Link |
|---|---|
| Official NumPy documentation | numpy.org/doc |
| NumPy tutorials | numpy.org/learn |
| Practice environment | Google Colab |
Search Terms
operations · arrays · numpy · python · foundations · data · analysis · engineering · analytics · copy · matrix · view · array · linear · matrices · reference · algebra · cases · functions · linalg · resources · solving