Current state and the future of PrettyTables.jl

Sure

See below
\documentclass[12pt]{article}
\usepackage[utf8]{inputenc}
\usepackage{geometry}
\usepackage{adjustbox}
\geometry{margin = 1in}

\begin{document}

\begin{table}
\centering
\caption{Table is too big}
% This would be \input{path/to/table.tex} normally
\begin{tabular}{rrrrrrrr}
  \hline
  \textbf{Variable} & \textbf{Column 1} & \textbf{Column 2} & \textbf{Column 3} & \textbf{Column 4} & \textbf{Column 5} & \textbf{Column 6} & \textbf{Column 7} \\\hline
  Row label 1 & 0.01 & 0.02 & 0.03 & 0.04 & 0.05 & 0.06 & 0.07 \\
  Row label 2 & 0.1 & 0.2 & 0.3 & 0.4 & 0.5 & 0.6 & 0.7 \\
  Row label 3 & 1.0 & 2.0 & 3.0 & 4.0 & 5.0 & 6.0 & 7.0 \\
  Row label 4 & 10.0 & 20.0 & 30.0 & 40.0 & 50.0 & 60.0 & 70.0 \\
  Row label 5 & 100.0 & 200.0 & 300.0 & 400.0 & 500.0 & 600.0 & 700.0 \\
  Row label 6 & 1000.0 & 2000.0 & 3000.0 & 4000.0 & 5000.0 & 6000.0 & 7000.0 \\
  Row label 7 & 10000.0 & 20000.0 & 30000.0 & 40000.0 & 50000.0 & 60000.0 & 70000.0 \\
  Row label 8 & 100000.0 & 200000.0 & 300000.0 & 400000.0 & 500000.0 & 600000.0 & 700000.0 \\
  Row label 9 & 1.0e6 & 2.0e6 & 3.0e6 & 4.0e6 & 5.0e6 & 6.0e6 & 7.0e6 \\
  Row label 10 & 1.0e7 & 2.0e7 & 3.0e7 & 4.0e7 & 5.0e7 & 6.0e7 & 7.0e7 \\\hline
\end{tabular}
\end{table}


\begin{table}
\centering
\caption{Table is correct size}
\adjustbox{width = \textwidth}{
\begin{tabular}{rrrrrrrr}
  \hline
  \textbf{Variable} & \textbf{Column 1} & \textbf{Column 2} & \textbf{Column 3} & \textbf{Column 4} & \textbf{Column 5} & \textbf{Column 6} & \textbf{Column 7} \\\hline
  Row label 1 & 0.01 & 0.02 & 0.03 & 0.04 & 0.05 & 0.06 & 0.07 \\
  Row label 2 & 0.1 & 0.2 & 0.3 & 0.4 & 0.5 & 0.6 & 0.7 \\
  Row label 3 & 1.0 & 2.0 & 3.0 & 4.0 & 5.0 & 6.0 & 7.0 \\
  Row label 4 & 10.0 & 20.0 & 30.0 & 40.0 & 50.0 & 60.0 & 70.0 \\
  Row label 5 & 100.0 & 200.0 & 300.0 & 400.0 & 500.0 & 600.0 & 700.0 \\
  Row label 6 & 1000.0 & 2000.0 & 3000.0 & 4000.0 & 5000.0 & 6000.0 & 7000.0 \\
  Row label 7 & 10000.0 & 20000.0 & 30000.0 & 40000.0 & 50000.0 & 60000.0 & 70000.0 \\
  Row label 8 & 100000.0 & 200000.0 & 300000.0 & 400000.0 & 500000.0 & 600000.0 & 700000.0 \\
  Row label 9 & 1.0e6 & 2.0e6 & 3.0e6 & 4.0e6 & 5.0e6 & 6.0e6 & 7.0e6 \\
  Row label 10 & 1.0e7 & 2.0e7 & 3.0e7 & 4.0e7 & 5.0e7 & 6.0e7 & 7.0e7 \\\hline
\end{tabular}
} % adjustbox
\end{table}

\end{document}

</details>
1 Like

