One legend for all subplots in pyplot - python

I am currently plotting the same data but visualizing it differently in two subplots (see figure):
Code snippet used for producing the above figure:
# Figure
plt.figure(figsize=(14,8), dpi=72)
plt.gcf().suptitle(r'Difference between TI and $\lambda$D', size=16)
# Subplot 1
ax1 = plt.subplot2grid((1,3),(0,0),colspan=2)
# Plot scattered data in first subplot
plt.scatter(LE_x, LE_y, s=40, lw=0, color='gold', marker='o', label=r'$\lambda$D')
plt.scatter(MD_x, MD_y, s=40, lw=0, color='blue', marker='^', label=r'TI')
# Subplot 2
ax2 = plt.subplot2grid((1,3),(0,2))
plt.barh(vpos1, LE_hist, height=4, color='gold', label=r'$\lambda$D')
plt.barh(vpos2, MD_hist, height=4, color='blue', label=r'TI')
# Legend
legend = plt.legend()
Is there any way to make the legend show both the scatter dots and the bars? Would this also go per dummy as described here? Could somebody then please post a minimal working example for this, since I'm not able to wrap my head around this.

This worked for me, you essentially capture the patch handles for each graph plotted and manually create a legend at the end.
import pylab as plt
import numpy as NP
plt.figure(figsize=(14,8), dpi=72)
plt.gcf().suptitle(r'Difference between TI and $\lambda$D', size=16)
# Subplot 1
ax1 = plt.subplot2grid((1,3),(0,0),colspan=2)
N = 100
LE_x = NP.random.rand(N)
LE_y = NP.random.rand(N)
MD_x = NP.random.rand(N)
MD_y = NP.random.rand(N)
# Plot scattered data in first subplot
s1 = plt.scatter(LE_x, LE_y, s=40, lw=0, color='gold', marker='o', label=r'$\lambda$D')
s2 = plt.scatter(MD_x, MD_y, s=40, lw=0, color='blue', marker='^', label=r'TI')
data = NP.random.randn(1000)
LE_hist, bins2 = NP.histogram(data, 50)
data = NP.random.randn(1000)
MD_hist, bins2 = NP.histogram(data, 50)
# Subplot 2
ax2 = plt.subplot2grid((1,3),(0,2))
vpos1 = NP.arange(0, len(LE_hist))
vpos2 = NP.arange(0, len(MD_hist)) + 0.5
h1 = plt.barh(vpos1, LE_hist, height=0.5, color='gold', label=r'$\lambda$D')
h2 = plt.barh(vpos2, MD_hist, height=0.5, color='blue', label=r'TI')
# Legend
#legend = plt.legend()
lgd = plt.legend((s1, s2, h1, h2), (r'$\lambda$D', r'TI', r'$\lambda$D', r'TI'), loc='upper center')
plt.show()

Related

Python 3d scatter plot legend for colors shows only first color

