matplotlib interactive subplots with sharex and twiny - python

I am plotting a figure with matplotlib using the following code. I have two stacked subplots, as well as a added second x axis using twiny.
#!/usr/bin/python
import os
import numpy as np
import matplotlib.pylab as mp
# random data
data = np.random.random((10,3))
data[:,0] = np.linspace(0,1,10)
# init figure
fig, axs = mp.subplots(2, sharex=True)
axs = np.append(axs,axs[1].twiny())
# plot top
axs[0].plot(data[:,0],data[:,1],'bo-',linewidth=2.0)
axs[0].axis([data[0,0],data[-1,0],data[:,1].min(),data[:,1].max()])
# plot bottom
axs[1].plot(data[:,0],data[:,2],'rx-',linewidth=2.0)
# add second axis
axs[2].xaxis.set_ticks_position('bottom')
axs[2].xaxis.set_label_position('bottom')
axs[2].spines['bottom'].set_position(('outward', 40))
axs[2].set_xlim(-180,180)
# plot
mp.tight_layout(pad=0.6)
mp.show()
My problem is in the interactive plot window: if I pan the lower plot, all 3 x-axes move accordingly. If I pan the top plot, the added twiny axis does not move. Would this be possible in the current set up?

Related

Matplotlib - duplicated axes - prevent gridlines from covering data - set right ylabel

I want to duplicate axes so that I can express an exponent in terms of its doubling time.
I think I am doing things right, but I have two problems
no label on the right hand side of the chart and
y-axis gridlines that are plotted above the data that I cannot shift to the bottom, nor remove.
Example code follows:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
MARGINS = 0.02
data = pd.Series(np.arange(0.05, 1.0, 0.1))
# preliminaries
plt.style.use('ggplot')
fig, ax = plt.subplots()
ax.figure.set_size_inches((8, 4))
ax.set_ylabel('$k$') # This works
ax.margins(MARGINS)
ax.set_axisbelow(True)
# duplicate the axes
axr = ax.twinx().twiny()
axr.margins(MARGINS)
axr.set_ylabel('Doubling time') # This does not work
# No x-ticks at the top
axr.xaxis.set_ticks([])
axr.xaxis.set_ticklabels([])
# plot the data
ax.plot(data.index, data)
# label right-hand y-axis
locations = ax.get_yticks()
new_labels = [f'{np.log(2)/x:,.2f}' if x != 0 else '∞' for x in locations ]
axr.yaxis.set_ticks(locations)
axr.yaxis.set_ticklabels(new_labels)
axr.set_axisbelow(True) # this does not work
# match the left and right ylim settings
axr.set_ylim(ax.get_ylim())
axr.set_xlim(ax.get_xlim())
# remove the grid
axr.grid(False, which='both')
axr.yaxis.grid(False, which='both') # this does not work
# finish-up
ax.set_title('Chart')
fig.tight_layout(pad=1.1)
plt.show()
plt.close('all')
Desired output:
Similar chart to above but with:
a right hand side y-axis label
no y-axis gridlines over the data line (but keep the horizontal gridlines under the dataline)
Change the order of twiny and twinx:
axr = ax.twiny().twinx()

How to insert a triangle/contour plot generated with GetDist into a Matplotlib subplot?

I'm doing some analysis on MCMC samples and I'm using the GetDist python package to create my contour plots. However the contour plots are only a part of the whole analysis, and I would like to show some other plots along with the contour plot in the same figure.
I'm using matplotlib to generate all my other plots, so my question is: is there any way to have a GetDist plot in a matplotlib subplot, so that I have a matplotlib figure with multiple plots and a GetDist plot in it?
I'm using GridSpec to split the figure in subplots (and also to split subplots in subsubplots).
I tried to set a particular subplot as the current axis before creating the triangle plot, and I also tried to look at the GetDist source code to find a way to pass the wanted subplot as an argument to GetDist, but with no luck.
Right now, my code looks something like this
import matplotlib.pyplot as plt
import getdist.plots
from matplotlib import gridspec
import numpy as np
import numpy.random
N = 4
# Generate mock random data
chain = np.array([numpy.random.normal(loc=(i+0.5), scale=(i+0.5), size=100000) for i in xrange(N)])
Names = [str(unichr(i+97)) for i in xrange(N)]
Labels = [str(unichr(i+97)) for i in xrange(N)]
Sample = getdist.MCSamples(samples=chain.T, names=Names, labels=Labels)
#Set up plot layout
fig = plt.figure(figsize=(10.5,12.5))
gs = gridspec.GridSpec(3, 2, width_ratios=[2,3], height_ratios=[10,0.5,4], wspace=0.2, hspace=.05)
Lplot = gridspec.GridSpecFromSubplotSpec(N, 1, subplot_spec=gs[0,0], hspace=0.)
Rplot = gridspec.GridSpecFromSubplotSpec(2,1,subplot_spec=gs[0,1], height_ratios=[4,1.265])
Dplot = plt.subplot(gs[2,0:2])
axL = [plt.subplot(Lplot[i]) for i in xrange(N)]
axR = plt.subplot(Rplot[1])
GD = plt.subplot(Rplot[0])
for i in axL+[axR]+[GD]+[Dplot]: i.set_xticks([]); i.set_yticks([])
plt.sca(GD)
# Generate triangle plot
g = getdist.plots.getSubplotPlotter()
g.triangle_plot(Sample, filled=True)
plt.savefig("outfile.pdf", bbox_inches='tight')
I would like to have my contour plot in the "GD" subplot.
Any help?

