Barplot python yticks in equal range despite value (pseudo logarithmic) - python

I'm trying to manipulate my y axis in a barplot. Say I have this code
import numpy as np
import matplotlib.pyplot as plt
N = 11
men_means = (-500,0,1,2,5,10,20,50,100,200,500)
men_std = (-500,0,1,2,5,10,20,50,100,200,500)
ind = np.arange(N) # the x locations for the groups
width = 0.25 # the width of the bars
fig, ax = plt.subplots()
rects1 = ax.bar(ind, men_means, width, color='r', yerr=men_std)
women_means = (-500,0,1,2,5,10,20,50,100,200,500)
women_std = (-500,0,1,2,5,10,20,50,100,200,500)
rects2 = ax.bar(ind + width, women_means, width, color='y', yerr=women_std)
ax.set_ylabel('Scores')
ax.set_title('Scores by group and gender')
ax.set_xticks(ind + width)
ax.set_xticklabels(('G1', 'G2', 'G3', 'G4', 'G5', 'G6', 'G7'))
ax.legend((rects1[0], rects2[0]), ('Men', 'Women'))
plt.show()
It will give me a y axis that is linear. Of course I could use 'symlog' to make the y-axis logarithmic (for my original data there are negative values as well) but what I really want is to have a 'pseudo logarithmic y axis', where the ticks are set to -500, -200, -100, -50, [..],0, 1, 2 ,5, 10, 20, [...], 500, but are equally distributed along the yaxis.
Can anyone help? :-)

Related

How to display the label for 0 length bars when using a log axis

I'm using Python 3.9.7
I have some data that I'd like to plot as bar charts (sample code and pic below).
I want to plot the bar charts using a logarithmic y scale.
However, some of the bar values might be zero, so they won't show as a bar on the log scale, but I would still like to show the label for each bar even if it is zero. If I plot the y-axis linearly, the 0 label shows up, but not with log-scaling.
Is there a way to do this?
I'm not wedded to matplotlib if there are other ways of plotting.
Thank you.
import matplotlib.pyplot as plt
import numpy as np
month1 = [11, 1200, 0]
month2 = [55, 14, 37]
month3 = [111, 222, 300]
labels = ['a','b','c']
x_positions = np.arange(len(labels))
bar_width = 0.15
fig, ax = plt.subplots()
rects1 = ax.bar(x_positions - bar_width, month1, bar_width, label=labels[0])
rects2 = ax.bar(x_positions, month2, bar_width, label=labels[1])
rects3 = ax.bar(x_positions + bar_width, month3, bar_width, label=labels[2])
# Add some text for labels, title and custom x-axis tick labels, etc.
ax.set_ylabel('Count')
ax.set_xticks(x_positions, labels)
ax.set_yscale('log') # the 0 label will appear if I omit this line.
ax.legend()
ax.bar_label(rects1, padding=3)
ax.bar_label(rects2, padding=3)
ax.bar_label(rects3, padding=3)
fig.tight_layout()
plt.show()
The zero bar label is not shown, because on this log scale, 0 is infinitely far below the other bars' tops, so technically it can't be seen.
You can of course add a label manually:
ax.text(x = x_positions[2] - bar_width,
y = ax.get_ylim()[0] + 1,
s = '0',
horizontalalignment='center')
The +1 is there to match the padding=3 of the other labels. You may need to change this for other scales.
This approach could be automated by iterating over all the values, e.g. like this (setting two more y values to zero for testing):
month1 = [11, 1200, 0]
month2 = [55, 0, 37]
month3 = [0, 222, 300]
labels = ['a', 'b', 'c']
x_positions = np.arange(len(labels))
bar_width = 0.15
y_min = 10
fig, ax = plt.subplots()
fig.tight_layout()
ax.set_yscale('log')
ax.set_ylim(y_min, 2000)
rects1 = ax.bar(x_positions - bar_width, month1, bar_width, label=labels[0])
rects2 = ax.bar(x_positions, month2, bar_width, label=labels[1])
rects3 = ax.bar(x_positions + bar_width, month3, bar_width, label=labels[2])
ax.set_ylabel('Count')
ax.set_xticks(x_positions, labels)
ax.legend()
ax.bar_label(rects1, padding=3)
ax.bar_label(rects2, padding=3)
ax.bar_label(rects3, padding=3)
for x, month in enumerate([month1, month2, month3]):
for x_offset, y in zip([-1, 0, 1], month):
if y < y_min:
ax.text(x = x + x_offset * bar_width,
y = y_min + 1,
s = str(y),
horizontalalignment='center')

