Annotating matplotlib heatmap y-axis ticks in monthly date increments

Viewed 49

I am currently trying to create a heatmap to represent a set of data that is currently stored in a matrix of numerial values. However each row of the matrix is also tied to a specific date value, which I would like to represent on the y-axis tick labels. I currently have some code to do this that looks a little like this:

matrix = [[5, 9, 0, 4],
          [0, 8, 3, 6],
          [9, 1, 0, 4],
          [0, 0, 3, 1]]

dates = [datetime.today() - timedelta(weeks=x * random.getrandbits(2)) for x in range(4)]
x_labels = ['Y29K', 'D950N', 'D142G', 'T95I']

title = 'Example Heatmap'
cbtitle = 'Mutation Count'

fig, ax = plt.subplots()
im = ax.imshow(matrix)

# Implement colorbar
max_val = round(int(np.max(matrix))/10)*10
color_bar_rnge = np.linspace(0,max_val,10, endpoint=False)
cb = fig.colorbar(im, ticks=color_bar_rnge)
cb.ax.set_title(cbtitle)

# Show all x axis ticks and label them with the respective list entries
ax.set_xticks(np.arange(len(x_labels)), labels=x_labels)

m_dates = mdates.date2num(dates)
ax.set_yticks(np.arange(len(m_dates)), labels=m_dates)
ax.yaxis_date()

# Rotate the tick labels and set their alignment.
plt.setp(ax.get_xticklabels(), rotation=90, ha="right",
         rotation_mode="anchor")

dx, dy = -6, 0
offset = ScaledTranslation(dx / fig.dpi, dy / fig.dpi, fig.dpi_scale_trans)

for label in ax.xaxis.get_majorticklabels():
    label.set_transform(label.get_transform() + offset)

ax.yaxis.set_major_formatter(mdates.DateFormatter('%Y-%m'))
ax.set_title(title)
fig.tight_layout()
fig.show()

Outputted heatmap

However for some reason the resulting output seems to label dates on the y-axis as being in the 1970s. This issue only arrises when I implement:

ax.yaxis.set_major_formatter(mdates.DateFormatter('%Y-%m'))

However I would like to format my dates in this way as my real data set contains hundreds of dates (with multiple dates per month) and so labelling each tick with an individual date value creates an extremely overcrowded set of labels on the y-axis. Is there anyway to format just the date y_tick labels to be annotated in monthly increments, or does anyone know a potentially better solution to plot this sort of data?

Many thanks

1 Answers

One way to get rid of the 1970 year is to convert the randomly generated dates to string list and use that as the labels using set_yticklabels(). After you create the dates, you can convert the list of dates to string using dateLabels = [date.strftime('%Y-%m') for date in dates]. But, do note that all months are likely to be close or same. You can add -%d to see the date as well. Add ax.set_yticklabels(dateLabels) at the end to replace labels with this list.

Note that I have had to make a couple of other changes like set_xticks(), set_yticks() and split them to include set_xticklables(), set_yticklabels() as your code was not running on my version. Please do check if all your code is required, as some might not be required anymore.

Full code below.

import matplotlib.transforms as transforms
matrix = [[5, 9, 0, 4],
          [0, 8, 3, 6],
          [9, 1, 0, 4],
          [0, 0, 3, 1]]

dates = [datetime.today() - timedelta(weeks=x * random.getrandbits(2)) for x in range(4)]
dateLabels = [date.strftime('%Y-%m') for date in dates]
print(dateLabels)

x_labels = ['Y29K', 'D950N', 'D142G', 'T95I']

title = 'Example Heatmap'
cbtitle = 'Mutation Count'

fig, ax = plt.subplots()
im = ax.imshow(matrix)

# Implement colorbar
max_val = round(int(np.max(matrix))/10)*10
color_bar_rnge = np.linspace(0,max_val,10, endpoint=False)
cb = fig.colorbar(im, ticks=color_bar_rnge)
cb.ax.set_title(cbtitle)

# Show all x axis ticks and label them with the respective list entries
ax.set_xticks(np.arange(len(x_labels)))
ax.set_xticklabels(x_labels)

m_dates = mdates.date2num(dates)
ax.set_yticks(np.arange(len(m_dates)))
ax.yaxis_date()

# Rotate the tick labels and set their alignment.
plt.setp(ax.get_xticklabels(), rotation=90, ha="right",
         rotation_mode="anchor")

dx, dy = -6, 0
offset = transforms.ScaledTranslation(dx / fig.dpi, dy / fig.dpi, fig.dpi_scale_trans)

for label in ax.xaxis.get_majorticklabels():
    label.set_transform(label.get_transform() + offset)

#ax.yaxis.set_major_formatter(mdates.DateFormatter('%Y-%m'))
ax.set_yticklabels(dateLabels)

ax.set_title(title)
fig.tight_layout()
fig.show()

Output

['2022-07', '2022-07', '2022-06', '2022-06']

enter image description here

Related