How do I invert matplotlib bars at a specific point instead of when negative? - matplotlib

I'd like to invert the bars in this diagram when they are below 1, not when they are negative. Additionally I'd like to have even spacing between the ticks/steps on the y-axis
Here is my current code
import matplotlib.pyplot as plt
import numpy as np
labels = ['A','B','C']
Vals1 = [28.3232, 12.232, 9.6132]
Vals2 = [0.00456, 17.868, 13.453]
Vals3 = [0.0032, 1.234, 0.08214]
x = np.arange(len(labels))
width = 0.2
fig, ax = plt.subplots()
rects1 = ax.bar(x - width, Vals1, width, label='V1')
rects2 = ax.bar(x, Vals2, width, label='V2')
rects3 = ax.bar(x + width, Vals3, width, label='V3')
ax.set_xticks(x)
ax.set_xticklabels(labels)
plt.xticks(rotation=90)
ax.legend()
yScale = [0.0019531,0.0039063,0.0078125,0.015625,0.03125,0.0625,0.125,0.25,0.5,1,2,4,8,16,32]
ax.set_yticks(yScale)
plt.show()
I believe I've stumbled upon the answer, here it is for anyone else looking for the solution. Add the argument bottom='1' to ax.bar instantiation, and then flip the values in the array.
for i in range(len(Vals1)):
Vals1[i] = (1 - Vals1[i]) * -1

As you mentioned, the key is the bottom param of Axes.bar:
bottom (default: 0): The y coordinate(s) of the bars bases.
But beyond that, you can simplify your plotting code using pandas:
Put your data into a DataFrame:
import pandas as pd
df = pd.DataFrame({'V1': Vals1, 'V2': Vals2, 'V3': Vals3}, index=labels)
# V1 V2 V3
# A 28.3232 0.00456 0.00320
# B 12.2320 17.86800 1.23400
# C 9.6132 13.45300 0.08214
Then use DataFrame.sub to subtract the offset and DataFrame.plot.bar with the bottom param:
bottom = 1
ax = df.sub(bottom).plot.bar(bottom=bottom)

Related

Matplotlib: how to automatically draw an axes title at the left-most position?

I'm drawing my axes title with the method ax.set_title("Horizontal Bars", ha="left", x=0, fontsize=16) and it draw as below:
How do I draw it in the left-most position, as the "title here" in red above? I know I can use a negative value for x, but I'd like to find this value automatically.
To dynamically generate the bounds you would do:
import matplotlib.pyplot as plt
import numpy as np
# Fixing random state for reproducibility
np.random.seed(19680801)
plt.rcdefaults()
fig, ax = plt.subplots()
# Example data
people = ('Tom', 'Dick', 'Harry', 'Slim', 'Jim')
y_pos = np.arange(len(people))
performance = 3 + 10 * np.random.rand(len(people))
error = np.random.rand(len(people))
ax.barh(y_pos, performance, xerr=error, align='center')
ax.set_yticks(y_pos)
ax.set_yticklabels(people)
ax.invert_yaxis() # labels read top-to-bottom
ax.set_xlabel('Performance')
# Get min x and max y
# get the inverse of the transformation from data coordinates to pixels
transf = ax.transData.inverted()
bb = plt.figure().get_window_extent(renderer = plt.figure().canvas.get_renderer())
bb_datacoords = bb.transformed(transf)
points = bb_datacoords.get_points()
x_lim = points[0][0]
y_lim = points[1][1]
ax.text(x=x_lim, y=y_lim, s="Horizontal Bars", weight="bold", fontsize=16) # <- Use text instead of title
which gives you an output of:

jupyter notebook matplotlib show plot and then plot on the origin figure

