How to convert Vector of Vectors to Matrix

I think I come to an understanding of how the splatting command works. Thank you, it is very useful.

julia> hcat(x)
3×1 Matrix{Any}:
  [1.0, 3.0]
 2.0
 3.0

julia> hcat(x...)
ERROR: DimensionMismatch("mismatch in dimension 1 (expected 2 got 1)")
Stacktrace:
  [1] _cs
    @ ./abstractarray.jl:1626 [inlined]
  [2] _cshp
    @ ./abstractarray.jl:1622 [inlined]
  [3] _cat_size_shape(dims::Tuple{Bool, Bool}, shape::Tuple{Int64, Int64}, X::Float64, tail::Float64)
    @ Base ./abstractarray.jl:1602
  [4] cat_size_shape(::Tuple{Bool, Bool}, ::Vector{Float64}, ::Float64, ::Vararg{Float64, N} where N)
    @ Base ./abstractarray.jl:1600
  [5] _cat_t(::Val{2}, ::Type{Float64}, ::Vector{Float64}, ::Vararg{Any, N} where N)
    @ Base ./abstractarray.jl:1646
  [6] cat_t(::Type{Float64}, ::Vector{Float64}, ::Vararg{Any, N} where N; dims::Val{2})
    @ Base ./abstractarray.jl:1643
  [7] _cat
    @ ./abstractarray.jl:1641 [inlined]
  [8] #cat#129
    @ ./abstractarray.jl:1781 [inlined]
  [9] hcat(::Vector{Float64}, ::Float64, ::Float64)
    @ Base ./abstractarray.jl:1761
 [10] top-level scope
    @ none:1

julia> x=[[1., 3.], [2., 2.], [3., 3.]]
3-element Vector{Vector{Float64}}:
 [1.0, 3.0]
 [2.0, 2.0]
 [3.0, 3.0]

julia> hcat(x...)
2×3 Matrix{Float64}:
 1.0  2.0  3.0
 3.0  2.0  3.0
vov = [rand(0:1,100) for _ in 1:100_000]
r=size(vov)[1]
c=size(vov[1])[1]
reshape(reduce(vcat,vov),(r,c)) 

Hi all,
I came up with this one, where people can handle nested vectors of any level:

unsqueeze(a::Array) = begin 
    nsize = foldl(append!, size(a); init=[1])
    return reshape(a, Tuple(nsize))
end
stack(vs::Vector{T}) where T<:Vector = begin
    return reduce(vcat, map(unsqueeze, map(stack, vs)))
end
stack(vs::Vector{T}) where T<:Real = vs

some use cases:

julia> stack([[1,2,3], [1,2,3]])
2×3 Matrix{Int64}:
 1  2  3
 1  2  3

julia> stack([[[1],[2],[3]], [[1],[2],[3]]])
2×3×1 Array{Int64, 3}:
[:, :, 1] =
 1  2  3
 1  2  3

The idea is that just unsqueeze every array at the first dimension, then do vcat, and recursively repeat this over and over again, until the targeted array is returned.

cheers!

I wish Matrix( vector_of_vectors ) would result in the matrix
code would be so easy to read

5 Likes

Welcome to the Julia community in your first post, @alanedelman.

8 Likes

Right?! This would make so much sense! A vector of equal-size vectors is a matrix! Julia even displays vectors of vectors neatly by rows (well, all vectors are displayed by rows, but this looks a lot like a matrix):

julia> sol.u
1001-element Vector{Vector{Float64}}:
 [1.0, 0.0, 0.0]
 [1.2714553023510824, 2.5502478518250142, 0.1141384466168722]
 [3.9131447607344776, 8.433083437324328, 1.2683805363770986]
 [11.696231301993413, 22.993582131160995, 11.769786900084997]
 [19.698658008680177, 16.751621637382875, 45.82588214497926]
 [6.503654894191205, -8.508354690536336, 38.091997347142616]
 [-3.332076962123722, -8.698838751487166, 28.734916780144353]
 [-6.535890901038596, -8.425700745706362, 25.905899192608068]
...

Matrix(sol.u) is easy to read and easy to write: want to convert stuff to a matrix? - use the Matrix constructor!

