Matplotlib: Overlay Plots with Different Scales

matplotlib: overlay plots with different scales?

It sounds like what you're wanting is subplots... What you're doing now doesn't make much sense (Or I'm very confused by your code snippet, at any rate...).

Try something more like this:

import matplotlib.pyplot as plt
import numpy as np

fig, axes = plt.subplots(nrows=3)

colors = ('k', 'r', 'b')
for ax, color in zip(axes, colors):
data = np.random.random(1) * np.random.random(10)
ax.plot(data, marker='o', linestyle='none', color=color)

plt.show()

Sample Image

Edit:

If you don't want subplots, your code snippet makes a lot more sense.

You're trying to add three axes right on top of each other. Matplotlib is recognizing that there's already a subplot in that exactly size and location on the figure, and so it's returning the same axes object each time. In other words, if you look at your list ax, you'll see that they're all the same object.

If you really want to do that, you'll need to reset fig._seen to an empty dict each time you add an axes. You probably don't really want to do that, however.

Instead of putting three independent plots over each other, have a look at using twinx instead.

E.g.

import matplotlib.pyplot as plt
import numpy as np
# To make things reproducible...
np.random.seed(1977)

fig, ax = plt.subplots()

# Twin the x-axis twice to make independent y-axes.
axes = [ax, ax.twinx(), ax.twinx()]

# Make some space on the right side for the extra y-axis.
fig.subplots_adjust(right=0.75)

# Move the last y-axis spine over to the right by 20% of the width of the axes
axes[-1].spines['right'].set_position(('axes', 1.2))

# To make the border of the right-most axis visible, we need to turn the frame
# on. This hides the other plots, however, so we need to turn its fill off.
axes[-1].set_frame_on(True)
axes[-1].patch.set_visible(False)

# And finally we get to plot things...
colors = ('Green', 'Red', 'Blue')
for ax, color in zip(axes, colors):
data = np.random.random(1) * np.random.random(10)
ax.plot(data, marker='o', linestyle='none', color=color)
ax.set_ylabel('%s Thing' % color, color=color)
ax.tick_params(axis='y', colors=color)
axes[0].set_xlabel('X-axis')

plt.show()

Sample Image

two (or more) graphs in one plot with different x-axis AND y-axis scales in python

The idea would be to create three subplots at the same position. In order to make sure, they will be recognized as different plots, their properties need to differ - and the easiest way to achieve this is simply to provide a different label, ax=fig.add_subplot(111, label="1").

The rest is simply adjusting all the axes parameters, such that the resulting plot looks appealing.
It's a little bit of work to set all the parameters, but the following should do what you need.

Sample Image

import matplotlib.pyplot as plt

x_values1=[1,2,3,4,5]
y_values1=[1,2,2,4,1]

x_values2=[-1000,-800,-600,-400,-200]
y_values2=[10,20,39,40,50]

x_values3=[150,200,250,300,350]
y_values3=[10,20,30,40,50]

fig=plt.figure()
ax=fig.add_subplot(111, label="1")
ax2=fig.add_subplot(111, label="2", frame_on=False)
ax3=fig.add_subplot(111, label="3", frame_on=False)

ax.plot(x_values1, y_values1, color="C0")
ax.set_xlabel("x label 1", color="C0")
ax.set_ylabel("y label 1", color="C0")
ax.tick_params(axis='x', colors="C0")
ax.tick_params(axis='y', colors="C0")

ax2.scatter(x_values2, y_values2, color="C1")
ax2.xaxis.tick_top()
ax2.yaxis.tick_right()
ax2.set_xlabel('x label 2', color="C1")
ax2.set_ylabel('y label 2', color="C1")
ax2.xaxis.set_label_position('top')
ax2.yaxis.set_label_position('right')
ax2.tick_params(axis='x', colors="C1")
ax2.tick_params(axis='y', colors="C1")

ax3.plot(x_values3, y_values3, color="C3")
ax3.set_xticks([])
ax3.set_yticks([])

plt.show()

multiple axis in matplotlib with different scales

If I understand the question, you may interested in this example in the Matplotlib gallery.

Sample Image

Yann's comment above provides a similar example.


Edit - Link above fixed. Corresponding code copied from the Matplotlib gallery:

from mpl_toolkits.axes_grid1 import host_subplot
import mpl_toolkits.axisartist as AA
import matplotlib.pyplot as plt

host = host_subplot(111, axes_class=AA.Axes)
plt.subplots_adjust(right=0.75)

par1 = host.twinx()
par2 = host.twinx()

offset = 60
new_fixed_axis = par2.get_grid_helper().new_fixed_axis
par2.axis["right"] = new_fixed_axis(loc="right", axes=par2,
offset=(offset, 0))

par2.axis["right"].toggle(all=True)

host.set_xlim(0, 2)
host.set_ylim(0, 2)

host.set_xlabel("Distance")
host.set_ylabel("Density")
par1.set_ylabel("Temperature")
par2.set_ylabel("Velocity")

p1, = host.plot([0, 1, 2], [0, 1, 2], label="Density")
p2, = par1.plot([0, 1, 2], [0, 3, 2], label="Temperature")
p3, = par2.plot([0, 1, 2], [50, 30, 15], label="Velocity")

par1.set_ylim(0, 4)
par2.set_ylim(1, 65)

host.legend()

host.axis["left"].label.set_color(p1.get_color())
par1.axis["right"].label.set_color(p2.get_color())
par2.axis["right"].label.set_color(p3.get_color())

plt.draw()
plt.show()

#plt.savefig("Test")

How do I put two plots next to each other when having plots with different scales (twinaxes)?

I want to point out this,

  • You generally don't plot after you do the plt.show().

You can create all the axes first and then later use them. Refer below example.

import numpy as np
import matplotlib.pyplot as plt

# Create some mock data
t = np.arange(0.01, 10.0, 0.01)
data1 = np.exp(t)
data2 = np.sin(2 * np.pi * t)

f = plt.figure(figsize=(10,3))

# create all axes we need
ax1 = plt.subplot(121)
ax2 = ax1.twinx()
ax3 = plt.subplot(122)
ax4 = ax3.twinx()

# share the secondary axes
ax1.get_shared_y_axes().join(ax1, ax3)

color = 'tab:red'
ax1.set_xlabel('time (s)')
ax1.set_ylabel('exp', color=color)
ax1.plot(t, data1, color=color)
ax1.tick_params(axis='y', labelcolor=color)
ax1.grid()

color = 'tab:blue'
ax2.set_ylabel('sin', color=color) # we already handled the x-label with ax1
ax2.plot(t, data2, color=color)
ax2.tick_params(axis='y', labelcolor=color)
plt.xlim(0,4)

color = 'tab:red'
ax3.set_xlabel('time (s)')
ax3.set_ylabel('exp', color=color)
ax3.plot(t, data1, color=color)
ax3.tick_params(axis='y', labelcolor=color)
ax3.grid()

color = 'tab:blue'
ax4.set_ylabel('sin', color=color) # we already handled the x-label with ax1
ax4.plot(t, data2, color=color)
ax4.tick_params(axis='y', labelcolor=color)
plt.xlim(4,6)

plt.tight_layout() # otherwise the right y-label is slightly clipped
plt.show()

Output Image:

Sample Image

Matplotlib: two plots on the same axes with different left right scales

try

x1.plot(x,np.array(data))  # do you mean "array" here?

in both places, instead of

x1.plot(x,np.arange(data))

But why do you want to use anything here at all? If you just

x1.plot(data) 

it will generate your x values automatically, and matplotlib will handle a variety of different iterables without converting them.

You should supply an example that someone else can run right away by adding some sample data. That may help you debug also. It's called a Minimal, Complete, and Verifiable Example.

You can get rid of some of that script too:

import matplotlib.pyplot as plt

things = ['2,3', '4,7', '4,1', '5,5']

da, oda = [], []
for thing in things:
a, b = thing.split(',')
da.append(a)
oda.append(b)

fig, ax1 = plt.subplots()
ax2 = ax1.twinx()

ax1.plot(da)
ax2.plot(oda)

plt.savefig("da oda") # save to a .png so you can paste somewhere
plt.show()

note 1: matplotlib is generating the values for the x axis for you as default. You can put them in if you want, but that's just an opportunity to make a mistake.

note 2: matplotlib is accepting the strings and will try to convert to numerical values for you.