Controlling legend across multiple subplots with windrose axes

I cannot figure out how to make the legends not overlap with my figures (see below figure) in subplots. The problem is my axes are complicated because they are from a windrose. To get the axes:
1) I have downloaded the windrose.py from https://github.com/akrherz/windrose/tree/darylchanges
2) I copied the windrose.py into the same path with my python script, example.py
3) I changed windrose.py so that it is able to do subplots, according to the steps from Subplot of Windrose in matplotlib . Those steps were to make WindroseAxes as a projection into matplotlib. I edited the file windrose.py:
3a) Include an
import from matplotlib.projections import register_projection
at the beginning of the file.
3b) Then add a name variable :
class WindroseAxes(PolarAxes):
name = 'windrose'
...
3c) Finally, at the end of windrose.py, you add:
register_projection(WindroseAxes)
Once that is done, you can easily create your windrose axes using the projection argument to the matplotlib axes.
4) Now I ran my script below (example of my real script)
from windrose import WindroseAxes
import numpy as np
import matplotlib.pyplot as plt
from windrose_subplot import WindroseAxes
wind_speeds1 = np.array([12,10,13,15])
wind_dirs1 = np.array([60,76,32,80]) # in degrees
wind_speeds2 = np.array([23,12,10,8])
wind_dirs2 = np.array([23,45,29,13])
fig = plt.figure()
ax1 = fig.add_subplot(231,projection='windrose')
ax1.bar(wind_dirs1,wind_speeds1,normed=True,opening=0.8,edgecolor='white')
ax2 = fig.add_subplot(232,projection='windrose')
ax2.bar(wind_dirs2,wind_speeds2,normed=True,opening=0.8,edgecolor='white')
ax1.legend()
ax2.legend()
plt.tight_layout()
plt.show()
Ideally, I would like to create one legend with the max/min of all the subplots because they are all the same units . This legend will have to be the corresponding colors for each subplot for the same values across subplots (eg, a single normal legend relevant to all subplots). There will be 6 subplots in the real script but 2 here for now shows the point.
This is simple to fix. In order to only plot one legend, comment out or delete where you plot the first legend. In order to move the legend off of the plot, use bbox_to_anchor=() with some logical location. See below for an example that works for this example.
import numpy as np
import matplotlib.pyplot as plt
from windrose_subplot import WindroseAxes
wind_speeds1 = np.array([12,10,13,15])
wind_dirs1 = np.array([60,76,32,80]) # in degrees
wind_speeds2 = np.array([23,12,10,8])
wind_dirs2 = np.array([23,45,29,13])
fig = plt.figure()
ax1 = fig.add_subplot(231,projection='windrose')
ax1.bar(wind_dirs1,wind_speeds1,normed=True,opening=0.8,edgecolor='white')
ax2 = fig.add_subplot(232,projection='windrose')
ax2.bar(wind_dirs2,wind_speeds2,normed=True,opening=0.8,edgecolor='white')
# ax1.legend()
ax2.legend(bbox_to_anchor=(1.2 , -0.1))
plt.tight_layout()
plt.show()
However, note the bbox_to_anchor is reliant on the axis that the legend comes from, so
ax1.legend(bbox_to_anchor=1.2, -0.1))
#ax2.legend()
would display the legend underneath the second axis:
Thank you Hazard11, I found your answer very useful :) There is an issue with the answer though is the legend does not represent the first subplot because the bins are generated when creating the second subplot.
I just solved this issue by calculating the bins using numpy.histogram first and then passing that to windrose.WindroseAxes.bar() when creating each wind rose. Doing it this way means you need to pick which one you want to use to generate the bins. Another way to do it would be to define the bins manually or to create a function which generates some efficient binning for both which could then be used.
wind_speeds1 = np.array([12,10,13,15])
wind_dirs1 = np.array([60,76,32,80]) # in degrees
wind_speeds2 = np.array([23,12,10,8])
wind_dirs2 = np.array([23,45,29,13])
wind_speeds_bins = np.histogram(wind_speeds2, 5)[1]
fig = plt.figure()
ax1 = fig.add_subplot(231, projection='windrose')
ax1.bar(wind_dirs1 ,wind_speeds1, normed=True, opening=0.8, edgecolor='white', bins=wind_speeds_bins)
ax2 = fig.add_subplot(232, projection='windrose')
ax2.bar(wind_dirs2, wind_speeds2, normed=True, opening=0.8, edgecolor='white', bins=wind_speeds_bins)
# ax1.legend()
ax2.legend(bbox_to_anchor=(1.2 , -0.1))
plt.tight_layout()
plt.show()

adjust matplotlib subplot spacing after tight_layout

