I suppose that
f1(dfs) = cat(Matrix.(dfs)..., dims=3)
is a reasonably elegant one-liner, but it allocates temporaries.
From a speed perspective you can probably beat it easily with the following one-liner
f2(dfs) = [ dfs[k][n,m] for n = 1:size(dfs[1],1), m = 1:size(dfs[1],2), k = 1:length(dfs) ]
Having said that, if you're willing to be a little more verbose, you can probably do better again using the iteration protocols specifically designed for use with DataFrame.
function f3(dfs)
y = Array{Float64,3}(undef, size(dfs[1],1), size(dfs[1],2), length(dfs))
for k = 1:length(dfs) ; for (n,col) in enumerate(eachcol(dfs[k]))
y[:,n,k] = col
end ; end
return y
end
As a general rule, if you want speed in Julia, loops are often the best approach. Let's do a quick comparison of the three approaches:
julia> using BenchmarkTools
julia> @btime f1($dfs);
182.454 μs (132 allocations: 7.89 KiB)
julia> @btime f2($dfs);
935.217 ns (21 allocations: 672 bytes)
julia> @btime f3($dfs);
338.664 ns (11 allocations: 368 bytes)
So f3 is pretty much 6x faster than f1. You could throw an @inbounds in f2 and f3 for further optimization although I suspect it won't gain you that much...
Now, to be fair, I just assumed everything was Float64 here. However, with a quick type check up front, you can generalise this to any type (as long as it is all one type - which presumably it is given that you're wanting to convert to a single array).