matplotlib two y axes with mixed z order - python

I would like to plot data on two y axes such that some of the data on the second y axis is behind the first y axis graph and part of it is above. Essentially I would like to have use "global" zorder parameter. Is that possible?
Here is a minimal example:
import numpy as np
import matplotlib.pyplot as plt
# generate data
x = np.linspace(0,30,30)
y1 = np.random.random(30)+x
y2 = np.random.random(30)+x*2
# create figure
fig, ax = plt.subplots()
# y1 axis
ax.plot(x,y1,lw=5,c='#006000', zorder=2)
ax.set_ylim((0,30))
ax.set_xlim((0,30))
# y2 axis
ax2 = ax.twinx() # instantiate a second axes that shares the same x-axis
ax2.fill_between([0, 30], [10, 10], color='pink', lw=0, zorder=1)
ax2.fill_between([0, 30], [60, 60], y2=[10, 10], color='gray', lw=0, zorder=1)
ax2.plot(x, y2,'o',ms=3,c='black', zorder=3)
ax2.set_ylim((0,60))
ax2.set_xlim((0,30))
# move y1 axis to the front
ax.set_zorder(ax2.get_zorder()+1)
ax.patch.set_visible(False)
I would like the background fill color to be in the background but the black data points should be on top of the green line. I tried to achieve this by defining the zorder parameter for these curves but apparently the zorder is only defined within one axis and not across multiple axes.

Here is a solution that gets what you want, however sub-ideal it may be in implementation.
import numpy as np
import matplotlib.pyplot as plt
# generate data
x = np.linspace(0,30,30)
y1 = np.random.random(30)+x
y2 = np.random.random(30)+x*2
# create figure
fig, ax1 = plt.subplots()
ax2 = ax1.twinx()
ax3 = ax2.twiny()
ax1.get_shared_x_axes().join(ax2, ax3)
# line
ax1.plot(x,y1,lw=5,c='#006000')
ax1.set_ylim((0,30))
ax1.set_xlim((0,30))
# points
ax2.plot(x, y2,'o',ms=3,c='black')
ax2.set_ylim((0,60))
# fills
ax3.set_xticklabels([])
ax3.get_xaxis().set_visible(False)
ax3.fill_between([0, 30], [10, 10], color='pink', lw=0)
ax3.fill_between([0, 30], [60, 60], y2=[10, 10], color='gray', lw=0)
# order
ax3.zorder = 1 # fills in back
ax1.zorder = 2 # then the line
ax2.zorder = 3 # then the points
ax1.patch.set_visible(False)
plt.show()

It seems there is a clear relationship between the two axes (in this case a factor of 2). So one could plot everything in the same axes and just scale the necessary parts by the factor. (This requires matplotlib >= 3.1)
import numpy as np
import matplotlib.pyplot as plt
# generate data
x = np.linspace(0,30,30)
y1 = np.random.random(30)+x
y2 = np.random.random(30)+x*2
# create figure
fig, ax = plt.subplots()
f = lambda x: 2*x
g = lambda x: x/2
ax2 = ax.secondary_yaxis('right', functions=(f,g))
ax.plot(x, y1,lw=5,c='#006000', zorder=2)
ax.plot(x, g(y2),'o',ms=3,c='black', zorder=3)
ax.set_ylim((0,30))
ax.set_xlim((0,30))
ax.fill_between([0, 30], [5, 5], color='pink', lw=0, zorder=1)
ax.fill_between([0, 30], [30, 30], y2=[5, 5], color='gray', lw=0, zorder=0)
plt.show()

Related

How to plot 2D density clouds so that multiple clouds can be combined?

