Matplotlib: combination of inverted plots - python

I need to achieve the following effect using matplotlib:
As you can see it's a combination of plots in different quadrants.
I do know how to generate each quadrant individually. For example, for the 'x invert' quadrant's plot I would simply use:
plt.plot(x, y)
plt.gca().invert_yaxis()
plt.show()
to draw the plot. It properly inverts the x axis. However, it would only generate top-left quadrant's plot for me.
How can I generate a combination of plots described in the above picture? Each quadrant has its own plot with different inverted axises.
My best idea was to merge it in some tool like Paint.

I don't have enough reputation to add a comment to add on to ImportanceOfBeingErnest's comment, but when you create the 4 subplots you'll want to remove the space between the plots as well as have shared axes (and clean up overlapping ticks).
There are various ways to do subplots, but I prefer gridspec. You can create a 2x2 grid with gridspec and do all of this, here's an example:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
fig = plt.figure()
# lines to plot
x = np.arange(0, 10)
y = np.arange(0, 10)
# gridspec for 2 rows, 2 cols with no space between
grid = gridspec.GridSpec(nrows=2, ncols=2, hspace=0, wspace=0, figure=fig)
x_y = fig.add_subplot(grid[0, 1], zorder=3)
x_y.plot(x, y)
x_y.margins(0)
invx_y = fig.add_subplot(grid[0, 0], zorder=2, sharey=x_y)
invx_y.plot(-x, y)
invx_y.margins(0)
invx_invy = fig.add_subplot(grid[1, 0], zorder=0, sharex=invx_y)
invx_invy.plot(-x, -y)
invx_invy.margins(0)
x_invy = fig.add_subplot(grid[1, 1], zorder=1, sharey=invx_invy, sharex=x_y)
x_invy.plot(x, -y)
x_invy.margins(0)
# clean up overlapping ticks
invx_y.tick_params(labelleft=False, length=0)
invx_invy.tick_params(labelleft=False, labelbottom=False, length=0)
x_invy.tick_params(labelbottom=False, length=0)
x_y.set_xticks(x_y.get_xticks()[1:-1])
invx_y.set_xticks(invx_y.get_xticks()[1:-1])
x_invy.set_yticks(x_invy.get_yticks()[1:-1])
plt.show()
This yields the following figure:

Related

How to increase plottable space above a subplot in matplotlib?

I am currently making a plot on matplotlib, which looks like below.
The code for which is:
fig, ax1 = plt.subplots(figsize=(20,5))
ax2 = ax1.twinx()
# plt.subplots_adjust(top=1.4)
ax2.fill_between(dryhydro_df['Time'],dryhydro_df['Flow [m³/s]'],0,facecolor='lightgrey')
ax2.set_ylim([0,10])
AB = ax2.fill_between(dryhydro_df['Time'],[12]*len(dryhydro_df['Time']),9.25,facecolor=colors[0],alpha=0.5,clip_on=False)
ab = ax2.scatter(presence_df['Datetime'][presence_df['AB']==True],[9.5]*sum(presence_df['AB']==True),marker='X',color='black')
# tidal heights
ax1.plot(tide_df['Time'],tide_df['Tide'],color='dimgrey')
I want the blue shaded region and black scatter to be above the plot. I can move the elements above the plot by using clip_on=False but I think I need to extend the space above the plot to do visualise it. Is there a way to do this? Mock-up of what I need is below:
You can use clip_on=False to draw outside the main plot. To position the elements, an xaxis transform helps. That way, x-values can be used in the x direction, while the y-direction uses "axes coordinates". ax.transAxes() uses "axes coordinates" for both directions.
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
dates = pd.date_range('2018-07-01', '2018-07-31', freq='H')
xs = dates.to_numpy().astype(float)
ys = np.sin(xs * .091) * (np.sin(xs * .023) ** 2 + 1)
fig, ax1 = plt.subplots(figsize=(20, 5))
ax1.plot(dates, ys)
ax1.scatter(np.random.choice(dates, 10), np.repeat(1.05, 10), s=20, marker='*', transform=ax1.get_xaxis_transform(),
clip_on=False)
ax1.plot([0, 1], [1.05, 1.05], color='steelblue', lw=20, alpha=0.2, transform=ax1.transAxes, clip_on=False)
plt.tight_layout() # fit labels etc. nicely
plt.subplots_adjust(top=0.9) # make room for the additional elements
plt.show()

