matplotlib: 2 different legends on same graph - python

I have a plot where different colors are used for different parameters, and where different line styles are used for different algorithms. The goal is to compare the results of the different algorithms performed with similar parameters. It means in total I use 4 different colors, and 3 different line styles, for a total of 12 plots on the same graph.
I actually build the legend based on colors, associating each color with the corresponding parameter. Now I'd like to display a second legend on the same graph, with the meaning of each line style. It is possible to achieve that? How?
Here is what my code looks like actually:
colors = ['b', 'r', 'g', 'c']
cc = cycle(c)
for p in parameters:
d1 = algo1(p)
d2 = algo2(p)
d3 = algo3(p)
pyplot.hold(True)
c = next(cc)
pyplot.plot(d1, '-', color=c, label="d1")
pyplot.plot(d1, '--', color=c)
pyplot.plot(d2, '.-', color=c)
pyplot.legend()

There's a section in the matplotlib documentation on that exact subject.
Here's code for your specific example:
import itertools
from matplotlib import pyplot
colors = ['b', 'r', 'g', 'c']
cc = itertools.cycle(colors)
plot_lines = []
for p in parameters:
d1 = algo1(p)
d2 = algo2(p)
d3 = algo3(p)
pyplot.hold(True)
c = next(cc)
l1, = pyplot.plot(d1, '-', color=c)
l2, = pyplot.plot(d2, '--', color=c)
l3, = pyplot.plot(d3, '.-', color=c)
plot_lines.append([l1, l2, l3])
legend1 = pyplot.legend(plot_lines[0], ["algo1", "algo2", "algo3"], loc=1)
pyplot.legend([l[0] for l in plot_lines], parameters, loc=4)
pyplot.gca().add_artist(legend1)
Here's an example of its output:

Here is also a more "hands-on" way to do it (i.e. interacting explicitely with any figure axes):
import itertools
from matplotlib import pyplot
fig, axes = plt.subplot(1,1)
colors = ['b', 'r', 'g', 'c']
cc = itertools.cycle(colors)
plot_lines = []
for p in parameters:
d1 = algo1(p)
d2 = algo2(p)
d3 = algo3(p)
c = next(cc)
axes.plot(d1, '-', color=c)
axes.plot(d2, '--', color=c)
axes.plot(d3, '.-', color=c)
# In total 3x3 lines have been plotted
lines = axes.get_lines()
legend1 = pyplot.legend([lines[i] for i in [0,1,2]], ["algo1", "algo2", "algo3"], loc=1)
legend2 = pyplot.legend([lines[i] for i in [0,3,6]], parameters, loc=4)
axes.add_artist(legend1)
axes.add_artist(legend2)
I like this way of writing it since it allows potentially to play with different axes in a less obscure way. You can first create your set of legends, and then add them to the axes you want with the method "add_artist". Also, I am starting with matplotlib, and for me at least it is easier to understand scripts when objets are explicited.
NB: Be careful, your legends may be cutoff while displaying/saving. To solve this issue, use the method axes.set_position([left, bottom, width, length]) to shrink the subplot relatively to the figure size and make the legends appear.

What about using a twin ghost axis?
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
colors = ['b', 'r', 'g', ]
styles = ['-', '--', '-.']
for cc, col in enumerate(colors):
for ss, sty in enumerate(styles):
print(cc, ss)
ax.plot([0, 1], [cc, ss], c=colors[cc], ls=styles[ss])
for cc, col in enumerate(colors):
ax.plot(np.NaN, np.NaN, c=colors[cc], label=col)
ax2 = ax.twinx()
for ss, sty in enumerate(styles):
ax2.plot(np.NaN, np.NaN, ls=styles[ss],
label='style ' + str(ss), c='black')
ax2.get_yaxis().set_visible(False)
ax.legend(loc=1)
ax2.legend(loc=3)
plt.show()

You can also use line.get_label()
import matplotlib.pyplot as plt
plt.figure()
colors = ['b', 'r', 'g', 'c']
parameters = [1,2,3,4]
for p in parameters:
color = colors[parameters.index(p)]
plt.plot([1,10],[1,p], '-', c=color, label='auto label '+str(p))
lines = plt.gca().get_lines()
include = [0,1]
legend1 = plt.legend([lines[i] for i in include],[lines[i].get_label() for i in include], loc=1)
legend2 = plt.legend([lines[i] for i in [2,3]],['manual label 3','manual label 4'], loc=4)
plt.gca().add_artist(legend1)
plt.show()

