Transpose of an array of Strings

Consider the statement:

a = [1 2 3 4 ]

and its transpose a', which generates a column vector.

Now do the same with strings:

b = ["a" "b" "c"]

and transpose it. I get the following error (which is very hard to believe, since ["a"; "b"; "c"] works perfectly well.). I cannot transpose an array of symbols either.

 b'
Error showing value of type LinearAlgebra.Adjoint{Union{},Array{String,2}}:
ERROR: MethodError: no method matching adjoint(::String)
Closest candidates are:
  adjoint(::Missing) at missing.jl:100
  adjoint(::LightGraphs.DefaultDistance) at /Users/erlebach/.julia/packages/LightGraphs/siFgP/src/distance.jl:22
  adjoint(::Number) at number.jl:169
  ...
Stacktrace:
 [1] getindex at /Users/sabae/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.4/LinearAlgebra/src/adjtrans.jl:178 [inlined]
 [2] show(::IOContext{Base.GenericIOBuffer{Array{UInt8,1}}}, ::MIME{Symbol("image/svg+xml")}, ::LinearAlgebra.Adjoint{Union{},Array{String,2}}; max_swatches::Int64) at /Users/erlebach/.julia/packages/Colors/P2rUx/src/display.jl:122
 [3] show at /Users/erlebach/.julia/packages/Colors/P2rUx/src/display.jl:91 [inlined]
 [4] show(::IOContext{Base.GenericIOBuffer{Array{UInt8,1}}}, ::String, ::LinearAlgebra.Adjoint{Union{},Array{String,2}}) at ./multimedia.jl:109
 [5] displayinplotpane(::LinearAlgebra.Adjoint{Union{},Array{String,2}}) at /Users/erlebach/.julia/packages/Atom/9h5Up/src/display/showdisplay.jl:67
 [6] display(::Atom.JunoDisplay, ::LinearAlgebra.Adjoint{Union{},Array{String,2}}) at /Users/erlebach/.julia/packages/Atom/9h5Up/src/display/showdisplay.jl:118
 [7] display(::Any) at ./multimedia.jl:323
 [8] #invokelatest#1 at ./essentials.jl:712 [inlined]
 [9] invokelatest at ./essentials.jl:711 [inlined]
 [10] print_response(::IO, ::Any, ::Bool, ::Bool, ::Any) at /Users/sabae/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.4/REPL/src/REPL.jl:161
 [11] print_response(::REPL.AbstractREPL, ::Any, ::Bool, ::Bool) at /Users/sabae/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.4/REPL/src/REPL.jl:146
 [12] (::REPL.var"#do_respond#38"{Bool,Atom.var"#250#251",REPL.LineEditREPL,REPL.LineEdit.Prompt})(::Any, ::Any, ::Any) at /Users/sabae/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.4/REPL/src/REPL.jl:729
 [13] #invokelatest#1 at ./essentials.jl:712 [inlined]
 [14] invokelatest at ./essentials.jl:711 [inlined]
 [15] run_interface(::REPL.Terminals.TextTerminal, ::REPL.LineEdit.ModalInterface, ::REPL.LineEdit.MIState) at /Users/sabae/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.4/REPL/src/LineEdit.jl:2354
 [16] run_frontend(::REPL.LineEditREPL, ::REPL.REPLBackendRef) at /Users/sabae/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.4/REPL/src/REPL.jl:1055
 [17] run_repl(::REPL.AbstractREPL, ::Any) at /Users/sabae/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.4/REPL/src/REPL.jl:206
 [18] (::Base.var"#764#766"{Bool,Bool,Bool,Bool})(::Module) at ./client.jl:383
 [19] #invokelatest#1 at ./essentials.jl:712 [inlined]
 [20] invokelatest at ./essentials.jl:711 [inlined]
 [21] run_main_repl(::Bool, ::Bool, ::Bool, ::Bool, ::Bool) at ./client.jl:367
 [22] exec_options(::Base.JLOptions) at ./client.jl:305
 [23] _start() at ./client.jl:484
3 Likes

I have found that one can use the function ‘permutedims’ to accomplish the task. I do not know why one could not simply overload the transpose function (or the apostrophy operator) via dispatch? I assume that transpose is a more efficient version of permutedims that is optimized for Numbers.

In the end, here is some code I wrote related to the question asked. All is solved, although there is probably a more elegant way to accomplish this. I appreciate all the help.