But no, here we have 25 comments and a bunch of complex solutions:

  • mapreduce(permutedims, vcat, x) is complex because, arguably, mapreduce is an advanced concept from functional programming, big data and Apache Hadoop. This also requires knowing what mapreduce, permutedims and vcat do - all this for converting a vector of vectors into a matrix? IMO, it’s way too general.

  • permutedims(hcat(x...)) is complex because I need to know what permutedims and hcat do to use this. I also need to splat my potentially huge vector x and thus pass a million arguments to hcat. Indeed, “don’t use splatting of large arrays”.

    Yep, it’s not immediately clear what this code does. Matrix(x) won’t leave people wondering what’s going on.

  • reduce(vcat,transpose.(x)) again requires me to know three unrelated functions. Okay, transpose is simple because it does what its name says. But it’s not immediately clear that this incantation builds a matrix from a vector of vectors. Why write this convoluted code instead of Matrix(vec_of_vecs)??

  • reduce(hcat,x)' is basically the same as the previous point.

  • IMO, this is the most straightforward code in this thread. However, shouldn’t this be part of the standard library?

  • I find it disturbing that I can install a package to cleanly convert a vector of vectors to a matrix. It could simply be one of Matrix’s constructors.

3 Likes

Julia 1.9 (available in beta right now) has a new stack function:

a = [1,2,3]
b = [4,5,6]

julia> stack([a,b])
3×2 Matrix{Int64}:
 1  4
 2  5
 3  6

julia> stack([a,b], dims=1)
2×3 Matrix{Int64}:
 1  2  3
 4  5  6
10 Likes

stack is nice but Matrix is better, can we have both?
I wish almost every data structure could be "Matrix"ified to some default
even if it’s not what every user would want

it’s just give me a Matrix da** it function , i don’t want to look up a new command

9 Likes

This is actually really useful and seems to be by far the most straightforward way of doing this. This function actually tells me what it’s doing (stacking stuff), so it’s easy to read and write. Very nice!

Matrix(thing) would’ve been even better, though. It just makes more sense and makes code even more readable. Of course, “makes more sense”, “most straightforward” and “more readable” aren’t terribly objective arguments, but come on, Matrix(stuff) is easier to read, so why not have it?

I think there’s a kind of convention in Julia to design APIs like this:

  • Define a constructor such as Matrix for creating instances through a trivial operation (wrapping the arguments, initializing to a default value, etc.)
  • Define a function (with name in lowercase) for more complex operations. The function does the heavy work and eventually calls a constructor to create the instance.

I remember reading about this somewhere but a quick look at the manual didn’t yield anything.

Anyway I think it’s a good principle for Julia: it’s a great language to write generic code, in which case you don’t actually know the type of the result. So you cannot directly call a constructor such as Matrix because you don’t know if the result should be a Matrix or another fancy array type such as OffsetArray or ElasticArray or whatever. The type of the result depends on the type of the argument (is it a list of Vector, or vectors of some fancy type that has defined it’s own version of stack?).

Also, there may be other sensible ways to make a matrix from a list of vectors. For example, you could define a function to make a matrix from 3 vectors: a vector of values, a vector of i indices and a vector of j indices. Then should the Matrix function concatenate the vectors, or use them as values and indices for the new matrix? You don’t have this problem if you define stack for concatenation and matrix_from_vij (or whatever) for the second option.

There are trade offs of course. The worst is probably that it’s harder this way to find the “right” function for the job. For example, ideally the documentation for Matrix would point to all the relevant functions for creating matrices, but currently it only mentions fill, zeros, undef and similar.

2 Likes

I understand that you were an early user of MATLAB. :slight_smile: . Below is what you basically want, right?

julia> Base.Matrix(vv::Vector{Vector{T}}) where T = hcat(vv...)

julia> Base.Matrix(vm::Vector{Matrix{T}}) where T = vcat(vm...)

julia> Matrix([[1,2], [3, 4], [5, 6]])
2×3 Matrix{Int64}:
 1  3  5
 2  4  6

julia> Matrix([[1 2], [3 4], [5 6]])
3×2 Matrix{Int64}:
 1  2
 3  4
 5  6

julia> Matrix([[1 2 3], [3 4], [5 6]])
ERROR: ArgumentError: number of columns of each array must match (got (3, 2, 2))
...

julia> Matrix([[1, 2], [3, 4], [5, 6, 7]])
ERROR: DimensionMismatch: vectors must have same lengths
Stacktrace:
...
1 Like

