I made in Python a horizontal bar plot, with a bar coming from left to right:
I would like to add an additional horizontal bar, on the same horizontal plane of the previous bar, this time coming from right to left. Both horizontal bars should be present at the same time.
Does anybody have any idea on how to do that? If I use the reverse function everything is reversed, but I need to reverse only the new specific bar without changing anything else.
Ideally, on the new picture the new bar graph would come from the right and stop at 25, with error bars from 23 to 27 (-/+ 2).
Here is my script:
import numpy as np
import matplotlib.pyplot as plt
plt.figure(figsize=(9.5, 2.7))
# Create horizontal bars
plt.barh(0, 18,height=0.2,facecolor='orange',edgecolor='black',linewidth=2)
plt.errorbar(x=[18], y=[0], xerr=[2],color='black',fmt='none',linewidth=5,zorder=4)
plt.xticks(np.arange(10, 30+1, 1.0),fontsize=14)
plt.yticks([])
plt.xlim(10, 30)
plt.ylim(-.13, .13)
plt.show()
The trick is to use left to specify where the bar should start, then pass a negative width for your bar to make it extend right to left. Since the right side of the window will also change with your data, you might also want to make that some sort of parameter i.e. x_max:
import numpy as np
import matplotlib.pyplot as plt
x_max = 30
plt.figure(figsize=(9.5, 2.7))
# Create horizontal bars
plt.barh(0, 18,height=0.2,facecolor='orange',edgecolor='black',linewidth=2)
plt.errorbar(x=[18], y=[0], xerr=[2],color='black',fmt='none',linewidth=5,zorder=4)
# new code: use left to specify the start position, then make its width negative
# to extend right to left
plt.barh(0, -5, height=0.2, left=x_max, facecolor='red',edgecolor='black',linewidth=2)
# place error bars the same as you did for the above.
plt.errorbar(x=[x_max - 5], y=[0], xerr=[2],color='black',fmt='none',linewidth=5,zorder=4)
plt.xticks(np.arange(10, 30+1, 1.0),fontsize=14)
plt.yticks([])
plt.xlim(10, x_max)
plt.ylim(-.13, .13)
plt.show()
Related
I'm using matplotlib to draw a bar chart with 3 bars. I want to add some extra space along the x-axis (so that the x-axis line is drawn longer).
Below is what I have:
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
dt = [1,3,2]
plt.figure()
xvals = range(len(dt))
plt.bar(xvals, dt, width=0.5)
plt.tick_params(bottom=False)
plt.xticks(xvals, ['a','b','c'])
plt.yticks(range(0,4), [0,1,2,3])
plt.gca().spines['top'].set_visible(False)
plt.gca().spines['right'].set_visible(False)
plt.show()
This code produces:
I simply want (note the elongated x-axis):
Change the limits of the x-axis using xlim()
for ex:
plt.xlim(-0.5,3.5) # adjust as necessary
Just add the following limits. Yo can use None as the left hand limit to let the plot choose the limit as the default value. Since the x-values are 0, 1, 2 and now you add the right hand side limit as 3, you will have an extended axis. Replace 3 by whatever value you want.
plt.xlim(None, 3)
I would like to move some ticks' labels horizontally along the x-axis, without moving the corresponding ticks.
More specifically, when rotating labels with plt.setp, the centers of the labels' text stay aligned with the ticks. I would like to shift those labels to the right, so that the near ends of the labels get aligned instead as suggested on the image below.
I am aware of this post and this one, however the answers are interesting kludges rather than strict answers to the question.
my code:
import matplotlib.pyplot as plt
import numpy as np
import datetime
# my fake data
dates = np.array([datetime.datetime(2000,1,1) + datetime.timedelta(days=i) for i in range(365*5)])
data = np.sin(np.arange(365*5)/365.0*2*np.pi - 0.25*np.pi) + np.random.rand(365*5) /3
# creates fig with 2 subplots
fig = plt.figure(figsize=(10.0, 6.0))
ax = plt.subplot2grid((2,1), (0, 0))
ax2 = plt.subplot2grid((2,1), (1, 0))
## plot dates
ax2.plot_date( dates, data )
# rotates labels
plt.setp( ax2.xaxis.get_majorticklabels(), rotation=-45 )
# try to shift labels to the right
ax2.xaxis.get_majorticklabels()[2].set_y(-.1)
ax2.xaxis.get_majorticklabels()[2].set_x(10**99)
plt.show()
Strangely enough, set_y behaves as expected, but even if I set x to a fantasillion, the labels would not move by one iota.
(The use of plot_date may introduce additional confusion, but the same actually happens with plot.)
First of all, let's use a mcve to show the problem.
import numpy as np
import datetime
import matplotlib.pyplot as plt
plt.rcParams["date.autoformatter.month"] = "%b %Y"
# my fake data
dates = np.array([datetime.datetime(2000,1,1) + datetime.timedelta(days=i) for i in range(365)])
data = np.sin(np.arange(365)/365.0*2*np.pi - 0.25*np.pi) + np.random.rand(365) /3
# creates fig with 2 subplots
fig, ax = plt.subplots(figsize=(6,2))
## plot dates
ax.plot_date( dates, data )
# rotates labels
plt.setp( ax.xaxis.get_majorticklabels(), rotation=-45 )
plt.tight_layout()
plt.show()
Now as other anwers pointed out already, you may use horizontal alignment of the text.
# rotates labels and aligns them horizontally to left
plt.setp( ax.xaxis.get_majorticklabels(), rotation=-45, ha="left" )
You may use the rotation_mode argument to let the rotation happen about the top left point of the text, giving a slightly nicer result in this case.
# rotates labels and aligns them horizontally to left
plt.setp( ax.xaxis.get_majorticklabels(), rotation=-45, ha="left", rotation_mode="anchor")
In case those options are not fine grained enough, i.e. you want to position the labels more accurately, e.g. shifting it to the side by some points, you may use a transform. The following would offset the label by 5 points in horizontal direction, using a matplotlib.transforms.ScaledTranslation.
import matplotlib.transforms
plt.setp( ax.xaxis.get_majorticklabels(), rotation=-45)
# Create offset transform by 5 points in x direction
dx = 5/72.; dy = 0/72.
offset = matplotlib.transforms.ScaledTranslation(dx, dy, fig.dpi_scale_trans)
# apply offset transform to all x ticklabels.
for label in ax.xaxis.get_majorticklabels():
label.set_transform(label.get_transform() + offset)
The advantage of this, compared to e.g. the solution provided by #explorerDude is that the offset is independent on the data in the graph, such that it is generally applicable to any plot and would look the same for a given fontsize.
Instead of
ax2.xaxis.get_majorticklabels()[2].set_y(-.1)
ax2.xaxis.get_majorticklabels()[2].set_x(10**99)
use the set_horizontalalignment() for each tick on the axis:
for tick in ax2.xaxis.get_majorticklabels():
tick.set_horizontalalignment("left")
resulting in:
I found a way to shift the tick labels of the x-axis by an arbitrary and exact amount, but this way runs dangerously close to the steep and slippery cliffs towering above the sea of madness. So only the very brave or desperate should read on...
That being said, the problem is that the x position of the labels are set when the drawing is rendered (I have not looked into that part of the code, but that is my understanding). So everything you do with set_x() is overridden later. However, there is a way around that: you can monkey patch set_x for certain ticks so that the labels are not drawn where the renderer wants to draw them:
import types
SHIFT = 10. # Data coordinates
for label in ax2.xaxis.get_majorticklabels():
label.customShiftValue = SHIFT
label.set_x = types.MethodType( lambda self, x: matplotlib.text.Text.set_x(self, x-self.customShiftValue ),
label, matplotlib.text.Text )
You can do this selectively only for the labels you want to shift and you can of course also use a different shift for every label.
If anybody knows how to do this on a lower madness level, I would be very interested...
Another way of doing a horizontal alignment:
plt.xticks(ha='left')
I have a simple plot executed using matplotlib. My x and y axes start at 0,0 respectively. A matplotlib plot shows 2 zeros corresponding to the 2 axes. I want only one zero (somewhere in the middle of the start point, if possible).
How can this be done?
Here's what I used:
import matplotlib.pyplot as plt
plt.plot([1,2,3], [4,5,6])
plt.xlim([0,5])
plt.ylim([0,10])
plt.show()
UPDATE:
I used #nostradamus' solution and it got rid of one of the zeros. I want the zero a little centred if possible.
I used:
plt.gca().xaxis.set_major_locator(MaxNLocator(prune='lower'))
plt.gca().yaxis.get_majorticklabels()[0].set_x(-0.05)
I want the reverse of this. I want the zero on the y axis to move down or the one from x axis left. So tried:
plt.gca().yaxis.set_major_locator(MaxNLocator(prune='lower'))
plt.gca().xaxis.get_majorticklabels()[0].set_x(-0.05)
It doesn't work. I think the bottom and left boundaries for the zeros are set to ensure they don't go beyond the area.
The keyword is prune, which allows you to kill the upper, lower or both tick labels. Here is a working examples that gets rid of the 0 of the x-axis:
import matplotlib.pyplot as plt
from matplotlib.ticker import MaxNLocator
plt.plot([1,2,3], [4,5,6])
plt.xlim([0,5])
plt.ylim([0,10])
plt.gca().xaxis.set_major_locator(MaxNLocator(prune='lower'))
plt.show()
The second part (to move the remaining zero to the corner) seems to be much more difficult. It seems that you can move single tick labels using
plt.gca().yaxis.get_majorticklabels()[0].set_x(-0.05)
However, I was unable to figure out how to move it below the lower limit of the corresponding axis (i.e. plt.gca().yaxis.get_majorticklabels()[0].set_y(-0.05) is doing nothing).
I would like to move some ticks' labels horizontally along the x-axis, without moving the corresponding ticks.
More specifically, when rotating labels with plt.setp, the centers of the labels' text stay aligned with the ticks. I would like to shift those labels to the right, so that the near ends of the labels get aligned instead as suggested on the image below.
I am aware of this post and this one, however the answers are interesting kludges rather than strict answers to the question.
my code:
import matplotlib.pyplot as plt
import numpy as np
import datetime
# my fake data
dates = np.array([datetime.datetime(2000,1,1) + datetime.timedelta(days=i) for i in range(365*5)])
data = np.sin(np.arange(365*5)/365.0*2*np.pi - 0.25*np.pi) + np.random.rand(365*5) /3
# creates fig with 2 subplots
fig = plt.figure(figsize=(10.0, 6.0))
ax = plt.subplot2grid((2,1), (0, 0))
ax2 = plt.subplot2grid((2,1), (1, 0))
## plot dates
ax2.plot_date( dates, data )
# rotates labels
plt.setp( ax2.xaxis.get_majorticklabels(), rotation=-45 )
# try to shift labels to the right
ax2.xaxis.get_majorticklabels()[2].set_y(-.1)
ax2.xaxis.get_majorticklabels()[2].set_x(10**99)
plt.show()
Strangely enough, set_y behaves as expected, but even if I set x to a fantasillion, the labels would not move by one iota.
(The use of plot_date may introduce additional confusion, but the same actually happens with plot.)
First of all, let's use a mcve to show the problem.
import numpy as np
import datetime
import matplotlib.pyplot as plt
plt.rcParams["date.autoformatter.month"] = "%b %Y"
# my fake data
dates = np.array([datetime.datetime(2000,1,1) + datetime.timedelta(days=i) for i in range(365)])
data = np.sin(np.arange(365)/365.0*2*np.pi - 0.25*np.pi) + np.random.rand(365) /3
# creates fig with 2 subplots
fig, ax = plt.subplots(figsize=(6,2))
## plot dates
ax.plot_date( dates, data )
# rotates labels
plt.setp( ax.xaxis.get_majorticklabels(), rotation=-45 )
plt.tight_layout()
plt.show()
Now as other anwers pointed out already, you may use horizontal alignment of the text.
# rotates labels and aligns them horizontally to left
plt.setp( ax.xaxis.get_majorticklabels(), rotation=-45, ha="left" )
You may use the rotation_mode argument to let the rotation happen about the top left point of the text, giving a slightly nicer result in this case.
# rotates labels and aligns them horizontally to left
plt.setp( ax.xaxis.get_majorticklabels(), rotation=-45, ha="left", rotation_mode="anchor")
In case those options are not fine grained enough, i.e. you want to position the labels more accurately, e.g. shifting it to the side by some points, you may use a transform. The following would offset the label by 5 points in horizontal direction, using a matplotlib.transforms.ScaledTranslation.
import matplotlib.transforms
plt.setp( ax.xaxis.get_majorticklabels(), rotation=-45)
# Create offset transform by 5 points in x direction
dx = 5/72.; dy = 0/72.
offset = matplotlib.transforms.ScaledTranslation(dx, dy, fig.dpi_scale_trans)
# apply offset transform to all x ticklabels.
for label in ax.xaxis.get_majorticklabels():
label.set_transform(label.get_transform() + offset)
The advantage of this, compared to e.g. the solution provided by #explorerDude is that the offset is independent on the data in the graph, such that it is generally applicable to any plot and would look the same for a given fontsize.
Instead of
ax2.xaxis.get_majorticklabels()[2].set_y(-.1)
ax2.xaxis.get_majorticklabels()[2].set_x(10**99)
use the set_horizontalalignment() for each tick on the axis:
for tick in ax2.xaxis.get_majorticklabels():
tick.set_horizontalalignment("left")
resulting in:
I found a way to shift the tick labels of the x-axis by an arbitrary and exact amount, but this way runs dangerously close to the steep and slippery cliffs towering above the sea of madness. So only the very brave or desperate should read on...
That being said, the problem is that the x position of the labels are set when the drawing is rendered (I have not looked into that part of the code, but that is my understanding). So everything you do with set_x() is overridden later. However, there is a way around that: you can monkey patch set_x for certain ticks so that the labels are not drawn where the renderer wants to draw them:
import types
SHIFT = 10. # Data coordinates
for label in ax2.xaxis.get_majorticklabels():
label.customShiftValue = SHIFT
label.set_x = types.MethodType( lambda self, x: matplotlib.text.Text.set_x(self, x-self.customShiftValue ),
label, matplotlib.text.Text )
You can do this selectively only for the labels you want to shift and you can of course also use a different shift for every label.
If anybody knows how to do this on a lower madness level, I would be very interested...
Another way of doing a horizontal alignment:
plt.xticks(ha='left')
Changing the vertical distance between two subplot using tight_layout(h_pad=-1) changes the total figuresize. How can I define the figuresize using tight_layout?
Here is the code:
#define figure
pl.figure(figsize=(10, 6.25))
ax1=subplot(211)
img=pl.imshow(np.random.random((10,50)), interpolation='none')
ax1.set_xticklabels(()) #hides the tickslabels of the first plot
subplot(212)
x=linspace(0,50)
pl.plot(x,x,'k-')
xlim( ax1.get_xlim() ) #same x-axis for both plots
And here is the results:
If I write
pl.tight_layout(h_pad=-2)
in the last line, then I get this:
As you can see, the figure is bigger...
You can use a GridSpec object to control precisely width and height ratios, as answered on this thread and documented here.
Experimenting with your code, I could produce something like what you want, by using a height_ratio that assigns twice the space to the upper subplot, and increasing the h_pad parameter to the tight_layout call. This does not sound completely right, but maybe you can adjust this further ...
import numpy as np
from matplotlib.pyplot import *
import matplotlib.pyplot as pl
import matplotlib.gridspec as gridspec
#define figure
fig = pl.figure(figsize=(10, 6.25))
gs = gridspec.GridSpec(2, 1, height_ratios=[2,1])
ax1=subplot(gs[0])
img=pl.imshow(np.random.random((10,50)), interpolation='none')
ax1.set_xticklabels(()) #hides the tickslabels of the first plot
ax2=subplot(gs[1])
x=np.linspace(0,50)
ax2.plot(x,x,'k-')
xlim( ax1.get_xlim() ) #same x-axis for both plots
fig.tight_layout(h_pad=-5)
show()
There were other issues, like correcting the imports, adding numpy, and plotting to ax2 instead of directly with pl. The output I see is this:
This case is peculiar because of the fact that the default aspect ratios of images and plots are not the same. So it is worth noting for people looking to remove the spaces in a grid of subplots consisting of images only or of plots only that you may find an appropriate solution among the answers to this question (and those linked to it): How to remove the space between subplots in matplotlib.pyplot?.
The aspect ratios of the subplots in this particular example are as follows:
# Default aspect ratio of images:
ax1.get_aspect()
# 1.0
# Which is as it is expected based on the default settings in rcParams file:
matplotlib.rcParams['image.aspect']
# 'equal'
# Default aspect ratio of plots:
ax2.get_aspect()
# 'auto'
The size of ax1 and the space beneath it are adjusted automatically based on the number of pixels along the x-axis (i.e. width) so as to preserve the 'equal' aspect ratio while fitting both subplots within the figure. As you mentioned, using fig.tight_layout(h_pad=xxx) or the similar fig.set_constrained_layout_pads(hspace=xxx) is not a good option as this makes the figure larger.
To remove the gap while preserving the original figure size, you can use fig.subplots_adjust(hspace=xxx) or the equivalent plt.subplots(gridspec_kw=dict(hspace=xxx)), as shown in the following example:
import numpy as np # v 1.19.2
import matplotlib.pyplot as plt # v 3.3.2
np.random.seed(1)
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 6.25),
gridspec_kw=dict(hspace=-0.206))
# For those not using plt.subplots, you can use this instead:
# fig.subplots_adjust(hspace=-0.206)
size = 50
ax1.imshow(np.random.random((10, size)))
ax1.xaxis.set_visible(False)
# Create plot of a line that is aligned with the image above
x = np.arange(0, size)
ax2.plot(x, x, 'k-')
ax2.set_xlim(ax1.get_xlim())
plt.show()
I am not aware of any way to define the appropriate hspace automatically so that the gap can be removed for any image width. As stated in the docstring for fig.subplots_adjust(), it corresponds to the height of the padding between subplots, as a fraction of the average axes height. So I attempted to compute hspace by dividing the gap between the subplots by the average height of both subplots like this:
# Extract axes positions in figure coordinates
ax1_x0, ax1_y0, ax1_x1, ax1_y1 = np.ravel(ax1.get_position())
ax2_x0, ax2_y0, ax2_x1, ax2_y1 = np.ravel(ax2.get_position())
# Compute negative hspace to close the vertical gap between subplots
ax1_h = ax1_y1-ax1_y0
ax2_h = ax2_y1-ax2_y0
avg_h = (ax1_h+ax2_h)/2
gap = ax1_y0-ax2_y1
hspace=-(gap/avg_h) # this divided by 2 also does not work
fig.subplots_adjust(hspace=hspace)
Unfortunately, this does not work. Maybe someone else has a solution for this.
It is also worth mentioning that I tried removing the gap between subplots by editing the y positions like in this example:
# Extract axes positions in figure coordinates
ax1_x0, ax1_y0, ax1_x1, ax1_y1 = np.ravel(ax1.get_position())
ax2_x0, ax2_y0, ax2_x1, ax2_y1 = np.ravel(ax2.get_position())
# Set new y positions: shift ax1 down over gap
gap = ax1_y0-ax2_y1
ax1.set_position([ax1_x0, ax1_y0-gap, ax1_x1, ax1_y1-gap])
ax2.set_position([ax2_x0, ax2_y0, ax2_x1, ax2_y1])
Unfortunately, this (and variations of this) produces seemingly unpredictable results, including a figure resizing similar to when using fig.tight_layout(). Maybe someone else has an explanation for what is happening here behind the scenes.