How to set the location of bars in python matplotlib?

I use the following code to generate the following image.
import numpy as np
import matplotlib.pyplot as plt
labels = ['G1', 'G2']
men_means = [20, 35]
women_means = [25, 32]
men_std = [2, 3]
women_std = [3, 5]
width = 0.25 # the width of the bars: can also be len(x) sequence
fig, ax = plt.subplots()
ax.bar(labels, men_means, width, yerr=men_std, label='Men')
ax.bar(labels, women_means, width, yerr=women_std, bottom=men_means,
label='Women')
ax.set_ylabel('Scores')
ax.set_title('Scores by group and gender')
ax.legend()
plt.show()
The two bars are too far from each other. How can I make them come closer but still be centered? Can I add some padding on the left and right?
You can manually set the positions of the bars in the x axis. You then have to add the tick labels manually:
import numpy as np
import matplotlib.pyplot as plt
men_means = [20, 35]
women_means = [25, 32]
men_std = [2, 3]
women_std = [3, 5]
width = 0.25 # the width of the bars: can also be len(x) sequence
fig, ax = plt.subplots()
# Define positions for each bar... just random here
# Change the 2nd argument to move bars around; play with bar widths also
positions = (0.5, 0.8)
# Here the first argument is the x position for the bars
ax.bar(positions, men_means, width, yerr=men_std, label='Men')
ax.bar(positions, women_means, width, yerr=women_std, bottom=men_means,
label='Women')
# Now set the ticks and the corresponding labels
labels = ('G1', 'G2')
plt.xticks(positions, labels)
ax.set_ylabel('Scores')
ax.set_title('Scores by group and gender')
ax.legend()
plt.show()
Result:
You can play around with the bar widths and the 2nd argument in positions to get the distance you'd like.
Increase the width?
width = 0.6 # the width of the bars: can also be len(x) sequence
Output:

How to plot.bar base on other value than zero

Generally, the bar chart will show bottom on zero. which change the bottom, the bar move up or down.
import numpy as np
import matplotlib.pyplot as plt
N = 5
menMeans = (20, 35, 30, 35, 27)
ind = np.arange(N) # the x locations for the groups
width = 0.35 # the width of the bars: can also be len(x) sequence
p1 = plt.bar(ind, menMeans, width, bottom=0,color='#d62728')
plt.ylabel('Scores')
plt.title('Scores by group and gender')
plt.xticks(ind, ('G1', 'G2', 'G3', 'G4', 'G5'))
plt.yticks(np.arange(0, 81, 10))
plt.legend('Men')
plt.savefig('bar.png')
plt.show()
while I want isn't moving up or down. the following code show the bottom of zero.
I want to show the chart based on value such as 25. if the data,such as 20, then it shows 5 below the 25 in the chart.
You can convert menMeans to numpy and then subtract the bottom. Subtracting 25, the example array would be [-5, 10, 5, 10, 2].
The x-axis can be moved to that height via ax.spines['bottom'].set_position(('data', bottom)). Similarly, the other spines can be made invisible (ax.spines[...].set_color('none')).
plt.tick_params() can remove the tick marks by setting their length to 0.
ax.text(x, y, text) can be used to set a text at a given position. Newlines can help to get an adequate padding independent of the y-axis.
import numpy as np
import matplotlib.pyplot as plt
N = 5
menMeans = (20, 35, 30, 35, 27)
menMeans = np.array(menMeans)
ind = np.arange(N) # the x locations for the groups
width = 0.35 # the width of the bars: can also be len(x) sequence
bottom = 25
p1 = plt.bar(ind, menMeans-bottom, width, bottom=bottom, color='#d62728', label='Men')
plt.ylabel('Scores')
plt.title('Scores by group and gender')
plt.xticks(ind, ('G1', 'G2', 'G3', 'G4', 'G5'))
plt.yticks(np.arange(0, 81, 10))
plt.tick_params(axis='both', length=0)
ax = plt.gca()
ax.spines['left'].set_color('none')
ax.spines['right'].set_color('none')
ax.spines['bottom'].set_position(('data', bottom))
ax.spines['top'].set_color('none')
for i, mean in zip(ind, menMeans):
ax.text(i, mean, f'{mean}\n' if mean >= bottom else f'\n{mean}', ha='center', va='center')
plt.legend()
plt.savefig('bar.png')
plt.show()

