Bar chart help in python with value label alignment in the center - python

I use the below code in order to display the bar chart.
CODE
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
#creating the dataset
data = {'apples':20,'Mangoes':15,'Lemon':30,'Oranges':10}
names = list(data.keys())
values = list(data.values())
bars = plt.bar(names, height=values, width=0.9)
for bar in bars:
yval = bar.get_height()
plt.text(bar.get_x(), yval + .005, yval)
plt.rcParams['xtick.bottom'] = plt.rcParams['xtick.labelbottom'] = True
plt.show()
OUTPUT
My requirement is i want the labels aligned in the center of each bar and has to sorted in descending order. Looking for Output like below.

you can use this code to fix your problem. your arbitrary bar chart figure
%matplotlib notebook
import matplotlib.pyplot as plt
data = {'apples' : 20, 'Mangoes' : 15,
'Lemon' : 30, 'oranges' : 10}
# we apply asterisk sign on a list of tuples which is returened by
# sorted() function.
names, values = zip(*sorted(data.items(), key= lambda x: x[1], reverse=True))
plt.figure()
plt.bar(names, values, width=0.9)
# add Bar labels
for c in plt.gca().containers:
plt.gca().bar_label(c)
plt.show()
Called with a BarContainer artist, add a label to each bar, which, by default, is data value, so exactly what you want.

To sort use:
import numpy as np
import matplotlib.pyplot as plt
#creating the dataset
data = {'apples':20,'Mangoes':15,'Lemon':30,'Oranges':10}
names, values = zip(*sorted(data.items(), key=lambda x: x[1], reverse=True))
bars = plt.bar(names, height=values, width=0.9)
for bar in bars:
yval = bar.get_height()
plt.text(bar.get_x(), yval + .005, yval)
plt.rcParams['xtick.bottom'] = plt.rcParams['xtick.labelbottom'] = True
plt.show()

Related

Change "Q" quarterly data to custom "kv" in Matplotlib timeline chart on x axis Python

I have done the following timeline chart in Python. Where the data is in quarterly format by datetimeindex. However, I need to translate the graph into my local language and therefore replace "Q1", "Q2", "Q3", "Q4" with "kv1", "kv2", "kv3", "kv4". Is this possible? So I need the x axsis to be kv3, kv4, kv1 2022, kv2 instead of Q3, Q4, Q1 2022, Q2 and so fourth.
import random
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.pyplot import figure
import pandas
plt.style.use('seaborn-whitegrid')
matplotlib.rcParams['font.sans-serif'] = "Arial"
matplotlib.rcParams['font.family'] = "Arial"
categories = ['Car','Train','Boat', 'Plane', 'Walk' ]
cat_dict = dict(zip(categories, range(1, len(categories)+1)))
val_dict = dict(zip(range(1, len(categories)+1), categories))
dates = pandas.DatetimeIndex(freq='Q', start='2021-09-30', end='2023-12-31')
values = [random.choice(categories) for _ in range(len(dates))]
df = pandas.DataFrame(data=values, index=dates, columns=['category'])
df['plotval'] = [float('NaN'),1,1,3,1,float('NaN'),5,2,1,float('NaN')]
df['plotval'][0] = np.nan
plt.rcParams["figure.figsize"] = 4,3.5
plt.figure(dpi=1000)
fig, ax = plt.subplots()
df['plotval'].plot(ax=ax, style='^',color='darkblue', label = "Renteheving", markersize=12)
ax.margins(0.2)
ax.spines['top'].set_visible(False)
ax.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, pos: val_dict.get(x)))
plt.yticks( weight = 'bold')
I tried to add
plt.xlabel(["kv1", "kv2", "kv3", "kv4"])
Which gave me
Help is as always highly appreciated.
Try to add this to your code:
# Call draw to populate tick labels
plt.draw()
# Change major labels
new_major_labels = []
for label in ax.get_xticklabels(minor=False):
s = label.get_text()
label.set_text(s.replace('Q', 'kv'))
new_major_labels.append(label)
ax.set_xticklabels(new_major_labels, minor=False)
# Change minor labels
new_minor_labels = []
for label in ax.get_xticklabels(minor=True):
s = label.get_text()
label.set_text(s.replace('Q', 'kv'))
new_minor_labels.append(label)
ax.set_xticklabels(new_minor_labels, minor=True)
It throws a warning which I don't understand, but I think it does what you want.
I could not test it because I can't reproduce your graph, but this should work:
D = {'Q1':'kv1', 'Q2':'kv2', 'Q3':'kv3', 'Q4':'kv4'} # define a dictionnary to replace labels
labels = [i.get_text() for i in ax.get_xticklabels()] # get former labels
labels = [i if i not in D.keys() else D[i] for i in labels] # replace it if in dictionnary
ax.set_xticklabels(labels) # apply the new labels

