Share X axis between line and bar plot in Python's Matplotlib - python

I have the following script for generating a figure with two subplots: one line plot, and one bar plot.
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
plt.close('all')
np.random.seed(42)
n = 1000
idx = pd.date_range(end='2020-02-27', periods=n)
df = pd.Series(np.random.randint(-5, 5, n),
index=idx)
curve = df.cumsum()
bars = df.resample('M').sum()
fig = plt.figure()
ax1 = fig.add_subplot(211)
ax2 = fig.add_subplot(212)
curve.plot(ax=ax1)
bars.plot(kind='bar', ax=ax2)
fig.set_tight_layout(True)
I would like to share the x axis between the two subplots, however the command ax2 = fig.add_subplot(212, sharex=ax1) will result in an empty graph for the line plot like the following figure.

Here is my version based on Matplotlib (without pandas api for plotting), may be it would be helpful.
I explicitly set the width of bars.
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
%matplotlib inline
plt.close('all')
np.random.seed(42)
n = 1000
idx = pd.date_range(end='2020-02-27', periods=n)
df = pd.Series(np.random.randint(-5, 5, n), index=idx)
curve = df.cumsum()
bars = df.resample('M').sum()
#fig = plt.figure()
#ax1 = fig.add_subplot(211)
#ax2 = fig.add_subplot(212)
#curve.plot(ax=ax1)
#bars.plot(kind='bar', ax=ax2)
fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True, gridspec_kw={'hspace': 0})
ax1.plot(curve.index, curve.values)
ax2.bar(bars.index, bars.values, width = (bars.index[0] - bars.index[1])/2)
fig.set_tight_layout(True)
_ = plt.xticks(bars.index, bars.index, rotation=90)

Related

How to normalize a 2d histogram in python?

I'm trying to plot a 2d histogram. The histogram is basically a galaxy and I have the points of each luminous point. I have plotted the histogram but it's not properly normalized, as the values of the colorbar should go from 0 to 1. How can I fix this?
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.stats import kde
fig, axes = plt.subplots(ncols=2, nrows=1, figsize=(20, 8))
data1 = pd.read_csv('mydata.txt', sep='\s+', header=None)
az1 = data1[0]
el1 = data1[1]
nbins = 250
hist1 = axes[0].hist2d(az1, el1, bins=nbins, cmap='magma', density=True)
fig.colorbar(hist1[3], ax = axes)
I tried with the function hist2Dbut I didn't find a way to normalized the result with it. So what I suggest is using the hitrogram from the numpy modul: np.nistogram2d where you can extract the result and then normalized the output before display it.
Here an example with random numbers:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.stats import kde
fig, axes = plt.subplots(ncols=2, nrows=1, figsize=(20, 8))
# data1 = pd.read_csv('mydata.txt', sep='\s+', header=None)
N=10000
az1 = np.random.random(N)
el1 = np.random.random(N)
nbins = 250
hist1 = axes[0].hist2d(az1, el1, bins=nbins, cmap='magma', density=True)
fig.colorbar(hist1[3], ax = axes)
H, xedges, yedges = np.histogram2d(el1, az1, bins=(nbins, nbins),density=True )
# H_normalized = H/float(az1.shape[0]) # the integral over the histogrm is 1
H_normalized = H/H.max((0,1)) # the max value of the histogrm is 1
extent = [xedges[0], xedges[-1], yedges[0], yedges[-1]]
im = axes[1].imshow(H_normalized, extent=extent, cmap='magma', interpolation='none',origin ='lower')
fig.colorbar(im, ax=axes[1])
plt.show()

matplotlib multiple Y-axis pandas plot