Are you sure you pasted the right version ? This looks like what i have at the moment, that is not adjust to line size :slight_smile:

errrr: saw your edit.

The edit shouldn’t change that. I’m not sure what “adjusting the font to line size” means, I thought the problem you were trying to solve is putting everything on one page.

Ok @pdeffebach i’ve managed to test it. It’s not as good as the text size inside the table is also zoomed in right ? Can you make it with constant font size ? The problem is not putting everything on one page, but the other way around: I have small tables that i want to span \textwidth, but here they end up with a font that is maybe 25pt and thus this is not a solution for me :rofl:

@Ronis_BR, @pdeffebach I solve my issue using type piracy and replacement in the output, which feels very wrong but in the context of me and myself is good enough. In case someone wanders, I used:

function PrettyTables._latex_alignment(s::Symbol)
    if (s == :l) || (s == :L)
        return "l"
    elseif (s == :c) || (s == :C)
        return "c"
    elseif (s == :r) || (s == :R)
        return "r"
    elseif (s == :X)
        return "X" ### I simply added this case
    else
        error("Invalid LaTeX alignment symbol: $s.")
    end
end

function save_table(tbl, filename)
    open(filename, "w") do  f
        pretty_table(
            ... # all others arguments. 
            alignment=:X,
        )
    end
    s = read(filename, String)
    write(filename, replace(s, "\\begin{tabular}" => "\\begin{tabularx}{\\linewidth}", "\\end{tabular}" => "\\end{tabularx}"))
end
2 Likes

I use LaTeXTabulars.jl for that exact purpose. No fuzz.
(PrettyTables.jl is amazing)

4 Likes

That certainly beats my current method of raw""" """ and then a bunch of replacing for custom tables that can’t be made with PrettyTables! Thanks! I will start using this.

1 Like

That was part of mty previous question about how much can PrettyTables.jl do :slight_smile:

And after soooo many years, we finally can merge cells at the table header :slight_smile:

25 Likes

How deeply can those column and row headers nest? Eg can there be Group 1.i.a, Group 1.ii.a etc?

You can have as many column labels as you like as merge than without restrictions (rows only).

2 Likes

@jar1 if you want to try, clone v3 branch and the code is:

using PrettyTables

column_labels = [
    ["" for _ in 1:6],
    ["" for _ in 1:6],
    ["Test $i" for i in 1:6]
]

column_label_alignment = :c

merge_column_label_cells = [
    MergeCells(1, 1, 4, "Group #1", :c)
    MergeCells(1, 5, 2, "Group #2", :c)
    MergeCells(2, 1, 2, "Group #1.1", :c)
    MergeCells(2, 3, 2, "Group #1.2", :c)
]

summary_rows = [
    (data, i) -> sum(data[i, :]) / length(data[i, :])
]

summary_row_labels = [
    "Mean"
]

A = randn(10, 6)

tf = TextTableFormat(
    suppress_vertical_lines_at_column_labels = true,
    right_vertical_lines_at_data_columns = :none
)

pretty_table(
    A;
    column_labels,
    column_label_alignment,
    merge_column_label_cells,
    show_row_number_column = true,
    summary_rows,
    summary_row_labels,
    tf
)

The only point is that I decided for an API very different from R’s gt package. Here, you have to “design” the labels yourself. See how you need to add the blank fields, allowing to merge the cells. However, in this case, we have much more flexibility, since we can merge cells below the topmost one.

In the future, we might provide a macro or something for assisting in the design.

EDIT: Ah btw, I assume the terminal supports rendering underlines. Otherwise you will not get the “border” below the merged cell. We could add a new line, but it will lead to an extremely complicated API to select which cells you want a line and which cells you do not want.

3 Likes

First of all, thank you for the package! I think you are doing an amazing job.

Influenced by LaTeX, I can’t help but wonder, why not go for column label definition similar to

