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)
Related
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)
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()
Here is the code:
import numpy as np
import matplotlib.pyplot as plt
import datetime
from mpl_finance import candlestick_ochl
from matplotlib.dates import num2date
%matplotlib notebook
# data in a text file, 5 columns: time, opening, close, high, low
# note that I'm using the time you formated into an ordinal float
# data = np.loadtxt('finance-data.txt', delimiter=',')
data = np.array([[1, 2, 3, 4, 1],
[2, 3, 1, 5, 1],
[3, 4, 3, 5, 2.3]
])
# determine number of days and create a list of those days
ndays = np.unique(np.trunc(data[:,0]), return_index=True)
xdays = []
for n in np.arange(len(ndays[0])):
xdays.append(datetime.date.isoformat(num2date(data[ndays[1],0][n])))
# creation of new data by replacing the time array with equally spaced values.
# this will allow to remove the gap between the days, when plotting the data
data2 = np.hstack([np.arange(data[:,0].size)[:, np.newaxis], data[:,1:]])
# plot the data
fig = plt.figure(figsize=(10, 5))
ax = fig.add_axes([0.1, 0.2, 0.85, 0.7])
# customization of the axis
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
ax.tick_params(axis='both', direction='out', width=2, length=8,
labelsize=12, pad=8)
ax.spines['left'].set_linewidth(2)
ax.spines['bottom'].set_linewidth(2)
# set the ticks of the x axis only when starting a new day
ax.set_xticks(data2[ndays[1],0])
ax.set_xticklabels(xdays, rotation=45, horizontalalignment='right')
ax.set_ylabel('Quote ($)', size=20)
ax.set_ylim([0, 10])
drawData = candlestick_ochl(ax, data2, width=0.5, colorup='g', colordown='r')
and here is the output:
I would like to change the middle one to orange. How can I find out the tick and change its' color? Thanks.
Most artists in matplotlib have a .set_color() method.
lines, rectangles = candlestick_ochl(ax, data2, width=0.5, colorup='g', colordown='r')
lines[1].set_color("orange")
rectangles[1].set_color("orange")
By default, tick-labels are aligned on the subplot axis they belong to.
Is it possible to align the labels so they are centered between two subplots, instead?
import numpy as np
import matplotlib.pyplot as plt
data = [7, 2, 3, 0]
diff = [d - data[0] for d in data]
y = np.arange(len(data))
ax1 = plt.subplot(1, 2, 1)
ax1.barh(y, diff)
ax1.set_yticks(y + 0.4)
ax1.yaxis.set_major_formatter(matplotlib.ticker.NullFormatter())
ax2 = plt.subplot(1, 2, 2)
ax2.barh(y, data)
ax2.set_yticks(y + 0.4)
ax2.set_yticklabels(['reference', 'something', 'something else', 'nothing', ])
plt.tight_layout()
plt.show()
Here is a working, but not very convenient way of doing so. You can provide a position keyword when setting the xticklabels. This allows you to use a negative offset in axes coordinates. If you set the position of the axes, and the spacing between them manually, you can calculate what this negative offset needs to be for the labels to be exactly in the center between the two axes.
Given your example data:
fig = plt.figure(figsize=(10, 2), facecolor='w')
fig.subplots_adjust(wspace=0.2)
ax1 = fig.add_axes([0.0, 0, 0.4, 1])
ax2 = fig.add_axes([0.6, 0, 0.4, 1])
ax1.barh(y, diff, align='center')
ax1.set_yticks(y)
ax1.yaxis.set_major_formatter(matplotlib.ticker.NullFormatter())
ax2.barh(y, data, align='center')
ax2.set_yticks(y)
ax2.set_yticklabels(['reference', 'something', 'something else', 'nothing', ],
ha='center', position=(-0.25, 0))
The axes both have a width of 0.4 in figure coordinates, and they are spaced with 0.2. That means the labels would have to be at 0.5 in figure coordinates. Since the second axes starts at 0.6, it would need an offset in figure coordinates of -0.1. Unfortunately the position should be given in axes coordinates. The axes is 0.4 wide, so a quarter of the axes width is 0.1 in figure coordinates. That means specifying an offset of a negative quarter, -0.25, would place the labels right between the two axes. I hope that makes sense.....
Note that i have center the yticklabels with ha='center'. And also centered your bars, so you dont have to specify the offset anymore when setting the ticks.
edit:
You could do it automatically by reading the position of both axes.
def center_ylabels(ax1, ax2):
pos2 = ax2.get_position()
right = pos2.bounds[0]
pos1 = ax1.get_position()
left = pos1.bounds[0] + pos1.bounds[2]
offset = ((right - left) / pos2.bounds[2]) * -0.5
for yt in ax2.get_yticklabels():
yt.set_position((offset, yt.get_position()[1]))
yt.set_ha('center')
plt.setp(ax2.yaxis.get_major_ticks(), pad=0)
fig, (ax1, ax2) = plt.subplots(1,2, figsize=(10,2))
fig.subplots_adjust(wspace=0.5)
ax1.barh(y, diff, align='center')
ax1.set_yticks(y)
ax1.yaxis.set_major_formatter(matplotlib.ticker.NullFormatter())
ax2.barh(y, data, align='center')
ax2.set_yticks(y)
ax2.set_yticklabels(['reference', 'something', 'something else', 'nothing'])
center_ylabels(ax1, ax2)
import matplotlib.pyplot as plt
x, y = [1, 2, 3], [5, 7, 2]
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(x, y)
fig.tight_layout() #растягивает графики на всё окно
leg = ax.legend(['legend'], bbox_to_anchor = (1.0, 0.5), loc='upper left',)
plt.show()
Legend is outside the frame. I see part of the legend, but I want to see all. How to do it?
This is what bbox_to_anchor does:
Users can specify any arbitrary location for the legend using the
*bbox_to_anchor* keyword argument. bbox_to_anchor can be an instance of BboxBase(or its derivatives) or a tuple of 2 or 4 floats. For
example:
loc = 'upper right', bbox_to_anchor = (0.5, 0.5)
will place the legend so that the upper right corner of the legend at
the center of the axes.
So play around with that tuple, for example try bbox_to_anchor = (0.05, 0.95). Or just leave it out altogether, and the legend will be in the upper left corner.
Edit: If you want the legend to be out of the subplot, you can try the following:
import matplotlib.pyplot as plt
x, y = [1, 2, 3], [5, 7, 2]
fig = plt.figure()
ax = fig.add_axes((0.2, 0.05, 0.75, 0.9))
ax.plot(x, y)
leg = ax.legend(['legend'], bbox_to_anchor = (0, 0.9))
plt.show()
You can tweak the numbers to fine-tune the positions.