yup
and this has nothing to do with Matlab and everything to do with ease of use

5 Likes

This combination seems pretty confusing to me. If you have a vector of vectors they are stacked horizontally, but if you have a vector of 1-column matrices they are stacked vertically?

A compromise would be to simply throw a more informative error:

Matrix(::AbstractVector{<:AbstractArray}) =
     throw(ArgumentError("Matrix(...) does not support vectors of arrays; perhaps you mean stack(...)?"))
3 Likes

This seems like the right way. Convenience functions like the requested could be a function matrix (lowercase), defined in the user’s own startup.jl, or maybe in a Convenience.jl package.

The idea was to stack row vectors (1xN matrices) vertically. That’s the inherent flaw in this though is that there is an ambiguity in both cases of how one wants to concatenate. I suppose we could take a dims parameter and just forward to cat.

We could then generalize this so that one could convert of a Vector of n-dimensional Arrays to a n+1 dimensional array.

julia> const NArray{N} = Array{T,N} where T
Array{T, N} where {N, T}

julia> function (NArray{N})(vector_of_arrays::Vector{Array{T,M}}; dims = 1) where {T, N,M}
           M <= N || throw(MethodError(NArray{N}, (vector_of_arrays,)))
           A = cat(vector_of_arrays...; dims)
           reshape(A, ntuple(i->i <= ndims(A) ? size(A,i) : 1,N))
       end

julia> Matrix([[1 2], [3 4], [5 6]])
3×2 Matrix{Int64}:
 1  2
 3  4
 5  6

julia> Matrix([[1 2], [3 4], [5 6]]; dims=2)
1×6 Matrix{Int64}:
 1  2  3  4  5  6

julia> NArray{3}([[1, 2], [3, 4], [5, 6]]; dims=1)
6×1×1 Array{Int64, 3}:
[:, :, 1] =
 1
 2
 3
 4
 5
 6

julia> NArray{3}([[1, 2], [3, 4], [5, 6]]; dims=2)
2×3×1 Array{Int64, 3}:
[:, :, 1] =
 1  3  5
 2  4  6

julia> NArray{3}([[1, 2], [3, 4], [5, 6]]; dims=3)
2×1×3 Array{Int64, 3}:
[:, :, 1] =
 1
 2

[:, :, 2] =
 3
 4

[:, :, 3] =
 5
 6

julia> NArray{3}([[1 2], [3 4], [5 6]]; dims=3)
1×2×3 Array{Int64, 3}:
[:, :, 1] =
 1  2

[:, :, 2] =
 3  4

[:, :, 3] =
 5  6

julia> NArray{3}([[1 2], [3 4], [5 6]]; dims=1)
3×2×1 Array{Int64, 3}:
[:, :, 1] =
 1  2
 3  4
 5  6

julia> NArray{3}([[1 2], [3 4], [5 6]]; dims=2)
1×6×1 Array{Int64, 3}:
[:, :, 1] =
 1  2  3  4  5  6

julia> NArray{0}(x::T) where T = fill!(NArray{0}{T}(undef),x)

julia> Vector([NArray{0}(6), NArray{0}(3)])
2-element Vector{Int64}:
 6
 3
1 Like

That’s what the stack function already does.

2 Likes

It’s perhaps not elegant, but how about this:

“”"

Convert a vector of vectors to an array

This function assumes that each vector is the same length;
i.e.
v1 = [ [1,2], [2,3], [5,6] ] # this is fine
v2 = [ [1,2,8], [2,3], [5,6] ] # this is not fine

Example of usage:

vv = [[1,2,3], [4,5,6], [7,8,9],[12,13,14]]
println("num components in each vector = ", length(vv[1]))
println("num of vectors = ", length(vv))
vecvec_to_array(vv)

“”"

function vecvec_to_array(vecvec)
    dim1 = length(vecvec)
    dim2 = length(vecvec[1])
    my_array = zeros(Int64, dim1, dim2)
    for i in 1:dim1
        for j in 1:dim2
            my_array[i,j] = vecvec[i][j]
        end
    end
    return my_array
end
2 Likes

Any way to do exactly the opposite? from matrix to a vector of vectors

eachrow()
eachcol()
6 Likes

Thanks!