Two y axes for a single plot

I'm trying to create a plot with two Y axes (left and right) for the same data, that is, one is a scaled version of the other. I would like also to preserve the tick positions and grid positions, so the grid will match the ticks at both sides.
I'm trying to do this by plotting twice the same data, one as-is and the other scaled, but they are not coincident.
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(17, 27, 0.1)
y1 = 0.05 * x + 100
fig, ax1 = plt.subplots()
ax2 = ax1.twinx()
ax1.plot(x, y1, 'g-')
ax2.plot(x, y1/max(y1), 'g-')
ax1.set_xlabel('X data')
ax1.set_ylabel('Y data', color='g')
ax2.set_ylabel('Y data normalized', color='b')
plt.grid()
plt.show()
Any help will be appreciated.
Not sure if you can achieve this without getting ugly-looking numbers on your normalized axis. But if that doesn't bother you, try adding this to your code:
ax2.set_ylim([ax1.get_ylim()[0]/max(y1),ax1.get_ylim()[1]/max(y1)])
ax2.set_yticks(ax1.get_yticks()/max(y1))
Probably not the most elegant solution, but it scales your axis limits and tick positions similarly to what you do with the data itself so the grid matches both axes.

python matplotlib gridspec, unwanted arbitrary axis labels

I have some code to plot a grid, with the data in each cell being distinct and having a very specific position. The easiest way I found to do this was to create the grid with gridspec and use it to precisely position my subplots, however I'm having a problem where the overall grid is labelled from 0 to 1 along each axis. This happens every time, even when the dimensions of the grid are changed. Obviously these numbers have no relevance to my data, and as what I am aiming to display is qualitative rather than quantitative I would like to remove all labels from this plot entirely.
Here is a link to an image with an example of my problem
And here is the MWE that I used to create that image:
import numpy as np
import matplotlib.gridspec as gridspec
import matplotlib.pyplot as plt
# mock-up of data being used
x = 6
y = 7
table = np.zeros((x, y))
# plotting
fig = plt.figure(1)
gs = gridspec.GridSpec(x, y, wspace=0, hspace=0)
plt.title('Example Plot')
for (j, k), img in np.ndenumerate(table):
ax = fig.add_subplot(gs[x - j - 1, k])
ax.set_xticklabels('')
ax.set_yticklabels('')
plt.show()
I have not been able to find note of anything like this problem, so any help would be greatly appreciated.
If you just want to draw a grid over the plot, use this code:
import numpy as np
import matplotlib.pyplot as plt
# mock-up of data being used
x = 6
y = 7
table = np.zeros((x, y))
# plotting
fig = plt.figure(1)
plt.title('Example Plot')
plt.gca().xaxis.grid(True, color='darkgrey', linestyle='-')
plt.gca().yaxis.grid(True, color='darkgrey', linestyle='-')
plt.show()
Another variant is used gridspec:
...
# hide ticks of main axes
ax0 = plt.gca()
ax0.get_xaxis().set_ticks([])
ax0.get_yaxis().set_ticks([])
gs = gridspec.GridSpec(x, y, wspace=0, hspace=0)
plt.title('Example Plot')
for (j, k), img in np.ndenumerate(table):
ax = fig.add_subplot(gs[x - j - 1, k])
# hide ticks of gribspec axes
ax.get_xaxis().set_ticks([])
ax.get_yaxis().set_ticks([])

matplotlib prune tick labels

