Add a legend in a 3D scatterplot with scatter() in Matplotlib - python

I want to create a 3D scatterplot with different datasets in the same plot and a legend with their labels. The problem I am facing is that I cannot properly add the legend and I get a plot with an empty label as the figure in:
http://tinypic.com/view.php?pic=4jnm83&s=5#.Uqd-05GP-gQ.
More specifically, I get the error:
/usr/lib/pymodules/python2.7/matplotlib/legend.py:610: UserWarning: Legend does not support <mpl_toolkits.mplot3d.art3d.Patch3DCollection object at 0x3bf46d0>
Use proxy artist instead."
Please find below an example demo of what I have tried so far:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import random
import csv
from os import listdir
from os.path import isfile, join
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
handles = []
colors = ['blue', 'red']
X1 = range(0,10)
Y1 = range(0,10)
Z1 = range(0,10)
random.shuffle(X1)
random.shuffle(Y1)
random.shuffle(Z1)
scatter1 = ax.scatter(X1, Y1, Z1, c = colors[0], marker = 'o')
random.shuffle(X1)
random.shuffle(Y1)
random.shuffle(Z1)
scatter2 = ax.scatter(X1, Y1, Z1, c = colors[1], marker = 'v')
ax.set_xlabel('X', fontsize = 10)
ax.set_ylabel('Y', fontsize = 10)
ax.set_zlabel('Z', fontsize = 10)
ax.legend([scatter1, scatter2], ['label1', 'label2'])
plt.show()
I have seen other roughly similar examples but none of them uses the scatter() plot. Apart from a working solution, can someone explain what am I doing wrong?

scatter1_proxy = matplotlib.lines.Line2D([0],[0], linestyle="none", c=colors[0], marker = 'o')
scatter2_proxy = matplotlib.lines.Line2D([0],[0], linestyle="none", c=colors[1], marker = 'v')
ax.legend([scatter1_proxy, scatter2_proxy], ['label1', 'label2'], numpoints = 1)
The problem is that the legend function don't support the type returned by a 3D scatter. So you have to create a "dummy plot" with the same characteristics and put those in the legend.
numpoints = 1 to get only one dot in the legend
linestyle= "none" So there is no line drawn in the legend

Related

Matplotlib : Horizontal Bar Plot with Color Range

Please forgive the crude explanation but I'm unsure how to describe the issue and as they say, a picture says a thousand words, so what I am trying to achieve is to draw a graph in matplotlib that looks like the below:
whereby the scale of the color range is the same across all bars as the x limits of the x-axis.
The closest I have got to so far is this (please ignore the fact it's not horizontal - I was planning on editing that once I had figured out the coloring):
fig, ax = plt.subplots()
mpl.pyplot.viridis()
bars = ax.bar(df['Profile'], df['noise_result'])
grad = np.atleast_2d(np.linspace(0,1,256)).T
ax = bars[0].axes
lim = ax.get_xlim()+ax.get_ylim()
for bar in bars:
bar.set_zorder(1)
bar.set_facecolor('none')
x,y = bar.get_xy()
w, h = bar.get_width(), bar.get_height()
ax.imshow(grad, extent=[x,x+w,y,y+h], aspect='auto', zorder=1,interpolation='nearest')
ax.axis(lim)
which only results in a graph like below:
Many thanks
I'm going along with your approach. The idea is to:
choose an appropriate colormap
create a normalizer for the bar values.
create a mappable which is going to map the normalized values to the colormap in order to create a colorbar.
import matplotlib.pyplot as plt
import matplotlib.cm as cm
from matplotlib.colors import Normalize
import pandas as pd
import numpy as np
df = pd.DataFrame({'key':['A', 'B', 'C', 'D', 'E'], 'val':[100, 20, 70, 40, 100]})
# create a normalizer
norm = Normalize(vmin=df['val'].min(), vmax=df['val'].max())
# choose a colormap
cmap = cm.plasma
# map values to a colorbar
mappable = cm.ScalarMappable(norm=norm, cmap=cmap)
mappable.set_array(df['val'])
fig, ax = plt.subplots()
bars = ax.bar(df['key'], df['val'])
ax = bars[0].axes
lim = ax.get_xlim()+ax.get_ylim()
for bar, val in zip(bars, df['val']):
grad = np.atleast_2d(np.linspace(0,val,256)).T
bar.set_zorder(1)
bar.set_facecolor('none')
x, y = bar.get_xy()
w, h = bar.get_width(), bar.get_height()
ax.imshow(np.flip(grad), extent=[x,x+w,y,y+h], aspect='auto', zorder=1,interpolation='nearest', cmap=cmap, norm=norm)
ax.axis(lim)
cb = fig.colorbar(mappable)
cb.set_label("Values")
Using what you have, you could change line 12 to:
ax.imshow(grad, extent=[x,x+w,y,y+h], aspect='auto', zorder=1, cmap = plt.get_cmap('gist_heat_r'))
or some other color map from:
https://matplotlib.org/stable/tutorials/colors/colormaps.html
You could also change line 3 to start as:
bars = ax.barh
for horizontal bars.

How to specify coordinates of a point in a matplotlib plot?

How can I make that when I plot a function (based on a np.array) certain values have their coordinates in the plot?
I know how to change color and other little things with code lines like:
line1, = plt.plot(t, f, '*-', label='force', color='#4F81BD') # blue
line2, = plt.plot(t, a, 'o-', label='acceleration', color='#C0504D') # red
but for example if I have a "peak" in the plot line, I don't know how to make their coordinates to appear in the same plot
This code snippet might help you:
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111)
x=[1,2,3,4,5,6,7,8,9,10]
y=[1,1,1,2,10,2,1,1,1,1]
line, = ax.plot(x, y)
ymax = max(y)
xpos = y.index(ymax)
xmax = x[xpos]
#Labeling the graph (ymax+1 is defining the distance from the word to the point)
ax.annotate('local max', xy=(xmax, ymax), xytext=(xmax, ymax+1))
ax.set_ylim(0,20)
plt.show()
Output:
I hope I could help you out a bit.