Could someone give me a tip on how to do multiple Y axis plots?
This is some made up data below, how could I put Temperature its own Y axis, Pressure on its own Y axis, and then have both Value1 and Value2 on the same Y axis. I am trying to go for the same look and feel of this SO post answer. Thanks for any tips, I don't understand ax3 = ax.twinx() process, like as far as do I need to define an ax.twinx() for each separate Y axis plot I need?
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
rows,cols = 8760,4
data = np.random.rand(rows,cols)
tidx = pd.date_range('2019-01-01', periods=rows, freq='H')
df = pd.DataFrame(data, columns=['Temperature','Value1','Pressure','Value2'], index=tidx)
# using subplots() function
fig, ax = plt.subplots(figsize=(25,8))
plt.title('Multy Y Plot')
ax2 = ax.twinx()
ax3 = ax.twinx()
ax4 = ax.twinx()
plot1, = ax.plot(df.index, df.Temperature)
plot2, = ax2.plot(df.index, df.Value1, color = 'r')
plot3, = ax3.plot(df.index, df.Pressure, color = 'g')
plot4, = ax4.plot(df.index, df.Value2, color = 'b')
ax.set_xlabel('Date')
ax.set_ylabel('Temperature')
ax2.set_ylabel('Value1')
ax3.set_ylabel('Pressure')
ax4.set_ylabel('Value2')
plt.legend([plot1,plot2,plot3,plot4],list(df.columns))
# defining display layout
plt.tight_layout()
# show plot
plt.show()
This will output everything jumbled up on the same side without separate Y axis for Pressure, Value1, and Value2.
You are adding 4 different plots in one, which is not helpful. I would recommend breaking it into 2 plots w/ shared x-axis "Date":
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
rows,cols = 8760,4
data = np.random.rand(rows,cols)
tidx = pd.date_range('2019-01-01', periods=rows, freq='H')
df = pd.DataFrame(data, columns=['Temperature','Value1','Pressure','Value2'], index=tidx)
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(25,8))
plt.title('Multy Y Plot')
ax1b = ax1.twinx()
plot1a, = ax1.plot(df.index, df.Temperature)
plot1b, = ax1b.plot(df.index, df.Pressure, color='r')
ax1.set_ylabel('Temperature')
ax1b.set_ylabel('Pressure')
ax2b = ax2.twinx()
plot2a, = ax2.plot(df.index, df.Value1, color='k')
plot2b, = ax2b.plot(df.index, df.Value2, color='g')
ax2.set_xlabel('Date')
ax2.set_ylabel('Value1')
ax2b.set_ylabel('Value2')
plt.legend([plot1a, plot1b, plot2a, plot2b], df.columns)
# defining display layout
plt.tight_layout()
# show plot
plt.show()
Here I have added in the first plot (on the top) Temperature and Pressure and on the second plot (on the bottom) Value 1 and Value 2. Normally, we add in the same plot things that make sense to compare on the same x-axis. Pressure and Temperature is a valid combination that is why I combined those two together. But you can do as you wish.
This answer below uses mpatches is how to make the subplot of Value1 and Value2 on the same axis. The solution for this post has subplot for Value1 and Value2 on different axis. Thanks for the help #tzinie!
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
rows,cols = 8760,4
data = np.random.rand(rows,cols)
tidx = pd.date_range('2019-01-01', periods=rows, freq='H')
df = pd.DataFrame(data, columns=['Temperature','Value1','Pressure','Value2'], index=tidx)
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(25,8))
plt.title('Multy Y Plot')
ax1b = ax1.twinx()
plot1a, = ax1.plot(df.index, df.Temperature, color='r') # red
plot1b, = ax1b.plot(df.index, df.Pressure, color='b') # blue
ax1.set_ylabel('Temperature')
ax1b.set_ylabel('Pressure')
ax2.plot(df.index, df.Value1, color='k') # black
ax2.plot(df.index, df.Value2, color='g') # green
ax2.set_xlabel('Date')
ax2.set_ylabel('Value1 & Value2')
red_patch = mpatches.Patch(color='red', label='Temperature')
blue_patch = mpatches.Patch(color='blue', label='Pressure')
green_patch = mpatches.Patch(color='green', label='Value2')
black_patch = mpatches.Patch(color='black', label='Value1')
plt.legend(handles=[red_patch,blue_patch,green_patch,black_patch])
# defining display layout
#plt.tight_layout()
# show plot
plt.show()

barh with plot : cannot get different scale for data on secondary x axis

