`show` methods for arrays

I’m working on something I’m currently calling a TupleVector for representing an MCMC chain where each sample is a NamedTuple. Internally the structure is something like

julia> unwrap(tv)
(a = [1, 3, 4, 5], b = [[0.36932704908184055, 0.38928341185135773, 0.531478384195232], [1.1206308130858036, 0.186852988457207, -0.7630326680035839], [-0.36521261611586725, -1.2910950683384825, -1.3507690793756433], [1.8710082871859233, 1.8902622784110135, 0.542632944187108]])

But instead of 4 samples, it’s typically hundreds or thousands.

I have this set up so it works like an Array and also like a NamedTuple, so

julia> tv[1]
(a = 1, b = [0.36932704908184055, 0.38928341185135773, 0.531478384195232])

julia> tv.a
4-element ElasticVector{Int64, 0, Vector{Int64}}:
 1
 3
 4
 5

julia> tv.b
4-element ArrayOfSimilarArrays{Float64, 1, 1, 2, ElasticMatrix{Float64, 1, Vector{Float64}}}:
 [0.36932704908184055, 0.38928341185135773, 0.531478384195232]
 [1.1206308130858036, 0.186852988457207, -0.7630326680035839]
 [-0.36521261611586725, -1.2910950683384825, -1.3507690793756433]
 [1.8710082871859233, 1.8902622784110135, 0.542632944187108]

So far, so good. Rather than show such a long list, I want to be able to summarize it, and also not show so many useless decimal places. So I have a summarize function to turn each column into one of these:

struct RealSummary <: Summary
    μ :: Float64
    σ :: Float64
end

function Base.show(io::IO, s::RealSummary)
    io = IOContext(io, :compact => true)
    print(io, s.μ, " ± ", s.σ)
end

So now the whole thing looks like this:

julia> tv
4-element TupleVector with schema (a = Int64, b = Vector{Float64})
(a = 3.25 ± 1.70783, b = NestedTuples.RealSummary[0.43003 ± 0.088422 0.181484 ± 0.941843 -1.00236 ± 0.552591 1.43463 ± 0.772556])

It’s getting there, but still not great:

  • Things like 3.25 ± 1.70783 are silly, still way too many decimal places
  • The NestedTuples.RealSummary needs to go
  • The array is hard to read, especially with no commas

Looking at the show methods for arrays, it seems like really fine-tuning this would require copying and pasting a lot of code. I was hoping there might be something more easily composable where I could try things out, swap out components, etc. For example, I’d really like to avoid working through all the logic about when to have the REPL show ... and leave out some array entries, that kind of thing.

Any suggestions for a better way to go about this?

1 Like

Are you aware of GitHub - JuliaArrays/StructArrays.jl: Efficient implementation of struct arrays in Julia?

Thanks, yes I’m aware of StructArrays. When I started this it seemed StructArrays didn’t work well for nested structure. Then I learned how to get them working, but I was beating them on performance (some discussion here).

Even if they can match performance, that doesn’t address how to change the show representation. So I could wrap a StructArray, but I’d be back to the same problem as my OP.

Re: the decimal places, try rounding. Re: the RealSummary[...], try defining a show method for a vector/array of RealSummary as well. This would also fix the comma issue.

1 Like

Of course! It’s obvious now, thanks @mohamed82008 .

I think I like the way this is working:

struct RealSummary <: Summary
    μ :: Float64
    σ :: Float64
end

function Base.show(io::IO, s::RealSummary)
    io = IOContext(io, :compact => true)
    σ = round(s.σ, sigdigits=2)
    μdigits = max(2, ceil(Int, log10(2) * (exponent(s.μ) - exponent(σ))) + 2)
    μ = round(s.μ, sigdigits = μdigits)
    print(io, μ, " ± ", σ)
end

Some examples:

julia> RealSummary(ℯ,π)
2.7 ± 3.1

julia> RealSummary(10000ℯ,π)
27182.8 ± 3.1

julia> RealSummary(1e9ℯ,π)
2.71828e9 ± 3.1

julia> RealSummary(ℯ,1000π)
2.7 ± 3100.0

julia> RealSummary(ℯ,1e9π)
2.7 ± 3.1e9

julia> RealSummary(ℯ,1e-9π)
2.71828 ± 3.1e-9
1 Like

Hmm, just realized this doesn’t yet take care of it, since it misses all the ... logic in default show methods for arrays

I managed to get rid of the RealSummary “prefix” with

Base.typeinfo_prefix(io::IO, ::AbstractArray{<:Summary}) = ("", false)

so now it’s

julia> tv
4-element TupleVector with schema (a = Int64, b = Vector{Float64})
(a = 3.25 ± 1.7, b = [0.43 ± 0.088 0.18 ± 0.94 -1.0 ± 0.55 1.43 ± 0.77])

Getting closer!

Ok, now that I have fewer decimal places I think this looks fine, and it saves some space too:

julia> tv
4-element TupleVector with schema (a = Int64, b = Vector{Float64})
(a = 3.25±1.7, b = [0.43±0.088 0.18±0.94 -1.0±0.55 1.43±0.77])

Think I’ll call it done, for now anyway :slight_smile:

2 Likes