import numpy as np9 NumPy 2D-Arrays
(CSE331) Python for Data Science
A 2D array arranges values in rows and columns. We will use it both as a data table and as a matrix for numerical calculations.
1 Creating and Inspecting a 2D Array
Create a rectangular array from a list of equal-length lists. NumPy converts the values to a common dtype.
A = np.array([[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]])
print(A)
print(A.ndim) # 2
print(A.shape) # (3, 4): 3 rows, 4 columns
print(A.size) # 12: total number of elements
print(len(A)) # 3: length of the first axis, not total size[[ 1 2 3 4]
[ 5 6 7 8]
[ 9 10 11 12]]
2
(3, 4)
12
3
For an array with shape (rows, columns), axis 0 is the row dimension and axis 1 is the column dimension.
2 Indexing and Slicing
Use array[row, column]. Both positions start at zero.
print(A[1, 2]) # 7: second row, third column
print(A[2, -1]) # 12: last element in the third row
print(A[:2, :3]) # first two rows and first three columns7
12
[[1 2 3]
[5 6 7]]
A lone colon selects all positions along that dimension:
print(A[1:, :]) # second row onward, all columns
print(A[1, :]) # entire second row
print(A[:, 3]) # entire fourth column[[ 5 6 7 8]
[ 9 10 11 12]]
[5 6 7 8]
[ 4 8 12]
2.1 Keeping one or two dimensions
An integer index removes that dimension. A slice retains it, even if it selects only one row or column.
| Selection | Shape | Result |
|---|---|---|
A[1, :] |
(4,) |
1D array |
A[1:2, :] |
(1, 4) |
2D row |
A[:, 3] |
(3,) |
1D array |
A[:, 3:4] |
(3, 1) |
2D column |
print(A[:, 3].shape)
print(A[:, 3:4].shape)(3,)
(3, 1)
2.2 Changing part of an array
B = A.copy()
B[0, 0] = 99
B[:, 1] = 0
print(B)
print(A) # unchanged because B was copied[[99 0 3 4]
[ 5 0 7 8]
[ 9 0 11 12]]
[[ 1 2 3 4]
[ 5 6 7 8]
[ 9 10 11 12]]
Basic slices share data, as in Lecture 8. For an independent rectangular subset, use A[:2, :3].copy().
3 Shape and Reshaping
reshape() returns an array with a different shape. The total number of elements must remain the same. By default, values are read and placed in row order.
reshaped = A.reshape(2, 6)
print(reshaped)
print(reshaped.shape)
print(A.shape) # A itself still has shape (3, 4)[[ 1 2 3 4 5 6]
[ 7 8 9 10 11 12]]
(2, 6)
(3, 4)
Reshaping preserves the sequence of values, but may return a view or a copy. Do not assume the result has independent data; add .copy() if independence is needed.
3.1 Let NumPy infer one dimension
print(A.reshape(4, -1)) # 12 elements / 4 rows = 3 columns[[ 1 2 3]
[ 4 5 6]
[ 7 8 9]
[10 11 12]]
Only one dimension can be -1, and the inferred dimension must be a whole number. Reshaping this array to (5, -1) would fail.
3.2 A single row or column
v = np.array([3, 1, 4])
row = v.reshape(1, -1)
column = v.reshape(-1, 1)
print(row, row.shape)
print(column, column.shape)[[3 1 4]] (1, 3)
[[3]
[1]
[4]] (3, 1)
The shapes (3,), (1, 3), and (3, 1) are different and can behave differently in calculations.
4 Special Arrays
Many array constructors accept a shape tuple:
print(np.zeros((2, 3)))
print(np.ones((3, 2)))[[0. 0. 0.]
[0. 0. 0.]]
[[1. 1.]
[1. 1.]
[1. 1.]]
5 Elementwise Operations
Arrays of the same shape can be combined element by element:
x1 = np.array([[7, 0, 8],
[5, 6, 2]])
x2 = np.array([[2, 5, 3],
[7, 1, 4]])
print(x1 + x2)
print(x1 * x2)
print(x1**x2)[[ 9 5 11]
[12 7 6]]
[[14 0 24]
[35 6 8]]
[[ 49 0 512]
[78125 6 16]]
* multiplies corresponding elements. It is not matrix multiplication. A scalar also applies to every element, as in x1 + 10.
6 Summaries by Row or Column
A reduction combines values along an axis. The selected axis is removed from the output unless keepdims=True.
values = np.array([[1, 2, 3, 4],
[5, 6, 7, 8]])
print(np.sum(values)) # 36: all values
print(np.sum(values, axis=0)) # [6 8 10 12]: one sum per column
print(np.sum(values, axis=1)) # [10 26]: one sum per row36
[ 6 8 10 12]
[10 26]
| Argument | What is combined? | Result for shape (2, 4) |
|---|---|---|
| No axis specified | All elements | One value |
axis=0 |
Rows, within each column | Shape (4,) |
axis=1 |
Columns, within each row | Shape (2,) |
The same pattern applies to other summaries:
print(np.mean(values, axis=0))
print(np.mean(values, axis=1))
print(np.std(values, axis=0, ddof=1)) # sample SD for each column
print(np.prod(values, axis=0))[3. 4. 5. 6.]
[2.5 6.5]
[2.82842712 2.82842712 2.82842712 2.82842712]
[ 5 12 21 32]
6.1 Retaining the reduced dimension
row_means = np.mean(values, axis=1, keepdims=True)
print(row_means) # [[2.5], [6.5]]
print(row_means.shape) # (2, 1)[[2.5]
[6.5]]
(2, 1)
This shape is useful when subtracting each row’s mean from that row.
7 Broadcasting
Broadcasting lets compatible shapes participate in elementwise arithmetic. Compare dimensions from the right: sizes must match, or one must be 1. A missing leading dimension is treated as 1.
y = np.array([[3, 1, 0],
[5, 2, 7]])
row_offsets = np.array([[10], [20]]) # shape (2, 1)
column_weights = np.array([10, 20, 30]) # shape (3,)
print(y + row_offsets)
print(y * column_weights)[[13 11 10]
[25 22 27]]
[[ 30 20 0]
[ 50 40 210]]
The first calculation adds 10 to row 1 and 20 to row 2. The second multiplies columns by 10, 20, and 30.
| Shapes | Compatible? | Result shape |
|---|---|---|
(2, 3) and (3,) |
Yes | (2, 3) |
(2, 3) and (2, 1) |
Yes | (2, 3) |
(2, 3) and (2,) |
No | Error |
To apply one value per row, use a column shape such as (2, 1). Broadcasting avoids explicitly duplicating the smaller input, although the resulting calculation still requires memory.
8 Worked Example: Scores by Student and Quiz
Each row is a student and each column is a quiz, scored out of 100.
scores = np.array([[60, 70, 80],
[40, 50, 60],
[80, 85, 90],
[30, 40, 50]], dtype=float)
student_means = np.mean(scores, axis=1)
quiz_means = np.mean(scores, axis=0)
print("Student means:", student_means)
print("Quiz means:", quiz_means)Student means: [70. 50. 85. 40.]
Quiz means: [52.5 61.25 70. ]
Select whole rows using one Boolean value per student:
print(scores[student_means >= 60, :])[[60. 70. 80.]
[80. 85. 90.]]
A mask with the same shape as the full array selects individual elements and produces a 1D result:
print(scores[scores >= 80])[80. 80. 85. 90.]
Centre each quiz by subtracting its column mean:
centered = scores - quiz_means
print(centered)
print(np.mean(centered, axis=0)) # zeros, apart from possible roundoff[[ 7.5 8.75 10. ]
[-12.5 -11.25 -10. ]
[ 27.5 23.75 20. ]
[-22.5 -21.25 -20. ]]
[0. 0. 0.]
To centre each student’s row instead:
row_centered = scores - np.mean(scores, axis=1, keepdims=True)
print(row_centered)[[-10. 0. 10.]
[-10. 0. 10.]
[ -5. 0. 5.]
[-10. 0. 10.]]
9 Matrix Multiplication
9.1 Inner product of two vectors
u = np.array([2, 5, 1])
v = np.array([3, 4, 2])
print(np.dot(u, v)) # 2*3 + 5*4 + 1*2 = 2828
9.2 Multiplying matrices
If M has shape (m, k) and N has shape (k, n), their matrix product has shape (m, n). The inner dimensions must agree.
M = np.array([[1, 2], [3, 4]])
N = np.array([[5, 6, 7], [8, 9, 0]])
print(M @ N)
print(np.dot(M, N)) # same result for these 2D arrays[[21 24 7]
[47 54 21]]
[[21 24 7]
[47 54 21]]
Use @ for matrix multiplication. For 2D arrays it agrees with np.matmul() and np.dot(). These functions have different rules for higher-dimensional inputs, which are outside this lecture.
N @ M # incompatible inner dimensions: 3 and 2--------------------------------------------------------------------------- ValueError Traceback (most recent call last) Cell In[24], line 1 ----> 1 N @ M ValueError: matmul: Input operand 1 has a mismatch in its core dimension 0, with gufunc signature (n?,k),(k,m?)->(n?,m?) (size 2 is different from 3)
This deliberately produces a ValueError.
9.3 Matrix-vector multiplication
weights = np.array([0.2, 0.3, 0.5])
weighted_scores = scores @ weights
print(weighted_scores) # [73, 53, 86.5, 43][73. 53. 86.5 43. ]
The shape calculation is (4, 3) @ (3,), giving one weighted score per student, with shape (4,).
10 Other Matrix Operations
10.1 Transpose
print(M.T)
print(N.T.shape) # (3, 2)[[1 3]
[2 4]]
(3, 2)
Transpose swaps the two axes of a 2D array. For a 1D array, v.T still has shape (3,); use reshape(-1, 1) to create a 2D column.
10.2 Determinant and inverse
C = np.array([[2.0, 1.0],
[5.0, 3.0]])
print(np.linalg.det(C))
inverse_C = np.linalg.inv(C)
print(inverse_C)
print(C @ inverse_C) # approximately the identity matrix1.0000000000000009
[[ 3. -1.]
[-5. 2.]]
[[ 1.00000000e+00 2.22044605e-16]
[-8.88178420e-16 1.00000000e+00]]
Mathematically, a square matrix is invertible exactly when its determinant is nonzero. With floating-point data, testing det(C) == 0 is not a reliable numerical diagnostic; nearly singular matrices can also give inaccurate results. NumPy may raise LinAlgError for a singular matrix.
10.3 Solving a linear system
For \(C\mathbf{x}=\mathbf{b}\), solve directly rather than calculating the inverse explicitly:
\[ \begin{aligned} 2x_1+x_2 &= 5,\\ 5x_1+3x_2 &= 13. \end{aligned} \]
b = np.array([5.0, 13.0])
solution = np.linalg.solve(C, b)
print(solution) # [2. 1.]
print(np.allclose(C @ solution, b)) # True[2. 1.]
True
solve() requires a square, full-rank coefficient matrix for this unique-solution problem. np.allclose() checks whether all corresponding values are approximately equal, allowing for small floating-point differences.
10.4 Rank and trace
D = np.array([[1, 2, 3],
[2, 4, 6],
[1, 1, 1]])
print(np.linalg.matrix_rank(D)) # 2
print(np.trace(D)) # 62
6
Rank counts linearly independent rows or columns; NumPy estimates it numerically using a tolerance. For a square matrix, the trace is the sum of its main diagonal.
10.5 Eigenvalues and eigenvectors
F = np.array([[4.0, 2.0],
[1.0, 3.0]])
eigenvalues, eigenvectors = np.linalg.eig(F)
print(eigenvalues)
print(eigenvectors)[5. 2.]
[[ 0.89442719 -0.70710678]
[ 0.4472136 0.70710678]]
Eigenvectors are stored in columns. Column i corresponds to eigenvalues[i] and satisfies \(F\mathbf{v}_i=\lambda_i\mathbf{v}_i\).
v0 = eigenvectors[:, 0]
print(np.allclose(F @ v0, eigenvalues[0] * v0))True
Eigenvalue order and eigenvector signs may differ between valid outputs. General real matrices can have complex eigenvalues and eigenvectors.
| Operation | NumPy expression |
|---|---|
| Elementwise product | A * B |
| Matrix product | A @ B |
| Transpose | A.T |
| Determinant | np.linalg.det(A) |
| Inverse | np.linalg.inv(A) |
| Solve a square linear system | np.linalg.solve(A, b) |
| Rank | np.linalg.matrix_rank(A) |
| Trace | np.trace(A) |
| Eigenvalues and eigenvectors | np.linalg.eig(A) |
11 Stacking Arrays
For 2D arrays, horizontal stacking joins columns and vertical stacking joins rows.
a1 = np.array([[11, 12], [13, 14], [15, 16]])
a2 = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
a3 = np.array([[-1, -2], [-3, -4]])
print(np.hstack([a1, a2])) # same number of rows
print(np.vstack([a1, a3])) # same number of columns[[11 12 1 2 3]
[13 14 4 5 6]
[15 16 7 8 9]]
[[11 12]
[13 14]
[15 16]
[-1 -2]
[-3 -4]]
concatenate() expresses the same operations using an axis:
print(np.concatenate([a1, a2], axis=1))
print(np.concatenate([a1, a3], axis=0))[[11 12 1 2 3]
[13 14 4 5 6]
[15 16 7 8 9]]
[[11 12]
[13 14]
[15 16]
[-1 -2]
[-3 -4]]
All dimensions except the joining axis must match. These calls return new arrays. For 1D inputs, hstack() joins the sequences into a longer 1D array; the row/column interpretation above applies to 2D inputs.
12 Exercises
- Create a
(3, 4)array containing integers 1 through 12. Display its second row, last column, and top-left(2, 2)block. - Select its first column as a 1D array and as a 2D column. Display both shapes.
- Reshape it to
(2, 6)and(4, 3). Use-1in one call. - Calculate its column means, row sums, and sample standard deviation within each column.
- Add
[10, 20, 30, 40]to its columns. Then add 100, 200, and 300 to its first, second, and third rows using broadcasting. - For the worked score data, select students whose average is at least 60. Calculate the mean score for each quiz among those students.
- Centre every row of the score array and verify that the resulting row means are approximately zero.
- With
P = np.array([[1, 2], [3, 4]])andQ = np.array([[2, 0], [1, 3]]), calculateP * Q,P @ Q, andQ @ P. Explain the differences. - Solve \(3x+y=9\) and \(x+2y=8\) using
np.linalg.solve(). Check the solution by multiplication. - For
H = np.array([[4, 1], [1, 3]]), obtain its transpose, determinant, inverse, rank, trace, and eigenvalues. Verify one eigenvector equation withnp.allclose(). - Join
a1anda2horizontally, anda1anda3vertically. State the resulting shapes before running the code.
13 Summary
- Read a 2D shape as
(rows, columns); indexing can remove a dimension. axis=0gives column summaries andaxis=1gives row summaries.- Inspect shapes before broadcasting, reshaping, stacking, or matrix multiplication.
- Distinguish
*from@; solve linear systems directly withnp.linalg.solve(). - Use approximate comparisons when checking floating-point results.