Show xtick labels on upper subplot with matplotlib

Viewed 401

I feel like I've tried everything I've been able to find here, google, matplotlib documentation, and a couple of other forums. I can't get xticks to show on the upper subplot/graph, and the lower. I read in a forum related to matplotlib that others had the same problem as I, with earlier versions of matplotlib, but the thread died a couple of years ago, and the fixes that was suggested to work, didn't in my case.

The data is pulled from CSV files, and converted to floats, and datetime values

import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import datetime as dt
import numpy as np

y = [26.0, 24.6, 23.9, 23.7, 23.6, 21.8, 22.0, 23.6]
x = [datetime.datetime(2020, 9, 2, 14, 13), datetime.datetime(2020, 9, 2, 14, 23), datetime.datetime(2020, 9, 2, 14, 33), datetime.datetime(2020, 9, 2, 14, 43), datetime.datetime(2020, 9, 2, 14, 53), datetime.datetime(2020, 9, 3, 0, 3), datetime.datetime(2020, 9, 4, 0, 3), datetime.datetime(2020, 9, 4, 0, 13)]
out = {datetime.date(2020, 9, 2): [26.0, 24.6, 23.9, 23.7, 23.6], datetime.date(2020, 9, 3): [21.8], datetime.date(2020, 9, 4): [21.6, 21.6]}

'''I found a thread a couple of days ago, where these 2 lines were the fix. honestly don't remember what the problem was, except it was related to datetime and plt'''
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%d-%m-%Y %H:%M'))
plt.gca().xaxis.set_major_locator(mdates.DayLocator())

ax1 = plt.subplot(2, 1, 1)
plt.plot(x, y)
plt.xlabel('time')
plt.ylabel('temp')
plt.title('logs')
plt.grid()
ax1.xaxis.set_major_formatter(mdates.DateFormatter('%d-%m-%Y %H:%M'))

'''and also found how to subplot a boxplot from dict, from another thread'''
ax2 = plt.subplot(2, 1, 2)
labels, data = [*zip(*out.items())]  # 'transpose' items to parallel key, value lists
plt.boxplot(data)
plt.xticks(range(1, len(labels) + 1), labels)
plt.xlabel('date')
plt.ylabel('temp')

'''for rotating the xticklabels, and as far as I've been able to read, the most likely culprit'''
plt.gcf().autofmt_xdate()

plt.draw() # non-blocking call

plt.show() # keep last in script to keep windows open after execution

This is the result

It might just be plt.gcf().autofmt_xdate() and if it is, is there a way to show xticklabels for both subplots, and still have them rotated?

2 Answers

Here is a solution using Matplotilb's object-oriented interface that let's you align the axes and rotate the labels on them. Sharing the xaxis in subplots makes it so that the zoom works for both subplots.

The ticks on the upper subplot are turned on by using

up.tick_params(labelbottom=True)

The boxplots are positioned using matplotlib's date-value corresponding to the datetimes using

mdates.date2num(d)
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import datetime as dt
import numpy as np
import datetime


y = [26.0, 24.6, 23.9, 23.7, 23.6, 21.8, 22.0, 23.6]
x = [datetime.datetime(2020, 9, 2, 14, 13), datetime.datetime(2020, 9, 2, 14, 23), datetime.datetime(2020, 9, 2, 14, 33), datetime.datetime(2020, 9, 2, 14, 43), datetime.datetime(2020, 9, 2, 14, 53), datetime.datetime(2020, 9, 3, 0, 3), datetime.datetime(2020, 9, 4, 0, 3), datetime.datetime(2020, 9, 4, 0, 13)]
out = {datetime.date(2020, 9, 2): [26.0, 24.6, 23.9, 23.7, 23.6], datetime.date(2020, 9, 3): [21.8], datetime.date(2020, 9, 4): [21.6, 21.6]}


f, (up, down) = plt.subplots(2, 1, sharex=True)

up.plot(x, y)
up.set_xlabel('time')
up.set_ylabel('temp')
up.set_title('logs')
up.grid()

down.boxplot(
    out.values(),
    positions=[mdates.date2num(d) for d in out.keys()]
)

down.get_xaxis().set_major_formatter(mdates.DateFormatter('%d-%m-%Y %H:%M'))
down.get_xaxis().set_major_locator(mdates.DayLocator())

up.tick_params(labelbottom=True)

plt.setp( up.xaxis.get_majorticklabels(), rotation=30 )
plt.setp( down.xaxis.get_majorticklabels(), rotation=30 )

f.tight_layout()
plt.draw()

Note that autofmt_xdate explicitly turns off the ticklabels for all subplots except the bottom one(s?).

Date ticklabels often overlap, so it is useful to rotate them and right align them. Also, a common use case is a number of subplots with shared x-axis where the x-axis is date data. The ticklabels are often long, and it helps to rotate them on the bottom subplot and turn them off on other subplots, as well as turn off xlabels.

This answer is similar to @Zephyr's answer, but I'm posting it anyway as it properly aligns the data between the plots. You could change the alignment by setting a different time of the day on the lower plot or by changing the boxes' width.

The result

As already pointed out by JohanC, you should use the matplotlib's object-oriented interface.

You can create the two subplots you need and then customize the axis based on your needs:

import matplotlib.pyplot as plt
import datetime as datetime
import matplotlib.dates as md


y = [26.0, 24.6, 23.9, 23.7, 23.6, 21.8, 22.0, 23.6]
x = [datetime.datetime(2020, 9, 2, 14, 13), datetime.datetime(2020, 9, 2, 14, 23), datetime.datetime(2020, 9, 2, 14, 33), datetime.datetime(2020, 9, 2, 14, 43), datetime.datetime(2020, 9, 2, 14, 53), datetime.datetime(2020, 9, 3, 0, 3), datetime.datetime(2020, 9, 4, 0, 3), datetime.datetime(2020, 9, 4, 0, 13)]
out = {datetime.date(2020, 9, 2): [26.0, 24.6, 23.9, 23.7, 23.6], datetime.date(2020, 9, 3): [21.8], datetime.date(2020, 9, 4): [21.6, 21.6]}

fig, ax = plt.subplots(2, 1)

ax[0].plot(x, y)
ax[0].set_xlabel('time')
ax[0].set_ylabel('temp')
ax[0].set_title('logs')
ax[0].grid()

labels, data = [*zip(*out.items())]
ax[1].boxplot(data)
ax[1].set_xticklabels([label.strftime('%Y-%m-%d') for label in labels])
ax[1].set_xlabel('date')
ax[1].set_ylabel('temp')

ax[0].xaxis.tick_top()
ax[0].xaxis.set_major_formatter(md.DateFormatter('%Y-%m-%d'))
ax[0].xaxis.set_major_locator(md.DayLocator())
plt.setp(ax[0].xaxis.get_majorticklabels(), rotation = 45)
ax[0].set_xlim(labels[0], labels[-1])

plt.setp(ax[1].xaxis.get_majorticklabels(), rotation = 45)

plt.show()

enter image description here

Side note: lower and upper labels are not perfectly aligned because you are trying to compare two different types of axis: to top upper one is continuous (between one tick and the next there are many other possible values for hours, minutes, seconds) while the lower one is categorical (you are plotting values for days only and between one thick and the next there are no others).

Related