Search code examples
pythonmatplotlibaxes

Understanding maplotlib and how to format matplotlib axis with a single digit?


I am having an very hard time getting the ticklabels of a seaborn heatmap to show only single integers (i.e. no floating numbers). I have two lists that form the axes of a data frame that i plot using seaborn.

import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import matplotlib.ticker as tkr

x = np.linspace(0, 15, 151)
y = np.linspace(0, 15, 151)
#substitute random data for my_data
df_map = pd.DataFrame(my_data, index = y, columns = x) 
plt.figure()
ax = sns.heatmap(df_map, square = True, xticklabels  = 20, yticklabels = 20)
ax.invert_yaxis()

original map

I've reviewed many answers and the documents. My biggest problem is I have little experience and a very poor understanding of matplotlib and the docs feel like a separate language... Here are the things I've tried.

ATTEMPT 1: A slightly modified version of the solution to this question:

fmtr = tkr.StrMethodFormatter('{x:.0f}')

plt.gca().xaxis.set_major_formatter(fmtr)

indices or decimal moving?

I'm pretty sure tkr.StrMethodFormatter() is displaying every 20th index of the value it encounters in my axis string, which is probably due to my settings in sns.heatmap(). I tried different string inputs to tkr.StrMethodFormatter() without success. I looked at two other questions and tried different combinations of tkr classes that were used in answers for here and here.

ATTEMPT 2:

fmtr = tkr.StrMethodFormatter("{x:.0f}")
locator = tkr.MultipleLocator(50)
fstrform = tkr.FormatStrFormatter('%.0f')

plt.gca().xaxis.set_major_formatter(fmtr)
plt.gca().xaxis.set_major_locator(locator)
#plt.gca().xaxis.set_major_formatter(fstrform)

last attempt

And now i'm at a complete loss. I've found out locator changes which nth indices to plot, and both fmtr and fstrform change the number of decimals being displayed, but i cannot for the life of me get the axes to display the integer values that exist in the axes lists!

Please help! I've been struggling for hours. It's probably something simple, and thank you!

As an aside:

Could someone please elaborate on the documentation excerpt in that question, specifically:

...and the field used for the position must be labeled pos.

Also, could someone please explain the differences between tkr.StrMethodFormatter("{x:.0f}") and tkr.FormatStrFormatter('%.0f')? I find it annoying there are two ways, each with their own syntax, to produce the same result.

UPDATE:

It took me a while to get around to implementing the solution provided by @ImportanceOfBeingErnest. I took an extra precaution and rounded the numbers in the x,y arrays. I'm not sure if this is necessary, but I've produced the result I wanted:

x = np.linspace(0, 15, 151)
y = np.linspace(0, 15, 151)
# round float numbers in axes arrays
x_rounded = [round(i,3) for i in x]
y_rounded = [round(i,3) for i in y]
#substitute random data for my_data
df_map = pd.DataFrame(my_data, index = y_rounded , columns = x_rounded) 
plt.figure()
ax0 = sns.heatmap(df_map, square = True, xticklabels  = 20)
ax0.invert_yaxis()

labels = [label.get_text() for label in ax0.get_xticklabels()]
ax0.set_xticklabels(map(lambda x: "{:g}".format(float(x)), labels))

solved

Although I'm still not entirely sure why this worked; check the comments between me and them for clarification.


Solution

  • The sad thing is, you didn't do anything wrong. The problem is just that seaborn has a very perculiar way of setting up its heatmap.
    The ticks on the heatmap are at fixed positions and they have fixed labels. So to change them, those fixed labels need to be changed. An option to do so is to collect the labels, convert them back to numbers, and then set them back.

    labels = [label.get_text() for label in ax.get_xticklabels()]
    ax.set_xticklabels(map(lambda x: "{:g}".format(float(x)), labels))
    
    labels = [label.get_text() for label in ax.get_yticklabels()]
    ax.set_yticklabels(map(lambda x: "{:g}".format(float(x)), labels))
    

    A word of caution: One should in principle never set the ticklabels without setting the locations as well, but here seaborn is responsible for setting the positions. We just trust it do do so correctly.


    If you want numeric axes with numeric labels that can be formatted as attempted in the question, one may directly use a matplotlib plot.

    import numpy as np
    import seaborn as sns # seaborn only imported to get its rocket cmap
    import matplotlib.pyplot as plt
    
    my_data = np.random.rand(150,150)
    x = (np.linspace(0, my_data.shape[0], my_data.shape[0]+1)-0.5)/10
    y = (np.linspace(0, my_data.shape[1], my_data.shape[1]+1)-0.5)/10
    
    
    fig, ax = plt.subplots()
    pc = ax.pcolormesh(x, y, my_data, cmap="rocket")
    fig.colorbar(pc)
    ax.set_aspect("equal")
    plt.show()
    

    While this already works out of the box, you may still use locators and formatters as attempted in the question.