matplotlib: how to simultaneously change tick position and figure shape - python

I'm plotting a figure and hope to set the figure shape and tick positions. But I find that I cannot do the two things together. For example, if I use the following code:
import matplotlib
import matplotlib.pyplot as plt
ls = range(0,10)
fig, ax = plt.subplots()
# set figure shape
plt.figure(figsize=(10,5))
plt.ylim([0,10])
plt.plot(ls)
figname = 'aaa.jpg'
# set ytick positions
ax.set_yticks([1,3,5,7,9])
plt.savefig(figname,format='jpg')
Then I get the following figure.
The shape is correct. But the ytick is not changed by the code line ax.set_yticks([1,3,5,7,9]).
Then I try the following code (i.e. move the sentence plt.figure(figsize=(10,5)) to the beginning of the program):
import matplotlib
import matplotlib.pyplot as plt
# set figure shape
plt.figure(figsize=(10,5))
ls = range(0,10)
fig, ax = plt.subplots()
plt.ylim([0,10])
plt.plot(ls)
figname = 'aaa.jpg'
# set ytick position
ax.set_yticks([1,3,5,7,9])
plt.savefig(figname,format='jpg')
Then I get the following figure:
The ytick is correct. Yicks appear in positions [1,3,5,7,9]. However, the figure shape is not the shape I set.
How to do the two things together?
Thank you all for helping me!!!

you can set the figsize in the subplot function instead.
what went wrong in the first graph:
plt.figure(figsize=(10,5))
the above line of code is creating a new figure on which your graph is being plotted, the 'ax' on which you are setting the y ticks is related to a subplot which is different.
ls = range(0,10)
fig, ax = plt.subplots(figsize=(10,5))
ax.set_yticks([1,3,5,7,9])
ax.set_xticks([1,3,5,7,9])
plt.grid()
plt.plot(ls)
The plot is showing exactly what you're trying to do

Related

How to show multiple already plotted matplotlib figures side-by-side or on-top in Python without re-plotting them?

I have already plotted two figures separately in a single jupyter notebook file, and exported them.
What I want is to show them side by side, but not plot them again by using matplotlib.pyplot.subplots.
For example, in Mathematica, it's easier to do this by just saving the figures into a Variable, and displaying them afterwards.
What I tried was saving the figures, using
fig1, ax1 = plt.subplots(1,1)
... #plotting using ax1.plot()
fig2, ax2 = plt.subplots(1,1)
... #plotting using ax2.plot()
Now, those fig1 or fig2 are of type Matplotlib.figure.figure which stores the figure as an 'image-type' instance. I can even see them separately by calling just fig1 or fig2 in my notebook.
But, I can not show them together as by doing something like
plt.show(fig1, fig2)
It returns nothing since, there wasn't any figures currently being plotted.
You may look at this link or this, which is a Mathematica version of what I was talking about.
assuming u want to merge those subplots in the end.
Here is the code
import numpy as np
import matplotlib.pyplot as plt
#e.x function to plot
x = np.linspace(0, 10)
y = np.exp(x)
#almost your code
figure, axes = plt.subplots(1,1)
res_1, = axes.plot(x,y) #saving the results in a tuple
plt.show()
plt.close(figure)
figure, axes = plt.subplots(1,1)
res_2, = axes.plot(x,-y) #same before
plt.show()
#restructure to merge
figure_2, (axe_1,axe_2) = plt.subplots(1,2) #defining rows and columns
axe_1.plot(res_1.get_data()[0], res_1.get_data()[1]) #using the already generated data
axe_2.plot(res_2.get_data()[0], res_2.get_data()[1])
#if you want show them in one
plt.show()
Not quite sure what you mean with:
but not plot them again by using matplotlib.pyplot.subplots.
But you can display two figures next to each other in a jupyter notebook by using:
fig, ax = plt.subplots(nrows=1, ncols=2)
ax[0] = ... # Code for first figure
ax[1] = ... # Code for second figure
plt.show()
Or above each other:
fig, ax = plt.subplots(nrows=2, ncols=1)
ax[0] = ... # Top figure
ax[1] = ... # Bottom figure
plt.show()

Matplotlib: wrong colors on lineplot legend when using colormap