I want to plot a white plot with two axes, show it to the user, then add a line to the white plot with two axes, show it to the user, then add some dot to the line, then show it to the user. How can I do this without copying the code again and again?
What I'm doing now is in the first code chunk
import math
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
fig = plt.figure(figsize=(5,5))
ax = plt.axes()
ax.set_xlabel('cat')
ax.set_ylabel('dog')
plt.title("Set of 2 animals")
plt.show()
then in the second code chunk
fig = plt.figure(figsize=(5,5))
ax = plt.axes()
x = np.linspace(0, 1.0, 1000)
ax.plot(x, 1.0-x,zorder = 0)
ax.set_xlabel('cat')
ax.set_ylabel('dog')
plt.title("Set of 2 animals")
plt.show()
then in the third code chunk
fig = plt.figure(figsize=(5,5))
ax = plt.axes()
x = np.linspace(0, 1.0, 1000)
ax.plot(x, 1.0-x,zorder = 0)
ax.set_xlabel('cat')
ax.set_ylabel('dog')
plt.title("Set of 2 animals")
p0 = 0.5
p1 = 0.5
color = "blue"
textd =0.05
ax.scatter([p0],[p1], color = color,zorder=1)
ax.text(p0+textd, p1+textd, 'tiger',color = color,zorder =2)
plt.show()
What I'm looking for is things like in the first code chunk
import math
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
fig = plt.figure(figsize=(5,5))
ax = plt.axes()
ax.set_xlabel('cat')
ax.set_ylabel('dog')
plt.title("Set of 2 animals")
plt.show()
then in the second code chunk
add line directly without duplicating the code for making axes
plt.show()
then in the third code chunk
add point directly without duplicating the code for making axes and lines
plt.show()
Update: I actually figured out the answer.
def plot(step):
fig = plt.figure(figsize=(5,5))
ax = plt.axes()
ax.set_xlabel('cat')
ax.set_ylabel('dog')
plt.title("Set of 2 animals")
if step>=1:
x = np.linspace(0, 1.0, 1000)
ax.plot(x, 1.0-x,zorder = 0)
if step>=2:
p0 = 0.5
p1 = 0.5
color = "blue"
textd =0.05
ax.scatter([p0],[p1], color = color,zorder=1)
ax.text(p0+textd, p1+textd, 'tiger',color = color,zorder =2)
plot.show()
should be able to solve the problem.

define size of individual subplots side by side

I am using subplots side by side
plt.subplot(1, 2, 1)
# plot 1
plt.xlabel('MEM SET')
plt.ylabel('Memory Used')
plt.bar(inst_memory['MEMORY_SET_TYPE'], inst_memory['USED_MB'], alpha = 0.5, color = 'r')
# pol 2
plt.subplot(1, 2, 2)
plt.xlabel('MEM POOL')
plt.ylabel('Memory Used')
plt.bar(set_memory['POOL_TYPE'], set_memory['MEMORY_POOL_USED'], alpha = 0.5, color = 'g')
they have identical size - but is it possible to define the width for each subplot, so the right one could be wider as it has more entries and text would not squeeze or would it be possible to replace the bottom x-text by a number and have a legend with 1:means xx 2:means yyy
I find GridSpec helpful for subplot arrangements, see this demo at matplotlib.
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
import pandas as pd
N=24
inst_memory = pd.DataFrame({'MEMORY_SET_TYPE': np.random.randint(0,3,N),
'USED_MB': np.random.randint(0,1000,N)})
set_memory = pd.DataFrame({'MEMORY_POOL_USED': np.random.randint(0,1000,N),
'POOL_TYPE': np.random.randint(0,10,N)})
fig = plt.figure()
gs = GridSpec(1, 2, width_ratios=[1, 2],wspace=0.3)
ax1 = fig.add_subplot(gs[0])
ax2 = fig.add_subplot(gs[1])
ax1.bar(inst_memory['MEMORY_SET_TYPE'], inst_memory['USED_MB'], alpha = 0.5, color = 'r')
ax2.bar(set_memory['POOL_TYPE'], set_memory['MEMORY_POOL_USED'], alpha = 0.5, color = 'g')
You may need to adjust width_ratios and wspace to get the desired layout.
Also, rotating the text in x-axis might help, some info here.

How to fill histogram with color gradient where a fixed point represents the middle of of colormap

