Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Matplotlib bug: Data is shown above spine with twinx()

I'm trying to plot two data sets that differ by some orders of magnitude. After some research (it was literally one google search away) I found out about the twinx() function.

However, I've run into some trouble when using it. I'm producing publication-grade figures with this tool, and there is something that is bugging me.

System: matplotlib v1.3.1, python v2.7.4 on Ubuntu

Consider the following code:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import MaxNLocator
from matplotlib.ticker import AutoMinorLocator


time = np.arange(0, 365, 1)
y1 = np.random.rand(len(time)) * np.exp(-0.03 * time)
y2 = 0.001 * np.random.rand(len(time)) * np.exp(-0.02 * time)

for i in range(30):
    y2[-i] = 0

fig = plt.figure(figsize=(8,6), dpi=300)
fig.subplots_adjust(hspace=0.0, right=0.9, top=0.94, left=0.12, bottom=0.07)
ax1 = fig.add_subplot(111)
ax2 = ax1.twinx()

for tl in ax1.get_yticklabels():
    tl.set_color('r')
for tl in ax2.get_yticklabels():
    tl.set_color('b')

for ax in [ax1, ax2]:
    ax.yaxis.set_major_locator(MaxNLocator(
        nbins=5,
        steps=[1,2,3,4,5,10],
        integer=False,
        symmetric=False,
        prune=None))
    ax.yaxis.set_minor_locator(AutoMinorLocator(4))

ax2.plot(time, y1, 'b-')
ax1.plot(time, y2, 'r-')
ax1.set_ylabel(r"y value", labelpad=15)
ax1.set_xlabel(r"x value")
ax1.set_xlim(0,365)
ax1.set_xticks(range(0,370,30))
ax1.xaxis.set_minor_locator(AutoMinorLocator(3))

fig.savefig("Errorplot.png", dpi=60)

Now, when looking at this low resolution output, I see nothing weird: Error plot

However, when zooming in on a high-resolution (say, dpi=300) version, I see the following: Zoomed section of the error plot

It is obvious that the lines in the second axes are drawn over the spine of the first.

How do I alleviate this problem? Is there a way to redraw the spines of an axes instance?

What I've tried

  • Changing the order of the plot() calls
  • Setting the zorder kwarg to -1, -10. Even when the ax2 zorder was smaller than the ax1, I had this behaviour.
  • In combination with the above: ax2.spines['bottom'].set_zorder(10)
like image 688
tschoppi Avatar asked Feb 17 '26 23:02

tschoppi


2 Answers

It does not seem this post gets many views but I found it helpful and would like to add another way to change the zorder on the off chance it will help someone else. This post more closely matched what I wanted to do but the important thing is changing the zorder, which @Ffisegydd's outlined. Another way to do this is using the following:

fig, ax1 = plt.subplots()
ax2 = ax1.twinx()
ax1.set_zorder(ax2.get_zorder()+1) # put ax1 in front of ax2 
ax1.patch.set_visible(False) # hide the 'canvas' 

ax1.plot(data1[:,0], data1[:,0])  # plot whatever data you want
ax2.plot(data2[:,0], data2[:,0])  # plot commands can precede the set_zorder command

I realize this does not directly solve the asked question but hopefully it can help someone who, like me, finds this post searching for a solution to a closely related issue.

like image 95
Steven C. Howell Avatar answered Feb 19 '26 14:02

Steven C. Howell


You can use the zorder kwarg in matplotlib to change the order that objects are drawn, see an example code here

Below I have added two examples (I have cut out the majority of your code to save space). In one example I set the zorder to a larger value, meaning that it will be drawn last (and hence on top). In the other example I set zorder to -1 meaning that it will be drawn first (and hence below).

ax2.plot(time, y1, 'b-', zorder=5)
ax1.plot(time, y2, 'r-', zorder=5)

Example plot

ax2.plot(time, y1, 'b-', zorder=-1)
ax1.plot(time, y2, 'r-', zorder=-1)

Example plot 2

like image 28
Ffisegydd Avatar answered Feb 19 '26 13:02

Ffisegydd



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!