MatPlotLib share y across entire figure - python

Here is an example of a plot I am generating using Pandas and MatPlotLib.
Please note that even though I stated sharey = True in the code, the y-Axis is only shared across each row.This isn't much help to me, as I need to compare all plots against each other.
How can I use just one axis for the entire plot? I'd also ideally want that axis repeated for each plot.
Thank you!
for field in chosenFields:
for dataID in dataIDs:
fig = plt.figure()
subplots = [fig.add_subplot(rows, cols, subplot) for
subplot in range(1, len(fileNames) + 1)]
for subplot, plot, fileName in zip(subplots, plots, fileNames):
graphData = Build_Graphs.prepareOutputGraph(plot[0],
field,
dataID,
batchName,
segmentName)
haveLegend = True if len(graphData.columns) < 12 else False
subplt = graphData.plot(ax = subplot,
legend = haveLegend,
title = fileName,
sharey = True)
Build_Graphs.labelGraph(subplt, field, dataID, batchName, segmentName)
plt.get_current_fig_manager().window.showMaximized()
writeOutput(outputDirectory, field, dataID, graphData)
plt.show()

In order to get the same axis range repeated for each plot, you can get_ylim from all existing and use global min/max to set all the axes,
import numpy as np
import matplotlib.pyplot as plt
#Setup dummy data
fig, subplots = plt.subplots(2,3)
x = np.linspace(0,2.*np.pi,1000)
[sp.plot(x,np.sin(x)*(10*np.random.randn(1))) for sp in subplots.reshape(-1)]
#Get global minimum and maximum y values accross all axis
ygmin = 0.; ygmax = 0.
for sp in subplots.reshape(-1):
ymin, ymax = sp.get_ylim()
ygmin = min(ygmin,ymin)
ygmax = max(ygmax,ymax)
#Set same axis for all subplots
[sp.set_ylim((ygmin,ygmax)) for sp in subplots.reshape(-1)]
plt.show()
As suggested by paulH, this can also be done with sharey=True as part of plt.subplots. However, the y axis is hidden for anything but the first axis by default, so you need to tell matplotlib to show these again,
import numpy as np
import matplotlib.pyplot as plt
#Setup dummy data
fig, subplots = plt.subplots(2,3,sharey=True)
x = np.linspace(0,2.*np.pi,1000)
[sp.plot(x,np.sin(x)*(10*np.random.randn(1))) for sp in subplots.reshape(-1)]
#Show axis on all subplots
[plt.setp(sp.get_yticklabels(), visible=True) for sp in subplots.reshape(-1)]
plt.show()
You can also specify sharey="col" or sharey="row" to share axes alone the column or row respectively.

Related

Matplotlib: add twin y axis without using its values in the plots