I am using GridSpec to plot two plots one below the other without a gap in between with
gs = gridspec.GridSpec(3, 1)
gs.update(hspace=0., wspace=0.)
ax1 = plt.subplot(gs[0:2, 0])
ax2 = plt.subplot(gs[2, 0], sharex=ax1)
which works fine. However, I want to get rid of each subplot's top and bottom tick label.
For that I use
nbins = len(ax1.get_yticklabels())
ax1.yaxis.set_major_locator(MaxNLocator(nbins=nbins, prune='both'))
nbins = len(ax2.get_yticklabels())
ax2.yaxis.set_major_locator(MaxNLocator(nbins=nbins, prune='both'))
which in many cases works fine. In some plots, however, one or more of the 4 labels to prune are still there. I looked at e.g. ax1.get_ylim() and noticed that instead of for example the upper limit being 10 (as it is shown in the plot itself), it is actually 10.000000000000002, which I suspect is the reason why it is not pruned. How does that happen and how can I get rid of that?
Here is an example: Note that in the figure the y axis is inverted and no label is pruned, altough it should be. Also note that for some reason the lowest y-label is set to a negative position, which I don't see. The y-tick positions are shown in in axis coordinates in the text within the plots. In the image below, the label at 10.6 should not be there!
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
from matplotlib.ticker import MaxNLocator
import numpy as np
x1 = 1
y1 = 10.53839
err1 = 0.00865
x2 = 2
y2 = 9.43045
err2 = 0.00658
plt.clf()
fig = plt.figure(figsize=(6, 6))
gs = gridspec.GridSpec(3, 1)
gs.update(hspace=0., wspace=0.)
ax1 = plt.subplot(gs[0:2, 0])
ax1.errorbar(x1, y1, yerr=err1)
ax1.errorbar(x2, y2, yerr=err2)
ax1.invert_yaxis()
plt.setp(ax1.get_xticklabels(), visible=False) # Remove x-labels between the plots
plt.xlim(0, 3)
ax2 = plt.subplot(gs[2, 0], sharex=ax1)
nbins = len(ax1.get_yticklabels())
ax1.yaxis.set_major_locator(MaxNLocator(nbins=8, prune='both'))
nbins = len(ax2.get_yticklabels())
ax2.yaxis.set_major_locator(MaxNLocator(nbins=6, prune='both'))
plt.savefig('prune.png')
plt.close()
Could it be, that you are looking at the left most label on the x axis of the upper plot? If so, this should do the trick:
ax1.set_xticklabels([])
EDIT: If you use sharex, you have to use this, otherwise the tick labels are removed on both axes.
plt.setp(ax1.get_xticklabels(), visible=False)
You can try to use this:
import matplotlib.ticker as mticker
ax2.yaxis.set_major_locator(mticker.MaxNLocator(nbins=7, prune='upper'))
I found the above command only works for the y-axis.
Does someone know how to set up the maximum limits of x-axis tickers' number?

In matplotlib, how do you display an axis on both sides of the figure?

I want to draw a plot with matplotlib with axis on both sides of the plot, similar to this plot (the color is irrelevant to this question):
How can I do this with matplotlib?
Note: contrary to what is shown in the example graph, I want the two axis to be exactly the same, and want to show only one graph. Adding the two axis is only to make reading the graph easier.
You can use tick_params() (this I did in Jupyter notebook):
import matplotlib.pyplot as plt
bar(range(10), range(10))
tick_params(labeltop=True, labelright=True)
Generates this image:
UPD: added a simple example for subplots. You should use tick_params() with axis object.
This code sets to display only top labels for the top subplot and bottom labels for the bottom subplot (with corresponding ticks):
import matplotlib.pyplot as plt
f, axarr = plt.subplots(2)
axarr[0].bar(range(10), range(10))
axarr[0].tick_params(labelbottom=False, labeltop=True, labelleft=False, labelright=False,
bottom=False, top=True, left=False, right=False)
axarr[1].bar(range(10), range(10, 0, -1))
axarr[1].tick_params(labelbottom=True, labeltop=False, labelleft=False, labelright=False,
bottom=True, top=False, left=False, right=False)
Looks like this:
There are a couple of relevant examples in the online documentation:
Two Scales (seems to do exactly what you're asking for)
Dual Fahrenheit and Celsius
I've done this previously using the following:
# Create figure and initial axis
fig, ax0 = plt.subplots()
# Create a duplicate of the original xaxis, giving you an additional axis object
ax1 = ax.twinx()
# Set the limits of the new axis from the original axis limits
ax1.set_ylim(ax0.get_ylim())
This will exactly duplicate the original y-axis.
Eg:
ax = plt.gca()
plt.bar(range(3), range(1, 4))
plt.axhline(1.75, color="gray", ls=":")
twin_ax = ax.twinx()
twin_ax.set_yticks([1.75])
twin_ax.set_ylim(ax.get_ylim())

Categories