I'd like to make some similar plots like my first figure. I already used the code below to get the second figure. How can I obtain the same effect as the first figure?
from scipy import stats
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
#read data
df=pd.read_excel('plot.xlsx')
df_data=pd.DataFrame(df,columns=df.columns)
df_data.dropna(how='any',subset=["F1","N1"],inplace=True)
m1=df_data.loc[:,['F1'][0]].values
m2=df_data.loc[:,['N1'][0]].values
xmin = m1.min()
xmax = m1.max()
ymin = m2.min()
ymax = m2.max()
#gaussian kernel density estimation
X, Y = np.mgrid[xmin:xmax:100j, ymin:ymax:100j]
positions = np.vstack([X.ravel(), Y.ravel()])
values = np.vstack([m1, m2])
kernel = stats.gaussian_kde(values)
Z = np.reshape(kernel(positions).T, X.shape)
fig, ax = plt.subplots(figsize=(6,6))
ax.pcolormesh(X, Y, Z.reshape(X.shape), shading='gouraud', cmap=plt.cm.viridis)
plt.xlabel('F1')
plt.ylabel('N1')
plt.show()
Desired plot:
Current plot:
You can use seaborn's kdeplot() with fill=True and setting a threshold (thresh= between 0 and 1) which cuts off the lowest densities. You may need to experiment to find a value that best fits your data. Note that nontransparent shapes are used, so the last one drawn will erase part of the previous ones.
If you have enough data, you can also create scatter plots with a very low alpha. Here also you'll need to experiment to find an alpha that works for your data. If the number of points is very high, you'll also need to reduce the scatter dot size (and set the edgecolor to 'none'). This approach handles overlap quite well, but needs sufficient data points.
Here is an example of both approaches:
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
F1 = np.random.normal(85, 5, 1000)
N1 = np.random.normal(1700, 60, F1.size)
F2 = np.random.normal(55, 5, 1500)
N2 = np.random.normal(1800, 70, F2.size)
F3 = np.random.normal(45, 20, 2100)
N3 = np.random.normal(2200, 70, F3.size)
F4 = np.random.normal(50, 22, 2500)
N4 = np.random.normal(1400, 60, F4.size)
fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(15, 5))
ax1.set_title('Seaborn kdeplot')
sns.kdeplot(x=F1, y=N1, cmap='Blues', fill=True, thresh=0.02, ax=ax1)
sns.kdeplot(x=F2, y=N2, cmap='Reds', fill=True, thresh=0.02, ax=ax1)
sns.kdeplot(x=F3, y=N3, cmap='Greens', fill=True, thresh=0.02, ax=ax1)
sns.kdeplot(x=F4, y=N4, cmap='Purples', fill=True, thresh=0.02, ax=ax1)
ax2.set_title('Scatter plot with high transparency')
ax2.scatter(F1, N1, color='blue', alpha=0.02)
ax2.scatter(F2, N2, color='red', alpha=0.02)
ax2.scatter(F3, N3, color='green', alpha=0.02)
ax2.scatter(F4, N4, color='purple', alpha=0.02)
plt.tight_layout()
plt.show()

plt.gcf() doesn't show previous instance of a plot