Wrap xlabels in Seaborn Plot

Have been trying to modify me plot such that the xlabels can be wrapped.
Have looked at few suggestions from similar questions.
But am unable to use them on this.
The ax.set_xticklabels code does not wrap the labels.
The plt.xticks code throws an error -
AttributeError: 'Text' object has no attribute 'expandtabs'
plt.figure(figsize = (7,5))
ax = sns.countplot(data = df3, x = df3.PaymentMethod, hue = df3.Churn)
#ax.set_xticklabels(ax.get_xticklabels(), ha="right", horizontalalignment = 'center', wrap = True)
plt.xticks([textwrap.fill(label, 10) for label in ax.get_xticklabels()],
rotation = 10, fontsize=8, horizontalalignment="center")
Image of plot with overlapping xlabels
textwrap works as expected with the code suggested in the comments:
import numpy as np # v 1.19.2
import pandas as pd # v 1.1.3
import seaborn as sns # v 0.11.0
import textwrap
# Create sample dataset
rng = np.random.default_rng(seed=1)
cat_names = ['Short name', 'Slightly longer name', 'Rather much longer name',
'Longest name of them all by far']
counts = rng.integers(10, 100, len(cat_names))
var_cat = np.repeat(cat_names, counts)
var_bool = rng.choice(['True', 'False'], size=len(var_cat))
df = pd.DataFrame(dict(vcat=var_cat, vbool=var_bool))
# Plot seaborn countplot with wrapped tick labels
ax = sns.countplot(data=df, x='vcat', hue='vbool')
labels = [textwrap.fill(label.get_text(), 12) for label in ax.get_xticklabels()]
ax.set_xticklabels(labels);

Merging legends of different lineplots using seaborn

I would like to plot two dataframes with a 'long' representation, and differing axis, to one plot using sns.lineplot(). Yet, I am failing plot it with a single legend containing the elements of both lineplots.
The issue is similar to this: Secondary axis with twinx(): how to add to legend?, though I'd like to use seaborn.
A minimal working example up to the point I got stuck is given below.
import pandas as pd
import seaborn as sns
import numpy as np
import itertools
# mock dataset
lst = range(1,11)
steps1 = list(itertools.chain.from_iterable(itertools.repeat(x, 4) for x in lst))
labels1 = ['A','B']*20
values1 = list(np.random.uniform(0,1,40))
df1 = pd.DataFrame({'steps':steps1, 'lab':labels1, 'vals':values1})
lst = range(6,11)
steps2 = list(itertools.chain.from_iterable(itertools.repeat(x, 4) for x in lst))
labels2 = ['C','D']*10
values2 = list(np.random.uniform(10,20,20))
df2 = pd.DataFrame({'steps':steps2, 'lab2':labels2, 'others':values2})
# plotting
fig, ax = plt.subplots()
fig = sns.lineplot(x='steps',y='vals', data=df1, hue='lab',palette='bright', legend='brief')
ax2 = ax.twinx()
fig2 = sns.lineplot(x='steps',y='others', hue='lab2', data=df2 ,palette='dark', legend='brief')
# How do I merge the legends into one?
# the solution below gives me one merged and one separate legend
h1,l1 = fig.get_legend_handles_labels()
h2,l2 = fig2.get_legend_handles_labels()
ax.legend(loc=3, handles=h1+h2, labels = l1+l2)
I just resolved it by removing the obsolete legend by ax2.get_legend().remove().

How to change the size of markers and line widths in boxplots (pyplot)

