Visibility for twinx grid lines - python

When creating overlaid bar charts with two different height scales using Axes.twinx(), I cannot set visible the vertical grid lines of the 'twin' axis set. The horizontal lines work fine though. Any thoughts on how to resolve this?
Below is some example code that illustrates what I want to do and what I cannot do. As seen, the vertical grid lines are hidden by the red bars of ax2, whereas I want the grid lines to be visible through all bars.
# Create figure and figure layout
ax1 = plt.subplot()
ax2 = ax1.twinx()
# Example data
x = [0, 1, 2, 3, 4, 5]
h1 = [55, 63, 70, 84, 73, 93]
h2 = [4, 5, 4, 7, 4, 3]
# Plot bars
h1_bars = ax1.bar(x, h1, width=0.6, color='darkblue')
h2_bars = ax2.bar(x, h2, width=0.6, color='darkred')
# Set y limits and grid visibility
for ax, ylim in zip([ax1, ax2], [100, 10]):
ax.set_ylim(0, ylim)
ax.grid(True)
The error comes about because the vertical grid lines of ax2 are not set visible. This can be tested by setting ax1.grid(False), in which case there are only horizontal grid lines.
I have tried all combinations of ax1.xaxis.grid(True), ax1.yaxis.grid(True), ax2.xaxis.grid(True) and ax2.yaxis.grid(True) without any luck. Any help on this matter deeply appreciated!

You may revert the role of ax1 and ax2, such that the blue bars are on ax2 and the red ones on ax1. Then you need to put the twin axes in the background and tick the respective y axes on the other side of the plot.
import matplotlib.pyplot as plt
# Create figure and figure layout
ax1 = plt.subplot()
ax2 = ax1.twinx()
# Example data
x = [0, 1, 2, 3, 4, 5]
h1 = [55, 63, 70, 84, 73, 93]
h2 = [4, 5, 4, 7, 4, 3]
# Plot bars
h1_bars = ax2.bar(x, h1, width=0.6, color='darkblue')
h2_bars = ax1.bar(x, h2, width=0.6, color='darkred')
# Set y limits and grid visibility
for ax, ylim in zip([ax1, ax2], [10, 100]):
ax.set_ylim(0, ylim)
ax.grid(True)
ax1.set_zorder(1)
ax1.patch.set_alpha(0)
ax2.set_zorder(0)
ax1.yaxis.tick_right()
ax2.yaxis.tick_left()
plt.show()

Related

How to disable the Matplotlib navigation toolbar in a particular axis?

I have a figure with different plots on several axes. Some of those axes do not play well with some of the navigation toolbar actions. In particular, the shortcuts to go back to the home view and the ones to go to the previous and next views.
Is there a way to disable those shortcuts only for those axes? For example, in one of the two in the figure from the example below.
import matplotlib.pyplot as plt
# Example data for two plots
x1 = [1, 2, 3, 4]
y1 = [10, 20, 25, 30]
x2 = [2, 3, 4, 5]
y2 = [5, 15, 20, 25]
# Create figure and axes objects
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 5))
# Plot data on the first axis
ax1.plot(x1, y1)
ax1.set_title("First Plot")
# Plot data on the second axis
ax2.plot(x2, y2)
ax2.set_title("Second Plot")
# Show plot
plt.show()
Edit 1:
The following method will successfully disable the pan and zoom tools from the GUI toolbox in the target axis.
ax2.set_navigate(False)
However, the home, forward, and back buttons remain active. Is there a trick to disable also those buttons in the target axis?
It worked for me when I did as below:
import matplotlib
matplotlib.rcParams['toolbar'] = 'None'
plt = matplotlib.pyplot
# Example data for two plots
x1 = [1, 2, 3, 4]
y1 = [10, 20, 25, 30]
x2 = [2, 3, 4, 5]
y2 = [5, 15, 20, 25]
# Create figure and axes objects
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 5))
# Plot data on the first axis
ax1.plot(x1, y1)
ax1.set_title("First Plot")
# Plot data on the second axis
ax2.plot(x2, y2)
ax2.set_title("Second Plot")
# Show plot
plt.show()
Source.
As I am not sure to fully understand which button you want to enable and disable, I provide here a code to customize your navigation toolbar individually across the different axes. It is using some matplotlib backend tools and you can change the line inside the class definition so it fits with what you want:
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
# Create a custom toolbar that only includes the pan and zoom tools
class CustomToolbar(NavigationToolbar):
toolitems = [t for t in NavigationToolbar.toolitems if
t[0] in ('Pan', 'Zoom')]
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 5))
ax1.plot(x1, y1)
ax1.set_title("First Plot")
ax2.plot(x2, y2)
ax2.set_title("Second Plot")
# Set the custom toolbar for the second axis
ax2_navigation_toolbar = CustomToolbar(ax2.figure.canvas, ax2)
ax2.navigation_toolbar = ax2_navigation_toolbar
# Show plot
plt.show()
Hope this helps.
You can try to use ax2.get_xaxis().set_visible(False)

Remove empty space from matplotlib bar plot

