Matplotlib broken twin x-axis - python

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:
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!

Related

Why is my MatPlotLib split axes graph not appearing?

I tried to create a graph side by side using matplotlib.
I don't get any errors when I run my code, instead, I just get a blank window from MatPlotLib.
Here's the link I used for my CSV.
https://ca.finance.yahoo.com/quote/%5EGSPTSE/history?p=%5EGSPTSE
Previously, I have also created a graph that overlayed the two lines(which works as intended), but they are not displaying as seperate graphs, which is what I am trying to do with my current code.
I tried this video for information in creating these graphs, but I can't replicate the graph shown in the video even when I copy the code.
https://www.youtube.com/watch?v=-2AMr95nUDw
from matplotlib import pyplot as mpl
import pandas as pd
data_better = pd.read_csv('What.csv')
# print(data_better.head()) #I used this part to find out what the headers were for x values
# print(data_better.columns[::])
mpl.axes([15000, 17000, 20000, 23000])
mpl.title("Open Values")
mpl.plot(data_better["Date"], data_better["Open"])
mpl.ylabel("Money")
mpl.axes([15000, 17000, 20000, 23000])
mpl.title("Close Values")
mpl.plot(data_better["Date"], data_better["Close"])
mpl.ylabel("Money")
mpl.show()
pyplot.axes accepts 4-tuple of floats in normalized (0, 1) units to place the axes. You can look at examples in Make Room For Ylabel Using Axesgrid to learn using it.
If you want to plot two plots in one figure, you need use different axes
from matplotlib import pyplot as plt
import pandas as pd
data_better = pd.read_csv('What.csv')
figure, (axes1, axes2) = plt.subplots(nrows=1, ncols=2)
axes1.set_title("Open Values")
axes1.plot(data_better["Date"], data_better["Open"])
axes1.set_ylabel("Money")
axes2.set_title("Close Values")
axes2.plot(data_better["Date"], data_better["Close"])
axes2.set_ylabel("Money")
plt.show()

Vertically align time series (plot and barplot) sharing same x-axis in matplotlib

Is there an easy way to align two subplots of a time series of different kinds (plot and barplot) in matplotlib? I use the pandas wrapper since I am dealing with pd.Series objects:
import pandas as pd
import matplotlib.pyplot as plt
series = pd._testing.makeTimeSeries()
fig, axes = plt.subplots(2, 1)
series.head(3).plot(marker='o', ax=axes[0])
series.head(3).plot.bar(ax=axes[1])
plt.tight_layout()
The result is not visually great, it would be great to keep the code simplicity and:
Vertically align data points in the top plot to the bars on the bottom plot
Share the axis of the bar plot with the first and remove the visibility on x-axis labels of the top plot altogether (but keep grids whenever present)
Based on the ideas thrown in the comments, I think that this is the simplest solution (giving up the pandas API), which is exactly what I needed:
import pandas as pd
import matplotlib.pyplot as plt
series = pd._testing.makeTimeSeries()
fig, axes = plt.subplots(2, 1, sharex=True)
axes[0].plot(series.head(3), marker='o')
axes[1].bar(series.head(3).index, series.head(3))
plt.tight_layout()
With eventual fix on the xticks for cases with missing values, where the xticks are not plotted daily (e.g. plt.xticks(series.head(3).index)).
Thanks for the help!

How to reposition title in seaborn.FacetGrid if tight_layout is applied?

I have a simple seaborn FacetGrid() with barplots inside.
I applied tight_layout() to my final plot, as xticks had to be properly positioned on the plot after rotation.
As result, when I want to add the title to the plot it is positioned in the wrong place, basically over the existing axes.
So, my question is how should the title be manipulated in order to be properly positioned in case tight_layout() is applied?
I reproduced the issue with the standard tips dataset:
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
tips = sns.load_dataset("tips")
days_dict = {day:day+', a long name' for day in tips['day'].unique()}
tips['day_long'] = tips['day'].map(lambda x: days_dict[x])
grid = sns.FacetGrid(tips,col='size',col_wrap=3,height=4,sharex=False)
grid.map(sns.barplot, 'day_long', 'total_bill').set_titles('{col_name}')
grid.fig.set_size_inches(10,10)
grid.fig.suptitle('Title (it should be placed higher)',fontsize=16)
for ax in grid.axes.flat:
for label in ax.get_xticklabels():
label.set_rotation(90)
plt.tight_layout()
plt.show()
Add (adjust the value to your taste)
grid.fig.subplots_adjust(top=0.90)
after tight_laout() to make some room at the top of the plot for the suptitle()

Seaborn/Matplotlib Date Axis barplot minor-major tick formatting