I want to create a 3D scatter plot with legends for the sizes and the colors. However, the legend for the colors only shows the first color in the list.
import matplotlib.pyplot as plt
import matplotlib.colors
# Visualizing 5-D mix data using bubble charts
# leveraging the concepts of hue, size and depth
fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection='3d')
t = fig.suptitle('Wine Residual Sugar - Alcohol Content - Acidity - Total Sulfur Dioxide - Type', fontsize=14)
xs = [1,2,3,5,4]
ys = [6,7,3,5,4]
zs = [1,5,3,9,4]
data_points = [(x, y, z) for x, y, z in zip(xs, ys, zs)]
ss = [100,200,390,500,400]
colors = ['red','red','blue','yellow','yellow']
scatter = ax.scatter(xs, ys, zs, alpha=0.4, c=colors, s=ss)
ax.set_xlabel('Residual Sugar')
ax.set_ylabel('Alcohol')
ax.set_zlabel('Fixed Acidity')
legend1 = ax.legend(*scatter.legend_elements()[0],
loc="upper right", title="Classes", labels=colors, bbox_to_anchor=(1.5, 1),prop={'size': 20})
ax.add_artist(legend1)
# produce a legend with a cross section of sizes from the scatter
handles, labels = scatter.legend_elements(prop="sizes", alpha=0.6)
legend2 = ax.legend(handles, labels, loc="upper right", title="Sizes", bbox_to_anchor=(1.5, 0.5), prop={'size': 20})
The issue might result from the fact that matplotlib only receives one series to plot and thus assumes that one legend entry suffices. If I make scatter plots of the red, blue and yellow series individually, then all three classes are displayed correctly in the legend (but it causes issues when plotting the legend with sizes).
It's perhaps not the most elegant solution, but the legend with classes can be created manually:
import matplotlib.pyplot as plt
import matplotlib.colors
from matplotlib.lines import Line2D
# Visualizing 5-D mix data using bubble charts
# leveraging the concepts of hue, size and depth
fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection='3d')
t = fig.suptitle('Wine Residual Sugar - Alcohol Content - Acidity - Total Sulfur Dioxide - Type', fontsize=14)
xs = [1,2,3,5,4]
ys = [6,7,3,5,4]
zs = [1,5,3,9,4]
data_points = [(x, y, z) for x, y, z in zip(xs, ys, zs)]
ss = [100,200,390,500,400]
colors = ['red','red','blue','yellow','yellow']
scatter = ax.scatter(xs, ys, zs, alpha=0.4, c=colors, s=ss)
ax.set_xlabel('Residual Sugar')
ax.set_ylabel('Alcohol')
ax.set_zlabel('Fixed Acidity')
# Create additional legend
UniqueColors = list(dict.fromkeys(colors))
Legend2Add = []
for color in UniqueColors:
Legend2Add.append( Line2D([0], [0], marker='o', color='w', label=color,
markerfacecolor=color, markersize=15, alpha=0.4) )
# Produce a legend with a cross section of sizes from the scatter
handles, labels = scatter.legend_elements(prop="sizes", alpha=0.6)
legend1 = ax.legend(handles,
loc="upper right", title="Classes", handles=Legend2Add, bbox_to_anchor=(1.5, 1),prop={'size': 20})
ax.add_artist(legend1)
legend2 = ax.legend(handles, labels, loc="upper right", title="Sizes", bbox_to_anchor=(1.5, 0.5), prop={'size': 20})
plt.show()

two barplots in one

I have two working barplots about the sentiments of tweets (neutral, positive, negative). How can I merge them into one, side by side?
First bar:
plt.figure(figsize=(6,5))
plt.title('Classification of All tweets into sentiment categories',fontsize=15)
plt.ylabel('Percentage [%]',fontsize=18)
ax = (df_navalny.sentiment.value_counts()/len(df_navalny)*100).plot(kind="bar", rot=0,color=['#04407F','#0656AC','#0A73E1'])
ax.set_yticks(np.arange(0, 110, 10))
plt.grid(color='#95a5a6', linestyle='-.', linewidth=1, axis='y', alpha=0.7)
ax2 = ax.twinx()
ax2.set_yticks(np.arange(0, 110, 10)*len(df_navalny)/100)
for p in ax.patches:
ax.annotate('{:.2f}%'.format(p.get_height()), (p.get_x()+0.15, p.get_height()+1))
Second bar:
plt.figure(figsize=(6,5))
plt.title('Classification of All tweets into sentiment categories',fontsize=15)
plt.ylabel('Percentage [%]',fontsize=18)
ax = (df_putin.sentiment.value_counts()/len(df_putin)*100).plot(kind="bar", rot=0,color=['#04407F','#0656AC','#0A73E1'])
ax.set_yticks(np.arange(0, 110, 10))
plt.grid(color='#95a5a6', linestyle='-.', linewidth=1, axis='y', alpha=0.7)
ax2 = ax.twinx()
ax2.set_yticks(np.arange(0, 110, 10)*len(df_putin)/100)
for p in ax.patches:
ax.annotate('{:.2f}%'.format(p.get_height()), (p.get_x()+0.15, p.get_height()+1))
It's a bit complicated but Matplotlib site offers a demo and when you copy and past you have the following
Here it is the code
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import host_subplot
from mpl_toolkits import axisartist
# fake data
a = [2341, 5670, 4822]
b = [4290, 5205, 3966]
pca, pcb = [[round(100*x/sum(l),2) for x in l] for l in (a, b)]
# prepare all the vertical axes
ax = host_subplot(111, axes_class=axisartist.Axes)
plt.subplots_adjust(right=0.67)
axa = ax.twinx() ; axb = ax.twinx()
axb.axis['right'] = axb.new_fixed_axis(loc="right", offset=(60, 0))
axa.axis['right'].toggle(all=True)
axb.axis['right'].toggle(all=True)
# plot the bars PLUS invisible lines to represent the samples numerosities
x, w = np.array((1,2,3)), 0.30
ax.bar(x-w, pca, width=w, align='edge', label='a', zorder=1)
ax.bar(x-0, pcb, width=w, align='edge', label='b', zorder=1)
axa.plot((1,1),(0,sum(a)),lw=0)
axb.plot((1,1),(0,sum(b)),lw=0)
# fix xtics, xlabels, 'regular' yticks
plt.xticks((1,2,3)) ; ax.set_xticklabels('NO == YES'.split())
ax.set_yticks(range(0, 101, 10))
# all the ylabels
ax.set_ylabel('Percentages')
axa.set_ylabel('Numerosity of a')
axb.set_ylabel('Numerosity of b')
axa.set_ylim(bottom=0.0)
axb.set_ylim(bottom=0.0)
plt.legend()
plt.grid(zorder=0)
plt.show()