I would like to minimize white space in my figure. I have a row of sub plots where four plots share their y-axis and the last plot has a separate axis.
There are no ylabels or ticklabels for the shared axis middle panels.
tight_layout creates a lot of white space between the the middle plots as if leaving space for tick labels and ylabels but I would rather stretch the sub plots. Is this possible?
import matplotlib.gridspec as gridspec
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
fig = plt.figure()
gs = gridspec.GridSpec(1, 5, width_ratios=[4,1,4,1,2])
ax = fig.add_subplot(gs[0])
axes = [ax] + [fig.add_subplot(gs[i], sharey=ax) for i in range(1, 4)]
axes[0].plot(np.random.randint(0,100,100))
barlist=axes[1].bar([1,2],[1,20])
axes[2].plot(np.random.randint(0,100,100))
barlist=axes[3].bar([1,2],[1,20])
axes[0].set_ylabel('data')
axes.append(fig.add_subplot(gs[4]))
axes[4].plot(np.random.randint(0,5,100))
axes[4].set_ylabel('other data')
for ax in axes[1:4]:
plt.setp(ax.get_yticklabels(), visible=False)
sns.despine();
plt.tight_layout(pad=0, w_pad=0, h_pad=0);
Setting w_pad = 0 is not changing the default settings of tight_layout. You need to set something like w_pad = -2. Which produces the following figure:
You could go further, to say -3 but then you would start to get some overlap with your last plot.
Another way could be to remove plt.tight_layout() and set the boundaries yourself using
plt.subplots_adjust(left=0.065, right=0.97, top=0.96, bottom=0.065, wspace=0.14)
Though this can be a bit of a trial and error process.
Edit
A nice looking graph can be achieved by moving the ticks and the labels of the last plot to the right hand side. This answer shows you can do this by using:
ax.yaxis.tick_right()
ax.yaxis.set_label_position("right")
So for your example:
axes[4].yaxis.tick_right()
axes[4].yaxis.set_label_position("right")
In addition, you need to remove sns.despine(). Finally, there is now no need to set w_pad = -2, just use plt.tight_layout(pad=0, w_pad=0, h_pad=0)
Using this creates the following figure:

matplotlib - pandas - No xlabel and xticks for twinx axes in subploted figures

I had a similar question, which was answered previously. However, it differs in usage of Pandas package with it.
Here is my previous question: matplotlib - No xlabel and xticks for twinx axes in subploted figures
So, my question like last one is that why it does not show xlabel and xticks for first row diagrams when using this Python code.
Two notes:
I also used subplots instead of gridspec but same result.
If you uncomment any of the commented lines in this code, which is related to using the Pandas on the axes in each diagram, the xlabel and xticks will disappear!
import matplotlib.pyplot as plt
import matplotlib.gridspec as gspec
import numpy as np
import pandas as pd
from math import sqrt
fig = plt.figure()
gs = gspec.GridSpec(2, 2)
gs.update(hspace=0.7, wspace=0.7)
ax1 = plt.subplot(gs[0, 0])
ax2 = plt.subplot(gs[0, 1])
ax3 = plt.subplot(gs[1, 0])
ax4 = plt.subplot(gs[1, 1])
x1 = np.linspace(1,10,10)
ax12 = ax1.twinx()
ax1.set_xlabel("Fig1")
ax12.set_xlabel("Fig1")
ax1.set_ylabel("Y1")
ax12.set_ylabel("Y2")
# pd.Series(range(10)).plot(ax=ax1)
ax12.plot(x1, x1**3)
ax22 = ax2.twinx()
ax2.set_xlabel("Fig2")
ax22.set_xlabel("Fig2")
ax2.set_ylabel("Y3")
ax22.set_ylabel("Y4")
# pd.Series(range(10)).plot(ax=ax2)
ax22.plot(x1, x1**0.5)
ax32 = ax3.twinx()
ax3.set_xlabel("Fig3")
ax32.set_xlabel("Fig3")
ax3.set_ylabel("Y5")
ax32.set_ylabel("Y6")
# pd.Series(range(200)).plot(ax=ax3)
ax42 = ax4.twinx()
ax4.set_xlabel("Fig4")
ax42.set_xlabel("Fig4")
ax4.set_ylabel("Y7")
ax42.set_ylabel("Y8")
# pd.Series(range(10)).plot(ax=ax42)
plt.subplots_adjust(wspace=0.8, hspace=0.8)
plt.show()
I just got the same issue because I was mixing plots made with matplotlib and made with Pandas.
You should not plot with Pandas, here is how you could replace:
pd.Series(range(10)).plot(ax=ax42)
with
ax42.plot(pd.Series(range(10))
As Scimonster mentioned above for me it worked when I plotted all the pandas before creating twinx axes.
I had few plots in twinx which were also coming from Pandas Dataframe objects (x,t plots). I created separate lists before starting the plot and then used them to plot after plotting the first pandas plots.
to summarize my work flow was
1. Creating lists for twinx plots
2. opening plot and plotting all pandas plots with normal axes.
3. creating twinx axes
4. plotting lists on twinx axes
fortunately, this flow is working for me

Categories

Resources