String broadcasting

So I was following a tutorial, but I got confused when they showed the following code:

vec(string.([“x”,“y”],[1 2 3 4]))
First, how am I supposed to infer the broadcasting of string given a vector and a matrix will return a matrix pairing each possible combinations?
Second, why with [1 2 3 4] the broadcasting works but it does not work with [1,2,3,4]? Aren’t the dimensions compatible?


Spaces create a 1-row matrix, commas or semicolons create a vector (~ 1-column matrix).

When a column and a row are broadcasted, the output shape is the same as the way a matrix multiplication works, then the vec flattens it into a vector.


So in general brosdcasting will form that resultant matrix from two separate arguments?

The broadcasting semantics (Single- and multi-dimensional Arrays · The Julia Language) are that vectors/matrices are treated as if they were arrays with singleton dimensions, then these dimensions are expanded to match. (There are some edge cases)

So, since the second argument is a 1x4 matrix (enter it in the REPL and see), the vector ["x", "y"] gets treated as a 2x1 matrix. Then the vector gets (virtually) expanded to a 2x4 matrix by repeating its columns and the matrix gets expanded to a 2x4 matrix by repeating the rows.

This produces the equivalent of the call

vec(string.(["x" "x" "x" "x"; "y" "y" "y" "y"], [1 2 3 4; 1 2 3 4]))

Here string is applied element-wise, so it’s equivalent to

vec(["x1" "x2" "x3" "x4"; "y1" "y2" "y3" "y4"])

which produces a vector of these elements.

As @BioTurboNick notes, this is equivalent to the semantics of multiplying a column-vector by a row-vector.

Also, please note that this only applies to arrays (and vectors/matrices), not general iterators. Iterators generally don’t have a shape, only a length, and so broadcasting across them just produces a flat array.


Yes. Here are some more examples of this in action:

julia> ["x", "y"] .^ [1, 2, 3, 4]' # I'm using transpose to get a row vector in this example
2×4 Matrix{String}:
 "x"  "xx"  "xxx"  "xxxx"
 "y"  "yy"  "yyy"  "yyyy"

julia> [10, 100] .+ [1 2; 3 4] # notice the second argument is a matrix already
2×2 Matrix{Int64}:
  11   12
 103  104

julia> [10, 100]' .+ [1 2; 3 4] # And it matters if the first is a column or row! notice the transpose this time
2×2 Matrix{Int64}:
 11  102
 13  104

julia> tuple.(0:0.5:1, (0:0.5:1)') # transpose of a range??? Yes.
3×3 Matrix{Tuple{Float64, Float64}}:
 (0.0, 0.0)  (0.0, 0.5)  (0.0, 1.0)
 (0.5, 0.0)  (0.5, 0.5)  (0.5, 1.0)
 (1.0, 0.0)  (1.0, 0.5)  (1.0, 1.0)

julia> LinearIndices((3,2,2)) .+ [0 100] # yes, even in higher dimensions!
3×2×2 Array{Int64, 3}:
[:, :, 1] =
 1  104
 2  105
 3  106

[:, :, 2] =
 7  110
 8  111
 9  112

Broadcasting is super powerful, but it does take some getting used to.

I don’t know where that tutorial you were following was, but it does seem like a pretty tricky example (was it somewhere in the official docs?)

Thank you. It was in the introduction to DataFrames video tutorial.