How Can I space legend items with variable spacing and have legend marker colors reflect the colormap

I would like to have an increasing spacing between legend items instead of a single value (labelspacing). The latter only accepts an int value type, but I want a variable spacing between legend items. Also, I want the markerfacecolor to follow the colormap used when creating the scatter plot.
N = 45
x, y = np.random.rand(2, N)
s = np.random.randint(10, 1000, size=N)
fig, ax = plt.subplots()
scatter = ax.scatter(x, y, c=s, s=s)
cbar = fig.colorbar(scatter,
ax=ax,
label='Size',
fraction=0.1,
pad=0.04)
# produce a legend with a cross section of sizes from the scatter
handles, labels = scatter.legend_elements(prop="sizes", alpha=0.6)
for hd in handles:
hd.set_markeredgewidth(2)
hd.set_markeredgecolor("red")
hd.set_markerfacecolor('blue')
legend2 = ax.legend(
handles[::2], labels[::2], loc="upper right", title="Sizes", labelspacing=1.2
)
plt.show()
I searched StackOverflow and tried some possible methods but without success. Could someone guide how I can achieve the desired output?
I managed to set markerfacecolor as the colormap. But I am still struggling with the variable labelspacing!.
Any help!
N = 45
x, y = np.random.rand(2, N)
s = np.random.randint(10, 1000, size=N)
fig, ax = plt.subplots()
scatter = ax.scatter(x, y, c=s, s=s)
cbar = fig.colorbar(scatter,
ax=ax,
label='Size',
fraction=0.1,
pad=0.04)
# produce a legend with a cross section of sizes from the scatter
handles, labels = scatter.legend_elements(prop="sizes", alpha=0.6)
leg_colrs = [color.get_markerfacecolor() for color in scatter.legend_elements()[0]]
for hd, color in zip(handles, leg_colrs):
hd.set_markeredgewidth(2)
hd.set_markeredgecolor("red")
hd.set_markerfacecolor(color)
legend2 = ax.legend(
handles[::2], labels[::2], loc="upper right", title="Sizes", labelspacing=1.2
)
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))

multiple savefig() 's with different styles

