Show dates on seaborn heatmap

2020-02-13 05:24发布

问题:

I am trying to create a heat map from pandas dataframe using seaborn library. Here, is the code:

test_df = pd.DataFrame(np.random.randn(367, 5), 
                 index = pd.DatetimeIndex(start='01-01-2000', end='01-01-2001', freq='1D'))

ax = sns.heatmap(test_df.T)
ax.xaxis.set_major_locator(mdates.MonthLocator())
ax.xaxis.set_minor_locator(mdates.DayLocator())
ax.xaxis.set_major_formatter(mdates.DateFormatter('%b'))
ax.xaxis.set_minor_formatter(mdates.DateFormatter('%d'))

However, I am getting a figure with nothing printed on the x-axis.

回答1:

Seaborn heatmap is a categorical plot. It scales from 0 to number of columns - 1, in this case from 0 to 366. The datetime locators and formatters expect values as dates (or more precisely, numbers that correspond to dates). For the year in question that would be numbers between 730120 (= 01-01-2000) and 730486 (= 01-01-2001).

So in order to be able to use matplotlib.dates formatters and locators, you would need to convert your dataframe index to datetime objects first. You can then not use a heatmap, but a plot that allows for numerical axes, e.g. an imshow plot. You may then set the extent of that imshow plot to correspond to the date range you want to show.

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.dates as mdates

df = pd.DataFrame(np.random.randn(367, 5), 
                 index = pd.DatetimeIndex(start='01-01-2000', end='01-01-2001', freq='1D'))

dates = df.index.to_pydatetime()
dnum = mdates.date2num(dates)
start = dnum[0] - (dnum[1]-dnum[0])/2.
stop = dnum[-1] + (dnum[1]-dnum[0])/2.
extent = [start, stop, -0.5, len(df.columns)-0.5]

fig, ax = plt.subplots()
im = ax.imshow(df.T.values, extent=extent, aspect="auto")

ax.xaxis.set_major_locator(mdates.MonthLocator())
ax.xaxis.set_minor_locator(mdates.DayLocator())
ax.xaxis.set_major_formatter(mdates.DateFormatter('%b'))

fig.colorbar(im)
plt.show()



回答2:

I found this question when trying to do a similar thing and you can hack together a solution but it's not very pretty.

For example I get the current labels, loop over them to find the ones for January and set those to just the year, setting the rest to be blank.

This gives me year labels in the correct position.

xticklabels = ax.get_xticklabels()

for label in xticklabels:
    text = label.get_text()
    if text[5:7] == '01':
        label.set_text(text[0:4])
    else:
        label.set_text('')

ax.set_xticklabels(xticklabels)

Hopefully from that you can figure out what you want to do.