Is it possible to use matplotlib to include a subheading in legend that isnt a part of the graph? - python-3.x

I am using matplotlib to plot a pie chart. I have added a legend to the chart. However, i would like to add a "Total" to the legend, to sum up the values of all the other categories. Hence the value of "Total" would not be a part of the pie chart, and would only be shown in the legend. Is it possible for me to do that? Thank you.

You can create 2 legends. On the second one, you can create/manipulate symbol/text/title as you want. Here is a runnable code that you can try.
from matplotlib import pyplot as plt
import matplotlib.patches as mpatches
import numpy as np
fig = plt.figure()
ax = fig.add_axes([0,0,1,1])
ax.axis('equal')
langs = ['C', 'C++', 'Java', 'Python', 'PHP']
students = [23,17,35,29,12]
ax.pie(students, labels = langs,autopct='%1.2f%%')
# first legend
lgn = plt.legend()
ax = plt.gca().add_artist(lgn)
# second legend
gold_patch = mpatches.Patch(color='gold', label='Total= 9999') # use your description text here
second_legend = plt.legend(handles=[gold_patch], loc=1, \
bbox_to_anchor=(0.5, 0.35, 0.55, 0.35)) # adjust location of legend here
second_legend.set_frame_on(False) # use True/False as needed
second_legend.set_title("Other categories")
plt.show()
The output plot:

Related

plt.legend only adds first element to scatter plot

I am trying to add a legend to my scatter plot with 13 classes, however, with my code below, I am only able to get the first label. Can you assist me in generating the full list to show up in the legend of the scatter plot?
Here is my example code:
from sklearn.datasets import make_blobs
from matplotlib import pyplot
from pandas import DataFrame
# generate 2d classification dataset
X, y = make_blobs(n_samples=1000, centers=13, n_features=2)
classes = [f"class {i}" for i in range(13)]
#fig = plt.figure()
plt.figure(figsize=(15, 12))
scatter = plt.scatter(
x=X[:,0],
y=X[:,1],
s = 20,
c = y,
cmap='Spectral'
#c=[sns.color_palette()[x] for x in y_train_new]
)
plt.gca().set_aspect('equal', 'datalim')
plt.legend(classes)
plt.title('Dataset', fontsize=24)
You can do that by replacing the plt.legend(classes) in your code by this line... I hope this is what you are looking for. I am using matplotlib 3.3.4.
plt.legend(handles=scatter.legend_elements()[0], labels=classes)
Output plot

Set specified grid lines in matplotlib without changing ticklabels

I try to plot a bar graph with a pre-defined number of grid lines like below. However, once I plot it, some yticklabels (A_2,A_3,etc) have not shown (only A_1, A_5, A_9,A_13,A_17 shown). I want to keep all ytick labels, but the gridline should be the same as x axis. Do you have any ideas to fix it?
import matplotlib.pyplot as plt
import numpy as np
mdict={"Column1":["A_"+str(i) for i in range(1,21)],"Value":[i for i in range(1,21)]}
# Create a dataframe
df=pd.DataFrame(mdict)
# Set plot params
fig, ax = plt.subplots(figsize=(12,8))
ax.barh(df.Column1,df.Value, color="darkgray",edgecolor="black", linewidth=0.5)
ax.set_xlabel("Numbers", fontsize=15)
# ax.set_yticklabels(list(df_cor.Country.values.tolist()), fontsize=15)
major_ticks_top=np.linspace(0,20,6)
minor_ticks_top=np.linspace(0,20,6)
ax.set_xticks(major_ticks_top)
ax.set_yticks(minor_ticks_top)
ax.grid(alpha=0.2,color="black")
plt.show()
I wouldn't explicitly set the ticks and labels but modify the output matplotlib generates:
import matplotlib.pyplot as plt
import pandas as pd
from matplotlib.ticker import MultipleLocator
mdict={"Column1":["A_"+str(i) for i in range(1,21)],"Value":[i for i in range(1,21)]}
df=pd.DataFrame(mdict)
fig, ax = plt.subplots(figsize=(12,8))
ax.barh(df.Column1, df.Value, color="darkgray", edgecolor="black", linewidth=0.5)
ax.set_xlabel("Numbers", fontsize=15)
#set every fourth tick
n=4
ax.xaxis.set_major_locator(MultipleLocator(n))
ax.grid(alpha=0.2,color="black")
#remove unwanted gridlines on the y-axis
ygrd_lines = ax.get_ygridlines()
[grd_line.set_visible(False) for i, grd_line in enumerate(ygrd_lines) if i%n]
plt.show()
Sample output:
Methods used:
MultipleLocator() setting ticks at defined intervals
.get_ygridlines returning gridlines as a list of Line2D objects for further modification

Why is the grid turned on only on the last subplot?