I'm writing a program, which has two outputs: a GUI and a printed report on paper (a simple pdf printed out).
On both outputs I would like to have a diagram, but with different styles.
dark_background on the GUI (http://matplotlib.org/examples/style_sheets/plot_dark_background.html)
and fivethirtyeight on the paper http://matplotlib.org/examples/style_sheets/plot_fivethirtyeight.html
Somehow I could not manage to generate 2 images with proper styles. Only one of them was correct. I do not have enough experience-points to post pictures. So I will post only my code.
My first idea was:
import numpy as np
import matplotlib.pyplot as plt
def plot():
#set pic size
fig = plt.figure(figsize=(16, 9), dpi=100)
ax = plt.subplot(111)
# set x data
x = range(10)
# set y data
y1 = np.zeros(10)
y2 = [0,1,2,3,4,5,6,7,8,9]
y3 = [10,9,8,7,6,5,4,3,2,1]
#plot as errorbar
ax.errorbar(x, y1, fmt='o', color='green', markersize=8, label='Normal')
ax.errorbar(x, y2, yerr=0.1, fmt='o', color='orange', markersize=8, label='abw_up')
ax.errorbar(x, y3, yerr=0.1, fmt='o', color='purple', markersize=8,label='abw_down')
# limits
ax.axhline(0.1*10, color='red', lw=2)
ax.axhline(-0.1*10, color='red', lw=2)
#set limit of y-Axis
ax.set_ylim((-1.3,1.3))
# Labels
ax.set_xlabel('points number')
ax.set_ylabel('values')
# legend
legend=ax.legend(loc=('upper center'), shadow='true',bbox_to_anchor=(0.5, 1.05),ncol=3, fancybox=True)
plt.style.use('dark_background')
plt.savefig('result_dark.png')
plt.style.use('fivethirtyeight')
plt.savefig('result_white.png')
But it did not work properly. One of the images was correct. The second had a correct backgroundcolor, but the fontcolor of legend/labels did not change. I tried to separate the 2 images, the result was the same:
import numpy as np
import matplotlib.pyplot as plt
import os
def plot():
#set pic size
ax = plt.subplot(111)
# set x data
x = range(10)
# set y data
y1 = np.zeros(10)
y2 = [1,2,3,1,2,3,1,2,3,1]
y3 = [3,1,2,3,1,2,3,1,2,3]
#plot as errorbar
ax.errorbar(x, y1, fmt='o', color='green', markersize=8, label='Normal')
ax.errorbar(x, y2, yerr=0.2, fmt='o', color='orange', markersize=8, label='abw_up')
ax.errorbar(x, y3, yerr=0.1, fmt='o', color='purple', markersize=8,label='abw_down')
# limits
ax.axhline(0.1*10, color='red', lw=2)
ax.axhline(-0.1*10, color='red', lw=2)
#set limit of y-Axis
ax.set_ylim((-1.3,5.3))
# Labels
ax.set_xlabel('Messpunkte-Nr.\nMeasurement points number')
ax.set_ylabel('Spezifikationsgrenze normiert\nnormed to specification')
# legend
legend=ax.legend(loc=('upper center'), shadow='true',bbox_to_anchor=(0.5, 1.05),ncol=3, fancybox=True)
texts =legend.get_texts()
texts[0].set_color('green')
texts[1].set_color('orange')
texts[2].set_color('purple')
fig = plt.figure(figsize=(16, 9), dpi=100)
plt.style.use('dark_background')
plot()
plt.savefig('result_dark.png')
plt.clf()
#plt.close()
fig = plt.figure(figsize=(16, 9), dpi=100)
plt.style.use('fivethirtyeight')
plot()
plt.savefig('result_white.png')
plt.clf()
#plt.close()
How should I fix my code to have 2 images with the same values, but different styles?
I would suggest structuring you code something like:
from matplotlib.style import context
def my_plot_function(ax, data, style):
# do all of your plotting in here, should be fast as no computation
pass
with context('dark'):
fig, ax = plt.subplots(1, 1)
my_plot_function(ax, data, style)
fig.savefig(...)
with context('fivethirtyeight'):
fig, ax = plt.subplots(1, 1)
my_plot_function(ax, data, style)
fig.savefig(...)
This is a design feature, not a bug. Almost all of the values controlled by rcparams are set at object creation time, not a draw time, because having what your figure will look like when you render it depend on global state is terrifying. This also allows you to use context managers for the rcparams, as shown above. Calling use only over-rides the values that the style sheet explicitly sets (which is also a design feature so you can apply multiple styles a-la cascading style sheets).
So your problem appears to be that you do a lot of plotting and then tell pylab you'd like your plots to have a particular style. That instruction doesn't seem to be updating everything. So instead, tell it you want to use a particular style. Then plot. Then clear everything. Then plot again.
import numpy as np
import matplotlib.pyplot as plt
def plot():
#set pic size
fig = plt.figure(figsize=(16, 9), dpi=100)
ax = plt.subplot(111)
# set x data
x = range(10)
# set y data
y1 = np.zeros(10)
y2 = [0,1,2,3,4,5,6,7,8,9]
y3 = [10,9,8,7,6,5,4,3,2,1]
#plot as errorbar
ax.errorbar(x, y1, fmt='o', color='green', markersize=8, label='Normal')
ax.errorbar(x, y2, yerr=0.1, fmt='o', color='orange', markersize=8, label='abw_up')
ax.errorbar(x, y3, yerr=0.1, fmt='o', color='purple', markersize=8,label='abw_down')
# limits
ax.axhline(0.1*10, color='red', lw=2)
ax.axhline(-0.1*10, color='red', lw=2)
#set limit of y-Axis
ax.set_ylim((-1.3,1.3))
# Labels
ax.set_xlabel('points number')
ax.set_ylabel('values')
# legend
legend=ax.legend(loc=('upper center'), shadow='true',bbox_to_anchor=(0.5, 1.05),ncol=3, fancybox=True)
plt.style.use('dark_background')
plot()
plt.savefig('result_dark.png')
plt.clf()
plt.style.use('fivethirtyeight')
plot()
plt.savefig('result_white.png')
Does this give what you want? Here are the figures I got.

Categories

Resources