This is to clarify the question title. Say you have four lists of integers, with which you want to produce a scatter plot:
a=[3,7,2,8,12,17]
b=[9,4,11,7,6,3]
c=[9,3,17,13,10,5]
d=[5,1,1,14,5,8]
You also have a function, for simplicity f(x)=1/x, that applies to all lists, so that:
from __future__ import division
a1=[1/i for i in a]
b1=[1/i for i in b]
c1=[1/i for i in c]
d1=[1/i for i in d]
My question: how to add a second y axis, knowing that the values returned by the function range from 0.06 to 1.0, without using any of the a1, b1, c1, d1 lists in the scatter plots?
What I am saying is: if you produce the following scatter plots in the traditional way, how can you then add the second y axis based on the values of a1, b1, c1, d1, without having any series using them in the plot itself?
import matplotlib.pyplot as plt
plt.scatter(a,b,c='red',label='reds')
plt.scatter(c,d,c='blue',label='blues')
plt.legend(loc='best')
This is the scatter without the second y axis:
And this is a made up version of the same one, including the second y axis discussed so far:
NB: This question is different from this, in that I am not trying to plot with different scales. I only want to add a second axis with the relevant values.
To make sure the numbers on the new axis are in the corresponding position to their inverses:
import matplotlib.pylab as plt
a=[3,7,2,8,12,17]
b=[9,4,11,7,6,3]
c=[9,3,17,13,10,5]
d=[5,1,1,14,5,8]
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(a,b,c='red',label='reds')
ax.scatter(c,d,c='blue',label='blues')
ax.legend(loc='best')
ax.set_ylabel('Y')
# make shared y axis
axi = ax.twinx()
# set limits for shared axis
axi.set_ylim(ax.get_ylim())
# set ticks for shared axis
inverse_ticks = []
label_format = '%.3f'
for tick in ax.get_yticks():
if tick != 0:
tick = 1/tick
inverse_ticks.append(label_format % (tick,))
axi.set_yticklabels(inverse_ticks)
axi.set_ylabel('1/Y')
fig.tight_layout()
fig.show()
And you can also do it for the X axis:
# make shared x axis
xaxi = ax.twiny()
# set limits for shared axis
xaxi.set_xlim(ax.get_xlim())
# set ticks for shared axis
inverse_ticks = []
label_format = '%.3f'
for tick in ax.get_xticks():
if tick != 0:
tick = 1/tick
inverse_ticks.append(label_format % (tick,))
xaxi.set_xticklabels(inverse_ticks)
xaxi.set_xlabel('1/X')
Just make shared y axis and set desired limits and ticks for new axis like here:
import matplotlib.pylab as plt
import numpy as np
a=[3,7,2,8,12,17]
b=[9,4,11,7,6,3]
c=[9,3,17,13,10,5]
d=[5,1,1,14,5,8]
plt.scatter(a,b,c='red',label='reds')
plt.scatter(c,d,c='blue',label='blues')
plt.legend(loc='best')
ax = plt.gca()
# make shared y axis
ax2 = ax.twinx()
# set limits for shared axis
ax2.set_ylim([0,1])
# set ticks for shared axis
plt.yticks(np.arange(0.06, 1, 0.14))
plt.show()

pyplot: changing font properties on secondary axis labels

In a Python plot I would like to use a secondary x-axis to display some alternative values. I'm also quite fond of the latex fonts, and would like those fonts to present throughout the plot. However, I find that when I set up my secondary axis, the latex font disappears. Here's a minimum working example:
import numpy as np
import matplotlib.pyplot as plt
Xvalues = np.linspace(0,10,100)
Yvalues = np.sqrt(Xvalues)
Xticks = np.linspace(0,10,6)
AltXvalues = np.log10(Xvalues+1)
AltLabels = ["%.2f" % x for x in AltXvalues] # Round these values
fig = plt.figure()
plt.rcParams['text.usetex'] = True
ax1 = fig.add_subplot(1,1,1)
ax1.plot(Xvalues, Yvalues)
ax1.set_xticks(Xticks)
ax1.set_xlabel('$x_1$')
ax1.set_ylabel('$y$')
ax2 = ax1.twiny()
ax2.set_xlabel('$\\log_{10}\\,(x_1+1)$')
ax2.set_xticks(Xticks)
ax2.set_xticklabels(AltLabels)
plt.show()
How can I ensure that the latex font is continued on the secondary axis?
Its because you are making those labels into strings when you set AltLabels. The different font you see on the primary axis tick labels is because those labels are printed in LaTeX's math-mode. So, the simple fix is to add the math-mode operators to the AltLabel strings:
AltLabels = ["$%.2f$" % x for x in AltXvalues] # Round these values
(Note the $ signs)

matplotlib colorbar placement and size