I cannot get two different scales for the plot:
I don't know how to activate the scale of the secondary x axis.
"STK" and "Material" are supposed to be displayed at different scales.
How to display "Material" on it's own scale (0,max) like it was done automatically for "STK"?
I need it to be displayed like on the image below :
Here's the code:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
df = [['MPP1',400,30],['MPP2',3500,700], ['MPP3',1900,3], ['MPP4',15000,56], ['MPP5',8500,306]]
df = pd.DataFrame(df)
df.columns =['MPP', 'STK', 'Material']
plt.rcdefaults()
fig, ax = plt.subplots(constrained_layout=True)
xdata = df.STK
x2data = df.Material
ydata = df.MPP
y_pos = np.arange(len(ydata))
ax.barh(y_pos, df.STK , label='STK per MPP')
ax.invert_yaxis()
ax.plot(x2data, ydata, label='Material per MPP', color='red')
ax.set_xlabel('STK')
ax.legend()
ax2 = ax.secondary_xaxis('top')
ax2.set_xlabel('Material')
ax2.set_xticks(df.Material)
ax2.set_xticklabels(df.Material)
ax2.set_xlabel(r"Material")
plt.show()
You should create the secondary axis with:
ax2 = ax.twiny()
and plot your data on it:
ax2.plot(x2data, ydata, label='Material per MPP', color='red')
Pay attention: ax2.plot, not ax.plot.
Complete Code
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
df = [['MPP1',400,30],['MPP2',3500,700], ['MPP3',1900,3], ['MPP4',15000,56], ['MPP5',8500,306]]
df = pd.DataFrame(df)
df.columns =['MPP', 'STK', 'Material']
plt.rcdefaults()
fig, ax = plt.subplots(constrained_layout=True)
xdata = df.STK
x2data = df.Material
ydata = df.MPP
y_pos = np.arange(len(ydata))
ax.barh(y_pos, df.STK , label='STK per MPP')
ax.invert_yaxis()
ax.set_xlabel('STK')
leg = plt.legend()
ax2 = ax.twiny()
ax2.plot(x2data, ydata, label='Material per MPP', color='red')
ax2.set_xlabel('Material')
leg2 = plt.legend()
plt.legend(leg.get_patches()+leg2.get_lines(),
[text.get_text() for text in leg.get_texts()+leg2.get_texts()])
leg.remove()
plt.show()

In python, how to correctly use `colorbar` and `pcolormesh`?

Here is my code,
from mpl_toolkits.axes_grid1 import make_axes_locatable # colorbar
from matplotlib import pyplot as plt
from matplotlib import cm # 3D surface color
import numpy as np
data1 = np.random.rand(10, 12)
data2 = np.random.rand(10, 12)
data3 = data1 - data2
vmin = min([data1.min(), data2.min(), data3.min()])
vmax = max([data1.max(), data2.max(), data2.max()])
fig, (ax_1, ax_2, ax_error) = plt.subplots(nrows=3, ncols=1, figsize=(6, 6))
ax_1.set_ylabel('x')
mesh_1 = ax_1.pcolormesh(data1.T, cmap = cm.coolwarm)
ax_2.set_ylabel('x')
mesh_2 = ax_2.pcolormesh(data2.T, cmap = cm.coolwarm)
mesh_error = ax_error.pcolormesh(data3.T, cmap = cm.coolwarm)
ax_error.set_ylabel('x')
ax_error.set_xlabel('t')
divider = make_axes_locatable(ax_2)
cax_val = divider.append_axes("right", size="2%", pad=.1)
fig.colorbar(mesh_2, ax=[ax_1, ax_2, ax_error], cax=cax_val)
fig.tight_layout()
plt.show()
and it produces an image
However, what I expect is that it produces the picture below
Can anyone help me with this problem? Thanks in advance for any helpful suggestion!
tight_layout doesn't help with this problem, unfortunately. No tight_layout and no axes_grid works fine:
from matplotlib import pyplot as plt
from matplotlib import cm # 3D surface color
import numpy as np
data1 = np.random.rand(10, 12)
data2 = np.random.rand(10, 12)
data3 = data1 - data2
fig, (ax_1, ax_2, ax_error) = plt.subplots(nrows=3, ncols=1, figsize=(6, 6))
mesh_1 = ax_1.pcolormesh(data1.T, cmap = cm.coolwarm)
mesh_2 = ax_2.pcolormesh(data2.T, cmap = cm.coolwarm)
mesh_error = ax_error.pcolormesh(data3.T, cmap = cm.coolwarm)
fig.colorbar(mesh_2, ax=[ax_1, ax_2, ax_error])
plt.show()
If you want better spacing you can try constrained_layout:
fig, (ax_1, ax_2, ax_error) = plt.subplots(nrows=3, ncols=1, figsize=(6, 6),
constrained_layout=True)
Constrained layout will also work for just one axes:
fig.colorbar(mesh_2, ax=ax_2)
With the help from #JodyKlymak, I finally solved the problem. The keypoint lies in using shrink, i.e. fig.colorbar(mesh_2, ax=[ax_1, ax_2, ax_error], shrink=0.3). Here is the solution
from matplotlib import pyplot as plt
from matplotlib import cm # 3D surface color
import numpy as np
data1 = np.random.rand(10, 12)
data2 = np.random.rand(10, 12)
data3 = data1 - data2
fig, (ax_1, ax_2, ax_error) = plt.subplots(nrows=3, ncols=1, figsize=(6, 6))
mesh_1 = ax_1.pcolormesh(data1.T, cmap = cm.coolwarm)
mesh_2 = ax_2.pcolormesh(data2.T, cmap = cm.coolwarm)
mesh_error = ax_error.pcolormesh(data3.T, cmap = cm.coolwarm)
fig.colorbar(mesh_2, ax=[ax_1, ax_2, ax_error], shrink=0.3)
plt.show()
and it produces