I'm building a Seaborn barplot. The x-axis are dates, and the y-axis are integers.
I'd like to format major/minor ticks for the dates. I'd like Mondays' ticks to be bold and a different color (ie, "major ticks"), with the rest of the week less bold.
I have not been able to get major and minor tick formatting on the x-axis to work with Seaborn barplots. I'm stumped, and thus turning here for help.
I'm starting with the stackoverflow example that answered this question: Pandas timeseries plot setting x-axis major and minor ticks and labels
If I do a simple modification it to use a Seaborn barplot and I lose my X-axis ticks:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.dates as dates
import seaborn as sns
idx = pd.date_range('2011-05-01', '2011-07-01')
s = pd.Series(np.random.randn(len(idx)), index=idx)
###########################################
## Swap out these two lines of code:
#fig, ax = plt.subplots()
#ax.plot_date(idx.to_pydatetime(), s, 'v-')
## with this one
ax = sns.barplot(idx.to_pydatetime(), s)
###########################################
ax.xaxis.set_minor_locator(dates.WeekdayLocator(byweekday=(1),
interval=1))
ax.xaxis.set_minor_formatter(dates.DateFormatter('%d\n%a'))
ax.xaxis.grid(True, which="minor")
ax.yaxis.grid()
ax.xaxis.set_major_locator(dates.MonthLocator())
ax.xaxis.set_major_formatter(dates.DateFormatter('\n\n\n%b\n%Y'))
plt.tight_layout()
## save the result to a png instead of plotting to screen:
myFigure = plt.gcf()
myFigure.autofmt_xdate()
myFigure.set_size_inches(11,3.8)
plt.title('Example Chart', loc='center')
plt.savefig('/tmp/chartexample.png', format='png', bbox_inches='tight')
I've tried a variety of approaches but something in Seaborn seems to be overriding or undoing any attempts at major and minor axis formatting that I've managed to cook up yet beyond some simple styling for all ticks when I use set_xticklabels().
I can sort of get formatting on just the major ticks by using MultipleLocator(), but I can't get any formatting on the minor ticks.
I've also experimented with myFigure.autofmt_xdate() to see if it would help, but it doesn't seem to like mixed major & minor ticks on the same axis either.
I came across this while trying to solve the same problem. Based on the useful pointer from #mwaskom (that categorical plots like boxplots lose their structure and just become date-named categories) and ended up doing the location and formatting in Python as so:
from datetime import datetime
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.dates as dates
import seaborn as sns
idx = pd.date_range('2011-05-01', '2011-07-01')
s = pd.Series(np.random.randn(len(idx)), index=idx)
fig, ax = plt.subplots(figsize = (12,6))
ax = sns.barplot(idx.to_pydatetime(), s, ax = ax)
major_ticks = []
major_tick_labels = []
minor_ticks = []
minor_tick_labels = []
for loc, label in zip(ax.get_xticks(), ax.get_xticklabels()):
when = datetime.strptime(label.get_text(), '%Y-%m-%d %H:%M:%S')
if when.day == 1:
major_ticks.append(loc)
major_tick_labels.append(when.strftime("\n\n\n%b\n%Y"))
else:
minor_ticks.append(loc)
if when.weekday() == 0:
minor_tick_labels.append(when.strftime("%d\n%a"))
else:
minor_tick_labels.append(when.strftime("%d"))
ax.set_xticks(major_ticks)
ax.set_xticklabels(major_tick_labels)
ax.set_xticks(minor_ticks, minor=True)
ax.set_xticklabels(minor_tick_labels, minor=True)
Of course, you don't have to set the ticks based on parsing the labels which were installed from the data, if it's easier to start with the source data and just keep the indices aligned, but I prefer to have a single source of truth.
You can also mess with font weight, rotation, etc, on individual labels by getting the Text objects for the relevant label and calling set_ methods on it.

Overlapping y-axis tick label and x-axis tick label in matplotlib

If I create a plot with matplotlib using the following code:
import numpy as np
from matplotlib import pyplot as plt
xx = np.arange(0,5, .5)
yy = np.random.random( len(xx) )
plt.plot(xx,yy)
plt.imshow()
I get a result that looks like the attached image. The problem is the
bottom-most y-tick label overlaps the left-most x-tick label. This
looks unprofessional. I was wondering if there was an automatic
way to delete the bottom-most y-tick label, so I don't have
the overlap problem. The fewer lines of code, the better.
In the ticker module there is a class called MaxNLocator that can take a prune kwarg.
Using that you can remove the first tick:
import matplotlib.pyplot as plt
from matplotlib.ticker import MaxNLocator
import numpy as np
xx = np.arange(0,5, .5)
yy = np.random.random( len(xx) )
plt.plot(xx,yy)
plt.gca().xaxis.set_major_locator(MaxNLocator(prune='lower'))
plt.show()
Result:
You can pad the ticks on the x-axis:
ax.tick_params(axis='x', pad=15)
Replace ax with plt.gca() if you haven't stored the variable ax for the current figure.
You can also pad both the axes removing the axis parameter.
A very elegant way to fix the overlapping problem is increasing the padding of the x- and y-tick labels (i.e. the distance to the axis). Leaving out the corner most label might not always be wanted. In my opinion, in general it looks nice if the labels are a little bit farther from the axis than given by the default configuration.
The padding can be changed via the matplotlibrc file or in your plot script by using the commands
import matplotlib as mpl
mpl.rcParams['xtick.major.pad'] = 8
mpl.rcParams['ytick.major.pad'] = 8
Most times, a padding of 6 is also sufficient.
This is answered in detail here. Basically, you use something like this:
plt.xticks([list of tick locations], [list of tick lables])

Categories

Resources