Matplotlib broken twin x-axis

804 Views Asked by At

I am trying to generate a matplotlib plot that consists of two subplots that share an x-axis. One subplot plots two sets of data on different y-axis scales, and the other just plots one set of data.

I would also like to "break" the x-axis of both subplots to account for a large period of time in between measurements in my data.

I have tried using the solution presented in this post, but as one poster claims, this method does not properly scale subplot x-axes.

I have been trying to use the brokenaxes package. It seems to create multiple subplots, scale them accordingly, and hide extraneous spines, ticks, and tick labels automatically. This package doesn't seem to support sharing an x-axis (sharex) with subplots created from GridSpec. Additionally, the method twinx does not work natively on a brokenaxes object. However each brokenaxes object contains a list of subplot axes objects. Each axes object can be used to generate a twinx, although this results in matplotlib rescaling subplots, redrawing spines, tick marks, and labels.

I have attempted to manually hide all the redrawn elements, however I'm still having trouble with a few things.

  • The lines that denote the broken axis are placed in an incorrect location
  • I have left y-axis tick marks on the top right subplot that I can't hide
  • The gridlines on the top right subplot are incorrect and inconsistent with the x-axis on the bottom subplot.
  • I want to use a date formatter using '%m-%d' for the x-axis ticks, but I'm getting an error attempting it:

AttributeError: 'CallCurator' object has no attribute 'set_major_formatter

Here is the result of my attempts: Attempted Plot

And the pickled pandas data and code used to generate it:

#!/usr/bin/env python3
import matplotlib as mpl
font= {'family': 'Arial',
        'size': 7}
mpl.rc('font', **font)
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
from matplotlib.ticker import NullFormatter
import matplotlib.dates as md
from brokenaxes import brokenaxes
import datetime
from pytz import timezone
import numpy as np
from pytz import timezone
import pandas as pd
from glob import glob

volt_color= 'tab:blue'
volt_style = 'solid'
amp_color = 'tab:red'
amp_style='dashed'

# starting date
start = pd.to_datetime("2020-05-20 15:40:09.059089184-07:00", infer_datetime_format=True).to_pydatetime()
start = timezone('US/Pacific').localize(start)

# load data
mudbat_data = pd.read_pickle("mudbat_data.pkl")

# perform rolling mean over data
mv = mudbat_data.rolling(5*60).mean()

# instantiate figures, and broken axes subplots
fig = plt.figure(figsize=(4,2))
gs = GridSpec(2,1)
tz = timezone('US/Pacific')
print(start)
[x1, x2, x3, x4] = [start, datetime.datetime(2020,5,27,tzinfo=tz), datetime.datetime(2020,7,20,tzinfo=tz),datetime.datetime(2020,7,22,tzinfo=tz)]
bax1 = brokenaxes(xlims=((x1,x2),(x3,x4)), subplot_spec=gs[0])
bax3 = brokenaxes(xlims=((x1,x2),(x3,x4)), subplot_spec=gs[1])

# plot first data
bax1.set_ylabel('Cell Voltage (V)')
bax1.plot(mv.index, mv['voltage'], color=volt_color, ls=volt_style)
bax1.tick_params(axis='y', labelcolor=volt_color)
bax1.grid(True)

# ensure all extraneous ticks for bax1 are hidden
bax1.axs[0].yaxis.tick_left()
bax1.axs[0].xaxis.set_ticklabels([])
bax1.axs[0].xaxis.set_ticklabels([])
bax1.axs[0].xaxis.set_ticks_position('none')
bax1.axs[0].yaxis.set_ticks_position('none')

bax1.axs[1].xaxis.set_ticklabels([])
bax1.axs[1].yaxis.set_ticklabels([])
bax1.axs[1].xaxis.set_ticks_position('none')
bax1.axs[1].yaxis.set_ticks_position('none')

# generate bax2 from bax1 axes
bax2 = []
for ax in bax1.axs:
    bax2.append(ax.twinx())

# plot data on bax2 subplots
bax2[0].plot(mv.index, -1E6*mv['current'], color=amp_color, ls=amp_style)
bax2[1].plot(mv.index, -1E6*mv['current'], color=amp_color, ls=amp_style)
bax2[1].set_ylabel('Harvesting Current (μA)')
bax2[1].tick_params(axis='y', labelcolor=amp_color)

# hide generated spines and ticks/labels
bax2[0].spines['right'].set_visible(False)
bax2[0].yaxis.set_ticklabels([])
bax2[0].xaxis.set_ticklabels([])
bax2[0].xaxis.set_ticks_position('none')
bax2[0].yaxis.set_ticks_position('none')

bax2[1].spines['left'].set_visible(False)
bax2[1].xaxis.set_ticklabels([])
bax2[1].xaxis.set_ticks_position('none')
bax2[1].yaxis.tick_right()

# I would like to use this formatter
#bax3.xaxis.set_major_formatter(md.DateFormatter('%m-%d'))
bax3.set_ylabel("Power (uW)")
bax3.grid(True)
bax3.plot(mv.index, 1E6*mv['power'])
bax3.tick_params(axis='x', labelsize=6, rotation=45)

plt.tight_layout(pad=0.4, w_pad=0.5, h_pad=0.5)
plt.subplots_adjust(hspace=0)
plt.savefig('plot.png', dpi=300)

Thank you for any help or suggestions you can give!

0

There are 0 best solutions below