import matplotlib.pyplot as plt
plt.figure()
colors = ['b', 'r', 'g', 'c']
parameters = [1,2,3,4]
for p in parameters:
color = colors[parameters.index(p)]
plt.plot([1,10],[1,p], '-', c=color, label='auto label '+str(p))
lines = plt.gca().get_lines()
include = [0,1]
legend1 = plt.legend([lines[i] for i in include],[lines[i].get_label() for i in include], loc=1)
legend2 = plt.legend([lines[i] for i in [2,3]],['manual label 3','manual label 4'], loc=4)
plt.gca().add_artist(legend1)
plt.show()

Related

How to create a 3D graph with filled-below curves and position the y-ticks?

I am making my first 3D graph in Python on an Anaconda Jupyter Notebook. The idea is to obtain a graph with a format similar to the following:
The code I made is as follows:
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
fig = plt.figure()
ax = Axes3D(fig)
def func(x, pos): # formatter function takes tick label and tick position
s = str(x)
ind = s.index('.')
return s[:ind] + ',' + s[ind+1:] # change dot to comma
x_format = tkr.FuncFormatter(func)
ax.xaxis.set_major_formatter(x_format)
ax.yaxis.set_major_formatter(x_format)
df = pd.read_excel('EDS 7.xlsx', header=None, usecols=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15], names=['A', 'B', 'C', 'D','E','F','G','H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P'])
plt.rcParams["figure.figsize"] = [14.5,10]
nomes = ['Triângulo de MoSe$_2$','Losango branco','Losango cinzento','Fundo']
ax.set_yticks(range(0,4))
ax.set_yticklabels(nomes)
ax.tick_params(axis='z', pad=10)
ax.tick_params(axis='y', pad=20)
# put 0s on the y-axis, and put the y axis on the z-axis
ax.plot(xs=df['A'], ys=df['B'], zs=df['C'], zdir='z', label='ys=0, zdir=z', color='blue', linewidth=3)
ax.plot(xs=df['D'], ys=df['E'], zs=df['F'], zdir='z', label='ys=0, zdir=z', color='red', linewidth=3)
ax.plot(xs=df['G'], ys=df['H'], zs=df['I'], zdir='z', label='ys=0, zdir=z', color='green', linewidth=3)
ax.plot(xs=df['J'], ys=df['K'], zs=df['L'], zdir='z', label='ys=0, zdir=z', color='orange', linewidth=3)
y=df['M'];
plt.xlim([0.0, 4.0])
#plt.ylim([0.0, 4.0])
ax.set_zlim(0,1400)
plt.rc('xtick', labelsize=16)
plt.rc('ytick', labelsize=16)
#plt.xticks(np.arange(0.0,1.4,0.1).round(decimals=1))
#plt.yticks(np.arange(-0.8,1.3,0.2).round(decimals=1))
ax.w_xaxis.set_pane_color((1.0, 1.0, 1.0, 1.0))
ax.w_yaxis.set_pane_color((1.0, 1.0, 1.0, 1.0))
ax.w_zaxis.set_pane_color((1.0, 1.0, 1.0, 1.0))
ax.xaxis._axinfo["grid"]['linestyle'] = '--'
ax.xaxis._axinfo["grid"]['color'] = 'silver'
ax.yaxis._axinfo["grid"]['linestyle'] = '--'
ax.yaxis._axinfo["grid"]['color'] = 'silver'
ax.zaxis._axinfo["grid"]['linestyle'] = '--'
ax.zaxis._axinfo["grid"]['color'] = 'silver'
ax.set_xlabel('Energia (keV)', fontsize=20, labelpad=18)
ax.set_zlabel('Contagens', fontsize=20, labelpad=18)
#plt.show()
plt.savefig('output.png', dpi=500, bbox_inches='tight')
Excel file:
The graphic I got is this:
I am having two problems that I am unable to solve:
The underside of the lines is not filled with color and I would like them to be opaque.
In the yy axis, the strings are too far to the left and for example the string "Triângulo de MoSe2" of the yy axis is to the left of the number 4.0 of the xx axis. I would like the y-axis strings to be more centered.
How can I adjust the code for the graph to have these two characteristics that I lack?
Here is an example to create something similar to the desired plot. Some toy data are used to create 4 curves.
To fill the area below the curves, the approach from this tutorial is used. For the y tick labels, it seems ax.set_yticklabels(..., ha='left') together with ax.tick_params(axis='y', pad=0) get quite close to the desired result.
To make the polygons fully opaque, set the opaqueness alpha in PolyCollection(...) to a value closer to 1. Usually a small bit of transparency gives a better feeling of being a 3D plot. You can leave out the call to ax.plot(...) if the thicker "border" isn't needed.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.collections import PolyCollection
def polygon_under_graph(xlist, ylist):
return [(xlist[0], 0.), *zip(xlist, ylist), (xlist[-1], 0.)]
x_cols = {col: np.linspace(0, 4, 50) for col in [*'ADGJ']}
z_cols = {col: 1400 ** np.random.rand(50) for col in [*'CFIL']}
df = pd.DataFrame({**x_cols, **z_cols})
fig = plt.figure()
ax = Axes3D(fig)
plt.rcParams["figure.figsize"] = [14.5, 10]
nomes = ['Triângulo de MoSe$_2$', 'Losango branco', 'Losango cinzento', 'Fundo']
ax.set_yticks(range(0, 4))
ax.set_yticklabels(nomes, ha='left')
ax.tick_params(axis='z', pad=10)
ax.tick_params(axis='y', pad=0)
color_list = ['cornflowerblue', 'crimson', 'limegreen', 'orange']
verts = []
ys = [3, 2, 1, 0]
for x_col, z_col, y, color in zip(['A', 'D', 'G', 'J'], ['C', 'F', 'I', 'L'], ys, color_list):
xs = df[x_col].to_numpy()
zs = df[z_col].to_numpy()
ax.plot(xs=xs, ys=np.repeat(y, len(xs)), zs=zs, zdir='z', color=color, linewidth=3)
verts.append(polygon_under_graph(xs, zs))
poly = PolyCollection(verts, facecolors=color_list, alpha=.8)
ax.add_collection3d(poly, zs=ys, zdir='y')
plt.show()
About having the outlines of the 3 panes in black, some experimenting with 3D figures from Matplotlib visibility of pane edge leads to the following. It is unclear to me why that hack works (an other approaches don't).
def lims(mplotlims):
scale = 1.021
offset = (mplotlims[1] - mplotlims[0]) * scale
return mplotlims[1] - offset, mplotlims[0] + offset
xlims, ylims, zlims = lims(ax.get_xlim()), lims(ax.get_ylim()), lims(ax.get_zlim())
i = np.array([xlims[0], ylims[0], zlims[0]])
f = np.array([xlims[0], ylims[0], zlims[1]])
p = art3d.Poly3DCollection(np.array([[i, f]]))
p.set_color('black')
ax.add_collection3d(p)
ax.xaxis.pane.set_edgecolor('#000000FF')
ax.yaxis.pane.set_edgecolor('#000000FF')
ax.zaxis.pane.set_edgecolor('#000000FF')

How to assign color to error bar caps? [Matplotlib]

I am new at Matplotlib and would like to assign colors to error bar caps...in my data (attached) the mean values are 'numbers' and the SD ('error') is in the column 'sd'. I grouped data by 'strain' (4 categories; mc, mut1, etc.). Colors are 'strains' (lines). The code below works BUT When I use "capsize" to add caps it throws an error...
I want the caps to have the same color as lines (from color vector "c"), any way? Thanks!
The file is https://anonfiles.com/d8A7m4F5o0/mutdp_csv
muts = pd.read_csv('mutdp.csv')
#SUBSET
# Select rows (i.e. 1 to 28)
gr=muts[1:28]
fig, ax = plt.subplots(figsize=(12,9.9))
c=['b','y','r','g']
#Data GR ---------------------------------------------------------------------------------------------
grstrain=gr.groupby(['time','strain']).mean()['numbers'].unstack()
grstrain.plot.line(ax=ax, style=['-o','-o','-o','-o'],color=c, ls = '--', linewidth=2.7)
# Error (-----HERE is where "capsize" causes the error----)
ax.errorbar(gr.time, gr.numbers, yerr=gr.sd, ls='', color=[i for i in c for _i in range(7)], capsize=3, capthick=3)
#(SCALE)
plt.yscale('log')
plt.ylim(0.04, 3)
#SAVE FIG!
plt.show()
As ax.errorbar only accepts one fixed color, it could be called in a loop, once for each color. The following code creates some random data to show how the loop could be written:
from matplotlib import pyplot as plt
import matplotlib
import numpy as np
import pandas as pd
gr = pd.DataFrame({'time': np.tile(range(0, 14, 2), 4),
'strain': np.repeat(['mc', 'mut1', 'mut2', 'mut3'], 7),
'numbers': 0.1 + np.random.uniform(-0.01, 0.06, 28).cumsum(),
'sd': np.random.uniform(0.01, 0.05, 28)})
fig, ax = plt.subplots(figsize=(12, 9.9))
colors = ['b', 'y', 'r', 'g']
grstrain = gr.groupby(['time', 'strain']).mean()['numbers'].unstack()
grstrain.plot.line(ax=ax, style=['-o', '-o', '-o', '-o'], color=colors, ls='--', linewidth=2.7)
for strain, color in zip(np.unique(gr.strain), colors):
grs = gr[gr.strain == strain]
ax.errorbar(grs.time, grs.numbers, yerr=grs.sd, ls='', color=color, capsize=3, capthick=3)
plt.yscale('log')
plt.ylim(0.04, 3)
plt.show()

How to move end plot on the first row downwards

I'm trying to move the radar plot on the end of the first row downwards so it's middle way between the first and second row. I have no idea where to even start to attempt this. I've added the desired location of the plot.
Some code to reproduce the problem is here:
import math
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
def make_spider(row, title, color, ax=None):
categories=list(radar_dfs)
N = len(categories)
angles = np.arange(N+1)/N*2*np.pi
values=radar_dfs.iloc[row].values.flatten().tolist()
values += values[:1]
ax.plot(angles, values, color=color,linewidth=2, linestyle='solid')
ax.fill(angles, values, color=color, alpha=0.4)
radar_dfs = pd.DataFrame.from_dict({"no_rooms":{"0":-0.3470532925,"1":-0.082144001,"2":-0.082144001,"3":-0.3470532925,"4":-0.3470532925},"total_area":{"0":-0.1858487321,"1":-0.1685491141,"2":-0.1632483955,"3":-0.1769700284,"4":-0.0389887094},"car_park_spaces":{"0":-0.073703681,"1":-0.073703681,"2":-0.073703681,"3":-0.073703681,"4":-0.073703681},"house_price":{"0":-0.2416123064,"1":-0.2841806825,"2":-0.259622004,"3":-0.3529449824,"4":-0.3414842657},"pop_density":{"0":-0.1271390651,"1":-0.3105853643,"2":-0.2316607937,"3":-0.3297832328,"4":-0.4599021194},"business_rate":{"0":-0.1662745006,"1":-0.1426329043,"2":-0.1577528867,"3":-0.163560133,"4":-0.1099718326},"noqual_pc":{"0":-0.0251535462,"1":-0.1540641646,"2":-0.0204666924,"3":-0.0515740013,"4":-0.0445135996},"level4qual_pc":{"0":-0.0826103951,"1":-0.1777759951,"2":-0.114263357,"3":-0.1787044751,"4":-0.2709496389},"badhealth_pc":{"0":-0.105481688,"1":-0.1760349683,"2":-0.128215043,"3":-0.1560577648,"4":-0.1760349683}})
fig, axes = plt.subplots(2, 3, subplot_kw=dict(polar=True), sharey=True,
figsize=(28,20))
palette =['#79BD9A','#69D2E7','#F38630', '#547980','#EDC951']
labels = ['A', 'B', 'C', 'D', 'E']
row_one = axes.flatten()
for row, (ax, letter, col) in enumerate(zip(row_one, labels, palette)):
make_spider( row = row, title='Group ' + str(letter), color=col, ax=ax)
fig.delaxes(axes[1][2])
plt.subplots_adjust(wspace=.4, hspace=.3)
plt.show()
Does anybody have any advice?
One way to do so would be to create each subplot with plt.subplot2grid() and locating them manually. A trick that can be used is to double the number of rows and the height of each chart, which will allow to tune the location more finely.
import math
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
def make_spider(row, title, color, ax=None):
categories=list(radar_dfs)
N = len(categories)
angles = np.arange(N+1)/N*2*np.pi
values=radar_dfs.iloc[row].values.flatten().tolist()
values += values[:1]
ax.plot(angles, values, color=color,linewidth=2, linestyle='solid')
ax.fill(angles, values, color=color, alpha=0.4)
radar_dfs = pd.DataFrame.from_dict({"no_rooms":{"0":-0.3470532925,"1":-0.082144001,"2":-0.082144001,"3":-0.3470532925,"4":-0.3470532925},"total_area":{"0":-0.1858487321,"1":-0.1685491141,"2":-0.1632483955,"3":-0.1769700284,"4":-0.0389887094},"car_park_spaces":{"0":-0.073703681,"1":-0.073703681,"2":-0.073703681,"3":-0.073703681,"4":-0.073703681},"house_price":{"0":-0.2416123064,"1":-0.2841806825,"2":-0.259622004,"3":-0.3529449824,"4":-0.3414842657},"pop_density":{"0":-0.1271390651,"1":-0.3105853643,"2":-0.2316607937,"3":-0.3297832328,"4":-0.4599021194},"business_rate":{"0":-0.1662745006,"1":-0.1426329043,"2":-0.1577528867,"3":-0.163560133,"4":-0.1099718326},"noqual_pc":{"0":-0.0251535462,"1":-0.1540641646,"2":-0.0204666924,"3":-0.0515740013,"4":-0.0445135996},"level4qual_pc":{"0":-0.0826103951,"1":-0.1777759951,"2":-0.114263357,"3":-0.1787044751,"4":-0.2709496389},"badhealth_pc":{"0":-0.105481688,"1":-0.1760349683,"2":-0.128215043,"3":-0.1560577648,"4":-0.1760349683}})
palette =['#79BD9A','#69D2E7','#F38630', '#547980','#EDC951']
labels = ['A', 'B', 'C', 'D', 'E']
fig = plt.figure(figsize=(28,20))
position = [[0,0], [0,1], [1,2], [2,0], [2,1]]
axes = []
for row, (letter, col) in enumerate(zip(labels, palette)):
ax = plt.subplot2grid([4,3], position[row], rowspan=2, colspan=1, **{'polar': True}, sharey=axes[0] if row else None)
axes.append(ax)
make_spider( row = row, title='Group ' + str(letter), color=col, ax=ax)
plt.subplots_adjust(wspace=.4, hspace=.3)
plt.show()
With this solution, sharey has to be set manually. Maybe a cleaner way to do so would be to use ax.get_shared_y_axes().join(*axes) (check here).
Hope it helped.

Scatter plots in Pandas: Plot by category with different color and shape combinations

I would like to plot a data-set by its categories, using geometric shapes such as circle, triangle and square to represent category 1 and colors to represent category 2. The output would have varying combination of the geometric shapes and colors and the legend would list the attributes of the categories separately i.e.:
circle = a
triangle = b square = c
red = I
green = II
blue = III
Looking for solutions I found following posts which would only give solutions for one specific geometric shape having one specific color.
How to plot by category with different markers
How to plot by category
I tried to work something out with the code from one of the posts but without success.
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
np.random.seed(1983)
num = 10
x, y = np.random.random((2, num))
cat1 = np.random.choice(['a', 'b', 'c'], num)
cat2 = np.random.choice(['I', 'II', 'III'], num)
df = pd.DataFrame(dict(x=x, y=y, cat1=cat1, cat2=cat2))
groups = df.groupby(['cat1', 'cat2'])
fig, ax = plt.subplots()
for name, group in groups:
ax.plot(group.x, group.y, marker='o', linestyle='', ms=12, label=name)
ax.legend()
plt.show()
you can try this code block
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
#Create mapping dictionary that you want
marker_dict = {'a':'o','b':'^','c':'s'}
color_dict = {'I':'red', 'II':'green', 'III':'blue'}
np.random.seed(1983)
num = 10
x, y = np.random.random((2, num))
cat1 = np.random.choice(['a', 'b', 'c'], num)
cat2 = np.random.choice(['I', 'II', 'III'], num)
df = pd.DataFrame(dict(x=x, y=y, cat1=cat1, cat2=cat2))
groups = df.groupby(['cat1', 'cat2'])
fig, ax = plt.subplots()
ax.margins(0.05)
for name, group in groups:
marker = marker_dict[name[0]]
color = color_dict[name[1]]
ax.plot(group.x, group.y, marker=marker, linestyle='', ms=12, label=name,color=color)
ax.legend()
plt.show()
Hope it helps.

How to add colorbars to scatterplots created like this?

I create scatterplots with code that, in essence, goes like this
cmap = (matplotlib.color.LinearSegmentedColormap.
from_list('blueWhiteRed', ['blue', 'white', 'red']))
fig = matplotlib.figure.Figure(figsize=(4, 4), dpi=72)
ax = fig.gca()
for record in data:
level = record.level # a float in [0.0, 1.0]
marker = record.marker # one of 'o', 's', '^', '*', etc.
ax.scatter(record.x, record.y, marker=marker,
c=level, vmin=0, vmax=1, cmap=cmap, **otherkwargs)
# various settings of ticks, labels, etc. omitted
canvas = matplotlib.backends.backend_agg.FigureCanvasAgg(fig)
fig.set_canvas(canvas)
canvas.print_png('/path/to/output/fig.png')
My question is this:
What do I need add to the code above to get a vertical colorbar (representing the colormap in cmap) along the plot's right edge?
NOTE: I find Matplotlib utterly incomprehensible, and this goes for both its design as well as its documentation. (Not for lack of trying: I have putting a lot of time, effort, and even some money, into it.) So I would appreciate complete, working code (even if it's just a toy example), because most likely I won't be able to fill in omitted details or fix bugs in the code.
EDIT: I fixed an important omission in the "code sketch" above, namely a record-specific marker specification in each call to ax.scatter. This is the reason for creating the scatterplot with multiple calls to ax.scatter, although, admittedly, one could at least reduce the number of calls to scatter to one per maker shape used; e.g.
for marker in set(record.marker for record in data):
X, Y, COLOR = zip(*((record.x, record.y, record.level)
for record in data if record.marker == marker))
ax.scatter(X, Y, marker=marker,
c=COLOR, vmin=0, vmax=1, cmap=cmap,
**otherkwargs)
I tried to extend the same trick to collapse all calls to ax.scatter into one (by passing a sequence of markers as the marker argument), like this:
X, Y, COLOR, MARKER = zip(*((record.x, record.y, record.level, record.marker)
for record in data))
ax.scatter(X, Y, marker=MARKER,
c=COLOR, vmin=0, vmax=1, cmap=cmap,
**otherkwargs)
...but this fails. The error goes something like this (after pruning some long paths):
Traceback (most recent call last):
File "src/demo.py", line 222, in <module>
main()
File "src/demo.py", line 91, in main
**otherkwargs)
File "<abbreviated-path>/matplotlib/axes.py", line 6100, in scatter
marker_obj = mmarkers.MarkerStyle(marker)
File "<abbreviated-path>/matplotlib/markers.py", line 113, in __init__
self.set_marker(marker)
File "<abbreviated-path>/matplotlib/markers.py", line 179, in set_marker
raise ValueError('Unrecognized marker style {}'.format(marker))
ValueError: Unrecognized marker style ('^', 'o', '^', '*', 'o', 's', 'o', 'o', '^', 's', 'o', 'o', '^', '^', '*', 'o', '*', '*', 's', 's', 'o', 's', 'o', '^', 'o', 'o', '*', '^', 's', '^', '^', 's', '*')
AFAICT, tcaswell's recipe requires reducing the calls to ax.scatter to a single one, but this requirement appears to conflict with my absolute requirement for multiple marker shapes in the same scatterplot.
If you have to use a different marker for each set, you have to do a bit of extra work and force all of the clims to be the same (otherwise they default to scaling from the min/max of the c data per scatter plot).
from pylab import *
import matplotlib.lines as mlines
import itertools
fig = gcf()
ax = fig.gca()
# make some temorary arrays
X = []
Y = []
C = []
cb = None
# generate fake data
markers = ['','o','*','^','v']
cmin = 0
cmax = 1
for record,marker in itertools.izip(range(5),itertools.cycle(mlines.Line2D.filled_markers)):
    x = rand(50)
    y = rand(50)
    c = rand(1)[0] * np.ones(x.shape)
    if cb is None:
        s = ax.scatter(x,y,c=c,marker=markers[record],linewidths=0)
        s.set_clim([cmin,cmax])
        cb = fig.colorbar(s)
    else:
        s = ax.scatter(x,y,c=c,marker=markers[record],linewidths=0)
        s.set_clim([cmin,cmax])
cb.set_label('Cbar Label Here')
thelinewidths=0 sets the width of the border on the shapes, I find that for small shapes the black border can overwhelm the color of the fill.
If you only need one shape you can do this all with a single scatter plot, there is no need to make a separate one for each pass through your loop.
from pylab import *
fig = gcf()
ax = fig.gca()
# make some temorary arrays
X = []
Y = []
C = []
# generate fake data
for record in range(5):
x = rand(50)
y = rand(50)
c = rand(1)[0] * np.ones(x.shape)
print c
X.append(x)
Y.append(y)
C.append(c)
X = np.hstack(X)
Y = np.hstack(Y)
C = np.hstack(C)
once you have the data all beaten down into a 1D array, make the scatter plot, and keep the returned value:
s = ax.scatter(X,Y,c=C)
You then make your color bar and pass the object returned by scatter as the first argument.
cb = plt.colorbar(s)
cb.set_label('Cbar Label Here')
You need do this so that the color bar knows which color map (both the map and the range) to use.
I think your best bet will be to stuff your data into a pandas dataframe, and loop through all of your markers like so:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
markers = ['s', 'o', '^']
records = []
for n in range(37):
records.append([np.random.normal(), np.random.normal(), np.random.normal(),
markers[np.random.randint(0, high=3)]])
records = pd.DataFrame(records, columns=['x', 'y', 'z', 'marker'])
fig, ax = plt.subplots()
for m in np.unique(records.marker):
selector = records.marker == m
s = ax.scatter(records[selector].x, records[selector].y, c=records[selector].z,
marker=m, cmap=plt.cm.coolwarm,
vmin=records.z.min(), vmax=records.z.max())
cbar = plt.colorbar(mappable=s, ax=ax)
cbar.set_label('My Label')
I think this should do the trick. I'm pretty sure I grabbed this from one of the matplotlib cookbook examples a while back, but I can't seem to find it now...
from mpl_toolkits.axes_grid1 import make_axes_locatable
cmap = (matplotlib.color.LinearSegmentedColormap.
from_list('blueWhiteRed', ['blue', 'white', 'red']))
fig = matplotlib.figure.Figure(figsize=(4, 4), dpi=72)
ax = fig.gca()
for record in data:
level = record.level # a float in [0.0, 1.0]
ax.scatter(record.x, record.y,
c=level, vmin=0, vmax=1, cmap=cmap, **otherkwargs)
# various settings of ticks, labels, etc. omitted
divider= make_axes_locatable(ax)
cax = divider.append_axes("right", size="1%", pad=0.05)
cb = plt.colorbar(cax=cax)
cb.set_label('Cbar Label Here')
canvas = matplotlib.backends.backend_agg.FigureCanvasAgg(fig)
fig.set_canvas(canvas)
canvas.print_png('/path/to/output/fig.png')
The answer to this can be to only plot a single scatter, which would then directly allow for a colobar to be created.
This involves putting the markers into the PathCollection created by the scatter a posteriori, but it can be easily placed in a function. This function comes from my answer on another question, but is directly applicable here.
Taking the data from #PaulH's post this would look like
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
def mscatter(x,y,ax=None, m=None, **kw):
import matplotlib.markers as mmarkers
ax = ax or plt.gca()
sc = ax.scatter(x,y,**kw)
if (m is not None) and (len(m)==len(x)):
paths = []
for marker in m:
if isinstance(marker, mmarkers.MarkerStyle):
marker_obj = marker
else:
marker_obj = mmarkers.MarkerStyle(marker)
path = marker_obj.get_path().transformed(
marker_obj.get_transform())
paths.append(path)
sc.set_paths(paths)
return sc
markers = ['s', 'o', '^']
records = []
for n in range(37):
records.append([np.random.normal(), np.random.normal(), np.random.normal(),
markers[np.random.randint(0, high=3)]])
records = pd.DataFrame(records, columns=['x', 'y', 'z', 'marker'])
fig, ax = plt.subplots()
sc = mscatter(records.x, records.y, c=records.z, m=records.marker, ax=ax)
fig.colorbar(sc, ax=ax)
plt.show()
This could be also a pretty easy solution.
fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(8,8))
p = ax.scatter(x, y, c=y, cmap='cmo.deep')
fig.colorbar(p,ax=ax,orientation='vertical',label='labelname')

Categories