I want to save an instance of a plot into an object so that I can display it later by just calling that object.
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(0, 10, 0.1)
y1 = x
y2 = np.sin(x)
plt.plot(x, y1, linewidth=1, color = 'deepskyblue')
fig1 = plt.gcf()
plt.plot(x, y2, linewidth=1, color = 'red')
fig2 = plt.gcf()
In this example, I first draw a blue line (y1=x) and use plt.gcf() to save an instance of this plot in fig1. Then I add a red curve (y2=sin(x)) to the plot and use plt.gcf() again to save this plot in fig2. Now, I expect that when I call fig1 I only get the blue line, and when I call fig2 I get both lines. Like this (I'm in Jupyter):
fig1 # or fig1.show() if not in Jupyter
Only blue curve
fig2
Both curves
But, in reality, when I call fig1 and fig2, both of them show both curves (like the second picture). Can someone please help how I can correctly get an instance of each plot so that I can display each of them later whenever I want?
You need to force matplotlib to actually draw the figure by setting a plot.show() in your code:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(0, 10, 0.1)
y1 = x
y2 = np.sin(x)
plt.plot(x, y1, linewidth=1, color = 'deepskyblue')
plt.show()
fig1 = plt.gcf()
plt.plot(x, y2, linewidth=1, color = 'red')
plt.show()
fig2 = plt.gcf()
Using the function plt.plot() always plots to the current axis (if no axis is present, a new one is created).
You can also tell matplotlib explicitly to open a new figure:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(0, 10, 0.1)
y1 = x
y2 = np.sin(x)
# open first figure
fig1 = plt.figure()
# plot
plt.plot(x, y1, linewidth=1, color = 'deepskyblue')
# open second figure
fig2 = plt.figure()
#plot
plt.plot(x, y2, linewidth=1, color = 'red')
Although this already fixes your problem, it is considered good practice to use an object-oriented version of this like this:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(0, 10, 0.1)
y1 = x
y2 = np.sin(x)
# open first figure, axis
fig1, ax1 = plt.subplots()
# plot
ax1.plot(x, y1, linewidth=1, color = 'deepskyblue')
# open second figure, axis
fig2, ax2 = plt.subplots()
#plot
ax2.plot(x, y2, linewidth=1, color = 'red')
In all cases, you will get:
Now, why don't you need plt.show() in the other approaches? Well, matplotlib by explicitly opening a new figure/axis, it is obvious that the previous axis is finished and can be drawn.
The last approach is the clearest as you tell exactly which figure and which axis you are considering.

How to plot data in background of multiple subplots in matplotlib

I am trying to create a figure consisting of multiple subplots stacked on top of each other. However, I also want a single plot that runs through all the stacked subplots and shows up "behind" them. I'm not concerned about the actual y-values so it's fine that the y-axis is unreadable in this case. Below is what I have so far:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-1, 1, 100)
y = x
y2 = x**2
y3 = x**3
fig = plt.figure()
ax1 = fig.add_axes([0.1, 0.1, 0.8, 0.4])
ax2 = fig.add_axes([0.1, 0.5, 0.8, 0.4])
ax3 = ax1.twinx()
ax4 = ax2.twinx()
ax1.plot(x, y)
ax2.plot(x, y3)
ax3.plot(x, y2)
ax4.plot(x, y2)
Essentially, I want ax3 and ax4 to combine into one large plot that shows a single quadratic function while having a cubic function stacked on top of a linear function in the same figure. Ideally, I'll have three actually separate axes since I'll want to be customizing and performing actions to one subplot without affecting the other two in the future.
Thanks!
I guess the idea would be to first create two subplots one below the other and reduce the spacing in between to 0. Then create a new subplot covering the complete area and make the background transparent. Also put the ticks and labels of the third axes to the right. Then plot to each of the three axes.
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-1, 1, 100)
y = x
y2 = x**2
y3 = x**3
fig, (ax1, ax2) = plt.subplots(nrows=2, sharex=True)
fig.subplots_adjust(hspace=0.0)
ax3 = fig.add_subplot(111, sharex=ax1, label="right axes")
l1, = ax1.plot(x, y, color="C0")
l2, = ax2.plot(x, y3, color="C2")
l3, = ax3.plot(x, y2, color="C3")
ax1.tick_params(axis="y", colors=l1.get_color())
ax2.tick_params(axis="y", colors=l2.get_color())
ax3.set_facecolor("none")
ax3.tick_params(labelbottom=False, bottom=False, labelleft=False, left=False,
right=True, labelright=True, colors=l3.get_color())
plt.show()

Seaborn plot with second y axis