Secondary y-axis in sympy

Sympy plotting has recently evolved a lot, but I cannot find a way to plot a second expression on a secondary y-axis. Below, I show an example where I combine two plots, but would like the second to use a secondary axis:
>>> from sympy import symbols
>>> from sympy.plotting import plot
>>> x = symbols('x')
>>> p1 = plot(x**4, (x,10,20), label='$x^4$', show=False, legend=True)
>>> p2 = plot(x, (x,10,20), line_color='red', label='$x$', show=False, legend= True)
>>> p1.extend(p2)
>>> p1.show()
Incidentally, is there a way to also modify the line style, e.g. make one of them dashed? The documentation suggests to use the _backend module for fine tuning, but it is not clear to me how to achieve this. See e.g. How do I use the `_backend` attribute of a Sympy `plot`.
The approach from this post can be adapted as follows. Note that sympy puts the spines at the zero-positions, which is confusing with two axes in the same plot. They can be moved again to their original position. Matplotlib can create a combined legend from the handles and labels of both axes.
from sympy import Symbol, plot, sin
import matplotlib.pyplot as plt
def move_sympyplot_to_axes(p, ax, is_twinx):
backend = p.backend(p)
backend.ax = ax
backend._process_series(backend.parent._series, ax, backend.parent)
if is_twinx:
backend.ax.spines['left'].set_color('none')
else:
backend.ax.spines['right'].set_color('none')
backend.ax.spines['left'].set_position(('axes', 0))
backend.ax.spines['bottom'].set_position(('axes', 0))
plt.close(backend.fig)
x = Symbol('x')
p1 = plot(x ** 4, (x, 10, 20), label='$x^4$', show=False)
p2 = plot(sin(10*x)/x**2, (x, 10, 20), adaptive=False, nb_of_points=500,
line_color='red', label='$sin(10x)/x^2$', show=False)
fig, ax = plt.subplots()
ax2 = ax.twinx()
move_sympyplot_to_axes(p1, ax, is_twinx=False)
move_sympyplot_to_axes(p2, ax2, is_twinx=True)
ax2.tick_params(axis='y', colors='red')
ax2.set_ylabel('', color='red')
handles1, labels1 = ax.get_legend_handles_labels()
handles2, labels2 = ax2.get_legend_handles_labels()
plt.legend(handles1 + handles2, labels1 + labels2, loc='upper center')
plt.tight_layout()
plt.show()

Seaborn plot with second y axis

i wanted to know how to make a plot with two y-axis so that my plot that looks like this :
to something more like this by adding another y-axis :
i'm only using this line of code from my plot in order to get the top 10 EngineVersions from my data frame :
sns.countplot(x='EngineVersion', data=train, order=train.EngineVersion.value_counts().iloc[:10].index);
I think you are looking for something like:
import matplotlib.pyplot as plt
x = [1,2,3,4,5]
y = [1000,2000,500,8000,3000]
y1 = [1050,3000,2000,4000,6000]
fig, ax1 = plt.subplots()
ax2 = ax1.twinx()
ax1.bar(x, y)
ax2.plot(x, y1, 'o-', color="red" )
ax1.set_xlabel('X data')
ax1.set_ylabel('Counts', color='g')
ax2.set_ylabel('Detection Rates', color='b')
plt.show()
Output:
#gdubs If you want to do this with Seaborn's library, this code set up worked for me. Instead of setting the ax assignment "outside" of the plot function in matplotlib, you do it "inside" of the plot function in Seaborn, where ax is the variable that stores the plot.
import seaborn as sns # Calls in seaborn
# These lines generate the data to be plotted
x = [1,2,3,4,5]
y = [1000,2000,500,8000,3000]
y1 = [1050,3000,2000,4000,6000]
fig, ax1 = plt.subplots() # initializes figure and plots
ax2 = ax1.twinx() # applies twinx to ax2, which is the second y axis.
sns.barplot(x = x, y = y, ax = ax1, color = 'blue') # plots the first set of data, and sets it to ax1.
sns.lineplot(x = x, y = y1, marker = 'o', color = 'red', ax = ax2) # plots the second set, and sets to ax2.
# these lines add the annotations for the plot.
ax1.set_xlabel('X data')
ax1.set_ylabel('Counts', color='g')
ax2.set_ylabel('Detection Rates', color='b')
plt.show(); # shows the plot.
Output:
Seaborn output example
You could try this code to obtain a very similar image to what you originally wanted.
import seaborn as sb
from matplotlib.lines import Line2D
from matplotlib.patches import Rectangle
x = ['1.1','1.2','1.2.1','2.0','2.1(beta)']
y = [1000,2000,500,8000,3000]
y1 = [3,4,1,8,5]
g = sb.barplot(x=x, y=y, color='blue')
g2 = sb.lineplot(x=range(len(x)), y=y1, color='orange', marker='o', ax=g.axes.twinx())
g.set_xticklabels(g.get_xticklabels(), rotation=-30)
g.set_xlabel('EngineVersion')
g.set_ylabel('Counts')
g2.set_ylabel('Detections rate')
g.legend(handles=[Rectangle((0,0), 0, 0, color='blue', label='Nontouch device counts'), Line2D([], [], marker='o', color='orange', label='Detections rate for nontouch devices')], loc=(1.1,0.8))

