Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Matplotlib axis with two scales shared origin

I need two overlay two datasets with different Y-axis scales in Matplotlib. The data contains both positive and negative values. I want the two axes to share one origin, but Matplotlib does not align the two scales by default.

import numpy as np import matplotlib.pyplot as plt  fig = plt.figure() ax1 = fig.add_subplot(111) ax2 = ax1.twinx()  ax1.bar(range(6), (2, -2, 1, 0, 0, 0)) ax2.plot(range(6), (0, 2, 8, -2, 0, 0)) plt.show() 

I suppose it is possible to perform some computation with .get_ylim() and .set_ylim() two align the two scales. Is there an easier solution?

Output from the sample above

like image 581
lyschoening Avatar asked May 07 '12 12:05

lyschoening


People also ask

Can a matplotlib figure can have more than one set of Axes?

Sometimes it is natural to have more than one distinct group of Axes grids, in which case Matplotlib has the concept of SubFigure : SubFigure. A virtual figure within a figure.

What does PLT axis (' equal ') do?

The plt. axis() method allows you to set the x and y limits with a single call, by passing a list which specifies [xmin, xmax, ymin, ymax] : In [11]: plt.


2 Answers

use the align_yaxis() function:

import numpy as np import matplotlib.pyplot as plt  def align_yaxis(ax1, v1, ax2, v2):     """adjust ax2 ylimit so that v2 in ax2 is aligned to v1 in ax1"""     _, y1 = ax1.transData.transform((0, v1))     _, y2 = ax2.transData.transform((0, v2))     inv = ax2.transData.inverted()     _, dy = inv.transform((0, 0)) - inv.transform((0, y1-y2))     miny, maxy = ax2.get_ylim()     ax2.set_ylim(miny+dy, maxy+dy)   fig = plt.figure() ax1 = fig.add_subplot(111) ax2 = ax1.twinx()  ax1.bar(range(6), (2, -2, 1, 0, 0, 0)) ax2.plot(range(6), (0, 2, 8, -2, 0, 0))  align_yaxis(ax1, 0, ax2, 0) plt.show() 

enter image description here

like image 78
HYRY Avatar answered Oct 18 '22 01:10

HYRY


In order to ensure that the y-bounds are maintained (so no data points are shifted off the plot), and to balance adjustment of both y-axes, I made some additions to @HYRY's answer:

def align_yaxis(ax1, v1, ax2, v2):     """adjust ax2 ylimit so that v2 in ax2 is aligned to v1 in ax1"""     _, y1 = ax1.transData.transform((0, v1))     _, y2 = ax2.transData.transform((0, v2))     adjust_yaxis(ax2,(y1-y2)/2,v2)     adjust_yaxis(ax1,(y2-y1)/2,v1)  def adjust_yaxis(ax,ydif,v):     """shift axis ax by ydiff, maintaining point v at the same location"""     inv = ax.transData.inverted()     _, dy = inv.transform((0, 0)) - inv.transform((0, ydif))     miny, maxy = ax.get_ylim()     miny, maxy = miny - v, maxy - v     if -miny>maxy or (-miny==maxy and dy > 0):         nminy = miny         nmaxy = miny*(maxy+dy)/(miny+dy)     else:         nmaxy = maxy         nminy = maxy*(miny+dy)/(maxy+dy)     ax.set_ylim(nminy+v, nmaxy+v) 
like image 39
drevicko Avatar answered Oct 18 '22 00:10

drevicko