Search code examples
pythonmatplotlibdata-visualizationseabornunderline

Seaborn Heatmap: underline text in a cell


I am making some data analysis in Python, and I am using Seaborn for visualization. Seaborn works very nice for creating heatmaps.

I am trying to underline the maximum values for each column in my heatmap.

I was able to correctly highlight the text in the maximum cells by making them italic and bold. Still, I found no way to underline it.

This is an example of my code:


data_matrix = < extract my data and put them into a matrix >
max_in_each_column = np.max(data_matrix, axis=0)

sns.heatmap(data_matrix,
            mask=data_matrix == max_in_each_column,
            linewidth=0.5,
            annot=True,
            xticklabels=my_x_tick_labels,
            yticklabels=my_y_tick_labels,
            cmap="coolwarm_r")

sns.heatmap(data_matrix,
            mask=data_matrix != max_in_each_column,
            annot_kws={"style": "italic", "weight": "bold"},
            linewidth=0.5,
            annot=True,
            xticklabels=my_x_tick_labels,
            yticklabels=my_y_tick_labels,
            cbar=False,
            cmap="coolwarm_r")

This is my current result: My current heatmap, with maximum values for each column *italic* and **bold**

Of course I have tried using argumentannot_kws={"style": "underlined"}, but apparently in Seaborn the "style" key only supports values "normal", "italic" or "oblique".

Is there a workaround to this?


Solution

  • Yes, you can workaround your problem using tex commands within your texts. The basic idea is that you use the annot key of seaborn.heatmap to assign an array of strings as text labels. These contain your data values + some tex prefixes/suffixes to allow tex making them bold/emphasized (italic)/underlined or whatsoever.

    An example (with random numbers):

    # random data
    data_matrix = np.round(np.random.rand(10, 10), decimals=2)
    max_in_each_column = np.max(data_matrix, axis=0)
    
    # Activating tex in all labels globally
    plt.rc('text', usetex=True)
    # Adjust font specs as desired (here: closest similarity to seaborn standard)
    plt.rc('font', **{'size': 14.0})
    plt.rc('text.latex', preamble=r'\usepackage{lmodern}')
    
    # remains unchanged
    sns.heatmap(data_matrix,
                mask=data_matrix == max_in_each_column,
                linewidth=0.5,
                annot=True,
                cmap="coolwarm_r")
    
    # changes here
    sns.heatmap(data_matrix,
                mask=data_matrix != max_in_each_column,
                linewidth=0.5,
                # Use annot key with np.array as value containing strings of data + latex 
                # prefixes/suffices making the bold/italic/underline formatting
                annot=np.array([r'\textbf{\emph{\underline{' + str(data) + '}}}'
                                for data in data_matrix.ravel()]).reshape(
                    np.shape(data_matrix)),
                # fmt key must be empty, formatting error otherwise
                fmt='',
                cbar=False,
                cmap="coolwarm_r")
    
    plt.show()
    

    Further explanation the annotation array:

    # For all matrix_elements in your 2D data array (2D requires the .ravel() and .reshape() 
    # stuff at the end) construct in sum a 2D data array consisting of strings 
    # \textbf{\emph{\underline{<matrix_element>}}}. Each string will be represented by tex as 
    # a bold, italic and underlined representation of the matrix_element
    np.array([r'\textbf{\emph{\underline{' + str(data) + '}}}'
                            for data in data_matrix.ravel()]).reshape(np.shape(data_matrix))
    

    The resulting plot is basically what you wanted:

    enter image description here