implement alpha for custom colormap

I need to plot a collection of patches using a custom colormap. I have gotten this working fine, however I can't use alpha properly with my custom colormap. As you can see in the images and attached code, the alpha is applied to the patches, but the colorbar still shows with alpha=1 making the map "wrong". To check, I tested the with jet, and the colorbar reflects the proper alpha. I am assuming something is missing in the way I defined my colorbar, but the documentation has not been helpful in figuring out what...
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
x = np.random.rand(50,1)*500; y = np.random.rand(50,1)*500;
radius = np.random.rand(50,1)*50
patches = []
for i in range(len(radius)):
circle = matplotlib.patches.Circle((x[i], y[i]), radius[i])
patches.append(circle)
fig, ax = plt.subplots()
colors = 100*np.random.rand(len(patches))
p = matplotlib.collections.PatchCollection(patches)
p.set(array = colors, cmap = 'Spectral', alpha=0.5)
ax.add_collection(p)
plt.colorbar(p, alpha=0.5)
plt.xlim(0,500);plt.ylim(0,500);
plt.gca().set_aspect(1)
plt.show()
############
r = np.hstack((np.zeros(425),np.linspace(0,255,430), np.linspace(254,0,425)))
g = np.hstack((np.linspace(0,255,430), np.linspace(254,0,425), np.zeros(425)))
b = np.hstack((np.linspace(255,0,430), np.zeros(425),np.linspace(0,254,425)))
c = np.array([r,g,b]).T
mycm = matplotlib.colors.ListedColormap(c/255.0)
###########
x = np.random.rand(50,1)*500; y = np.random.rand(50,1)*500;
radius = np.random.rand(50,1)*50
patches = []
for i in range(len(radius)):
circle = matplotlib.patches.Circle((x[i], y[i]), radius[i])
patches.append(circle)
fig, ax = plt.subplots()
colors = 100*np.random.rand(len(patches))
p = matplotlib.collections.PatchCollection(patches)
p.set(array = colors, cmap = mycm, alpha=0.5)
ax.add_collection(p)
plt.colorbar(p, alpha=0.5)
plt.xlim(0,500);plt.ylim(0,500);
plt.gca().set_aspect(1)
plt.show()
As can be seen in the first case picture, matplotlib adds small lines between the colors in the colorbar. Those lines come from the pcolormesh that is used to produce the colorbar. I have no idea why they are there, nor would I know how to get rid of them, but those lines are the reason for the problem of the alpha value not being shown correctly.
As they appear a bit darker then the real color shown, it is clear that if one adds more and more lines to the colorbar, the complete colorbar will be composed of those lines and thus appear darker or less transparent.
This is exactly what is done in the second case, where a ListedColormap with 430+425+425 = 1280 values is used.
The solution would therefore be to reduce the number of colors in the ListedColormap to a value below 255. In the example code below I used 90.
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
############
n= 30
r = np.hstack((np.zeros(n),np.linspace(0,255,n), np.linspace(254,0,n)))
g = np.hstack((np.linspace(0,255,n), np.linspace(254,0,n), np.zeros(n)))
b = np.hstack((np.linspace(255,0,n), np.zeros(n),np.linspace(0,254,n)))
c = np.array([r,g,b]).T
mycm = matplotlib.colors.ListedColormap(c/255.0)
###########
x = np.random.rand(50,1)*500; y = np.random.rand(50,1)*500;
radius = np.random.rand(50,1)*50
patches = []
for i in range(len(radius)):
circle = matplotlib.patches.Circle((x[i], y[i]), radius[i])
patches.append(circle)
fig, ax = plt.subplots()
colors = 100*np.random.rand(len(patches))
p = matplotlib.collections.PatchCollection(patches)
p.set(array = colors, cmap = mycm, alpha=0.5)
ax.add_collection(p)
plt.colorbar(p, alpha=0.5)
plt.xlim(0,500);plt.ylim(0,500);
plt.gca().set_aspect(1)
plt.savefig(__file__+".png")
plt.show()

Categories

Resources