There are several related questions (here, here, and here), but the suggested solutions don't work in my case.
I'm creating subplots iteratively, so I don't know ahead of time the width of each one (it gets calculated AFTER plt.subplots() is called), which means I can't set the size of each subplot when I initially create them.
I would like to set the size of the subplot x axis after it has already been created.
Imagine something like:
items = [A,B,C] #this could have any number of items in it
f,ax = plt.subplots(len(items),1, figsize=(10,10)) #figsize is arbitrary and could be anything
for i in range(len(items)):
#calculate x and y data for current item
#calculate width of x axis for current item
plt.sca(ax[i])
cax = plt.gca()
cax.plot(x,y)
#here is where I would like to set the x axis size
#something like cax.set_xlim(), but for the size, not the limit
Note 1: The units don't matter, but the relative size does, so it could be size in pixels, or centimeters, or even a ratio calculated based on the relative widths.
Note 2: The width of the x axis is NOT related in this case to the x limit, so I can't just set the x limit and expect the axis to scale correctly.
Also, I'm trying to keep this code short, since it's to be shared with people unfamiliar with Python, so if the only solution involves adding a bunch of lines, it's not worth it and I'll live with incorrectly scaled axes. This is an aesthetic preference but not a requirement.
Thanks!
EDIT: Here's what I'm aiming for
You can create a new GridSpec specifying the height_ratios and then updating each axs position:
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
# create figure
f, ax = plt.subplots(3, 1, figsize=(10,10))
# plot some data
ax[0].plot([1, 2, 3])
ax[1].plot([1, 0, 1])
ax[2].plot([1, 2, 20])
# adjust subplot sizes
gs = GridSpec(3, 1, height_ratios=[5, 2, 1])
for i in range(3):
ax[i].set_position(gs[i].get_position(f))
plt.show()
I asked a similar question before here. The use case was slightly different, but it might still be helpful.
Surely now you got the answer or this problem is deprecated but if someone else is searching, I solved this problem using "Bbox". The idea is something like this:
from matplotlib.transforms import Bbox
fig, ax = plt.subplots(3,1, figsize = (11,15))
ax[0].set_position(Bbox([[0.125, 0.6579411764705883], [0.745, 0.88]]))
ax[2].set_position(Bbox([[0.125, 0.125], [0.745, 0.34705882352941175]]))
For more information, check https://matplotlib.org/api/transformations.html#matplotlib.transforms.Bbox
Related
I am trying to create a figure with three bar plots side by side. These bar plots have different yscales, but the data is fundamentally similar so I'd like all the bars to have the same width.
The only way I was able to get the bars to have the exact same width was by using sharex when creating the subplots, in order to keep the same x scale.
import matplotlib.pyplot as plt
BigData = [[100,300],[400,200]]
MediumData = [[40, 30],[50,20],[60,50],[30,30]]
SmallData = [[3,2],[11,3],[7,5]]
data = [BigData, MediumData, SmallData]
colors = ['#FC766A','#5B84B1']
fig, axs = plt.subplots(1, 3, figsize=(30,5), sharex=True)
subplot = 0
for scale in data:
for type in range(2):
bar_x = [x + type*0.2 for x in range(len(scale))]
bar_y = [d[type] for d in scale]
axs[subplot].bar(bar_x,bar_y, width = 0.2, color = colors[type])
subplot += 1
plt.show()
This creates this figure:
The problem with this is that the x-limits of the plot are also shared, leading to unwanted whitespace. I've tried setting the x-bounds after the fact, but it doesn't seem to override sharex. Is there a way to make the bars have the same width, without each subplot also being the same width?
Additionally, is there a way to create such a plot (one with different y scales to depending on the size of the data) without having to sort the data manually beforehand, like shown in my code?
Thanks!
Thanks to Jody Klymak for help finding this solution! I thought I should document it for future users.
We can make use of the 'width_ratios' GridSpec parameter. Unfortunately there's no way to specify these ratios after we've already drawn a graph, so the best way I found to implement this is to write a function that creates a dummy graph, and measures the x-limits from that graph:
def getXRatios(data, size):
phig, aks = plt.subplots(1, 3, figsize=size)
subplot = 0
for scale in data:
for type in range(2):
bar_x = [x + type*0.2 for x in range(len(scale))]
bar_y = [d[type] for d in scale]
aks[subplot].bar(bar_x,bar_y, width = 0.2)
subplot += 1
ratios = [aks[i].get_xlim()[1] for i in range(3)]
plt.close(phig)
return ratios
This is essentially identical to the code that creates the actual figure, with the cosmetic aspects removed, as all we want from this dummy figure is the x-limits of the graph (something we can't get from our actual figure as we need to define those limits before we start in order to solve the problem).
Now all you need to do is call this function when you're creating your subplots:
fig, axs = plt.subplots(1, 3, figsize=(40,5), gridspec_kw = {'width_ratios':getXRatios(data,(40,5))})
As long as your XRatio function creates your graph in the same way your actual graph does, everything should work! Here's my output using this solution.
To save space you could re-purpose the getXRatios function to also construct your final graph, by calling itself in the arguments and giving an option to return either the ratios or the final figure. I couldn't be bothered.
I'm plotting a meshgrid with pyplot.pcolormesh, and I want to customize the ticklabels on the colorbar. I set a list of tick positions, and provide a list of ticklabels, which should match the tick positions, but I don't know ahead of time which ticks will actually be included, since I don't know the max and the min of the data. The problem is that the first ticklabel I provide is always used at the first visible tick, regardless of whether that is the first tick in my list or not.
Working example:
import matplotlib.pyplot as plt
import numpy as np
a = np.arange(1,10).reshape(3,3)
m = plt.pcolormesh(a)
c = plt.colorbar(m)
c.set_ticks(np.arange(11))
c.set_ticklabels(np.arange(11))
plt.savefig('mesh.png')
This code produces the image below, and the problem here is that the darkest blue is labled 0, while the value in that cell is actually 1, and similarly all the other labels are shifted by 1.
Is this a bug or a feature, and if it's a feature, how can I make sure the labels will match in an elegant manner? I guess I manage with some tests on the data and trying to figure out which tick will be the first visible and so on, but that doesn't seem very pythonic.
Its a feature, because you are setting the ticklabels yourself (with the wrong labels). Its best always trying to avoid setting the ticklabels manually, unless there is no other way.
If you remove this line, the labels will show up correctly:
c.set_ticklabels(np.arange(11))
To improve readability you could also consider normalizing the colors so they become discrete and match specific integer values. But this only works well if the total amount of colors is limited, like in this example.
fig, ax = plt.subplots()
cmap = plt.cm.jet
bounds = np.arange(0.5,10.5,1)
norm = mpl.colors.BoundaryNorm(bounds, cmap.N)
m = ax.pcolormesh(a, cmap=cmap, norm=norm)
c = plt.colorbar(m, ticks=bounds-0.5)
I am attempting to align two sets of separate y-axis using python and matplotlib, and am running into behavior I don't understand. Here is my code so far:
import matplotlib.pyplot as mplot
import numpy as np
fig = mplot.figure()
ax1 = fig.add_subplot(111)
t = np.arange(1, 4, 1)
s1 = np.exp(t)
ax2 = ax1.twinx()
ax1.semilogx(t, s1)
ax2.set_yticks(2*ax1.get_yticks())
mplot.show()
This produces the expected result (from http://postimg.org/image/qowrjnnr5/):
however, changing the definition of t to
t = np.arrange(1, 3, 1)
produces the result (http://postimg.org/image/swanojt0b):
where you can see that the y axis ticks on the right side are off-shifted.
What am I missing in order to prevent this issue?
Thanks!
The two y axes do not have the same limits: in one case you fluke the same lower value in the automatic range calculation while in the other you don't. If you define one yaxis range in terms of the other, I think you achieve what you want:
lim1 = ax1.get_ylim()
lim2 = (lim1[0]*2, lim1[1] *2)
ax2.set_ylim(lim2)
(and if you don't explicitly set the ax2 yticks then ticks will still get rendered if you move beyond the original range in interactive mode).
I want a simple x,y plot created with matplotlib stretched physically in x-direction.
The intention is to get a result were it is easier for me to detect features in the signal.
So I don't want to change any scales or values or limits. Just change the distance between two gridpoint in my output file...
I want to do that on four subplots which should have the same size afterwards.
Thanks in advance... I tried for hours now and I think one of you could probably help me...
David Zwicker already solved my problem in this special case, thanks a lot for that, but in general... If I plot 2 subplots like in this code:
fig = plt.figure()
ax1 = fig.add_subplot(1,2,1)
plot(u_av,z)
ax2 = fig.add_subplot(1,2,2)
plot(pgrd_av,z)
clf()
and want to stretch only one of them. What can I do?
You can change the figure size by using plt.figure(figsize=(20,5)). See the documentation of the figure command.
I know, this is a bit out of the context. But if someone is looking for a solution while using pandas plot which internally uses matplotlib. Here is the solution.
df.plot('col_x', 'col_y', title='stretched_plot', figsize=(20, 1))
You can directly add axes to the canvas at an arbitrary position with plt.axes(). For instance:
ax1 = plt.axes([0, 0, 3, 0.5])
ax2 = plt.axes([0, 0.6, 1, 1])
You can do this:
x = 1.5 # or your needed amount
plt.plot(x_array * x, y_array)
Your line or graph will move to the right depending on your x value
I am building a bar chart using matplotlib using the code below. When my first or last column of data is 0, my first column is wedged against the Y-axis.
An example of this. Note that the first column is ON the x=0 point.
If I have data in this column, I get a huge padding between the Y-Axis and the first column as seen here. Note the additional bar, now at X=0. This effect is repeated if I have data in my last column as well.
My code is as follows:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.ticker import MultipleLocator
binVals = [0,5531608,6475325,1311915,223000,609638,291151,449434,1398731,2516755,3035532,2976924,2695079,1822865,1347155,304911,3562,157,5,0,0,0,0,0,0,0,0]
binTot = sum(binVals)
binNorm = []
for v in range(len(binVals)):
binNorm.append(float(binVals[v])/binTot)
fig = plt.figure(figsize=(6,4))
ax1 = fig.add_subplot(1,1,1)
ax1.bar(range(len(binNorm)),binNorm,align='center', label='Values')
plt.legend(loc=1)
plt.title("Demo Histogram")
plt.xlabel("Value")
plt.xticks(range(len(binLabels)),binLabels,rotation='vertical')
plt.grid(b=True, which='major', color='grey', linestyle='--', alpha=0.35)
ax1.xaxis.grid(False)
plt.ylabel("% of Count")
plt.subplots_adjust(bottom=0.15)
plt.tight_layout()
plt.show()
How can I set a constant margin between the Y-axis and my first/last bar?
Additionally, I realize it's labeled "Demo Histogram", that is a because I missed it when correcting problems discussed here.
I can't run the code snippet you gave, and even with some modification I couldn't replicate the big space. Aside from that, if you need to enforce a border to matplotlib, you ca do somthing like this:
ax.set_xlim( min(your_data) - 10, None )
The first term tells the axis to put the border at 10 units of distance from the minimum of your data, the None parameter teels it to keep the present value.
to put it into contest:
from collections import Counter
from pylab import *
data = randint(20,size=1000)
res = Counter(data)
vals = arange(20)
ax = gca()
ax.bar(vals-0.4, [ res[i] for i in vals ], width=0.8)
ax.set_xlim( min(data)-1, None )
show()
searching around stackoverflow I just learned a new trick: you can call
ax.margins( margin_you_desire )
to let automatically let matplotlib put that amount of space around your plot. It can also be configured differently between x and y.
In your case the best solution would be something like
ax.margins(0.01, None)
The little catch is that the unit is in axes unit, referred to the size of you plot, so a margin of 1 will put space around your plot at both sizes big as your present plot
The problem is align='center'. Remove it.