I'm trying to annotate precentage in horizontal barplot with matplotlib.
The problem is that when I try to add the precentage annotation I get error:
"module 'matplotlib.pyplot' has no attribute 'patches'
This is how I try to create the chart:
import seaborn as sns
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
%matplotlib inline
sns.set(style="whitegrid")
#sns.set_color_codes("Spectral")
plt.figure(2, figsize=(20,15))
the_grid = GridSpec(2, 2)
plt.subplot(the_grid[0, 1], title='Original Dataset')
sns.barplot(x='count',y='land_cover_specific', data=df, palette='Spectral')
plt.xlabel('Count')
plt.ylabel('Land cover')
total = len(original)
print(total)
for p in plt.patches:
percentage = '{:.1f}%'.format(100 * p.get_width()/total)
x = p.get_x() + p.get_width() + 0.02
y = p.get_y() + p.get_height()/2
plt.annotate(percentage, (x, y))
plt.show()
I get the bar plot but I do not get the annotation due to this error.
My end goal: to add teh rpecentage of each bar count out of the total
I think you only need to change :
sns.barplot(x='count',y='land_cover_specific', data=df, palette='Spectral')
to:
ax = sns.barplot(x='count',y='land_cover_specific', data=df, palette='Spectral')
and
for p in plt.patches:
to:
for p in ax.patches:
import seaborn as sns
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
%matplotlib inline
sns.set(style="whitegrid")
#sns.set_color_codes("Spectral")
plt.figure(2, figsize=(20,15))
the_grid = GridSpec(2, 2)
plt.subplot(the_grid[0, 1], title='Original Dataset')
horizontal = sns.barplot(x='count',y='land_cover_specific', data=df, palette='Spectral')
plt.xlabel('Count')
plt.ylabel('Land cover')
total = len(original)
# print(total)
# for p in plt.patches:
# percentage = '{:.1f}%'.format(100 * p.get_width()/total)
# x = p.get_x() + p.get_width() + 0.02
# y = p.get_y() + p.get_height()/2
# plt.annotate(percentage, (x, y))
def auto_label(horizontal_):
for index, rectangle in enumerate(horizontal_):
height = rectangle.get_height()
width = rectangle.get_width()
y_value = rectangle.get_y()
# color is an array containing either hex or rgb values
# each should map to the color of each in your barchart
plt.text(width + height/2., y_value, "%d" % 100 * width / total, color=color[index])
auto_label(horizontal)
plt.show()
Related
So let's say I have a vector of numbers.
np.random.randn(5).round(2).tolist()
[2.05, -1.57, 1.07, 1.37, 0.32]
I want a draw a rectangle that shows this elements as numbers in a rectangle.
Something like this:
Is there an easy way to do this in matplotlib?
A bit convoluted but you could take advantage of seaborn.heatmap, creating a white colormap:
import seaborn as sns
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
data = np.random.randn(5).round(2).tolist()
linewidth = 2
ax = sns.heatmap([data], annot=True, cmap=LinearSegmentedColormap.from_list('', ['w', 'w'], N=1),
linewidths=linewidth, linecolor='black', square=True,
cbar=False, xticklabels=False, yticklabels=False)
plt.tight_layout()
plt.show()
In this case, the external lines won't be as thick as the internal ones. If needed, this can be fixed with:
ax.axhline(y=0, color='black', lw=linewidth*2)
ax.axhline(y=1, color='black', lw=linewidth*2)
ax.axvline(x=0, color='black', lw=linewidth*2)
ax.axvline(x=len(data), color='black', lw=linewidth*2)
Edit: avoid these lines and add clip_on=False to sns.heatmap (thanks/credit #JohanC)
Output:
We can add rectangles , and annotate them in a for loop.
from matplotlib import pyplot as plt
import numpy as np
# Our numbers
nums = np.random.randn(5).round(2).tolist()
# rectangle_size
rectangle_size = 2
# We want rectangles look squared, you can change if you want
plt.rcParams["figure.figsize"] = [rectangle_size * len(nums), rectangle_size]
plt.rcParams["figure.autolayout"] = True
fig = plt.figure()
ax = fig.add_subplot(111)
for i in range(len(nums)):
# We are adding rectangles
# You can change colors as you wish
plt.broken_barh([(rectangle_size * i, rectangle_size)], (0, rectangle_size), facecolors='white', edgecolor='black'
,linewidth = 1)
# We are calculating where to annotate numbers
cy = rectangle_size / 2.0
cx = rectangle_size * i + cy
# Annotation You can change color,font, etc ..
ax.annotate(str(nums[i]), (cx, cy), color='black', weight='bold', fontsize=20, ha='center', va='center')
# For squared look
plt.xlim([0, rectangle_size*len(nums)])
plt.ylim([0, rectangle_size])
# We dont want to show ticks
plt.axis('off')
plt.show()
One way using the Rectangle patch is:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.patches import Rectangle
x = np.random.randn(5).round(2).tolist()
fig, ax = plt.subplots(figsize=(9, 2)) # make figure
dx = 0.15 # edge size of box
buf = dx / 10 # buffer around edges
# set x and y limits
ax.set_xlim([0 - buf, len(x) * dx + buf])
ax.set_ylim([0 - buf, dx + buf])
# set axes as equal and turn off axis lines
ax.set_aspect("equal")
ax.axis("off")
# draw plot
for i in range(len(x)):
# create rectangle with linewidth=4
rect = Rectangle((dx * i, 0), dx, dx, facecolor="none", edgecolor="black", lw=4)
ax.add_patch(rect)
# get text position
x0, y0 = dx * i + dx / 2, dx / 2
# add text
ax.text(
x0, y0, f"{x[i]}", color="black", ha="center", va="center", fontsize=28, fontweight="bold"
)
fig.tight_layout()
fig.show()
which gives:
I would like to place labels on bars in Seaborn depending on how much space there is available.
For example in the example below I would like to use outside labels if they fit on the figure and inside labels when they don't. I would like to do this automatically for many plots, so I am looking for a smart way to do this flexibly. Any ideas?
import seaborn as sns
import matplotlib.pyplot as plt
titanic=sns.load_dataset('titanic')
titanic.head()
fig, ax = plt.subplots()
sns.countplot(
y='sex',
data=titanic,
orient="h",
ax=ax
)
for p in ax.patches:
perc = "{:.1f}%".format(100 * p.get_width() / titanic.shape[0])
x = p.get_width()
y = p.get_y() + p.get_height() / 2
# inside labels
ax.annotate(perc, (x*0.8, y), color="white")
# outside labels
ax.annotate(perc, (x*1.1, y), color="black")
Bad quality example of what I would like to achieve:
Does this help?
fig, ax = plt.subplots()
sns.countplot(
y='sex',
data=titanic,
orient="h",
ax=ax
)
values = []
for p in ax.patches:
x = p.get_width() # counts; dimension of the bar
y = p.get_y() + p.get_height() / 2
values.append(x) #collect the bar sizes in a list
x_max = max(values) #determine the largest bar size
#adjust percentage of the maximum bar size where inside/outside annotation occurs
cut_off = 0.8*x_max
for p in ax.patches:
perc = "{:.1f}%".format(100 * p.get_width() / titanic.shape[0]) #the label for the bar
x = p.get_width()
y = p.get_y() + p.get_height() / 2
if x >= cut_off:
ax.annotate(perc, (x*0.8, y), color="white")
else:
ax.annotate(perc, (x*1.1, y), color="black")
fig = plt.figure(figsize=[5, 5])
plt.plot(data["recall"])
plt.title('256 Classes Performance')
plt.xlabel('class')
plt.ylabel('Accuracy Rate')
plt.show()
image 1 is my image, image 2 is what I want, I want to add labels for those classes which more than 50% accuracy, including class number and accuracy rate displaying in the line chart
Maybe like so:
import matplotlib.pyplot as plt
import numpy as np
my_vals = np.random.rand(50)
categories = np.arange(50)
colors = ['red' if v >= 0.5 else 'green' for v in my_vals]
fig, ax = plt.subplots(figsize=(5, 4))
ax.bar(categories, my_vals, color=colors)
plt.show()
You can use a combination of ax.annotate and ax.scatter. With xytext you can move the text (see matplotlib.pyplot.annotate).
import matplotlib.pyplot as plt
import numpy as np
from numpy.lib.financial import pmt
y_vals = np.random.rand(50)
x_vals = np.arange(50)
annotations = [f'{y:.1f}' if y >= 0.5 else '' for y in y_vals]
dots = [y if y >= 0.5 else 9999.0 for y in y_vals]
fig, ax = plt.subplots(figsize=(5, 4))
ax.set_ylim(-0.1, 1.1)
ax.plot(x_vals, y_vals)
ax.scatter(x_vals, dots, color='red')
for x, y, text_val in zip(x_vals, y_vals, annotations):
ax.annotate(
text_val,
xy=(x, y),
)
plt.show()
I need to plot a hist with bot logarithmic y and x-axis, but I'd like also to have hist's bins displayed of same size.
How can I achieve this result with the following code (the x used is very long so I have intentionally avoided to insert it):
import matplotlib as plt
import numpy as np
fig, ax1 = plt.subplots()
hist, bins, _ = ax1.hist(x, log=True, color="red", rwidth=0.5)
plt.xscale("log")
np_x = np.array(x)
print("np_x.mean() = " + str(np_x.mean()))
plt.axvline(np_x.mean() * 1.1, color='lime', linestyle='dashed', linewidth=3,
label='Mean: {:.2f}'.format(np_x.mean()))
handles, labels = ax1.get_legend_handles_labels()
binwidth = math.floor(bins[1] - bins[0])
mylabel = "Binwidth: {}".format(binwidth) + ", Bins: {}".format(len(hist))
red_patch = mpatches.Patch(color='red', label=mylabel)
handles = [red_patch] + handles
labels = [mylabel] + labels
ax1.legend(handles, labels)
plt.xlabel(x_label)
plt.ylabel(y_label)
plt.show()
I want to produce in python with matplotlib/pyplot
a bar chart with a fill depending on the value.
legend color bar
while keeping module dependencies at a minimum.
Is there something simpler than:
import matplotlib.pyplot as plt
def color_gradient ( val, beg_rgb, end_rgb, val_min = 0, val_max = 1):
val_scale = (1.0 * val - val_min) / (val_max - val_min)
return ( beg_rgb[0] + val_scale * (end_rgb[0] - beg_rgb[0]),
beg_rgb[1] + val_scale * (end_rgb[1] - beg_rgb[1]),
beg_rgb[2] + val_scale * (end_rgb[2] - beg_rgb[2]))
# -----------------------------------------------
x_lbls = [ "09:00", "09:15", "10:10"]
y_vals = [ 7, 9, 5]
plt_idx = np.arange( len( x_lbls))
bar_wd = 0.35
grad_beg, grad_end = ( 0.5, 0.5, 0.5), (1, 1, 0)
col_list = [ color_gradient( val,
grad_beg,
grad_end,
min( y_vals),
max( y_vals)) for val in y_vals]
plt.bar( plt_idx, y_vals, color = col_list)
plt.xticks( plt_idx + bar_wd, x_lbls)
plt.show()
this is still missing the legend color bar
my solution in R with ggplot would be:
library(ggplot2)
df = data.frame( time = 1:10, vals = abs(rnorm( n = 10)))
ggplot( df, aes( x = time, y = vals, fill = vals)) +
geom_bar(stat = "identity") +
scale_fill_gradient(low="#888888",high="#FFFF00")
and produces the desired output:
I couldn't figure out how to get the colorbar to work without plotting something else and then clearing it, so it's not the most elegant solution.
import matplotlib.pyplot as plt
from matplotlib import cm
import numpy as np
y = np.array([1, 4, 3, 2, 7, 11])
colors = cm.hsv(y / float(max(y)))
plot = plt.scatter(y, y, c = y, cmap = 'hsv')
plt.clf()
plt.colorbar(plot)
plt.bar(range(len(y)), y, color = colors)
plt.show()
You can use Normalize and ScalarMappable without plotting a scatter. For example:
import matplotlib mpl
import matplotlib.pyplot as plt
from matplotlib import cm
f,(ax1,ax2) = plt.subplots(2)
#ax1 --> plot here your bar chart
norm = mpl.colors.Normalize(vmin=0, vmax=1)
mpl.colorbar.ColorbarBase(ax2, cmap=cm.RdBu,
norm=norm,
orientation='horizontal')
Finally, add the desired format to the colorbar.