This code
import numpy as np
import matplotlib.pyplot as plt
def randn(n, sigma, mu):
return sigma * np.random.randn(n) + mu
x = randn(1000, 40., -100.)
cm = plt.cm.get_cmap("seismic")
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
_, bins, patches = ax.hist(x,color="r",bins=30)
bin_centers = 0.5*(bins[:-1]+bins[1:])
col = bin_centers - min(bin_centers)
col /= max(col)
for c, p in zip(col, patches):
plt.setp(p, "facecolor", cm(c))
plt.savefig("b.png", dpi=300, bbox_inches="tight")
produces the following histograms
I want to use the diverging colormap seismic and would like to have all bars representing the occurrence of negative numbers to be bluish and all bars representing positive numbers reddish. Around zero the bars should always be white. Therefore the first graph should be mostly reddish and the last one should be mostly bluish. How can I achieve that?
If this is about visual appearance only, you can normalize your colors to the range between the maximum absolute value and its negative counterpart, such that zero is always in the middle (max |bins|).
import numpy as np; np.random.seed(42)
import matplotlib.pyplot as plt
plt.rcParams["figure.figsize"] = 6.4,4
def randn(n, sigma, mu):
return sigma * np.random.randn(n) + mu
x1 = randn(999, 40., -80)
x2 = randn(750, 40., 80)
x3 = randn(888, 16., -30)
def hist(x, ax=None):
cm = plt.cm.get_cmap("seismic")
ax = ax or plt.gca()
_, bins, patches = ax.hist(x,color="r",bins=30)
bin_centers = 0.5*(bins[:-1]+bins[1:])
maxi = np.abs(bin_centers).max()
norm = plt.Normalize(-maxi,maxi)
for c, p in zip(bin_centers, patches):
plt.setp(p, "facecolor", cm(norm(c)))
fig, axes = plt.subplots(nrows=3, sharex=True)
for x, ax in zip([x1,x2,x3], axes):
hist(x,ax=ax)
plt.show()
I have an alternative answer for a different use case. I wanted to have the different colours from the divergent colormap be dynamically mapped to their respective "width" on either side of the divergence point. Additionally, I wanted to explicitly set the divergence point (in my case, 1).
I achieved this by modifying the answer from #ImportanceofBeingErnest, although in the end I didn't need to do any normalization, I just used two plots on the same figure, and chose the sequential colormaps which, when put end-to-end, re-formed the target divergent colormap.
def hist2(x, vmin, vmax, cmmap_name, ax=None,):
cm = plt.cm.get_cmap(cmmap_name)
ax = ax or plt.gca()
_, bins, patches = ax.hist(x,color="r",bins=50)
bin_centers = 0.5*(bins[:-1]+bins[1:])
norm = plt.Normalize(vmin, vmax)
for c, p in zip(bin_centers, patches):
plt.setp(p, "facecolor", cm(norm(c)))
data = <YOUR DATA>
left_data = [i for i in data if i < <YOUR DIVERGENCE POINT>]
right_data = [i for i in data if i >= <YOUR DIVERGENCE POINT>]
fig, ax = plt.subplots(nrows=1)
hist2(left_data, min(left_data), max(left_data), "YlOrRd_r", ax=ax)
hist2(right_data, min(right_data), max(right_data), "YlGn", ax=ax)
plt.show()
Some of my results:

Embedding small plots inside subplots in matplotlib