imshow and plot side by side

I'm trying to put side-by-side numpy array displayed as image and seaborn distplot of the same array. I've came up with the following function:
def visualize(arr):
f, (ax1, ax2) = plt.subplots(1, 2, gridspec_kw = {'width_ratios': [1, 3]})
ax1.imshow(arr)
flat = arr.flatten()
x = flat[~np.isnan(flat)]
sns.distplot(x, ax=ax2)
plt.show()
which produces:
As you can see, the image has smaller height than the plot. How can I modify my function in order to have the same height for the plot and the imshow?
I want the following placement of the image and the plot:
There are just so many ways to tackle this. All of the following will give more or less the same image
A. Reduce the available space
You may reduce the available space such that both plots are constrained to the same vertical margins. This can be done by
reducing figure height
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(6,2.3), ...)
using subplots_adjust to limit the margins
fig.subplots_adjust(top=0.7, bottom=0.3)
B. Use InsetPosition
You may use mpl_toolkits.axes_grid1.inset_locator.InsetPosition to adjust the coordinates of the second axes to match those of the first one.
import seaborn as sns
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import InsetPosition
def visualize(arr):
fig, (ax1, ax2) = plt.subplots(1, 2,
gridspec_kw = {'width_ratios': [1, 3]})
ax1.imshow(arr)
flat = arr.flatten()
x = flat[~np.isnan(flat)]
sns.distplot(x, ax=ax2)
ip = InsetPosition(ax1, [1.5,0,3,1])
ax2.set_axes_locator(ip)
plt.show()
arr = np.random.randn(200,120)
visualize(arr)
C. Use an axes divider
You may create only the axes for the image and then use mpl_toolkits.axes_grid1.make_axes_locatable to create a new axes next to it.
import seaborn as sns
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
def visualize(arr):
fig, ax = plt.subplots()
divider = make_axes_locatable(ax)
ax2 = divider.new_horizontal(size="300%", pad=0.5)
fig.add_axes(ax2)
ax.imshow(arr)
flat = arr.flatten()
x = flat[~np.isnan(flat)]
sns.distplot(x, ax=ax2)
plt.show()
arr = np.random.randn(200,120)
visualize(arr)
D. calculate the desired aspect ratio
import seaborn as sns
import numpy as np
import matplotlib.pyplot as plt
def visualize(arr):
gkw = {'width_ratios':[1, 3] }
fig, (ax1, ax2) = plt.subplots(1, 2, gridspec_kw = gkw )
ax1.imshow(arr)
flat = arr.flatten()
x = flat[~np.isnan(flat)]
sns.distplot(x, ax=ax2)
ya = np.diff(np.array(ax2.get_ylim()))[0]
xa = np.diff(np.array(ax2.get_xlim()))[0]
wa = gkw['width_ratios'][0]/float(gkw['width_ratios'][1])
ia = arr.shape[0]/float(arr.shape[1])
ax2.set_aspect(float(wa*ia/(ya/xa)))
plt.show()
arr = np.random.randn(200,120)
visualize(arr)
E. Dynamically copy positions
You may get the position of the left plot and copy its y-coordinates to the right subplot's position. This is a nice add-on to existing code. The drawback is necessary because subsequent changes to the figure size require to recalculate the positions.
import seaborn as sns
import numpy as np
import matplotlib.pyplot as plt
def visualize(arr):
gkw = {'width_ratios':[1, 3] }
fig, (ax1, ax2) = plt.subplots(1, 2, gridspec_kw = gkw )
ax1.imshow(arr)
flat = arr.flatten()
x = flat[~np.isnan(flat)]
sns.distplot(x, ax=ax2)
def on_resize(evt=None):
ax1.apply_aspect()
bb1 = ax1.get_position()
bb2 = ax2.get_position()
bb2.y0 = bb1.y0; bb2.y1 = bb1.y1
ax2.set_position(bb2)
fig.canvas.mpl_connect("resize_event", on_resize)
on_resize()
plt.show()
arr = np.random.randn(200,120)
visualize(arr)

Categories