I have following code defining a function:
import pandas as pd
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns;sns.set()
from Utils import cmp_iso_forest_od_params
## agg backend is used to create plot as a .png file
mpl.use('agg')
def print_boxplots(data: pd.DataFrame,
graph_filename: str,
col_examined: str,
col_related: str,
sort_func,
title: str,
x_title: str,
y_title: str,
min_val=None,
max_val=None
):
g = data.groupby([col_related])
# graph parameters
scale = 2
show_fliers = True
mean_color='b'
mean_marker='o'
labels = []
data_to_plot_arr = []
#switch = True
for group, group_df in g:
data_to_plot_arr.append(group_df[col_examined])
labels.append(group)
# dynamically set parameters of the graphs so that they are uniform across all graphs, but are minimalised
figsize = ((len(g)) * scale, 25 * scale) # originally (60, 30)
if max_val is None:
max_val = data[col_examined].max()
if min_val is None:
min_val = data[col_examined].min()
tick = (max_val - min_val) / 40
y_labels = np.concatenate([ np.arange(0, min_val-tick, -tick)[::-1], np.arange(0, max_val+6*tick, tick)])
# Create a figure instance
_fig = plt.figure( figsize=figsize)
# Create an axes instance
_ax = _fig.add_subplot(111)
_ax.set_xlabel(col_related, fontsize=20*scale)
# this sorts times and labels for display in the boxplot by the parameters of the boxplots
data_to_plot_arr, labels = zip(*sorted(zip(data_to_plot_arr,labels), key=sort_func ))
# Create the boxplot
bp = _ax.boxplot(data_to_plot_arr, positions=[x for x in range(len(labels))], showfliers=show_fliers)
# following function is described here: https://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.plot
_ax.plot([x for x in range(len(labels))], list(map(lambda x: x.mean(), list(data_to_plot_arr))), marker=mean_marker, color=mean_color)
_ax.set_title(title,
fontsize=25 * scale)
_ax.set_xlabel(x_title, fontsize=25 * scale)
_ax.set_ylabel(y_title, rotation=90, fontsize=25 * scale)
_ax.set_xticklabels(labels, rotation=90)
_ax.set_yticks(y_labels)
_ax.tick_params(axis='x', labelsize=22*scale)
_ax.tick_params(axis='y', labelsize=22*scale)
# custom legend elements gymnastics (it is really awful, but I coudl not find better solution)
colors = [mean_color]
sizes = [6*scale]
texts = ["Mean"]
patches = [plt.plot([], [], marker=mean_marker, ms=sizes[i], ls="", mec=None, color=colors[i],
label="{:s}".format(texts[i]))[0] for i in range(len(texts))]
legend = plt.legend(handles=patches,
bbox_to_anchor=[0.5, -0.08],
loc='center',
title="Boxplots show first and third quartile,\n with variability represented with whiskers",
ncol=2,
prop={'size': 16 * scale})
legend.get_title().set_fontsize(16 * scale)
_ax.grid(True)
# Save the figure
_fig.savefig(graph_filename+'.png', bbox_inches='tight')
What it does is it saves a series of boxplots with plotted means to a file.
What I need is to adjust the thickness of lines and size of all markers in the graph in relation to the scale parameter so I can control the definition of the final picture.
I was not able to find any useful parameters in the documentation so I ended up here.
Current example picture looks like this:

Matplotlib barchart color based on dictionary value [duplicate]

Is it possible to change only the color of a single bar in a graph made by matplotlib?
Let's say I've evaluation 1 to 10 and for each one I've a graph generate when the user choice the evaluation. For each evaluation one of this boys will win.
So for each graph, I would like to leave the winner bar in a different color, let's say Jim won evaluation1. Jim bar would be red, and the others blue.
I have a dictionary with the values, what I tried to do was something like this:
for value in dictionary.keys(): # keys are the names of the boys
if winner == value:
facecolor = 'red'
else:
facecolor = 'blue'
ax.bar(ind, num, width, facecolor=facecolor)
Anyone knows a way of doing this?
You need to use color instead of facecolor. You can also specify color as a list instead of a scalar value. So for your example, you could have color=['r','b','b','b','b']
For example,
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111)
N = 5
ind = np.arange(N)
width = 0.5
vals = [1,2,3,4,5]
colors = ['r','b','b','b','b']
ax.barh(ind, vals, width, color=colors)
plt.show()
is a full example showing you what you want.
To answer your comment:
colors = []
for value in dictionary.keys(): # keys are the names of the boys
if winner == value:
colors.append('r')
else:
colors.append('b')
bar(ind,num,width,color=colors)
for seaborn you can do something like this:
import seaborn as sns
import numpy as np
values = np.array([2,5,3,6,4,7,1])
idx = np.array(list('abcdefg'))
clrs = ['grey' if (x < max(values)) else 'red' for x in values ]
sns.barplot(x=idx, y=values, palette=clrs) # color=clrs)
for matplotlib:
import numpy as np
import matplotlib.pyplot as plt
values = np.array([2,5,3,6,4,7,1])
idx = np.array(list('abcdefg'))
clrs = ['grey' if (x < max(values)) else 'red' for x in values ]
plt.bar(idx, values, color=clrs, width=0.4)
plt.show()

Categories