I am trying to plot multiple lines, each with a unique color selected from a colormap. The lines are plotted with the correct color, but in the legend, each line is labeled with the same color. Here is some sample code:
from matplotlib import pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(111)
#values associated with each line
values=(-4,-3,-2,-1, 0,1,2,3,4)
#get an array of colors associated with the list of values
col=plt.cm.viridis(0.125*np.array(values)+0.5)
j=0
x=np.linspace(-5,5,101)
for val in values:
y=get_associated_data(val)
ax.plot(x,y,color=col[j],label='val='+str(val))
j+=1
handles,labels = ax.get_legend_handles_labels()
ax.legend(np.unique(labels))
plt.show()
The above code produces a plot like this
How do I fix this? Also note that the labels are out of order, which I'd also like to fix.
EDIT:
Here is the exact code I am using to make the plot, including #William Miller's fix for the out of order legend.
from matplotlib import pyplot as plt
import numpy as np
rc('font', size=16)
rc('text', usetex=True)
fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_xlabel(r'$N^\prime$')
ax.set_ylabel(r'$\Gamma$')
ax.set_title(r'Particle flux at $U=U^{\prime\prime}=0,\varepsilon=20$')
gradlist=(-4,-3,-2,-1, 0,1,2,3,4)
col=plt.cm.viridis(0.125*np.array(gradlist)+0.5)
x=[i/10. for i in range(-50,51)]
j=0
for grad in gradlist:
average_flux=np.load('flux_'+str(grad)+'.npy')
ax.plot(x,average_flux,color=col[j],label=r'$U^\prime=$'+str(grad))
time.sleep(1)
print(grad)
j+=1
handles,labels = ax.get_legend_handles_labels()
labels = np.array(labels)
ax.legend(labels[np.sort(np.unique(labels, return_index=True)[1])])
plt.show()
You'll need the following dataset: https://www.dropbox.com/sh/2l2pot21f5sp6cw/AAD1xJcl-FLVf79ylpf7SZiTa?dl=0
You can resolve the sorting according to this answer by using a combination of np.unique with np.sort to preserve the ordering.
handles, labels = ax.get_legend_handles_labels()
labels = np.array(labels)
ax.legend(labels[np.sort(np.unique(labels, return_index=True)[1])])
plt.show()
which will give you
The coloring issue is caused by only the first 9 handles of ~900 being used, instead of the handles corresponding to the correct 9 labels, you can resolve this by selecting the correct indices from both handles and labels, something like this
handles, labels = ax.get_legend_handles_labels()
idx = np.sort(np.unique(np.array(labels), return_index=True)[1])
ax.legend(np.array(handles)[idx], np.array(labels)[idx])
plt.show()
Should give you the correct result,

Controlling legend across multiple subplots with windrose axes

I cannot figure out how to make the legends not overlap with my figures (see below figure) in subplots. The problem is my axes are complicated because they are from a windrose. To get the axes:
1) I have downloaded the windrose.py from https://github.com/akrherz/windrose/tree/darylchanges
2) I copied the windrose.py into the same path with my python script, example.py
3) I changed windrose.py so that it is able to do subplots, according to the steps from Subplot of Windrose in matplotlib . Those steps were to make WindroseAxes as a projection into matplotlib. I edited the file windrose.py:
3a) Include an
import from matplotlib.projections import register_projection
at the beginning of the file.
3b) Then add a name variable :
class WindroseAxes(PolarAxes):
name = 'windrose'
...
3c) Finally, at the end of windrose.py, you add:
register_projection(WindroseAxes)
Once that is done, you can easily create your windrose axes using the projection argument to the matplotlib axes.
4) Now I ran my script below (example of my real script)
from windrose import WindroseAxes
import numpy as np
import matplotlib.pyplot as plt
from windrose_subplot import WindroseAxes
wind_speeds1 = np.array([12,10,13,15])
wind_dirs1 = np.array([60,76,32,80]) # in degrees
wind_speeds2 = np.array([23,12,10,8])
wind_dirs2 = np.array([23,45,29,13])
fig = plt.figure()
ax1 = fig.add_subplot(231,projection='windrose')
ax1.bar(wind_dirs1,wind_speeds1,normed=True,opening=0.8,edgecolor='white')
ax2 = fig.add_subplot(232,projection='windrose')
ax2.bar(wind_dirs2,wind_speeds2,normed=True,opening=0.8,edgecolor='white')
ax1.legend()
ax2.legend()
plt.tight_layout()
plt.show()
Ideally, I would like to create one legend with the max/min of all the subplots because they are all the same units . This legend will have to be the corresponding colors for each subplot for the same values across subplots (eg, a single normal legend relevant to all subplots). There will be 6 subplots in the real script but 2 here for now shows the point.
This is simple to fix. In order to only plot one legend, comment out or delete where you plot the first legend. In order to move the legend off of the plot, use bbox_to_anchor=() with some logical location. See below for an example that works for this example.
import numpy as np
import matplotlib.pyplot as plt
from windrose_subplot import WindroseAxes
wind_speeds1 = np.array([12,10,13,15])
wind_dirs1 = np.array([60,76,32,80]) # in degrees
wind_speeds2 = np.array([23,12,10,8])
wind_dirs2 = np.array([23,45,29,13])
fig = plt.figure()
ax1 = fig.add_subplot(231,projection='windrose')
ax1.bar(wind_dirs1,wind_speeds1,normed=True,opening=0.8,edgecolor='white')
ax2 = fig.add_subplot(232,projection='windrose')
ax2.bar(wind_dirs2,wind_speeds2,normed=True,opening=0.8,edgecolor='white')
# ax1.legend()
ax2.legend(bbox_to_anchor=(1.2 , -0.1))
plt.tight_layout()
plt.show()
However, note the bbox_to_anchor is reliant on the axis that the legend comes from, so
ax1.legend(bbox_to_anchor=1.2, -0.1))
#ax2.legend()
would display the legend underneath the second axis:
Thank you Hazard11, I found your answer very useful :) There is an issue with the answer though is the legend does not represent the first subplot because the bins are generated when creating the second subplot.
I just solved this issue by calculating the bins using numpy.histogram first and then passing that to windrose.WindroseAxes.bar() when creating each wind rose. Doing it this way means you need to pick which one you want to use to generate the bins. Another way to do it would be to define the bins manually or to create a function which generates some efficient binning for both which could then be used.
wind_speeds1 = np.array([12,10,13,15])
wind_dirs1 = np.array([60,76,32,80]) # in degrees
wind_speeds2 = np.array([23,12,10,8])
wind_dirs2 = np.array([23,45,29,13])
wind_speeds_bins = np.histogram(wind_speeds2, 5)[1]
fig = plt.figure()
ax1 = fig.add_subplot(231, projection='windrose')
ax1.bar(wind_dirs1 ,wind_speeds1, normed=True, opening=0.8, edgecolor='white', bins=wind_speeds_bins)
ax2 = fig.add_subplot(232, projection='windrose')
ax2.bar(wind_dirs2, wind_speeds2, normed=True, opening=0.8, edgecolor='white', bins=wind_speeds_bins)
# ax1.legend()
ax2.legend(bbox_to_anchor=(1.2 , -0.1))
plt.tight_layout()
plt.show()

