Skillversitas

Arithmetic Operations

Arithmetic on arrays is elementwise by default: a + b adds corresponding elements, and the loop runs in C over contiguous memory. On top of that sit the in-place forms, the aggregation family (sums and means along axes), and the linear-algebra products (dot, matmul) - the four groups that cover the numerical work of most applications. The chapter's through-line is the axis argument, which is how one-liners replace what would be nested Python loops.

Elementwise Arithmetic

import numpy as np

a = np.array([1, 2, 3, 4])
b = np.array([10, 20, 30, 40])

print(a + b)          # [11 22 33 44]
print(a - b)          # [-9 -18 -27 -36]
print(a * b)          # [10 40 90 160] - elementwise, NOT a dot product
print(a / b)          # [0.1 0.1 0.1 0.1]
print(a ** 2)         # [1 4 9 16]
print(-a)             # [-1 -2 -3 -4]
print(2 * a + 1)      # [3 5 7 9] - scalar mixing, elementwise
print(a % 3)          # [1 2 0 1]
print(a // 2)         # [0 1 1 2] - floor division

Operators map to ufuncs (the next chapter): + is np.add, * is np.multiply, and so on. The critical distinction: * is elementwise multiplication, not a dot product - the product lives in dot/@. Type promotion applies: int + float gives float, and the result dtype is the wider of the two.

In-Place Operations

a = np.arange(5, dtype=float)
a += 1                 # a is modified in place, no temporary
a *= 2
a **= 0.5

# the explicit form with an output buffer - no allocation at all
out = np.empty(4)
np.add(a[:4], b, out=out)

# caution: in-place with a different dtype can lose data
c = np.array([1, 2, 3])
c += np.array([0.5, 0.5, 0.5])   # ValueError in recent NumPy (casting rule)
c = c.astype(float); c += 0.5    # convert first

The augmented-assignment forms (+=, *=) write into the left operand's buffer, avoiding the temporary that a normal expression allocates. The explicit out= parameter on ufuncs goes further: it names the destination array, which is the zero-allocation form for hot loops. The casting rule is strict - an in-place operation cannot silently narrow (float into int raises or truncates per the casting mode), which is a feature: it makes dtype accidents loud.

Aggregation Along Axes

m = np.arange(12).reshape(3, 4)
print(m)
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

print(m.sum())            # 66 - all elements
print(m.sum(axis=0))      # [12 15 18 21] - down the rows (per column)
print(m.sum(axis=1))      # [ 6 22 38] - across the columns (per row)
print(m.mean(axis=1))     # [1.5 5.5 9.5]
print(m.max(axis=0))      # [8 9 10 11]
print(m.argmax(axis=0))   # [2 2 2 2] - positions of the maxima
print(m.sum(axis=1, keepdims=True))   # [[ 6] [22] [38]] - shape preserved

The axis argument is the concept to internalize: it names the axis that gets collapsed. axis=0 sums down (the result has one entry per column), axis=1 sums across (one per row). The full family - sum, mean, std, var, min, max, argmin, argmax, prod, median, percentile - takes the same axis (and tuple-of-axes) parameter, and keepdims=True keeps the collapsed axis as length 1 so the result still broadcasts against the input - the standard shape-preserving trick.

Dot and Matrix Products

v = np.array([1, 2, 3])
w = np.array([4, 5, 6])
print(v.dot(w))           # 32 - the scalar dot product
print(v @ w)              # 32 - the @ operator, same thing

A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
print(A @ B)              # [[19 22] [43 50]] - matrix product
print(A.dot(B))           # same

# batched products: (n, k, p) @ (n, p, m) -> (n, k, m)
X = np.random.default_rng(0).normal(size=(4, 3, 2))
W = np.random.default_rng(1).normal(size=(4, 2, 5))
print((X @ W).shape)      # (4, 3, 5)

@ (and np.matmul) is the modern matrix product: 1-D x 1-D is a dot, 2-D x 2-D a matrix product, and N-D inputs are treated as stacks of matrices with the last two axes as the matrix dimensions and the leading axes batched. np.dot is the older, more general function (it sums over the last axis of the first and the second-to-last of the second, and collapses one dimension). For matrix algebra, reach for @ and let the linalg chapter handle the rest.

Summary

Arithmetic is elementwise: operators apply to every pair of corresponding elements, with out= and augmented assignment for the zero-allocation forms. Aggregation is one call with an axis - the axis that gets collapsed - and keepdims when the result must keep broadcasting. Products live in @/dot, never in *. These four groups - elementwise, in-place, axis aggregation, products - are the computation model; everything else in the library is built on them.