This is fantastic, I will share it with students. A very minor comment:
>So, there’s a total of three types of vectors in NumPy: 1D arrays, 2D row vectors, and 2D column vectors.
I think you can have 'vectors' of arbitrarily high dimension, by adding empty axes, or doing, say, .reshape(-1, 1, 1, 1).
I would also tie this point with the rule for broadcasting that dimensions which do not match need to have a value of one. I'm endlessly confused about broadcasting, and find the explicit rule useful.
Alas, Numpy arrays are limited to thirty-two dimensions.
This is fine, because with high numbers of dimensions you really can't afford to store a dense-matrix representation in RAM and 32 is plenty for any low-dimensional problem.
> Yes, broadcasting is a cool thing, but the docs mostly explains 'how' but not 'why' or 'what for'.
I'd say that, even for the 'how', thinking "OK, I have a [1, 1, 2, 2, 1] and a [3, 2, 1, 2, 5] arrays, which means that element-wise multiplication would result in a [3, 2, 2, 2, 5] array" is very useful in reasoning about broadcasting.
Just relying on simple examples and trying to build an intuition for the general case (which is the route favored in the numpy docs, I'd say) doesn't quite work, at least for me.
>So, there’s a total of three types of vectors in NumPy: 1D arrays, 2D row vectors, and 2D column vectors.
I think you can have 'vectors' of arbitrarily high dimension, by adding empty axes, or doing, say, .reshape(-1, 1, 1, 1).
I would also tie this point with the rule for broadcasting that dimensions which do not match need to have a value of one. I'm endlessly confused about broadcasting, and find the explicit rule useful.