Related
I am trying to figure out how to fill between two lines that are on different scales & axes of subplot, however, I have not been able to figure out how to do this.
I have tried following the answer here for a similar question, but the formula supplied in the code doesn't work on my dataset and based on the responses from the author of that question the equation doesn't appear to work when the x limits are changed.
The following image is what I am after (created in Photoshop):
However, using the code below, I get:
Example Data & Code
import pandas as pd
import matplotlib.pyplot as plt
data = pd.DataFrame({'DEPTH':[4300, 4310, 4320, 4330, 4340, 4350, 4360, 4370, 4380, 4390],
'NEUT':[45, 40, 30, 12, 6, 12, 8, 10, 20, 18],
'DENS':[2.5, 2.55, 2.32, 2.35, 2.3, 2.55, 2.58, 2.6, 2.52, 2.53]})
fig = plt.subplots(figsize=(7,20))
ax1 = plt.subplot2grid((1,1), (0,0))
ax2 = ax1.twiny()
ax1.plot('DENS', 'DEPTH', data=data, color='red')
ax1.set_xlim(1.95, 2.95)
ax1.set_xlabel('Density')
ax1.xaxis.label.set_color("red")
ax1.tick_params(axis='x', colors="red")
ax1.spines["top"].set_edgecolor("red")
ax2.plot('NEUT', 'DEPTH', data=data, color='blue')
ax2.set_xlim(45, -15)
ax2.set_xlabel('Neutron')
ax2.xaxis.label.set_color("blue")
ax2.spines["top"].set_position(("axes", 1.04))
ax2.tick_params(axis='x', colors="blue")
ax2.spines["top"].set_edgecolor("blue")
ax1.fill_betweenx(data['DEPTH'], data['DENS'], data['NEUT'], where=data['DENS']>=data['NEUT'], interpolate=True, color='green')
ax1.fill_betweenx(data['DEPTH'], data['DENS'], data['NEUT'], where=data['DENS']<=data['NEUT'], interpolate=True, color='yellow')
for ax in [ax1, ax2]:
ax.set_ylim(4400, 4300)
ax.xaxis.set_ticks_position("top")
ax.xaxis.set_label_position("top")
Would anyone be able to help me with this please?
The difference between your code and the answer you linked is that your Neutron scale goes from the maximum value on the left to the minimum value on the right, which means the logic is slightly wrong. So we just need to switch a few min and max terms around.
Try this:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
data = pd.DataFrame({'DEPTH':[4300, 4310, 4320, 4330, 4340, 4350, 4360, 4370, 4380, 4390],
'NEUT':[45, 40, 30, 12, 6, 12, 8, 10, 20, 18],
'DENS':[2.5, 2.55, 2.32, 2.35, 2.3, 2.55, 2.58, 2.6, 2.52, 2.53]})
fig = plt.subplots(figsize=(6,8))
ax1 = plt.subplot2grid((1,1), (0,0))
ax2 = ax1.twiny()
ax1.plot('DENS', 'DEPTH', data=data, color='red')
ax1.set_xlim(1.95, 2.95)
ax1.set_xlabel('Density')
ax1.xaxis.label.set_color("red")
ax1.tick_params(axis='x', colors="red")
ax1.spines["top"].set_edgecolor("red")
ax2.plot('NEUT', 'DEPTH', data=data, color='blue')
ax2.set_xlim(45, -15)
ax2.set_xlabel('Neutron')
ax2.xaxis.label.set_color("blue")
ax2.spines["top"].set_position(("axes", 1.08))
ax2.tick_params(axis='x', colors="blue")
ax2.spines["top"].set_edgecolor("blue")
x = np.array(ax1.get_xlim())
z = np.array(ax2.get_xlim())
x1 = data['DENS']
x2 = data['NEUT']
nz=((x2-np.max(z))/(np.min(z)-np.max(z)))*(np.max(x)-np.min(x))+np.min(x)
ax1.fill_betweenx(data['DEPTH'], x1, nz, where=x1>=nz, interpolate=True, color='green')
ax1.fill_betweenx(data['DEPTH'], x1, nz, where=x1<=nz, interpolate=True, color='yellow')
for ax in [ax1, ax2]:
ax.set_ylim(4400, 4300)
ax.xaxis.set_ticks_position("top")
ax.xaxis.set_label_position("top")
plt.show()
(I changed the figure size so it would fit on my screen)
I'm trying to Add the slider in the plot similar to the slider demo example.
I'm plotting fill_between which gives PolyCollection object.
Although I tried with plot too which give Line2D object as shown picture below, but plot doesn't update as expected as in demo.
code
import numpy as np
import scipy.stats as ss
import matplotlib.pyplot as plt
import matplotlib.widgets as widgets
def get_pdf(mu, sigma=1, offset=4):
o = sigma * offset
x = np.linspace(mu - o, mu + o, 100)
rv = ss.norm(mu, sigma)
return x, rv.pdf(x)
fig, ax = plt.subplots()
plt.subplots_adjust(bottom=0.25)
ax.fill_between(*get_pdf(0, 1), alpha=0.7)
# t = plt.fill_between(*get_pdf(2, 1), alpha=0.7) # this gives ployCollection
t = ax.plot(*get_pdf(2, 1), label='treatment', alpha=0.7)
a = plt.axes([0.25, 0.1, 0.5, 0.03])
slider = widgets.Slider(a, "shift", 0, 10, valinit=2, valstep=1)
def update(val):
x, y = get_pdf(val)
t[0].set_ydata(y)
fig.canvas.draw_idle()
slider.on_changed(update)
plt.show()
To update the line plot, t[0].set_xdata(x) needs to be set, as it is different for each call. In this particular case, get_pdf each time returns the same y.
Updating the coordinates of the polyCollection generated by fill_between doesn't seem to be possible. However, you can delete and recreate it at every update. Note that this is slower than just updating the coordinates.
import numpy as np
import scipy.stats as ss
import matplotlib.pyplot as plt
import matplotlib.widgets as widgets
def get_pdf(mu, sigma=1, offset=4):
o = sigma * offset
x = np.linspace(mu - o, mu + o, 100)
rv = ss.norm(mu, sigma)
return x, rv.pdf(x)
fig, ax = plt.subplots()
plt.subplots_adjust(bottom=0.25)
ax.fill_between(*get_pdf(0, 1), alpha=0.7)
t = ax.fill_between(*get_pdf(2), color='crimson', alpha=0.7)
a = plt.axes([0.25, 0.1, 0.5, 0.03])
slider = widgets.Slider(a, "shift", 0, 10, valinit=2, valstep=1)
def update(val):
global t
t.remove()
t = ax.fill_between(*get_pdf(val), color='crimson', alpha=0.7)
fig.canvas.draw_idle()
slider.on_changed(update)
plt.show()
I have an array of shape(512,512).
Looks like, (row=x, column=y, density=z=the number of the array)
[[0.012825 0.020408 0.022976 ... 0.015938 0.02165 0.024357]
[0.036332 0.031904 0.025462 ... 0.031095 0.019812 0.024523]
[0.015831 0.027392 0.031939 ... 0.016249 0.01697 0.028686]
...
[0.024545 0.011895 0.022235 ... 0.033226 0.03223 0.030235]]
I had already drawn it into a 2D density plot. My goal is to find the center of the circle and draw a vertical and horizontal cross-section in one figure.
Now, I have the trouble to find the center of the circle and combine two cross-sections in one figure.
Please help.
This is my code:
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import scipy.ndimage
data = pd.read_csv('D:/BFP.csv', header=None)
# create data
data = np.array(data)
print(data)
#plot data
side = np.linspace(-1.5,1.5,512)
x,y = np.meshgrid(side,side)
z = [[data[i][j] for i in range(len(data[0]))]for j in range(len(data))]
#-- Extract the line...
# Make a line with "num" points...
x0, y0 = 270, 0 # These are in _pixel_ coordinates!!
x1, y1 = 270, 500
num = 512
x_, y_ = np.linspace(x0, x1, num), np.linspace(y0, y1, num)
# Extract the values along the line, using cubic interpolation
zi = scipy.ndimage.map_coordinates(z, np.vstack((x_,y_)))
#-- Plot...
fig, axes = plt.subplots(nrows=2)
axes[0].imshow(z,origin='lower')
axes[0].plot([x0, x1], [y0, y1], 'ro-')
#axes[0].axis('image')
axes[1].plot(zi)
plt.savefig('D:/vertical.png')
plt.show()
image here:
I cannot help you with finding the center of the circle, but you can create a nice visualization of the cross section by creating 3 axes in a grid. Usually, I would use GridSpec for this, but imhsow has a tendency to mess up the relative size of the axes to maintain square pixels. Thankfully, the AxesGrid toolkit can help.
The base of the code is inspired by this matplotlib example.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
from scipy.stats import multivariate_normal
import scipy
fig, main_ax = plt.subplots(figsize=(5, 5))
divider = make_axes_locatable(main_ax)
top_ax = divider.append_axes("top", 1.05, pad=0.1, sharex=main_ax)
right_ax = divider.append_axes("right", 1.05, pad=0.1, sharey=main_ax)
# make some labels invisible
top_ax.xaxis.set_tick_params(labelbottom=False)
right_ax.yaxis.set_tick_params(labelleft=False)
main_ax.set_xlabel('dim 1')
main_ax.set_ylabel('dim 2')
top_ax.set_ylabel('Z profile')
right_ax.set_xlabel('Z profile')
x, y = np.mgrid[-1:1:.01, -1:1:.01]
pos = np.empty(x.shape + (2,))
pos[:, :, 0] = x; pos[:, :, 1] = y
rv = multivariate_normal([-0.2, 0.2], [[1, 1.5], [0.25, 0.25]])
z = rv.pdf(pos)
z_max = z.max()
cur_x = 110
cur_y = 40
main_ax.imshow(z, origin='lower')
main_ax.autoscale(enable=False)
right_ax.autoscale(enable=False)
top_ax.autoscale(enable=False)
right_ax.set_xlim(right=z_max)
top_ax.set_ylim(top=z_max)
v_line = main_ax.axvline(cur_x, color='r')
h_line = main_ax.axhline(cur_y, color='g')
v_prof, = right_ax.plot(z[:,int(cur_x)],np.arange(x.shape[1]), 'r-')
h_prof, = top_ax.plot(np.arange(x.shape[0]),z[int(cur_y),:], 'g-')
plt.show()
Just for fun, you can even make it interactive
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
from scipy.stats import multivariate_normal
import scipy
fig, main_ax = plt.subplots(figsize=(5, 5))
divider = make_axes_locatable(main_ax)
top_ax = divider.append_axes("top", 1.05, pad=0.1, sharex=main_ax)
right_ax = divider.append_axes("right", 1.05, pad=0.1, sharey=main_ax)
# make some labels invisible
top_ax.xaxis.set_tick_params(labelbottom=False)
right_ax.yaxis.set_tick_params(labelleft=False)
main_ax.set_xlabel('dim 1')
main_ax.set_ylabel('dim 2')
top_ax.set_ylabel('Z profile')
right_ax.set_xlabel('Z profile')
x, y = np.mgrid[-1:1:.01, -1:1:.01]
pos = np.empty(x.shape + (2,))
pos[:, :, 0] = x; pos[:, :, 1] = y
rv = multivariate_normal([-0.2, 0.2], [[1, 1.5], [0.25, 0.25]])
z = rv.pdf(pos)
z_max = z.max()
main_ax.imshow(z, origin='lower')
main_ax.autoscale(enable=False)
right_ax.autoscale(enable=False)
top_ax.autoscale(enable=False)
right_ax.set_xlim(right=z_max)
top_ax.set_ylim(top=z_max)
v_line = main_ax.axvline(np.nan, color='r')
h_line = main_ax.axhline(np.nan, color='g')
v_prof, = right_ax.plot(np.zeros(x.shape[1]),np.arange(x.shape[1]), 'r-')
h_prof, = top_ax.plot(np.arange(x.shape[0]),np.zeros(x.shape[0]), 'g-')
def on_move(event):
if event.inaxes is main_ax:
cur_x = event.xdata
cur_y = event.ydata
v_line.set_xdata([cur_x,cur_x])
h_line.set_ydata([cur_y,cur_y])
v_prof.set_xdata(z[:,int(cur_x)])
h_prof.set_ydata(z[int(cur_y),:])
fig.canvas.draw_idle()
fig.canvas.mpl_connect('motion_notify_event', on_move)
plt.show()
NB: the lag is just due to the convertion in gif, the update is much smoother on my machine
Current version of matplotlib do not allow box-forced anymore, how should I do the same thing as the answer?
I am using matplotlib 3.1.0. After I ploted another set of data on the same plot with twinx() function, I want to change the aspect ratio of the actual plot area to 1.
Normally I do this and it works for non-twinx axis
ratio = 1
xleft, xright = ax.get_xlim()
ybottom, ytop = ax.get_ylim()
ax.set_aspect(abs((xright - xleft) / (ybottom - ytop)) * ratio)
For twinx axis, the above code do not work, but will not raise any error either.
Then I found an answer here
The code basically used the same method to set aspect ratio to 1, only with box-forced option.
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 1.6, 50) + 50.0
fig, ax = plt.subplots()
ax2 = ax.twinx()
XLIM = [50.0, 51.6]
YLIM = [0.0, 1.1, 0.0, 11.0]
ax.plot(x, np.sin(x - 50.0), 'b')
ax2.plot(x, np.cos(x - 50.0) * 10., 'r')
# set aspect to 1
ax.set(adjustable='box-forced',
xlim=XLIM, ylim=YLIM[:2],
xticks=np.arange(XLIM[0], XLIM[1], 0.2),
yticks=np.arange(YLIM[0], YLIM[1] + 0.1, 0.1)[:-1],
aspect=(XLIM[1] - XLIM[0]) / (YLIM[1] - YLIM[0]))
ax2.set(adjustable='box-forced',
ylim=YLIM[2:],
yticks=np.arange(YLIM[2], YLIM[3] + 1.0, 1.0),
aspect=(XLIM[1] - XLIM[0]) / (YLIM[3] - YLIM[2]))
ax.grid(True, which='major', linestyle='solid')
plt.show()
This code in my python don't work, raises
ValueError: 'box-forced' is not a valid value for adjustable; supported values are 'box', 'datalim'
And if I change that to 'box', it gives
RuntimeError: Adjustable 'box' is not allowed in a twinned Axes. Use 'datalim' instead.
I am not sure from when the box-forced was removed.
Now how should we set the aspect ratio in a 'box' manner?
Thanks!
For reference: matplotlib.axes.Axes.set_adjustable
As I just commented on a respective matplotlib issue,
"aspect" in matplotlib always refers to the data, not the axes box. Therefore setting the aspect for twinned or shared axes and letting the box be adjustable actually only makes sense when the scales are the same - or differ by an offset (as opposed to any other linear or nonlinear function). Matplotlib does not perform any check on this, so it disallows for adjustable='box' in such case.
It seems to me that using aspect here is merely a workaround for getting a fixed ratio for the axes box. Matplotlib does not provide any clear codepath for that as of now, but one could e.g. force the axes box into a square space by adjusting the subplot parameters
import numpy as np
import matplotlib.pyplot as plt
def squarify(fig):
w, h = fig.get_size_inches()
if w > h:
t = fig.subplotpars.top
b = fig.subplotpars.bottom
axs = h*(t-b)
l = (1.-axs/w)/2
fig.subplots_adjust(left=l, right=1-l)
else:
t = fig.subplotpars.right
b = fig.subplotpars.left
axs = w*(t-b)
l = (1.-axs/h)/2
fig.subplots_adjust(bottom=l, top=1-l)
x = np.linspace(0,1.6,50) + 50.0
fig, ax = plt.subplots()
ax2 = ax.twinx()
ax.set(xlim = [50.0, 51.6], ylim = [0.0, 1.1])
ax2.set(ylim = [0.0, 11.0])
ax.plot(x,np.sin(x-50.0),'b')
ax2.plot(x,np.cos(x-50.0)*10.,'r')
ax.grid(True, which='major',linestyle='solid')
squarify(fig)
fig.canvas.mpl_connect("resize_event", lambda evt: squarify(fig))
plt.show()
Also see this answer for more than one subplot.
If you want to use mpl_toolkits and make your hands dirty, this answer would be a good read.
Thanks to #ImportanceOfBeingErnest, but to make this work in several subplots, I found another way inspired by your answer:
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import Divider, Size
from mpl_toolkits.axes_grid1.axes_divider import AxesDivider
def make_patch_spines_invisible(ax):
ax.set_frame_on(True)
ax.patch.set_visible(False)
for sp in ax.spines.values():
sp.set_visible(False)
def demo_fixed_size_axes():
fig, axs = plt.subplots(1, 2, figsize=(12, 9))
axs[0].plot([1, 2, 3])
axs[1].plot([1, 2, 3.5])
ax3 = axs[1].twinx()
ax3.plot([1, 2, 3], [1, 25, 30])
axs[1].spines['right'].set_visible(False)
make_patch_spines_invisible(ax4Alt)
ax4Alt.spines['right'].set_visible(True)
for ax in fig.get_axes():
figPos = AxesDivider(ax).get_position()
h = [Size.Fixed(4)] # has to be fixed
v = h
divider = Divider(fig, figPos, h, v, aspect=False)
ax.set_axes_locator(divider.new_locator(nx=0, ny=0))
if __name__ == "__main__":
demo_fixed_size_axes()
plt.show()
The disadvantage is that one has to decide which size to use in inches.
I do not fully understand my code though...
What I would like to achieve is having a plot like
So, if the green line should have the line color of value 19, it should be the same color as between 19 and 20 on the right hand side. I also know exactly the range where I will have values (here from 17 to 25).
The approach below sometimes work, but strangely it does only work about each second time. When I execute it again, suddenly I get a completely blue line (rgb 0 0 255). Is there something wrong with my approach?
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
cmap = plt.get_cmap('cubehelix')
minval = 17
maxval = 25
bounds = np.arange(minval, maxval+1)
mynorm = matplotlib.colors.Normalize(vmin = minval, vmax = maxval)
sm = matplotlib.cm.ScalarMappable(norm=mynorm, cmap=cmap)
color = sm.to_rgba(20)
ax.plot([0, 100], [0, 100], c=color, lw=2)
ax2 = fig.add_axes([0.90, 0.1, 0.03, 0.8])
cb = matplotlib.colorbar.ColorbarBase(ax2, cmap=cmap, norm=mynorm, spacing='proportional', ticks=bounds, boundaries=bounds, format='%1i')
plt.show()
I'm using Python 3.5.0 with matplotlib 1.4.3.
I have found that the c parameter can sometimes produce non-intuitive results. However, simply using the color parameter tends to be much more consistent.
Does this code fix your issue?
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
cmap = plt.get_cmap('cubehelix')
minval = 17
maxval = 25
bounds = np.arange(minval, maxval+1)
mynorm = matplotlib.colors.Normalize(vmin = minval, vmax = maxval)
sm = matplotlib.cm.ScalarMappable(norm=mynorm, cmap=cmap)
color = sm.to_rgba(20)
ax.plot([0, 100], [0, 100], color=color, lw=2) # Using 'color' instead of 'c'
ax2 = fig.add_axes([0.90, 0.1, 0.03, 0.8])
cb = matplotlib.colorbar.ColorbarBase(
ax2, cmap=cmap, norm=mynorm, spacing='proportional',
ticks=bounds, boundaries=bounds, format='%1i')
plt.show()
This issue is referenced here, https://github.com/matplotlib/matplotlib/issues/5197 it will be fixed in 1.5.0
Your code looks fine. I ran it on Python 2.7.10 |Anaconda 2.3.0 (x86_64)|OS X 10.10.5 without getting your reported error.