I am using subplots in a function which is using a slider widget inputs to calculate some stuff and plotting results.
I want to turn on the grid for all subplots of ax1. But somehow jupternotebooks only turns it on only on the last plot...
import numpy as np
from matplotlib import pyplot as plt
import ipywidgets as widgets
from IPython.html.widgets import interact
%matplotlib inline
## Plot
fig, ax1 = plt.subplots(6,2)
plt.subplots_adjust(right = 2, top = 8 )
# Show the major grid lines with dark grey lines
plt.grid(b=True, which='major', color='#666666', linestyle='-')
# Show the minor grid lines with very faint and almost transparent grey lines
plt.minorticks_on()
plt.grid(b=True, which='minor', color='#999999', linestyle='-', alpha=0.2)
## Giergeschwindigkeit über v und ay
ax1[0,0].plot(v_ms, omega)
ax1[0,0].set_ylabel('Giergeschwindigkeit [rad/s]')
ax1[0,0].set_xlabel('Geschwindigkeit [m/s]')
ax1[0,0].set_title('Giergeschwindigkeit über Geschwindigkeit')
# ... more subplots
plt.show()
It looks like this:
And can you explain to me why in my case
ax1.grid()
throws an error?
AttributeError: 'numpy.ndarray' object has no attribute 'grid'
This is because plt will only operate on the last-created axes object.
And the reason you're getting that error is that ax1 is a numpy n-dimensional array, not an axes object.
You can do this to iterate over the numpy n-dimensional array to create the grids:
for row in axes:
for ax in row:
ax.grid(b=True, which='major', color='#666666', linestyle='-')
ax.minorticks_on()
ax.grid(b=True, which='minor', color='#999999', linestyle='-',alpha=0.2)
Result (without plt.subplots_adjust()):
You can set grid for every ax object, so in your case you should set like this:
ax1[0,0].grid()
ax1[0,1].grid()

Using "hue" for a Seaborn visual: how to get legend in one graph?

I created a scatter plot in seaborn using seaborn.relplot, but am having trouble putting the legend all in one graph.
When I do this simple way, everything works fine:
import pandas as pd
import numpy as np
from scipy import stats
import matplotlib.pyplot as plt
import seaborn as sns
df2 = df[df.ln_amt_000s < 700]
sns.relplot(x='ln_amt_000s', y='hud_med_fm_inc', hue='outcome', size='outcome', legend='brief', ax=ax, data=df2)
The result is a scatter plot as desired, with the legend on the right hand side.
However, when I try to generate a matplotlib figure and axes objects ahead of time to specify the figure dimensions I run into problems:
a4_dims = (10, 10) # generating a matplotlib figure and axes objects ahead of time to specify figure dimensions
df2 = df[df.ln_amt_000s < 700]
fig, ax = plt.subplots(figsize = a4_dims)
sns.relplot(x='ln_amt_000s', y='hud_med_fm_inc', hue='outcome', size='outcome', legend='brief', ax=ax, data=df2)
The result is two graphs -- one that has the scatter plots as expected but missing the legend, and another one below it that is all blank except for the legend on the right hand side.
How do I fix this such? My desired result is one graph where I can specify the figure dimensions and have the legend at the bottom in two rows, below the x-axis (if that is too difficult, or not supported, then the default legend position to the right on the same graph would work too)? I know the problem lies with "ax=ax", and in the way I am specifying the dimensions as matplotlib figure, but I'd like to know specifically why this causes a problem so I can learn from this.
Thank you for your time.
The issue is that sns.relplot is a "Figure-level interface for drawing relational plots onto a FacetGrid" (see the API page). With a simple sns.scatterplot (the default type of plot used by sns.relplot), your code works (changed to use reproducible data):
df = pd.read_csv("https://vincentarelbundock.github.io/Rdatasets/csv/datasets/iris.csv", index_col=0)
fig, ax = plt.subplots(figsize = (5,5))
sns.scatterplot(x = 'Sepal.Length', y = 'Sepal.Width',
hue = 'Species', legend = 'brief',
ax=ax, data = df)
plt.show()
Further edits to legend
Seaborn's legends are a bit finicky. Some tweaks you may want to employ:
Remove the default seaborn title, which is actually a legend entry, by getting and slicing the handles and labels
Set a new title that is actually a title
Move the location and make use of bbox_to_anchor to move outside the plot area (note that the bbox parameters need some tweaking depending on your plot size)
Specify the number of columns
fig, ax = plt.subplots(figsize = (5,5))
sns.scatterplot(x = 'Sepal.Length', y = 'Sepal.Width',
hue = 'Species', legend = 'brief',
ax=ax, data = df)
handles, labels = ax.get_legend_handles_labels()
ax.legend(handles=handles[1:], labels=labels[1:], loc=8,
ncol=2, bbox_to_anchor=[0.5,-.3,0,0])
plt.show()

better piechart color scheme

I am trying to create a pie chart, as follows:
import matplotlib.pyplot as plt
import pandas as pd
# make a square figure and axes
plt.figure(1, figsize=(10,10))
plt.axes([0.01, 0.1, 0.6, 0.6])
# plt.style.use('fivethirtyeight')
# The slices will be ordered and plotted counter-clockwise.
labels = 'foo1', 'foo2', 'foo3', 'foo4'
fracs = pd.Series([10,30, 50,10],index=labels)
fracs.plot(kind='pie', labels=None, autopct='%1.0f%%')
plt.legend(bbox_to_anchor=(0.95, .9), loc=2, borderaxespad=0.,labels=labels)
plt.title('pie chart demo which should be center aligned not left', bbox={'facecolor':'0.8', 'pad':5})
plt.show()
Which is yeilding a piechart as:
But, I am facing two problem:
1) I dont like the color scheme. I would like a color scheme more inline with (I need 12 colors)
2) Titel is centered at the pie chart only. The legend is somehow out. I am trying to get the title centered over the chart and the legend.
Can someone kindly help?
I think that is a ggplot colorscheme that you are trying to emulate.
And your plt.axes command is what is displacing your chart to the left.
Try this:
import matplotlib.pyplot as plt
plt.style.use('ggplot')
plt.figure(1, figsize=(10,10))
labels = 'foo1', 'foo2', 'foo3', 'foo4'
sizes = [10,30, 50,10]
plt.pie(sizes, labels=labels)
plt.show()

Resources