[HELP] PrettyTables.jl design change

Hi!

I am doing some optimization in PrettyTables.jl to reduce the time to print first table (TPFT). The results are very interesting.

I will also use this new release to break some things (sorry :smiley:) and I want help of the community about an API design. Right now, PrettyTables.jl normally receives two arguments: the table and its header. The header should be a matrix that has headers and subheaders:

julia> a = [1 2 3; 4 5 6];

julia> pretty_table(a, [:a :b :c; :d :e :f])
β”Œβ”€β”€β”€β”¬β”€β”€β”€β”¬β”€β”€β”€β”
β”‚ a β”‚ b β”‚ c β”‚
β”‚ d β”‚ e β”‚ f β”‚
β”œβ”€β”€β”€β”Όβ”€β”€β”€β”Όβ”€β”€β”€β”€
β”‚ 1 β”‚ 2 β”‚ 3 β”‚
β”‚ 4 β”‚ 5 β”‚ 6 β”‚
β””β”€β”€β”€β”΄β”€β”€β”€β”΄β”€β”€β”€β”˜

This made sense when PrettyTables was a very small package. Following an advice of @bkamins, I am planning to change this. First, I would like header to be a vector of vectors. The first is the header, and the rest are subheaders. Moreover, I would like to transform header into a keyword instead of an argument of pretty_table. I had some requests to add things like footer and then I asked me β€œwhy only header is an argument?”

Thus, the new API would be:

julia> a = [1 2 3; 4 5 6];

julia> pretty_table(a, header = [[:a, :b, :c], [:d, :e, :f]])
β”Œβ”€β”€β”€β”¬β”€β”€β”€β”¬β”€β”€β”€β”
β”‚ a β”‚ b β”‚ c β”‚
β”‚ d β”‚ e β”‚ f β”‚
β”œβ”€β”€β”€β”Όβ”€β”€β”€β”Όβ”€β”€β”€β”€
β”‚ 1 β”‚ 2 β”‚ 3 β”‚
β”‚ 4 β”‚ 5 β”‚ 6 β”‚
β””β”€β”€β”€β”΄β”€β”€β”€β”΄β”€β”€β”€β”˜

This is much more clean to me and also improved a bit the inference, leading the lower TPFT.

What do you think? Any problems with this new design?

5 Likes

Makes a lot of sense to me. I prefer keyword arguments when using APIs because it is self documenting.

Also, thanks for creating this package, it is very nice to use :grin:

5 Likes

Thanks for the feedback!! I am glad it is being useful :slight_smile:

Thank you for working on this.

Since header is not a required argument I think it is OK to have it as a keyword argument.

The only question is what one would do if one wanted only header and no subheader. Would you write then:

pretty_table(a, header = [[:a, :b, :c]])

?
which looks not super clean (it is OK for me, but likely users would get tripped by this).

An alternative would be to do e.g. conditional processing:

  • if eltype of header is <:AbstractVector assume that it is a vector of header and subheaders as you do now.
  • otherwise assume that the user just passed the header.

You know your internals better so please decide (an alternative would be to use a Tuple as a wrapper to disambiguate, e.g. header=([:a, :b], [:c, :d]) and then header=[:a, :b] is clear that it is just header (but this Tuple would have to be immediately pre-processed in top level function and not passed downwards to avoid excessive recompilation due to changing of its type).

3 Likes

Thanks @bkamins ! I did not mention but this is exactly what I am doing now. If a vector in which the elements are not AbstractVectors, then I transform it in a vector of vectors:

_header = (eltype(header) <: AbstractVector) ? header : [header]

This is a very nice suggestion. At first, I thought a vector of vectors will be better so that the user can construct everything dynamically, but this seems a very edge case. I will change to a Tuple of Vectors and check if the performance is not degraded. Thanks!

3 Likes

I think this is a great idea. I agree that the data and headers should be considered separate. I know I’ve mentioned this in other threads, but multi-column headers are something I would definitely like to see (or contribute to) in the future.

This API, particularly, the vector of vectors, seems like it’s very well-suited to that (eventual) feature. Perhaps some keyword argument like joinheaders such that [:b, :b, :a] would become

β”Œβ”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”
β”‚   b   β”‚ a β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€
β”‚ 1 β”‚ 2 β”‚ 3 β”‚
β”‚ 4 β”‚ 5 β”‚ 6 β”‚
β””β”€β”€β”€β”΄β”€β”€β”€β”΄β”€β”€β”€β”˜
2 Likes

Hi @pdeffebach,

Thanks! The multicolumn headers is definitely something I will implement. I just need to stabilize the API first to tag v1.0 to start working on new features :slight_smile:

2 Likes

Yes, it’s appreciated! I’m just saying that I like this new API change because it makes a feature I want a lot easier to do in the future, so I’m on board.

1 Like

Nice! Thanks :slight_smile:

I fully agree with the proposed breaking changes, in particular changing β€œheader” to a keyword argument. My personal preference would actually be to have two symmetric keyword arguments, β€œcol_names” and β€œrow_names” with a spectrum of functionality similar to col.names and row.names in kable in the R package knitr, which include using the default column names (for a DataFrame), or omitting column names alltogether, or provide overriding column names. This functionality is useful when outputting matrices.

You may like to know that I used your package in a piece of analysis I did the past months, in Julia, for the Fermilab measurement of the muon magnetic moment that has some space in the news in the past days. Thanks for your nice package!

As a side note, I have written some Julia code to use PrettyTables functions to print large matrices with adjustable wrapping for limited line width. I don’t think that it is general enough for inclusion in your package, but I can put that in a gist if you think it may be useful.

5 Likes

Hum, this suggestion col_names might be interesting. Let’s use header for now to tag 1.0 (since it was the name since forever) and then we can discuss it for a future release.

This is just awesome!! Thanks you very much to share it with me :slight_smile: I am really glad that this package is being useful for the community!

Yes, please! Put in a Gist and link it here, it maybe useful for other people.

I will publish a gist and let you know, I need some time to work out some self standing piece of code.

Cheers,

Alberto

1 Like

How do I get PrettyTables to automatically display a HTML table if the current display supports it (e.g., in IJulia or when exporting to HTML using Weave), and a text table otherwise?

2 Likes

Hi @baggepinnen ,

Currently, PrettyTables.jl does not analyze the capabilities of the IO. Hence, you need to check it manually and call pretty_tables with backend = Val(:html) if it supports it.

In the future, we can add a backend = Val(:auto) to automatically verify this.

2 Likes