12.3: Broadcasting
- Page ID
- 23847
\( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)
\( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)
\( \newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\)
( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\)
\( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\)
\( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\)
\( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\)
\( \newcommand{\Span}{\mathrm{span}}\)
\( \newcommand{\id}{\mathrm{id}}\)
\( \newcommand{\Span}{\mathrm{span}}\)
\( \newcommand{\kernel}{\mathrm{null}\,}\)
\( \newcommand{\range}{\mathrm{range}\,}\)
\( \newcommand{\RealPart}{\mathrm{Re}}\)
\( \newcommand{\ImaginaryPart}{\mathrm{Im}}\)
\( \newcommand{\Argument}{\mathrm{Arg}}\)
\( \newcommand{\norm}[1]{\| #1 \|}\)
\( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\)
\( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\AA}{\unicode[.8,0]{x212B}}\)
\( \newcommand{\vectorA}[1]{\vec{#1}} % arrow\)
\( \newcommand{\vectorAt}[1]{\vec{\text{#1}}} % arrow\)
\( \newcommand{\vectorB}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)
\( \newcommand{\vectorC}[1]{\textbf{#1}} \)
\( \newcommand{\vectorD}[1]{\overrightarrow{#1}} \)
\( \newcommand{\vectorDt}[1]{\overrightarrow{\text{#1}}} \)
\( \newcommand{\vectE}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash{\mathbf {#1}}}} \)
\( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)
\( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)
\(\newcommand{\avec}{\mathbf a}\) \(\newcommand{\bvec}{\mathbf b}\) \(\newcommand{\cvec}{\mathbf c}\) \(\newcommand{\dvec}{\mathbf d}\) \(\newcommand{\dtil}{\widetilde{\mathbf d}}\) \(\newcommand{\evec}{\mathbf e}\) \(\newcommand{\fvec}{\mathbf f}\) \(\newcommand{\nvec}{\mathbf n}\) \(\newcommand{\pvec}{\mathbf p}\) \(\newcommand{\qvec}{\mathbf q}\) \(\newcommand{\svec}{\mathbf s}\) \(\newcommand{\tvec}{\mathbf t}\) \(\newcommand{\uvec}{\mathbf u}\) \(\newcommand{\vvec}{\mathbf v}\) \(\newcommand{\wvec}{\mathbf w}\) \(\newcommand{\xvec}{\mathbf x}\) \(\newcommand{\yvec}{\mathbf y}\) \(\newcommand{\zvec}{\mathbf z}\) \(\newcommand{\rvec}{\mathbf r}\) \(\newcommand{\mvec}{\mathbf m}\) \(\newcommand{\zerovec}{\mathbf 0}\) \(\newcommand{\onevec}{\mathbf 1}\) \(\newcommand{\real}{\mathbb R}\) \(\newcommand{\twovec}[2]{\left[\begin{array}{r}#1 \\ #2 \end{array}\right]}\) \(\newcommand{\ctwovec}[2]{\left[\begin{array}{c}#1 \\ #2 \end{array}\right]}\) \(\newcommand{\threevec}[3]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \end{array}\right]}\) \(\newcommand{\cthreevec}[3]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \end{array}\right]}\) \(\newcommand{\fourvec}[4]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \\ #4 \end{array}\right]}\) \(\newcommand{\cfourvec}[4]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \\ #4 \end{array}\right]}\) \(\newcommand{\fivevec}[5]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \\ #4 \\ #5 \\ \end{array}\right]}\) \(\newcommand{\cfivevec}[5]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \\ #4 \\ #5 \\ \end{array}\right]}\) \(\newcommand{\mattwo}[4]{\left[\begin{array}{rr}#1 \amp #2 \\ #3 \amp #4 \\ \end{array}\right]}\) \(\newcommand{\laspan}[1]{\text{Span}\{#1\}}\) \(\newcommand{\bcal}{\cal B}\) \(\newcommand{\ccal}{\cal C}\) \(\newcommand{\scal}{\cal S}\) \(\newcommand{\wcal}{\cal W}\) \(\newcommand{\ecal}{\cal E}\) \(\newcommand{\coords}[2]{\left\{#1\right\}_{#2}}\) \(\newcommand{\gray}[1]{\color{gray}{#1}}\) \(\newcommand{\lgray}[1]{\color{lightgray}{#1}}\) \(\newcommand{\rank}{\operatorname{rank}}\) \(\newcommand{\row}{\text{Row}}\) \(\newcommand{\col}{\text{Col}}\) \(\renewcommand{\row}{\text{Row}}\) \(\newcommand{\nul}{\text{Nul}}\) \(\newcommand{\var}{\text{Var}}\) \(\newcommand{\corr}{\text{corr}}\) \(\newcommand{\len}[1]{\left|#1\right|}\) \(\newcommand{\bbar}{\overline{\bvec}}\) \(\newcommand{\bhat}{\widehat{\bvec}}\) \(\newcommand{\bperp}{\bvec^\perp}\) \(\newcommand{\xhat}{\widehat{\xvec}}\) \(\newcommand{\vhat}{\widehat{\vvec}}\) \(\newcommand{\uhat}{\widehat{\uvec}}\) \(\newcommand{\what}{\widehat{\wvec}}\) \(\newcommand{\Sighat}{\widehat{\Sigma}}\) \(\newcommand{\lt}{<}\) \(\newcommand{\gt}{>}\) \(\newcommand{\amp}{&}\) \(\definecolor{fillinmathshade}{gray}{0.9}\)In NumPy, broadcasting refers to the practice of performing operations on arrays of different shapes and sizes in a manner that "broadcasts" the smaller array across the larger one. This concept is particularly useful for efficient memory utilization and computational speed, as it eliminates the need to explicitly replicate the smaller array multiple times to match the shape of the larger array. Imagine you have a 2D array (often called a matrix) and you want to add a 1D array (a vector) to each row of that matrix. You could use a loop to manually add the vector to each row, but that wouldn't be computationally efficient. Broadcasting automates this process. When you add the matrix and the vector together, NumPy automatically understands that you intend to add the vector to each row of the matrix. The dimensions are implicitly expanded to align the shapes of the arrays being operated upon. This intrinsic feature of NumPy enables cleaner code, faster execution, and overall, a more elegant handling of mathematical operations between arrays.
For example, let's say you have a NumPy array representing grades for a class. In this array, each row corresponds to a student and each of the three columns corresponds to an exam. You've decided to add curve points to the exams: 3 points for the first exam, 5 points for the second, and 10 points for the third. You could accomplish this task using a for loop to iterate over each column and add the corresponding curve points to all the rows in that column. While this may not be the most efficient method, it serves as an instructive example for understanding basic array manipulations.
Here's how you could code this using a for loop:
import numpy as np
# Grades for 6 students over 3 exams
grades = np.array([[80, 85, 88],
[76, 80, 82],
[92, 94, 96],
[65, 70, 75],
[90, 85, 88],
[70, 75, 80]])
# Curve points for each exam
curve_points = np.array([3, 5, 10])
# Loop through each column (exam) and add curve points
for i in range(len(curve_points)):
grades[:, i] = grades[:, i] + curve_points[i]
print("Curved grades:")
print(grades)
output:
[[ 83 90 98]
[ 79 85 92]
[ 95 99 106]
[ 68 75 85]
[ 93 90 98]
[ 73 80 90]]
In this example, the `for` loop iterates through each of the three columns of the `grades` array and adds the curve points to all the rows in that column.
In NumPy, the concept of broadcasting allows you to elegantly and efficiently apply operations across arrays of different shapes. When adding curve points to each exam in our example, you can take advantage of broadcasting to sidestep explicit looping, thereby enhancing computational efficiency. You have a 2D array of grades with 6 rows (students) and 3 columns (exams), and you want to add an array of curve points with shape `(3,)` to each row.
Here's how you could do this using broadcasting:
import numpy as np
# Grades for 6 students over 3 exams
grades = np.array([[80, 85, 88],
[76, 80, 82],
[92, 94, 96],
[65, 70, 75],
[90, 85, 88],
[70, 75, 80]])
# Curve points for each exam
curve_points = np.array([3, 5, 10])
# Add curve points to grades using broadcasting
grades = grades + curve_points
print("Curved grades:")
print(grades)
This generates the same output!
In this example, the `curve_points` array has shape `(3,)`, and the `grades` array has shape `(6, 3)`. When you perform the operation `grades = grades + curve_points`, NumPy takes the 3-element array and reshapes it to a 1 by 3 array. Then, it can copy the rows until it's a 6 by 3 array and can directly add the two arrays.
In other words, the dimensions of the 3-element array are automatically reconciled so that the `curve_points` array is virtually stretched, or "broadcast," over the shape of the `grades` array, allowing the addition to occur element-wise across all rows for each column.
Thus, broadcasting provides a more efficient, less verbose, and arguably more readable way to perform operations like this compared to traditional for-loop constructs.FM
Broadcasting is one of the hardest concepts in NumPy, so if you feel a bit unsure, don't worry about it. Here's a video you can watch that will help you understand this. And here's another one.