prob_ode = ODEProblem(households!,u0,tspan,params);
sol_ode = solve(prob_ode, Tsit5());
df_ode = DataFrame(sol_ode(t)')
df_ode[!,:t] = t;

# Extract subsets of DataFrame
nb_save = 7
df_S = df_ode[!, 0*nb_houses+1 : 0*nb_houses+nb_save];
df_I = df_ode[!, 1*nb_houses+1 : 1*nb_houses+nb_save];
df_R = df_ode[!, 2*nb_houses+1 : 2*nb_houses+nb_save];
df_S[!,:t] = df_ode[!,:t];
df_I[!,:t] = df_ode[!,:t];
df_R[!,:t] = df_ode[!,:t];

nb_kept = 4;

# Put each state in its own database
function plotDF(dfr, state)
	labels=[];
	for i in 1:nb_kept push!(labels, "S"*repr(i) ) end
	@df dfr plot(:t,
    	cols([1:nb_kept]),
		label=permutedims(labels),
    	xlabel="Time",
    	ylabel="Number Susceptibles")
end

plotDF(df_S, "S")
plotDF(df_I, "I")
plotDF(df_R, "R")
8 Likes

The difference is that transpose (and adjoint, i.e. complex-conjugate transpose, which is what ' does), are recursive, so if you have a vector of matrices, you end up with a row vector of transposed matrices. On the other hand, permutedims is not recursive. I don’t know all the details but I think this came about because one needed a way to do each thing (recursive and non-recursive) and it was decided that recursive transpose is a linear-algebraic operation, and thus suited for arrays of numbers, while permutedims is a data-storage operation.

7 Likes

I think the point is that ' produces an adjoint, which isn’t really a concept that exists for vectors of strings?

2 Likes

Transpose does not do complex conjugation, whereas adjoint does. And the “'” operator is a synonym for adjoint.
However, Transpose does not work for 1D string arrays, although it is recursive, as is adjoint.
It is still not clear why transpose would not work for strings.

Do you have a suggestion for what the output of

julia> transpose("abc")

should be, and how it would make sense?

1 Like

Also, don’t you really want a vector? In which case I think the best practice is vec(x) or reshape(x, :).

Well, the transpose acts on an Array, and “abc” is not an array.

Transpose(3) returns 3, so I would expect transpose("abc") to return “abc”.

However, [“abc”, “def”] is an array. So I would expect the transpose to return [“abc” “def”] just as transpose([3,4]) returns [3 4].
I also checked the type hierarchy, and I do not believe that Array appears anywhere between String and Any.

What am I missing?

2 Likes

Why do I want to use a vector? What is the difference between a vector and a 1D Array? I thought it was a shortcut.

julia> x = ["a string" "another string"]
1×2 Array{String,2}:
 "a string"  "another string"

julia> permutedims(x)
2×1 Array{String,2}:
 "a string"
 "another string"

Notice that the result is still a Array{String, 2}, which is not a Vector i.e. Array{String, 1}

4 Likes

transpose and adjoint are linear algebra operations. If you want to treat your array as a container, use the container operations like reshape, vec or permutedims.

As far as the why goes, the answer is that LinearAlgebra wants to support matrices of matrices and not all LinearAlgebra objects are themselves ::AbstractArrays — so defaulting to a no-op is subtly error prone and defining a bunch of no-op transposes one-by-one is unsatisfactory. I’m not the biggest fan of this myself, but it is what it is.

6 Likes

I have no qualms about “how it is” as long as it is documented. In order to improve my julia programming skills, I find it important to better understand the “why” of certain decisions, which will lead to improved program efficiency by making correct programming choices more often. Thanks for all your input!

Gordon

3 Likes

Good on you. I’ve read this issue through a couple of times for the same reason. Still don’t totally understand all of it (linear algebra is not my first language), but I’ve learned a bunch!

I think transpose(scalar) = scalar makes sense. But a string is a collection, not a scalar, so I don’t think that really works. At the same time it’s not an array. I actually think throwing an error makes sense here.

Looks like your link didn’t copy-paste correctly.

Why not soulmate that the transpose of a collection is a collection?
So, transpose (1:5) should return 1:5.

Because then what you have is an identity function…

True, and whatmis wrong with that? If no harm done, why throw an error? For example, what if I take the transpose of [1:4, 2:6]? I would expect that to work.
Isn’t the transposemofma scalar an identity operator? Why isn’t that a concern?
The transposemofma diagonal matrixmis an identity operator. I have not checked, but I doubt an error exception is thrown.

I am new to Julia, so I might be missing something philosophically.

The thing you’re missing is that julia> 1:5 == [1,2,3,4,5]. Collections are kind of by definition not scalars.

1:5 is a vector, so transposing that returns a transposed vector, similar to a row vector (isomorphic is the word, I think). It would be bad if

(1:5)' * A

suddenly stopped working for appropriately sized matrices. Ranges are AbstractVectors and need to behave accordingly.

1 Like