If you want to insert a small plot inside a bigger one you can use Axes, like here.
The problem is that I don't know how to do the same inside a subplot.
I have several subplots and I would like to plot a small plot inside each subplot.
The example code would be something like this:
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
for i in range(4):
ax = fig.add_subplot(2,2,i)
ax.plot(np.arange(11),np.arange(11),'b')
#b = ax.axes([0.7,0.7,0.2,0.2])
#it gives an error, AxesSubplot is not callable
#b = plt.axes([0.7,0.7,0.2,0.2])
#plt.plot(np.arange(3),np.arange(3)+11,'g')
#it plots the small plot in the selected position of the whole figure, not inside the subplot
Any ideas?
I wrote a function very similar to plt.axes. You could use it for plotting yours sub-subplots. There is an example...
import matplotlib.pyplot as plt
import numpy as np
#def add_subplot_axes(ax,rect,facecolor='w'): # matplotlib 2.0+
def add_subplot_axes(ax,rect,axisbg='w'):
fig = plt.gcf()
box = ax.get_position()
width = box.width
height = box.height
inax_position = ax.transAxes.transform(rect[0:2])
transFigure = fig.transFigure.inverted()
infig_position = transFigure.transform(inax_position)
x = infig_position[0]
y = infig_position[1]
width *= rect[2]
height *= rect[3] # <= Typo was here
#subax = fig.add_axes([x,y,width,height],facecolor=facecolor) # matplotlib 2.0+
subax = fig.add_axes([x,y,width,height],axisbg=axisbg)
x_labelsize = subax.get_xticklabels()[0].get_size()
y_labelsize = subax.get_yticklabels()[0].get_size()
x_labelsize *= rect[2]**0.5
y_labelsize *= rect[3]**0.5
subax.xaxis.set_tick_params(labelsize=x_labelsize)
subax.yaxis.set_tick_params(labelsize=y_labelsize)
return subax
def example1():
fig = plt.figure(figsize=(10,10))
ax = fig.add_subplot(111)
rect = [0.2,0.2,0.7,0.7]
ax1 = add_subplot_axes(ax,rect)
ax2 = add_subplot_axes(ax1,rect)
ax3 = add_subplot_axes(ax2,rect)
plt.show()
def example2():
fig = plt.figure(figsize=(10,10))
axes = []
subpos = [0.2,0.6,0.3,0.3]
x = np.linspace(-np.pi,np.pi)
for i in range(4):
axes.append(fig.add_subplot(2,2,i))
for axis in axes:
axis.set_xlim(-np.pi,np.pi)
axis.set_ylim(-1,3)
axis.plot(x,np.sin(x))
subax1 = add_subplot_axes(axis,subpos)
subax2 = add_subplot_axes(subax1,subpos)
subax1.plot(x,np.sin(x))
subax2.plot(x,np.sin(x))
if __name__ == '__main__':
example2()
plt.show()
You can now do this with matplotlibs inset_axes method (see docs):
from mpl_toolkits.axes_grid.inset_locator import inset_axes
inset_axes = inset_axes(parent_axes,
width="30%", # width = 30% of parent_bbox
height=1., # height : 1 inch
loc=3)
Update: As Kuti pointed out, for matplotlib version 2.1 or above, you should change the import statement to:
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
There is now also a full example showing all different options available.
From matplotlib 3.0 on, you can use matplotlib.axes.Axes.inset_axes:
import numpy as np
import matplotlib.pyplot as plt
fig, axes = plt.subplots(2,2)
for ax in axes.flat:
ax.plot(np.arange(11),np.arange(11))
ins = ax.inset_axes([0.7,0.7,0.2,0.2])
plt.show()
The difference to mpl_toolkits.axes_grid.inset_locator.inset_axes mentionned in #jrieke's answer is that this is a lot easier to use (no extra imports etc.), but has the drawback of being slightly less flexible (no argument for padding or corner locations).
source: https://matplotlib.org/examples/pylab_examples/axes_demo.html
from mpl_toolkits.axes_grid.inset_locator import inset_axes
import matplotlib.pyplot as plt
import numpy as np
# create some data to use for the plot
dt = 0.001
t = np.arange(0.0, 10.0, dt)
r = np.exp(-t[:1000]/0.05) # impulse response
x = np.random.randn(len(t))
s = np.convolve(x, r)[:len(x)]*dt # colored noise
fig = plt.figure(figsize=(9, 4),facecolor='white')
ax = fig.add_subplot(121)
# the main axes is subplot(111) by default
plt.plot(t, s)
plt.axis([0, 1, 1.1*np.amin(s), 2*np.amax(s)])
plt.xlabel('time (s)')
plt.ylabel('current (nA)')
plt.title('Subplot 1: \n Gaussian colored noise')
# this is an inset axes over the main axes
inset_axes = inset_axes(ax,
width="50%", # width = 30% of parent_bbox
height=1.0, # height : 1 inch
loc=1)
n, bins, patches = plt.hist(s, 400, normed=1)
#plt.title('Probability')
plt.xticks([])
plt.yticks([])
ax = fig.add_subplot(122)
# the main axes is subplot(111) by default
plt.plot(t, s)
plt.axis([0, 1, 1.1*np.amin(s), 2*np.amax(s)])
plt.xlabel('time (s)')
plt.ylabel('current (nA)')
plt.title('Subplot 2: \n Gaussian colored noise')
plt.tight_layout()
plt.show()