Matplotlib mutli-barchart plots

can someone please post an example for drawing to images on one plot, both of them are barcharts.
Every one should look like this one here :
http://matplotlib.org/examples/api/barchart_demo.html
The problem with this example, it doesn't tell how add another chart to it, and if, how to set the configurations of every one of them.
You should use fig=plt.figure() and after that add two subplots ax=fig.add_subplot(2,2,1) and ax2=fig.add_subplot(2,2,2). After that you can do everything with ax and ax2.
A modified example from your reference:
import numpy as np
import matplotlib.pyplot as plt
N = 5
menMeans = (20, 35, 30, 35, 27)
menStd = (2, 3, 4, 1, 2)
ind = np.arange(N) # the x locations for the groups
width = 0.35 # the width of the bars
fig = plt.figure()
ax = fig.add_subplot(2,2,1)
rects1 = ax.bar(ind, menMeans, width, color='r', yerr=menStd)
womenMeans = (25, 32, 34, 20, 25)
womenStd = (3, 5, 2, 3, 3)
rects2 = ax.bar(ind+width, womenMeans, width, color='y', yerr=womenStd)
# add some text for labels, title and axes ticks
ax.set_ylabel('Scores')
ax.set_title('Scores by group and gender')
ax.set_xticks(ind+width)
ax.set_xticklabels( ('G1', 'G2', 'G3', 'G4', 'G5') )
ax.legend( (rects1[0], rects2[0]), ('Men', 'Women') )
def autolabel(rects):
# attach some text labels
for rect in rects:
height = rect.get_height()
ax.text(rect.get_x()+rect.get_width()/2., 1.05*height, '%d'%int(height),
ha='center', va='bottom')
autolabel(rects1)
autolabel(rects2)
ax2 = fig.add_subplot(2,2,2)
rects3 = ax2.bar(ind, menMeans, width, color='r', yerr=menStd)
womenMeans = (20, 32, 34, 20, 25)
womenStd = (3, 7, 2, 3, 3)
rects4 = ax2.bar(ind+width, womenMeans, width, color='y', yerr=womenStd)
ax2.set_ylabel('Scores_2')
ax2.set_title('Scores by group and gender_2')
ax2.set_xticks(ind+width)
ax2.set_xticklabels( ('G1_2', 'G2_2', 'G3_2', 'G4_2', 'G5_2') )
ax2.legend( (rects1[0], rects2[0]), ('Men_2', 'Women_2') )
def autolabel(rects):
# attach some text labels
for rect in rects:
height = rect.get_height()
ax2.text(rect.get_x()+rect.get_width()/2., 1.05*height, '%d'%int(height),
ha='center', va='bottom')
autolabel(rects3)
autolabel(rects4)
plt.show()

How to plot bar graphs with same X coordinates side by side ('dodged')