if you want to embrace python, use a list comprehension and then zip(*) - which is the inverse of zip():

import matplotlib.pyplot as plt

things = ['2,3', '4,7', '4,1', '5,5']

da, oda = zip(*[thing.split(',') for thing in things])

fig, ax1 = plt.subplots()
ax2 = ax1.twinx()

ax1.plot(da)
ax2.plot(oda)

plt.savefig("da oda") # save to a .png so you can paste somewhere
plt.show()

But if you really really want to use arrays, then transpose - array.T - does what zip(*) does will also work for nested iterables. However you should convert to numerical values first using int() or float():

import matplotlib.pyplot as plt
import numpy as np

things = ['2,3', '4,7', '4,1', '5,5']

strtup = [thing.split(',') for thing in things]
inttup = [(int(a), int(b)) for a, b in strtup]

da, oda = np.array(inttup).T

fig, ax1 = plt.subplots()
ax2 = ax1.twinx()

ax1.plot(da)
ax2.plot(oda)

plt.savefig("da oda")
plt.show()

Sample Image

Two Bar Plots Side by Side with Different Scales

remove plt.autoscale and put plt.tight_layout out of bar_plot:

def bar_plot(action_list, action_number, ax):
y_pos = np.arange(len(action_list))

ax.barh(y_pos, action_number, height=0.75, align='center', color='b')
ax.set_yticks(y_pos)
ax.set_yticklabels(action_list)

ax.invert_yaxis()

ax.tick_params(labelsize=12, which='both', axis='both')

ax.yaxis.set_ticks_position('left')
ax.xaxis.set_ticks_position('bottom')

# set the limits manually
ax.set_ylim(-.5, len(action_list)-0.5)

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 10))
fig.subplots_adjust(wspace=0, hspace=0)
bar_plot(action_list, action_number, ax1)
bar_plot(action_list, action_number, ax2)

plt.tight_layout()

Output:

Sample Image

Two seaborn plots with different scales displayed on same plot but bars overlap

The main problem of the approach in the question, is that the first countplot doesn't take hue into account. The second countplot won't magically move the bars of the first. An additional categorical column could be added, only taking on the 'weekend' value. Note that the column should be explicitly made categorical with two values, even if only one value is really used.

Things can be simplified a lot, just starting from the original dataframe, which supposedly already has a column 'is_weeked'. Creating the twinx ax beforehand allows to write a loop (so writing the call to sns.countplot() only once, with parameters).

import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np

sns.set_style('dark')
# create some demo data
data = pd.DataFrame({'ride_hod': np.random.normal(13, 3, 1000).astype(int) % 24,
'is_weekend': np.random.choice(['weekday', 'weekend'], 1000, p=[5 / 7, 2 / 7])})
# now, make 'is_weekend' a categorical column (not just strings)
data['is_weekend'] = pd.Categorical(data['is_weekend'], ['weekday', 'weekend'])
fig, ax1 = plt.subplots(figsize=(16, 6))
ax2 = ax1.twinx()
for ax, category in zip((ax1, ax2), data['is_weekend'].cat.categories):
sns.countplot(data=data[data['is_weekend'] == category], x='ride_hod', hue='is_weekend', palette='Blues', ax=ax)
ax.set_ylabel(f'Count ({category})')
ax1.legend_.remove() # both axes got a legend, remove one
ax1.set_xlabel('Hour of Day')
plt.tight_layout()
plt.show()

sns.countplot on twinx ax

Plot two lines in one graph with each line own y-values

Based on @rperezsoto's comment, I have the following working code:

fig, ax1 = plt.subplots()

color = 'tab:red'
ax1.set_xlabel('x-axis')
ax1.set_ylabel('AUC', color='blue')
ax1.plot(x, y1, color='blue')
ax1.tick_params(axis='y', labelcolor='blue')

ax2 = ax1.twinx() # instantiate a second axes that shares the same x-axis

color = 'tab:blue'
ax2.set_ylabel('Standard deviation', color='grey') # we already handled the x-label with ax1
ax2.plot(x, y2, color='grey')
ax2.tick_params(axis='y', labelcolor='grey')

fig.tight_layout() # otherwise the right y-label is slightly clipped
plt.title('Y1 and Y2')
plt.show()

Sample Image



Related Topics



Leave a reply



Submit