Related
I'm trying to plot 3D data in 2D using orthographic projection. Here is partially what I'm looking for:
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
fig = plt.figure(figsize=(10,10),facecolor='white')
axs = [fig.add_subplot(223)]
axs.append(fig.add_subplot(224))#,sharey=axs[0]))
axs.append(fig.add_subplot(221))#,sharex=axs[0]))
rng = np.random.default_rng(12345)
values = rng.random((100,3))-.5
values[:,1] = 1.6*values[:,1]
values[:,2] = .5*values[:,2]
for ax,axis in zip(axs,['y','x','z']):
axis1,axis2={'x':(1,2),'y':(0,2),'z':(0,1)}[axis]
ax.add_patch(plt.Circle([0,0], radius=.2, color='pink',zorder=-20))
ax.scatter(values[:,axis1],values[:,axis2])
axs[0].set_xlabel('x')
axs[2].set_ylabel('y')
axs[1].set_xlabel('y')
axs[0].set_ylabel('z')
fig.subplots_adjust(.08,.06,.99,.99,0,0)
plt.show()
There are some issues with this plot and the fixes I tried: I would need 'equal' aspect so that the circles are actually circle. I would also need the circles to be of the same size in each subplot. Finally, I would like the space to be optimized (i.e. with as little white space inside and between the subplots as possible).
I have tried sharing the axis between the subplots, then doing .axis('scaled') or .set_aspect('equal','box',share=True) for each axes, but the axis end up not being properly shared, and the circle in each subplot end up of different sizes. And while it crops the subplots to the data, it leaves a lot of space between the subplots. .axis('equal') or .set_aspect('equal','datalim',share=True) without axis shared leaves white space inside the subplots, and with shared axis, it leaves out some data.
Any way to make it work? And it would be perfect if it can work on matplotlib 3.4.3.
You can use a common xlim, ylim for your subplots and set your equal ratio with ax.set_aspect(aspect='equal', adjustable='datalim'):
See full code below:
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
fig = plt.figure(figsize=(10,10),facecolor='white')
axs = [fig.add_subplot(223)]
axs.append(fig.add_subplot(224))#,sharey=axs[0]))
axs.append(fig.add_subplot(221))#,sharex=axs[0]))
rng = np.random.default_rng(12345)
values = rng.random((100,3))-.5
values[:,1] = 1.6*values[:,1]
values[:,2] = .5*values[:,2]
for ax,axis in zip(axs,['y','x','z']):
axis1,axis2={'x':(1,2),'y':(0,2),'z':(0,1)}[axis]
ax.add_patch(plt.Circle([0,0], radius=.2, color='pink',zorder=-20))
ax.scatter(values[:,axis1],values[:,axis2])
ax.set_xlim([np.amin(values),np.amax(values)])
ax.set_ylim([np.amin(values),np.amax(values)])
ax.set_aspect('equal', adjustable='datalim')
axs[0].set_xlabel('x')
axs[2].set_ylabel('y')
axs[1].set_xlabel('y')
axs[0].set_ylabel('z')
fig.subplots_adjust(.08,.06,.99,.99,0,0)
plt.show()
The output gives:
I made it work using gridspec (I changed scatter for plot to visually make sure no data gets left out). It requires some tweaking of the figsize to really minimize the white space within the axes. Thank you to #jylls for the intermediate solution.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
%matplotlib inline
rng = np.random.default_rng(12345)
values = rng.random((100,3))-.5
values[:,1] = 1.6*values[:,1]
values[:,2] = .5*values[:,2]
fig = plt.figure(figsize=(10,8),facecolor='white')
ranges = np.ptp(values,axis=0)
gs = GridSpec(2, 2, None,.08,.06,.99,.99,0,0, width_ratios=[ranges[0], ranges[1]], height_ratios=[ranges[1], ranges[2]])
axs = [fig.add_subplot(gs[2])]
axs.append(fig.add_subplot(gs[3]))#,sharey=axs[0]))
axs.append(fig.add_subplot(gs[0]))#,sharex=axs[0]))
for ax,axis in zip(axs,['y','x','z']):
axis1,axis2={'x':(1,2),'y':(0,2),'z':(0,1)}[axis]
ax.add_patch(plt.Circle([0,0], radius=.2, color='pink',zorder=-20))
ax.plot(values[:,axis1],values[:,axis2])
ax.set_aspect('equal', adjustable='datalim')
axs[0].set_xlabel('x')
axs[2].set_ylabel('y')
axs[1].set_xlabel('y')
axs[0].set_ylabel('z')
plt.show()
I was trying to help someone add a colorbar for the vertical blue bar in the image below. We tried many variations of plt.colorbar(row_colors) (like above and below sns.clustermap()) and looked around online for 2 hours, but no luck. We just want to add a colorbar for the blues, please help!
import pickle
import numpy as np
import seaborn as sns
import pandas as pd
import matplotlib.pyplot as plt
feat_mat, freq, label = pickle.load(open('file.pkl', 'rb'))
feat_mat_df = pd.DataFrame(feat_mat[4])
freq_df = pd.DataFrame(freq)
freq_df_transposed = freq_df.transpose()
my_palette = dict(zip(set(freq_df_transposed[int('4')]), sns.color_palette("PuBu", len(set(freq_df_transposed[int('4')]))))))
row_colors = freq_df_transposed[int('4')].map(my_palette)
sns.clustermap(feat_mat_df, metric="euclidean", standard_scale=1, method="complete", cmap="coolwarm", row_colors = row_colors)
plt.show()
This is where he based his code from: #405 Dendrogram with heatmap and coloured leaves
I think something like this should work for your purposes- I didn't have a clustermap example available but the logic is the same to do what you want to do. Basically-you're going to take that list of colors you made and imshow it, then hide the imshow plot, and plot the colorbar in its place.
In my example, I use make_axes_locatable to place axes next to the plot with your data to put the colorbar inside - https://matplotlib.org/2.0.2/mpl_toolkits/axes_grid/users/overview.html. I find placing a new axes for other objects (legends color maps or otherwise) easier than trying to draw them on the same axes.
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np; np.random.seed(0)
import seaborn as sns
from mpl_toolkits.axes_grid1 import make_axes_locatable
import random
uniform_data = np.random.rand(10, 12)
fig, ax = plt.subplots(1,1, figsize = (5,5))
divider = make_axes_locatable(ax)
axDivY = divider.append_axes( 'right', size=0.2, pad= 0.1)
axDivY2 = divider.append_axes( 'right', size=0.2, pad= 0.2)
# we will use this for the colorscale bar
axDivY3 = divider.append_axes( 'right', size=0.2, pad= 0.2)
ax1 = sns.heatmap(uniform_data, ax=ax, cbar_ax=axDivY)
# the palette you were using to make the label column on the clustermap
# some simulated labels for your data with values
color_label_list =[random.randint(0,20) for i in range(20)]
pal = sns.color_palette("PuBu", len(set(color_label_list)))
n = len(pal)
size = 1
# plot the colors with imshow to make a colormap later
ax2 = axDivY2.imshow(np.array([color_label_list]),
cmap=mpl.colors.ListedColormap(list(pal)),
interpolation="nearest", aspect="auto")
# turn off the axes so they aren't visible- note that you need ax.axis('off) if you have older matplotlib
axDivY2.set_axis_off()
axDivY2.set_visible(False)
# plot the colorbar on the other axes (which is on top of the one that we turned off)
plt.colorbar(ax2, cax = axDivY3) ;
I frequently find myself working in log units for my plots, for example taking np.log10(x) of data before binning it or creating contour plots. The problem is, when I then want to make the plots presentable, the axes are in ugly log units, and the tick marks are evenly spaced.
If I let matplotlib do all the conversions, i.e. by setting ax.set_xaxis('log') then I get very nice looking axes, however I can't do that to my data since it is e.g. already binned in log units. I could manually change the tick labels, but that wouldn't make the tick spacing logarithmic. I suppose I could also go and manually specify the position of every minor tick such it had log spacing, but is that the only way to achieve this? That is a bit tedious so it would be nice if there is a better way.
For concreteness, here is a plot:
I want to have the tick labels as 10^x and 10^y (so '1' is '10', 2 is '100' etc.), and I want the minor ticks to be drawn as ax.set_xaxis('log') would draw them.
Edit: For further concreteness, suppose the plot is generated from an image, like this:
import matplotlib.pyplot as plt
import scipy.misc
img = scipy.misc.face()
x_range = [-5,3] # log10 units
y_range = [-55, -45] # log10 units
p = plt.imshow(img,extent=x_range+y_range)
plt.show()
and all we want to do is change the axes appearance as I have described.
Edit 2: Ok, ImportanceOfBeingErnest's answer is very clever but it is a bit more specific to images than I wanted. I have another example, of binned data this time. Perhaps their technique still works on this, though it is not clear to me if that is the case.
import numpy as np
import pandas as pd
import datashader as ds
from matplotlib import pyplot as plt
import scipy.stats as sps
v1 = sps.lognorm(loc=0, scale=3, s=0.8)
v2 = sps.lognorm(loc=0, scale=1, s=0.8)
x = np.log10(v1.rvs(100000))
y = np.log10(v2.rvs(100000))
x_range=[np.min(x),np.max(x)]
y_range=[np.min(y),np.max(y)]
df = pd.DataFrame.from_dict({"x": x, "y": y})
#------ Aggregate the data ------
cvs = ds.Canvas(plot_width=30, plot_height=30, x_range=x_range, y_range=y_range)
agg = cvs.points(df, 'x', 'y')
# Create contour plot
fig = plt.figure()
ax = fig.add_subplot(111)
ax.contourf(agg, extent=x_range+y_range)
ax.set_xlabel("x")
ax.set_ylabel("y")
plt.show()
The general answer to this question is probably given in this post:
Can I mimic a log scale of an axis in matplotlib without transforming the associated data?
However here an easy option might be to scale the content of the axes and then set the axes to a log scale.
A. image
You may plot your image on a logarithmic scale but make all pixels the same size in log units. Unfortunately imshow does not allow for such kind of image (any more), but one may use pcolormesh for that purpose.
import numpy as np
import matplotlib.pyplot as plt
import scipy.misc
img = scipy.misc.face()
extx = [-5,3] # log10 units
exty = [-45, -55] # log10 units
x = np.logspace(extx[0],extx[-1],img.shape[1]+1)
y = np.logspace(exty[0],exty[-1],img.shape[0]+1)
X,Y = np.meshgrid(x,y)
c = img.reshape((img.shape[0]*img.shape[1],img.shape[2]))/255.0
m = plt.pcolormesh(X,Y,X[:-1,:-1], color=c, linewidth=0)
m.set_array(None)
plt.gca().set_xscale("log")
plt.gca().set_yscale("log")
plt.show()
B. contour
The same concept can be used for a contour plot.
import numpy as np
from matplotlib import pyplot as plt
x = np.linspace(-1.1,1.9)
y = np.linspace(-1.4,1.55)
X,Y = np.meshgrid(x,y)
agg = np.exp(-(X**2+Y**2)*2)
fig, ax = plt.subplots()
plt.gca().set_xscale("log")
plt.gca().set_yscale("log")
exp = lambda x: 10.**(np.array(x))
cf = ax.contourf(exp(X), exp(Y),agg, extent=exp([x.min(),x.max(),y.min(),y.max()]))
ax.set_xlabel("x")
ax.set_ylabel("y")
plt.show()
I have an array in python, using matplotlib, with floats ranging between 0 and 1.
I am displaying this array with imshow, I am trying to create a custom cmap, which is identical to Greens, however when a cell becomes 0 I would like to be able to map that value to red, and leave the rest of he spectrum unchanged.
If anyone more familiar with matplotlib would be able to help me I would greatly appreciate it!
For instance how would I edit this script so that the zero value in the matrix showed as red?
import numpy as np
from matplotlib import pyplot as plt
import matplotlib
x = np.array([[0,1,2],[3,4,5],[6,7,8]])
fig = plt.figure()
cmap_custom = matplotlib.cm.Greens
plt.imshow( x, interpolation='nearest' ,cmap = cmap_custom)
plt.colorbar()
plt.show()
The colormaps in matplotlib allow you to set special colors for values that are outside of the defined range. In your case specify the color for values below the defined range with cmap_custom.set_under('r').
Then you also need to specify the lower end of the range: vmin=0.01 (just some value > 0).
Finally create the colorbar with plt.colorbar(extend='min').
import numpy as np
from matplotlib import pyplot as plt
import matplotlib
x = np.array([[0,1,2],[3,4,5],[6,7,8]])
fig = plt.figure()
cmap_custom = matplotlib.cm.Greens
cmap_custom.set_under('r')
plt.imshow( x, interpolation='nearest' ,cmap = cmap_custom, vmin=0.01)
plt.colorbar(extend='min')
plt.show()
I'm trying to compose an image with both 2D and 3D plot. so far I've done the following:
import idlsave
import matplotlib
from matplotlib import *
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from matplotlib.patches import Ellipse
from matplotlib import rc
rc('font',**{'family':'sans-serif','sans-serif':['Helvetica']})
fig = plt.figure(figsize=(18,5))
ax = fig.add_subplot(1,3,1, projection='3d',azim=-133,elev=14)
l = ax.plot3D(X3D,Y3D,Z3D,lw=2,color='red')
ax.set_xlim3d(-10,10)
ax.set_ylim3d(-10,10)
ax.set_zlim3d(-10,10)
ax.text(-2,-7,-11,'b$_r$ [mT]','x')
ax.text(-5,-1,-11,'b$_p$ [mT]','y')
ax.set_zlabel(r'b$_t$ [mT]')
ax.plot([bEq[0],-bEq[0]],[bEq[1],-bEq[1]],[bEq[2],-bEq[2]],'b--',lw=2)
ax.plot([pLe[0],-pLe[0]],[pLe[1],-pLe[1]],[pLe[2],-pLe[2]],color='black',lw=2)
ax.text(3,12,9.2,'(a)', fontsize=14)
ax = fig.add_subplot(1,3,2)
l = ax.plot(br,bp,'k-',lw=2)
ax.set_xlabel(r'b$_{\lambda_1}$ [mT]')
ax.set_ylabel(r'b$_{\lambda_2}$ [mT]')
ax.set_xlim(-2,6.3)
ax.set_ylim(-5.5,5.5)
ax.plot([0,0],[-5.5,5.5],'k-.')
ax.plot([-2,6.3],[0,0],'k-.')
e=Ellipse((pf[2],pf[3]),2*pf[0],2*pf[1],- pf[4]*57.2958,fc='none',lw=2,ls='dashed',ec='red')
ax.add_artist(e)
ax.text(-1,4, '(b)', fontsize=14)
ax = fig.add_subplot(1,3,3)
ax.plot(-bxDip,-byDip,'b-',lw=2,label='$\mathcal{D}$')
ax.plot(-bxMon,-byMon,'r-',lw=2,label='$\mathcal{M}$')
ax.set_xlabel(r'b$_{\lambda_1}$')
ax.set_ylabel(r'b$_{\lambda_2}$')
ax.set_xlim(-4,12)
ax.set_ylim(-6,7)
ax.plot([-4,12],[0,0],'k-.')
ax.plot([0,0],[-6,7],'k-.')
ax.legend(loc='upper right')
ax.text(-3,5.5, '(c)', fontsize=14)
plt.savefig("../pdf_box/fig3.pdf",bbox_inches='tight')
Wit the present code I was able to produce the figure reported here http://img219.imageshack.us/i/fig3e.png/
There are two question which puzzle me.
1) As you can see the 3D plot is smaller than the other two and there is enough white spaces between the subplots to increase the size. How can I do this? i.e. How can I enlarge the size of one subplot, eventually decreasing the other two?
2) I would like to exclude the grey background in the 3D plot.
Any help is very welcomed.
Change ax.dist for the 3D plot. This will cause the rendered graphic to fill more of the subplot area. Here is a similar question. You may find some more info there.
You may also want to adjust the widths of the subplots with respect to each other (increase the width of the 3d plot and shrink the 2D plots. This can be accomplished with subplots_adjust