import matplotlib.pyplot as plt
gridnumber = range(1,4)
b1 = plt.bar(gridnumber, [0.2, 0.3, 0.1], width=0.4,
label="Bar 1", align="center")
b2 = plt.bar(gridnumber, [0.3, 0.2, 0.2], color="red", width=0.4,
label="Bar 2", align="center")
plt.ylim([0,0.5])
plt.xlim([0,4])
plt.xticks(gridnumber)
plt.legend()
plt.show()
Currently b1 and b2 overlap each other. How do I plot them separately like so:
There is an example in the matplotlib site. Basically, you just shift the x values by width. Here is the relevant bit:
import numpy as np
import matplotlib.pyplot as plt
N = 5
menMeans = (20, 35, 30, 35, 27)
menStd = (2, 3, 4, 1, 2)
ind = np.arange(N) # the x locations for the groups
width = 0.35 # the width of the bars
fig = plt.figure()
ax = fig.add_subplot(111)
rects1 = ax.bar(ind, menMeans, width, color='royalblue', yerr=menStd)
womenMeans = (25, 32, 34, 20, 25)
womenStd = (3, 5, 2, 3, 3)
rects2 = ax.bar(ind+width, womenMeans, width, color='seagreen', yerr=womenStd)
# add some
ax.set_ylabel('Scores')
ax.set_title('Scores by group and gender')
ax.set_xticks(ind + width / 2)
ax.set_xticklabels( ('G1', 'G2', 'G3', 'G4', 'G5') )
ax.legend( (rects1[0], rects2[0]), ('Men', 'Women') )
plt.show()
Below answer will explain each and every line of code in the simplest manner possible:
# Numbers of pairs of bars you want
N = 3
# Data on X-axis
# Specify the values of blue bars (height)
blue_bar = (23, 25, 17)
# Specify the values of orange bars (height)
orange_bar = (19, 18, 14)
# Position of bars on x-axis
ind = np.arange(N)
# Figure size
plt.figure(figsize=(10,5))
# Width of a bar
width = 0.3
# Plotting
plt.bar(ind, blue_bar , width, label='Blue bar label')
plt.bar(ind + width, orange_bar, width, label='Orange bar label')
plt.xlabel('Here goes x-axis label')
plt.ylabel('Here goes y-axis label')
plt.title('Here goes title of the plot')
# xticks()
# First argument - A list of positions at which ticks should be placed
# Second argument - A list of labels to place at the given locations
plt.xticks(ind + width / 2, ('Xtick1', 'Xtick3', 'Xtick3'))
# Finding the best position for legends and putting it
plt.legend(loc='best')
plt.show()
Sometimes could be tricky to find the right bar width. I usually use this np.diff to find the right dimension.
import numpy as np
import matplotlib.pyplot as plt
#The data
womenMeans = (25, 32, 34, 20, 25)
menMeans = (20, 35, 30, 35, 27)
indices = [5.5,6,7,8.5,8.9]
#Calculate optimal width
width = np.min(np.diff(indices))/3
fig = plt.figure()
ax = fig.add_subplot(111)
# matplotlib 3.0 you have to use align
ax.bar(indices-width,womenMeans,width,color='b',label='-Ymin',align='edge')
ax.bar(indices,menMeans,width,color='r',label='Ymax',align='edge')
ax.set_xlabel('Test histogram')
plt.show()
# matplotlib 2.0 (you could avoid using align)
# ax.bar(indices-width,womenMeans,width,color='b',label='-Ymin')
# ax.bar(indices,menMeans,width,color='r',label='Ymax')
This is the result:
What if my indices on my x axis are nominal values like names:
#
import numpy as np
import matplotlib.pyplot as plt
# The data
womenMeans = (25, 32, 34, 20, 25)
menMeans = (20, 35, 30, 35, 27)
indices = range(len(womenMeans))
names = ['Asian','European','North Amercian','African','Austrailian','Martian']
# Calculate optimal width
width = np.min(np.diff(indices))/3.
fig = plt.figure()
ax = fig.add_subplot(111)
ax.bar(indices-width/2.,womenMeans,width,color='b',label='-Ymin')
ax.bar(indices+width/2.,menMeans,width,color='r',label='Ymax')
#tiks = ax.get_xticks().tolist()
ax.axes.set_xticklabels(names)
ax.set_xlabel('Test histogram')
plt.show()
Here are two examples of creating a side-by-side bar chart when you have more than two "categories" in a group.
Manual Method
Manually set the position and width of each bar.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import ticker
coins = ['penny', 'nickle', 'dime', 'quarter']
worth = np.array([.01, .05, .10, .25])
# Coin values times *n* coins
# This controls how many bars we get in each group
values = [worth*i for i in range(1,6)]
n = len(values) # Number of bars to plot
w = .15 # With of each column
x = np.arange(0, len(coins)) # Center position of group on x axis
for i, value in enumerate(values):
position = x + (w*(1-n)/2) + i*w
plt.bar(position, value, width=w, label=f'{i+1}x')
plt.xticks(x, coins);
plt.ylabel('Monetary Value')
plt.gca().yaxis.set_major_formatter(ticker.FormatStrFormatter('$%.2f'))
plt.legend()
Pandas Method
If you put the data into a pandas DataFrame, pandas will do the hard stuff for you.
import pandas as pd
coins = ['penny', 'nickle', 'dime', 'quarter']
worth = [0.01, 0.05, 0.10, 0.25]
df = pd.DataFrame(worth, columns=['1x'], index=coins)
df['2x'] = df['1x'] * 2
df['3x'] = df['1x'] * 3
df['4x'] = df['1x'] * 4
df['5x'] = df['1x'] * 5
from matplotlib import ticker
import matplotlib.pyplot as plt
df.plot(kind='bar')
plt.ylabel('Monetary Value')
plt.gca().yaxis.set_major_formatter(ticker.FormatStrFormatter('$%.2f'))
plt.gca().xaxis.set_tick_params(rotation=0)
Pandas creates a similar figure...

Categories