i wanted to know how to make a plot with two y-axis so that my plot that looks like this :
to something more like this by adding another y-axis :
i'm only using this line of code from my plot in order to get the top 10 EngineVersions from my data frame :
sns.countplot(x='EngineVersion', data=train, order=train.EngineVersion.value_counts().iloc[:10].index);
I think you are looking for something like:
import matplotlib.pyplot as plt
x = [1,2,3,4,5]
y = [1000,2000,500,8000,3000]
y1 = [1050,3000,2000,4000,6000]
fig, ax1 = plt.subplots()
ax2 = ax1.twinx()
ax1.bar(x, y)
ax2.plot(x, y1, 'o-', color="red" )
ax1.set_xlabel('X data')
ax1.set_ylabel('Counts', color='g')
ax2.set_ylabel('Detection Rates', color='b')
plt.show()
Output:
#gdubs If you want to do this with Seaborn's library, this code set up worked for me. Instead of setting the ax assignment "outside" of the plot function in matplotlib, you do it "inside" of the plot function in Seaborn, where ax is the variable that stores the plot.
import seaborn as sns # Calls in seaborn
# These lines generate the data to be plotted
x = [1,2,3,4,5]
y = [1000,2000,500,8000,3000]
y1 = [1050,3000,2000,4000,6000]
fig, ax1 = plt.subplots() # initializes figure and plots
ax2 = ax1.twinx() # applies twinx to ax2, which is the second y axis.
sns.barplot(x = x, y = y, ax = ax1, color = 'blue') # plots the first set of data, and sets it to ax1.
sns.lineplot(x = x, y = y1, marker = 'o', color = 'red', ax = ax2) # plots the second set, and sets to ax2.
# these lines add the annotations for the plot.
ax1.set_xlabel('X data')
ax1.set_ylabel('Counts', color='g')
ax2.set_ylabel('Detection Rates', color='b')
plt.show(); # shows the plot.
Output:
Seaborn output example
You could try this code to obtain a very similar image to what you originally wanted.
import seaborn as sb
from matplotlib.lines import Line2D
from matplotlib.patches import Rectangle
x = ['1.1','1.2','1.2.1','2.0','2.1(beta)']
y = [1000,2000,500,8000,3000]
y1 = [3,4,1,8,5]
g = sb.barplot(x=x, y=y, color='blue')
g2 = sb.lineplot(x=range(len(x)), y=y1, color='orange', marker='o', ax=g.axes.twinx())
g.set_xticklabels(g.get_xticklabels(), rotation=-30)
g.set_xlabel('EngineVersion')
g.set_ylabel('Counts')
g2.set_ylabel('Detections rate')
g.legend(handles=[Rectangle((0,0), 0, 0, color='blue', label='Nontouch device counts'), Line2D([], [], marker='o', color='orange', label='Detections rate for nontouch devices')], loc=(1.1,0.8))

invert_xaxis gives an error when using matplotlib plt.barh

I am trying to plot 2 way bar charts. I want to invert the x-axis of x1 so that 0 is in the middle of both. I keep getting the error:
AttributeError: 'BarContainer' object has no attribute 'invert_xaxis'
Here is my code:
import matplotlib.pyplot as plt
y = ['F','M','H']
x1 = [8, 4, 3]
x2 = [2, 4, 7]
fig, axes = plt.subplots(ncols=2, sharey=True)
axes[0] = plt.barh(y, x1, align='center', color='b')
axes[1] = plt.barh(y, x2, align='center', color='r')
axes[0].invert_xaxis()
plt.show()
The problem is that you are assigning the plots to the two axis objects instead of using them to plot. The correct way is to directly use the axis objects to plot the barh. Then the things will work as expected.
import matplotlib.pyplot as plt
y = ['F','M','H']
x1 = [8, 4, 3]
x2 = [2, 4, 7]
fig, axes = plt.subplots(ncols=2, sharey=True)
axes[0].barh(y, x1, align='center', color='b') # <---- Changed here
axes[1].barh(y, x2, align='center', color='r') # <---- Changed here
axes[0].invert_xaxis()
plt.show()

Categories

Resources