I plot two bar plots to the same ax, where the x axis contains values from 0 downwards. However, I have significant gaps in my data (like from 0 to -15), and would like to remove the empty space, so that the axis goes from 0 to -15 with no space in between - show on this image:
Ideally I would like to do this for all gaps. I have tried both plt.axis('tight') and fig.tight_layout(), but neither of them have worked.
Edit: sample code for a small example
keys = [0, -15, -16, -17]
values = [3, 5, 2, 1]
fig, ax = plt.subplots(ncols=1)
fig.tight_layout()
ax.bar(keys, values, 0.8, color='g', align='center')
ax.set_xticks(keys)
plt.setp(ax.xaxis.get_majorticklabels(), rotation=90 )
The easiest way to resolve the issue is plot values against an x that is a range corresponding to the len of keys, and then change the xticklabels.
import matplotlib.pyplot as plt
keys = [0, -15, -16, -17]
values = [3, 5, 2, 1]
fig, ax = plt.subplots()
# create the xticks locations
x = range(len(keys))
ax.bar(x, values, 0.8, color='g', align='center')
# set the ticks and labels
ax.set_xticks(x)
_ = ax.set_xticklabels(keys)
Sorting
keys = [0, -15, -16, -17]
values = [3, 5, 2, 1]
# zip, sort and unpack
keys, values = zip(*sorted(zip(keys, values)))
fig, ax = plt.subplots()
# create the xticks locations
x = range(len(keys))
ax.bar(x, values, 0.8, color='g', align='center')
# set the ticks and labels
ax.set_xticks(x)
_ = ax.set_xticklabels(keys)

matplotlib subplots last plot disturbs log scale

I am making a matplotlib figure with a 2x2 dimension where x- and y-axis are shared, and then loop over the different axes to plot in them. I'm plotting variant data per sample, and it is possible that a sample doesn't have variant data, so then I want the plot to say "NA" in the middle of it.
import matplotlib.pyplot as plt
n_plots_per_fig = 4
nrows = 2
ncols = 2
fig, axs = plt.subplots(nrows, ncols, sharex="all", sharey="all", figsize=(8, 6))
axs = axs.ravel()
for i, ax in enumerate(axs):
x = [1, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3] # example values, but this list CAN be empty
bins = 3 # example bins
if x:
ax.hist(x, bins=bins) # plot the hist
ax.set_yscale("log")
ax.set_title(str(i), fontsize="medium")
else:
ax.set_title(str(i), fontsize="medium")
ax.text(0.5, 0.5, 'NA', ha='center', va='center', transform=ax.transAxes)
fig.show()
This works in almost every case; example of wanted output:
However, only if the last plot in the figure doesn't have any data, then this disturbs the log scale. Example code that triggers this:
import matplotlib.pyplot as plt
n_plots_per_fig = 4
nrows = 2
ncols = 2
fig, axs = plt.subplots(nrows, ncols, sharex="all", sharey="all", figsize=(8, 6))
axs = axs.ravel()
for i, ax in enumerate(axs):
x = [1, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]
bins = 3
if i == n_plots_per_fig-1: # this will distort the log scale
ax.set_title(str(i), fontsize="medium")
ax.text(0.5, 0.5, 'NA', ha='center', va='center', transform=ax.transAxes)
elif x:
ax.hist(x, bins=bins) # plot the hist
ax.set_yscale("log")
ax.set_title(str(i), fontsize="medium")
else:
ax.set_title(str(i), fontsize="medium")
ax.text(0.5, 0.5, 'NA', ha='center', va='center', transform=ax.transAxes)
fig.show()
The log scale is now set to really low values, and this is not what I want. I've tried several things to fix this, like unsharing the y-axes for the plot that doesn't have any data [ax.get_shared_y_axes().remove(axis) for axis in axs] or hiding the plot ax.set_visible(False), but none of this works. The one thing that does work is removing the axes from the plot with ax.remove(), but since this is the bottom most sample, this also removes the values for the x ticks for that column:
And besides that, I would still like the name of the sample that didn't have any data to be visible in the axes (and the "NA" text), and removing the axes doesn't allow this.
Any ideas on a fix?
Edit: I simplified my example.
You can set the limits manually with ax.set_xlim() / ax.set_ylim().
Note, that if you share the axes it does not matter on which subplot you call those functions. For example:
axs[-1][-1].set_ylim(1e0, 1e2)
If you do not know the limits before, you can infer it from the other plots:
x = np.random.random(100)
bins = 10
if bins != 0:
...
yy, xx = np.histogram(x, bins=bins)
ylim = yy.min(), yy.max()
xlim = xx.min(), xx.max()
else:
ax.set_xlim(xlim)
ax.set_ylim(ylim)

matplotlib two y axes with mixed z order

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()

Python: Data Visualization of Outliers in a subplot

I use the following csv files: file1 and file2
to plot the following subplot:
The code to generate the subplot is the following:
df = {}
df[1] = pd.read_csv('file1.csv')
df[2] = pd.read_csv('file1.csv')
fig, axes = plt.subplots(1, 2, figsize=(10, 5), sharey=True)
for bet in [[1, 0], [2, 1]]:
betas = reg[bet[0]]
betas = betas.ix[int_col]
betas.dropna(inplace=1)
betas.index = range(25)
ax = betas.plot(ax=axes[bet[1]], grid=False, style=['b-', 'b--', 'b--'],
legend=None)
ax.lines[0].set_linewidth(1.5)
ax.lines[1].set_linewidth(0.6)
ax.lines[2].set_linewidth(0.6)
ax.axhline(y=0, color='k', linestyle='-', alpha=0.25, linewidth=0.5)
ax.axvline(x=13, color='k', linestyle='-', alpha=0.25, linewidth=0.5)
ax.set_xticks([0, 6, 13, 19, 24])
These plots show coefficients from a regression (solid blue lines) and the confidence intervals (dashed-lines).
As you can see, both plots in the subplot have outliers... the first point at x=0.
The outliers are important but it "deform" my graphs where the other points appear to be in a straight line but in fact there is important variations at x > 0.
What would be the proper data visualization to show both the outlier and have a better "zoom" on the other points at x > 0. Is a broken y-axis the best way? How can I do so in a subplot? Other suggestions?

Categories

Resources