I'm using quadmesh to create a simple polar projection plot. Here's a minimal script which produces basically what I'm trying to do:
from __future__ import unicode_literals
import numpy as np
import matplotlib.pyplot as plt
def make_plot(data,fig,subplot):
nphi,nt = data.shape
phi_coords = np.linspace(0,np.pi*2,nphi+1) - np.pi/2.
theta_coords = np.linspace(0,np.radians(35),nt+1)
ax = fig.add_subplot(subplot,projection='polar')
ax.set_thetagrids((45,90,135,180,225,270,315,360),(9,12,15,18,21,24,3,6))
ax.set_rgrids(np.arange(10,35,10),fmt='%s\u00b0')
theta,phi = np.meshgrid(phi_coords,theta_coords)
quadmesh = ax.pcolormesh(theta,phi,data)
ax.grid(True)
fig.colorbar(quadmesh,ax=ax)
return fig,ax
a = np.zeros((360,71)) + np.arange(360)[:,None]
b = np.random.random((360,71))
fig = plt.figure()
t1 = make_plot(a,fig,121)
t2 = make_plot(b,fig,122)
fig.savefig('test.png')
The above script creates a plot which looks like this:
I would like the colorbars to:
Not overlap the 6 label.
be scaled such that they are approximately the same height as the plot.
Is there any trick to make this work properly? (Note that this layout isn't the only one I will be using -- e.g. I might use a 1x2 layout, or a 4x4 layout ... It seems like there should be some way to scale the colorbar to the same height as the associated plot...)
This combination (and values near to these) seems to "magically" work for me to keep the colorbar scaled to the plot, no matter what size the display.
plt.colorbar(im,fraction=0.046, pad=0.04)
You can do this with a combination of the pad, shrink, and aspect kwargs:
from __future__ import unicode_literals
import numpy as np
import matplotlib.pyplot as plt
def make_plot(data,fig,subplot):
nphi,nt = data.shape
phi_coords = np.linspace(0,np.pi*2,nphi+1) - np.pi/2.
theta_coords = np.linspace(0,np.radians(35),nt+1)
ax = fig.add_subplot(subplot,projection='polar')
ax.set_thetagrids((45,90,135,180,225,270,315,360),(9,12,15,18,21,24,3,6))
ax.set_rgrids(np.arange(10,35,10),fmt='%s\u00b0')
theta,phi = np.meshgrid(phi_coords,theta_coords)
quadmesh = ax.pcolormesh(theta,phi,data)
ax.grid(True)
cb = fig.colorbar(quadmesh,ax=ax, shrink=.5, pad=.2, aspect=10)
return fig,ax,cb
a = np.zeros((360,71)) + np.arange(360)[:,None]
b = np.random.random((360,71))
fig = plt.figure()
t1 = make_plot(a,fig,121)
t2 = make_plot(b,fig,122)
figure.colorbar doc
The best value for these parameters will depend on the aspect ratio of the axes.
The size of the axes seems to not get shrink-wrapped to the polar plot, thus in the 1x2 arrangement there is a lot of space above and below the plot that are part in the axes object, but empty. The size of the color bar is keyed off of the rectangular size, not the round size, hence why the default values are not working well. There is probably a way to do the shrink-wrapping, but I do not know how to do that.
An alternate method is to force your figure to be the right aspect ratio ex:
fig.set_size_inches(10, 4) # for 1x2
fig.set_size_inches(4, 10) # for 2x1
which makes the sub plots square, so the default values more-or-less work.

Dynamically add/create subplots in matplotlib

I want to create a plot consisting of several subplots with shared x/y axes.
It should look something like this from the documentation (though my subplots will be scatterblots): (code here)
But I want to create the subplots dynamically!
So the number of subplots depends on the output of a previous function. (It will probably be around 3 to 15 subplots per diagram, each from a distinct dataset, depending on the input of my script.)
Can anyone tell me how to accomplish that?
Suppose you know total subplots and total columns you want to use:
import matplotlib.pyplot as plt
# Subplots are organized in a Rows x Cols Grid
# Tot and Cols are known
Tot = number_of_subplots
Cols = number_of_columns
# Compute Rows required
Rows = Tot // Cols
# EDIT for correct number of rows:
# If one additional row is necessary -> add one:
if Tot % Cols != 0:
Rows += 1
# Create a Position index
Position = range(1,Tot + 1)
First instance of Rows accounts only for rows completely filled by subplots, then is added one more Row if 1 or 2 or ... Cols - 1 subplots still need location.
Then create figure and add subplots with a for loop.
# Create main figure
fig = plt.figure(1)
for k in range(Tot):
# add every single subplot to the figure with a for loop
ax = fig.add_subplot(Rows,Cols,Position[k])
ax.plot(x,y) # Or whatever you want in the subplot
plt.show()
Please note that you need the range Position to move the subplots into the right place.
import matplotlib.pyplot as plt
from pylab import *
import numpy as np
x = np.linspace(0, 2*np.pi, 400)
y = np.sin(x**2)
subplots_adjust(hspace=0.000)
number_of_subplots=3
for i,v in enumerate(xrange(number_of_subplots)):
v = v+1
ax1 = subplot(number_of_subplots,1,v)
ax1.plot(x,y)
plt.show()
This code works but you will need to correct the axes. I used to subplot to plot 3 graphs all in the same column. All you need to do is assign an integer to number_of_plots variable. If the X and Y values are different for each plot you will need to assign them for each plot.
subplot works as follows, if for example I had a subplot values of 3,1,1. This creates a 3x1 grid and places the plot in the 1st position. In the next interation if my subplot values were 3,1,2 it again creates a 3x1 grid but places the plot in the 2nd position and so forth.
Based on this post, what you want to do is something like this:
import matplotlib.pyplot as plt
# Start with one
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot([1,2,3])
# Now later you get a new subplot; change the geometry of the existing
n = len(fig.axes)
for i in range(n):
fig.axes[i].change_geometry(n+1, 1, i+1)
# Add the new
ax = fig.add_subplot(n+1, 1, n+1)
ax.plot([4,5,6])
plt.show()
However, Paul H's answer points to the submodule called gridspec which might make the above easier. I am leaving that as an exercise for the reader ^_~.
Instead of counting your own number of rows and columns, I found it easier to create the subplots using plt.subplots first, then iterate through the axes object to add plots.
import matplotlib.pyplot as plt
import numpy as np
fig, axes = plt.subplots(nrows=3, ncols=2, figsize=(12, 8))
x_array = np.random.randn(6, 10)
y_array = np.random.randn(6, 10)
i = 0
for row in axes:
for ax in row:
x = x_array[i]
y = y_array[i]
ax.scatter(x, y)
ax.set_title("Plot " + str(i))
i += 1
plt.tight_layout()
plt.show()
Here I use i to iterate through elements of x_array and y_array, but you can likewise easily iterate through functions, or columns of dataframes to dynamically generate graphs.

creating over 20 unique legend colors using matplotlib

I am plotting 20 different lines on a single plot using matplotlib. I use a for loop for plotting and label every line with its key and then use the legend function
for key in dict.keys():
plot(x,dict[key], label = key)
graph.legend()
But using this way, the graph repeats a lot of colors in the legend. Is there any way to ensure a unique color is assigned to each line using matplotlib and over 20 lines?
thanks
The answer to your question is related to two other SO questions.
The answer to How to pick a new color for each plotted line within a figure in matplotlib? explains how to define the default list of colors that is cycled through to pick the next color to plot. This is done with the Axes.set_color_cycle method.
You want to get the correct list of colors though, and this is most easily done using a color map, as is explained in the answer to this question: Create a color generator from given colormap in matplotlib. There a color map takes a value from 0 to 1 and returns a color.
So for your 20 lines, you want to cycle from 0 to 1 in steps of 1/20. Specifically you want to cycle form 0 to 19/20, because 1 maps back to 0.
This is done in this example:
import matplotlib.pyplot as plt
import numpy as np
NUM_COLORS = 20
cm = plt.get_cmap('gist_rainbow')
fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_prop_cycle(color=[cm(1.*i/NUM_COLORS) for i in range(NUM_COLORS)])
for i in range(NUM_COLORS):
ax.plot(np.arange(10)*(i+1))
fig.savefig('moreColors.png')
plt.show()
This is the resulting figure:
Alternative, better (debatable) solution
There is an alternative way that uses a ScalarMappable object to convert a range of values to colors. The advantage of this method is that you can use a non-linear Normalization to convert from line index to actual color. The following code produces the same exact result:
import matplotlib.pyplot as plt
import matplotlib.cm as mplcm
import matplotlib.colors as colors
import numpy as np
NUM_COLORS = 20
cm = plt.get_cmap('gist_rainbow')
cNorm = colors.Normalize(vmin=0, vmax=NUM_COLORS-1)
scalarMap = mplcm.ScalarMappable(norm=cNorm, cmap=cm)
fig = plt.figure()
ax = fig.add_subplot(111)
# old way:
#ax.set_prop_cycle(color=[cm(1.*i/NUM_COLORS) for i in range(NUM_COLORS)])
# new way:
ax.set_prop_cycle(color=[scalarMap.to_rgba(i) for i in range(NUM_COLORS)])
for i in range(NUM_COLORS):
ax.plot(np.arange(10)*(i+1))
fig.savefig('moreColors.png')
plt.show()
I had a plot with 12 lines, and I found it hard to distinguish lines with similar colours when I tried Yann's technique. My lines also appeared in pairs, so I used the same colour for the two lines in each pair, and used two different line widths. You could also vary the line style to get more combinations.
You could use set_prop_cycle(), but I just modified the line objects after calling plot().
Here is Yann's example with three different line widths:
import matplotlib.pyplot as plt
import numpy as np
NUM_COLORS = 20
cm = plt.get_cmap('gist_rainbow')
fig = plt.figure()
ax = fig.add_subplot(111)
for i in range(NUM_COLORS):
lines = ax.plot(np.arange(10)*(i+1))
lines[0].set_color(cm(i//3*3.0/NUM_COLORS))
lines[0].set_linewidth(i%3 + 1)
fig.savefig('moreColors.png')
plt.show()
Here's the same example with different line styles. Of course you could combine the two if you wanted.
import matplotlib.pyplot as plt
import numpy as np
NUM_COLORS = 20
LINE_STYLES = ['solid', 'dashed', 'dashdot', 'dotted']
NUM_STYLES = len(LINE_STYLES)
cm = plt.get_cmap('gist_rainbow')
fig = plt.figure()
ax = fig.add_subplot(111)
for i in range(NUM_COLORS):
lines = ax.plot(np.arange(10)*(i+1))
lines[0].set_color(cm(i//NUM_STYLES*float(NUM_STYLES)/NUM_COLORS))
lines[0].set_linestyle(LINE_STYLES[i%NUM_STYLES])
fig.savefig('moreColors.png')
plt.show()
To build off of Don Kirkby's answer, if you're willing to install/use seaborn, then you can have colors computed for you:
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
NUM_COLORS = 20
LINE_STYLES = ['solid', 'dashed', 'dashdot', 'dotted']
NUM_STYLES = len(LINE_STYLES)
sns.reset_orig() # get default matplotlib styles back
clrs = sns.color_palette('husl', n_colors=NUM_COLORS) # a list of RGB tuples
fig, ax = plt.subplots(1)
for i in range(NUM_COLORS):
lines = ax.plot(np.arange(10)*(i+1))
lines[0].set_color(clrs[i])
lines[0].set_linestyle(LINE_STYLES[i%NUM_STYLES])
fig.savefig('moreColors.png')
plt.show()
Aside from being able to use seaborn's various color palettes, you can get a list of RGB tuples that can be used/manipulated later on if need be. Obviously, you could compute something similar using matplotlib's colormaps, but I find this to be handy.
These answers seemed more complicated than needed. If you are looping through a list to plot lines, then just enumerate on the list and assig color to some point on the colormap. Say you are looping through all the columns from a pandas dataframe:
fig, ax = plt.subplots()
cm = plt.get_cmap('gist_rainbow')
for count, col in enumerate(df.columns):
ax.plot(df[col], label = col, linewidth = 2, color = cm(count*20))
This works because cm is just an iterable dictionary of color numerics. Multiplying those by some factor gets you further along in the colormap (more difference in color).

Categories

Resources