How to colour individual array entries in a DataFrame cell with PrettyTables or similar?

I have a many-column DataFrames with most columns containing an array in each cell. (These are properties per solution in multi-constrained optimisation.) Here’s a snippet with 3-element arrays

Would it be possible to colour negative array elements in Red, and perhaps assign another colour to elements for a grossly violated constraint?

  • In the PrettyTables package (which is awesome!) I only see how to Highlight the whole array.

  • Another option — split an array column into scalar subcolumns — seems unwieldy for a wide table

  • There’s the LaTeX backend in PrettyTables, but I’d like to display the table in the terminal. So LaTeX textcolor commands are unlikely to work.

  • Using an R or Python tables package, which seems OTT.

Is there another solution? Thanks!

Link: PrettyTables announcement

@Ronis_BR is the best to answer this question :smile: so pinging him.

2 Likes

Hi @Olegg !

You can :smiley:, but not easily :cry:

The problem is that PrettyTables.jl is not aware of the cell data. Otherwise, we would need to consider many possibilities. To print a cell with colors, we need to use AnsiTextCell in the original table. This value is necessary because we perform some in-place modifications to align things when you have escape sequences. Hence, the only possibility is if you transform the DataFrame into another table with the AnsiTextCells and use pretty_table to print it. Here is an example:

using PrettyTables

df = DataFrame(a = [randn(3) for _ in 1:10], b = [randn(3) for _ in 1:10])

# This function re-implements the Julia array printing but adding escape sequences to
# colorize the output.
function colorize(v::AbstractVector)
    # Define the crayons.
    cred   = string(crayon"bold red")
    creset = string(crayon"reset")

    # Print the array.
    ansi_str = "["

    for element in v
        v_str = sprint(print, element; context = :compact => true)

        if element < 0
            ansi_str *= cred * v_str * creset
        else
            ansi_str *= v_str
        end

        ansi_str *= ", "
    end

    ansi_str = chop(ansi_str; tail = 2) * "]"

    # Return the AnsiTextCell.
    return AnsiTextCell(ansi_str)
end

A = [colorize(df[i, j]) for i in 1:size(df, 1), j in 1:size(df, 2)]

pretty_table(
    A;
    alignment       = :l,
    header          = names(df),
    show_row_number = true,
    hlines          = [:header],
    vlines          = [1]
)
3 Likes

2 Likes

Thank you – that’s perfect! It might be a good use case, as the red colour (without the minus) is common for negative numbers in applications.

Would something similar work with the HTML back-end in PrettyTables.jl ? (An excellent package, thanks!)

1 Like

Yes! In HTML is much easier because we can have HTML commands in cells if we set allow_html_in_cells to true. Hence, we only need a formatter:

ft = (v, i, j) -> begin
    # Print the array.
    str = "["

    for element in v
        v_str = sprint(print, element; context = :compact => true)

        if element < 0
            str *= "<span style=\"color: red\">" * v_str * "</span>"
        else
            str *= v_str
        end

        str *= ", "
    end

    str = chop(str; tail = 2) * "]"

    # Return the formatted text.
    return str
end
show(stdout, MIME("text/html"), df; formatters = ft, allow_html_in_cells = true)

4 Likes