column_labels = [
  MultiCol(1:4, "Group 1")                                   MultiCol(5:8, "Group 2");
  MultiCol(1:2, "Group 1.I")  MultiCol(3:4, "Group 1.II")    MultiCol(5:6, "Group 2.I") MultiCol(7:8, "Group 2.II");
  "Group 1.I.a" "Group 1.I.b" "Group 1.II.a" "Group 1.II.b" "Group 2.I.a" "Group 2.I.b" "Group 2.II a" "Group 2.II.b"
]

which would result in the following table header:

=============================================================================================================
Group 1                                                Group 2
-----------------------------------------------------  ------------------------------------------------------
Group 1.I                 Group 1.II                   Group 2.I                  Group 2.II
------------------------  ---------------------------  -------------------------  ---------------------------
Group 1.I.a  Group 1.I.b  Group 1.II.a   Group 1.II.b  Group 2.I.a   Group 2.I.b  Group 2.II a   Group 2.II.b
=============================================================================================================

It feels like a more convenient API to me (although that might be due to my familiarity with LaTeX). I have never written a table-formatting package so I am not aware of possible issues. I am genuinely asking.

There is only one problem. In this case, you will not be able to merge cells when you have a pre-defined column labels unless you rewrite the entire column label vector.

For example, in DataFrames, you have the row names and row types. In the current approach, we can, for example, merge some cells there. If we change to that approach, we must rewrite the entire vector as DataFrames does.

However, we can add a macro to provide the correct vectors, allowing the best of both worlds.

However, I think I will use your design regarding how to specify the columns to be merged :slight_smile:

Ah, there is another problem with that approach, the processing will be more difficult. In current case, we can loop through the column labels because it has the same number of columns as the table. In your case, it will not have. Hence, I will probably need to reprocess it inside the algorithm to avoid a huge modification in the printing state iterator. Thus, the macro is the best option here.

Okay, sure, I knew there would be some obstacles :slight_smile: I just wanted to bring it up in case you had not consider it.

1 Like

@barucden

I would like to thank you for the suggestion! Your proposal gave me one very nice idea. We can allow merge_column_label_cells = :auto. In this case, PrettyTables automatic regenerates column_labels and merge_column_label_cells using the specification in MultiColumn.

It is important to not let it happen automatically. Let’s say we have a very big table (10_000, 10_000). In this case, the column labels can be generated by an iterator that does not allocate, like 1:10_000. If we reprocess automatically, we will allocate an array with 10_000 elements, leading to a huge slowdown. Thus, we only do this if the user wants.

The good side is that we can generate the same table using:

using PrettyTables

column_labels = [
    [MultiColumn(4, "Group #1"), MultiColumn(2, "Group #2")],
    [MultiColumn(2, "Group #1.1"), MultiColumn(2, "Group #1.2"), "", ""],
    ["Test $i" for i in 1:6],
]

column_label_alignment = :c

merge_column_label_cells = :auto

summary_rows = [
    (data, i) -> sum(data[i, :]) / length(data[i, :])
]

summary_row_labels = [
    "Mean"
]

A = randn(10, 6)

tf = TextTableFormat(
    suppress_vertical_lines_at_column_labels = true,
    right_vertical_lines_at_data_columns = :none
)

pretty_table(
    A;
    column_labels,
    column_label_alignment,
    merge_column_label_cells,
    show_row_number_column = true,
    summary_rows,
    summary_row_labels,
    tf
)

The MultiColumn has only the number of columns because the column index is given by the position in the array. Thus, I do not need to check consistency at this stage.

1 Like

I cannot comment on the technical implications you mention, but that code feels very natural to me!

1 Like

Hi everyone!

Despite from very few niche features (like autowrap), the text back end is completed! Now I need to write the tests, which should take sometime…

I could re-implement all the features as we have now, including the custom text cells. I selected a more clean approach. Previously, I tried to cache as many things as I can, leading to a faster algorithm that allocates a lot and it is very difficult to maintain. Now, I try to cache almost nothing. Hence, in some cases we need to process the same thing twice. This approach reduces the allocation, but slightly increases the processing time. However, it turns out that this additional processing time is neglible for a package that print tables.

The good side is that we can now make tables like this one (yes, after typing a lot of things and tuning a lot of options :sweat_smile:)

28 Likes