How to properly display sufficient tick markers using plt.savefig?

After running the code below, the axis tick markers all overlap with each other. At this time, each marker could still have good resolution when zooming popped up by plt.show(). However, the figure saved by plt.savefig('fig.png') would lost its resolution. Can this also be optimised?
from matplotlib.ticker import FuncFormatter
from matplotlib.pyplot import show
import matplotlib.pyplot as plt
import numpy as np
a=np.random.random((1000,1000))
# create scaled formatters / for Y with Atom prefix
formatterY = FuncFormatter(lambda y, pos: 'Atom {0:g}'.format(y))
formatterX = FuncFormatter(lambda x, pos: '{0:g}'.format(x))
# apply formatters
fig, ax = plt.subplots()
ax.yaxis.set_major_formatter(formatterY)
ax.xaxis.set_major_formatter(formatterX)
plt.imshow(a, cmap='Reds', interpolation='nearest')
# create labels
plt.xlabel('nanometer')
plt.ylabel('measure')
plt.xticks(list(range(0, 1001,10)))
plt.yticks(list(range(0, 1001,10)))
plt.savefig('fig.png',bbox_inches='tight')
plt.show()
I think you can solve it by setting the size of the figure, e.g.
fig, ax = plt.subplots()
fig.set_size_inches(15., 15.)
As pointed out by #PatrickArtner in the comments, you can then also avoid the overlap of x-ticks by
plt.xticks(list(range(0, 1001, 10)), rotation=90)
instead of
plt.xticks(list(range(0, 1001,10)))
The rest of the code is completely unchanged; the output then looks reasonable (but is too large to upload here).

adjust matplotlib subplot spacing after tight_layout

I would like to minimize white space in my figure. I have a row of sub plots where four plots share their y-axis and the last plot has a separate axis.
There are no ylabels or ticklabels for the shared axis middle panels.
tight_layout creates a lot of white space between the the middle plots as if leaving space for tick labels and ylabels but I would rather stretch the sub plots. Is this possible?
import matplotlib.gridspec as gridspec
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
fig = plt.figure()
gs = gridspec.GridSpec(1, 5, width_ratios=[4,1,4,1,2])
ax = fig.add_subplot(gs[0])
axes = [ax] + [fig.add_subplot(gs[i], sharey=ax) for i in range(1, 4)]
axes[0].plot(np.random.randint(0,100,100))
barlist=axes[1].bar([1,2],[1,20])
axes[2].plot(np.random.randint(0,100,100))
barlist=axes[3].bar([1,2],[1,20])
axes[0].set_ylabel('data')
axes.append(fig.add_subplot(gs[4]))
axes[4].plot(np.random.randint(0,5,100))
axes[4].set_ylabel('other data')
for ax in axes[1:4]:
plt.setp(ax.get_yticklabels(), visible=False)
sns.despine();
plt.tight_layout(pad=0, w_pad=0, h_pad=0);
Setting w_pad = 0 is not changing the default settings of tight_layout. You need to set something like w_pad = -2. Which produces the following figure:
You could go further, to say -3 but then you would start to get some overlap with your last plot.
Another way could be to remove plt.tight_layout() and set the boundaries yourself using
plt.subplots_adjust(left=0.065, right=0.97, top=0.96, bottom=0.065, wspace=0.14)
Though this can be a bit of a trial and error process.
Edit
A nice looking graph can be achieved by moving the ticks and the labels of the last plot to the right hand side. This answer shows you can do this by using:
ax.yaxis.tick_right()
ax.yaxis.set_label_position("right")
So for your example:
axes[4].yaxis.tick_right()
axes[4].yaxis.set_label_position("right")
In addition, you need to remove sns.despine(). Finally, there is now no need to set w_pad = -2, just use plt.tight_layout(pad=0, w_pad=0, h_pad=0)
Using this creates the following figure:

Categories

Resources