Matplotlib - Plot data in square shape - python

I want to create a scatter plot of (x,y) values where the x axis limits are [0, 10] and the y-axis limits are [0, 250]. The outer shape of the plot is supposed to be square, so the unit length of both axis has to be different.
I have tried both ax.axis('square') and ax.axis('equal') , before and after setting the axis limits (set by ax.set_xbound() and ax.set_ybound()) but none of these combinations produces my desired outcome.
x = np.random.randint(0,10,100)
y = np.random.randint(0,250,100)
fig, ax = plt.subplots()
ax.scatter(x,y)
ax.set_xbound(0,10)
ax.set_ybound(0,250)
ax.axis('square')
plt.show()
Outcome with ax.axis('square'):
The shape of the plot is square but now the x and y limits are both [0,250]

Use axes.set_box_aspect if you have reasonably recent matplotlib:
https://matplotlib.org/api/_as_gen/matplotlib.axes.Axes.set_box_aspect.html
import numpy as np
import matplotlib.pyplot as plt
x = np.random.randint(0,10,100)
y = np.random.randint(0,250,100)
fig, ax = plt.subplots()
ax.scatter(x,y)
ax.set_xbound(0,10)
ax.set_ybound(0,250)
ax.set_box_aspect(1)
plt.show()

Related

Hiding axes values in Matplotlib

I want to hide the x,y axes values as highlighted in the figure. Is it possible to do it? I also attach the expected representation.
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
n=3
X = np.arange(n)
Y = -X
x_sorted = np.sort(X)
y_sorted = np.sort(Y)
ax.set_xticks(x_sorted)
ax.set_yticks(y_sorted)
ax.set_xlim(x_sorted[0], x_sorted[-1])
ax.set_ylim(y_sorted[0], y_sorted[-1])
ax.grid()
ax.set_aspect('equal', 'box')
plt.show()
The expected representation is
You need to empty x and y tick labels from ax variable:
ax.set_yticklabels([])
ax.set_xticklabels([])

matplotlib/mplot3d scatterplot respects masking but surfaceplot does not - why?

The source of this post is from an earlier post.
I have X,Y,Z 2D arrays. I only want to see the plot in the first-quadrant where all x, y and z are positive. I use masking on all negative z entries. However it was noticed that scatter plots indeed respect the masking and do not show the negative z-values. However surface-plots still display the negative values.
Please find below the code -
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits import mplot3d
from matplotlib import cm
%matplotlib notebook
x = np.linspace(0,1.5,10)
y = np.linspace(0,1.5,10)
X,Y = np.meshgrid(x,y)
Z = 1-X-Y
Z1 = np.ma.masked_less(Z,0)
X1 = np.ma.masked_array(X,Z1.mask)
Y1 = np.ma.masked_array(Y,Z1.mask)
fig = plt.figure()
# Two plots - one scatter plot, one surface plot
ax1 = fig.add_subplot(121,projection='3d')
ax2 = fig.add_subplot(122,projection='3d')
# Scatter plot
surf1 = ax1.scatter(X1,Y1,Z1,cmap=cm.coolwarm)
ax1.set_xlim(0,1.5)
ax1.set_ylim(0,1.5)
ax1.set_zlim(0,3)
ax1.set_xlabel('x')
ax1.set_ylabel('y')
# Surface plot
surf2 = ax2.plot_surface(X1,Y1,Z1,cmap=cm.coolwarm)
ax2.set_xlim(0,1.5)
ax2.set_ylim(0,1.5)
ax2.set_zlim(0,3)
ax2.set_xlabel('x')
ax2.set_ylabel('y')
fig.colorbar(surf1, ax=ax1, location='top')
fig.colorbar(surf2, ax=ax2, location='top')
fig.savefig('Question10CompareScatterAndSurfacePlots')
The colour-bars make it evident that there are no negative z-points in the scatter plot while the surface plot stretches to negative z. Why is it so?

How to plot a contour plot if the difference between Zmax and Zmin is of the order of 10^(-5)?

I want to produce a ramachandran plot which would look like the following
basically it is a superposition of two plots: contour and scatter. I have the data file for plotting the contour and scatter plot. The data for contour plot is present as three different columns denoting x, y and z values. the value of x and y varies from -180 to 180. Whereas the value z varies from 0 to 1 and the difference between z values can be as low as 10^(-5). In my code I tried to plot the contour using tricontourf where the difference each entry of the level is 0.01. Whenever I tried to make gap between those levels to 0.00001, the code just doesn't get over. That's why I am unable to generate a graph that I want.
The code that I wrote is the following:
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.tri as tri
import matplotlib.cm as cm
x=[]
y=[]
z=[]
x1=[]
y1=[]
lst = []
plt.style.use('seaborn-whitegrid')
for line in open('rama_data.txt', 'r'):
values = [float(s) for s in line.split()]
x.append(values[0])
y.append(values[1])
z.append(values[2])
f=open('all_str_C-S-S-C_Acceptor.txt',"r")
lines=f.readlines()
for m in lines:
x1.append(m.split(' ')[8])
y1.append(m.split(' ')[9])
f.close()
norm = cm.colors.Normalize(vmax=max(z), vmin=min(z))
cmap = cm.OrRd
fig2, ax2 = plt.subplots()
#ax2.set_aspect('equal')
levels = np.arange(0, 1,0.01)
tcf = ax2.tricontourf(x, y, z, levels, cmap=cm.get_cmap(cmap, len(levels)-1),norm=norm)
ax2.set_xticks(np.arange(-180,181,45))
ax2.set_yticks(np.arange(-180,181,45))
ax2.set_xlabel('$\Phi$ Dihedral angle($\circ$)', fontsize=12, fontweight='bold')
ax2.set_ylabel('$\Psi\'$ Dihedral angle($\circ$)', fontsize=12, fontweight='bold')
#cbar=fig2.colorbar(tcf)
#cbar.ax.set_ylabel('Relative Electronic energy(kJ/mol)', fontsize=12, fontweight='bold')
ax2.autoscale(False) # To avoid that the scatter changes limits
ax2.scatter(x1,y1,s=0.15,c='black',zorder=1)
fig2.savefig("Ramachandran plot",dpi=300)
plt.show()
My code generates an image which looks this this:
What modifications should I do do produce the desirable plot?
I have attached the rama_data.txt file. Anyone can download and try it once.
The main problem seems to be that for 100 levels (as in levels = np.arange(0, 1,0.01)) the colors get very smoothed out. Just reducing the number of levels gets a plot much closer to the example plot.
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.tri as tri
xyz = np.loadtxt('rama.txt')
x = xyz[:, 0]
y = xyz[:, 1]
z = xyz[:, 2]
fig2, (ax1, ax2) = plt.subplots(ncols=2)
cmap = 'OrRd'
tcf = ax2.tricontourf(x, y, z, levels=5, cmap=cmap) # norm=norm)
filter = (z > 0.2) & (np.random.randint(0, 10, z.size) == 0)
ax2.scatter(x[filter], y[filter], marker='.', s=1, color='black')
ax1.scatter(x, y, c=z, cmap=cmap)
ax1.set_xticks(np.arange(-180, 181, 45))
ax1.set_yticks(np.arange(-180, 181, 45))
ax2.set_xticks(np.arange(-180, 181, 45))
ax2.set_yticks(np.arange(-180, 181, 45))
plt.show()
The plot shows a regular scatter plot of the given data at the left, and the contourf plot at the right.

Plotting some third variable against x and y in matplotlib scatter?

I'm fairly new to scatter plots and python in general. I am trying to plot a third variable against an x and a y, however, I'm not quite sure how to about specifying that argument? So I would have X values which are ints, y values which are also ints and then on the graph itself I want the model scores to show. Is there any way to do this sort of thing?
Thank you.
You can use color to plot a third value. Here is a very minimal example :
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import numpy as np
x = np.random.rand(100)
y = np.random.rand(100)
z = np.random.rand(100)
plt.scatter(x,y, c=z, s=5, cmap=cm.hsv)
cbar= plt.colorbar()
plt.show()
Edit
You could also use the size of markers, their transparency, hue or rgb values to depict even more information. Here is an example with marker size, alpha level and color on a perceptually uniform colormap.
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.colors as colors
import matplotlib.cm as cmx
x = np.random.rand(100)
y = np.random.rand(100)
z = np.random.rand(100)
t = np.random.rand(100)
w = np.random.rand(100)
fig, ax = plt.subplots(1, 1)
cmap = plt.get_cmap('plasma')
cNorm = colors.Normalize(vmin=0, vmax=max(z))
scalarMap = cmx.ScalarMappable(norm=cNorm, cmap=cmap)
for i in range(100):
ax.scatter(x[i],y[i], c=scalarMap.to_rgba(z[i]), s=t[i]*100, cmap=cmx.plasma, alpha=w[i], edgecolor='none')
scalarMap.set_array([])
fig.colorbar(scalarMap,ax=ax)
for a in [0.1, 0.5, 0.9]:
ax.scatter([], [], c='k', alpha=0.5, s=a*100, label=str(a), edgecolors='none')
l1 = ax.legend(scatterpoints=1, frameon=True, loc='lower left' ,markerscale=1)
for b in [0.25, 0.5, 0.75]:
ax.scatter([], [], c='k', alpha=b, s=50, label=str(b), edgecolors='none')
ax.legend(scatterpoints=1, frameon=True, loc='lower right' ,markerscale=1)
fig.show()
At face value, that question doesn't really make sense because a conventional scatterplot has only two axes, and of course you can't plot points with three dimensions (x, y and accuracy).
However, there are alternative ways to do so.
Use colours
import numpy as np
from matplotlib import pyplot as plt
x = np.random.rand(200)
y = np.random.rand(200)
fig, ax = plt.subplots(figsize=(5, 5))
ax.scatter(x, y, c=(x + y), cmap='RdPu')
scatter takes a c argument, which can be a numeric value, as well as a cmap argument, which can be a string referencing a colormap.
The colormap object translates the numbers provided in c into points along a colour mapping, which you can think of as a gradient bar.
Use 3D axes
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure(figsize=(5, 5))
ax = Axes3D(fig)
ax.scatter(x, y, (x + y))
This turns your 3rd dimension, accuracy, into an ordinary spatial dimension.
Use size of the markers
Very similar to the color option in the first part, you can change the size of the scatter markers (given you have some idea about the scale of the values). So based on the first example, you can also do;
import numpy as np
from matplotlib import pyplot as plt
x = np.random.rand(200)
y = np.random.rand(200)
fig, ax = plt.subplots(figsize=(5, 5))
ax.scatter(x, y, c='k', s=5*(x + y), cmap='RdPu')
scatter takes also the s argument, that changes the size of the markers.

How do I align gridlines for two y-axis scales using Matplotlib?

I'm plotting two datasets with different units on the y-axis. Is there a way to make the ticks and gridlines aligned on both y-axes?
The first image shows what I get, and the second image shows what I would like to get.
This is the code I'm using to plot:
import seaborn as sns
import numpy as np
import pandas as pd
np.random.seed(0)
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax1.plot(pd.Series(np.random.uniform(0, 1, size=10)))
ax2 = ax1.twinx()
ax2.plot(pd.Series(np.random.uniform(10, 20, size=10)), color='r')
I am not sure if this is the prettiest way to do it, but it does fix it with one line:
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import pandas as pd
np.random.seed(0)
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax1.plot(pd.Series(np.random.uniform(0, 1, size=10)))
ax2 = ax1.twinx()
ax2.plot(pd.Series(np.random.uniform(10, 20, size=10)), color='r')
# ADD THIS LINE
ax2.set_yticks(np.linspace(ax2.get_yticks()[0], ax2.get_yticks()[-1], len(ax1.get_yticks())))
plt.show()
I could solve it by deactivating ax.grid(None) in one of the grid`s axes:
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import pandas as pd
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax1.plot(pd.Series(np.random.uniform(0, 1, size=10)))
ax2 = ax1.twinx()
ax2.plot(pd.Series(np.random.uniform(10, 20, size=10)), color='r')
ax2.grid(None)
plt.show()
I wrote this function that takes Matplotlib axes objects ax1, ax2, and floats minresax1 minresax2:
def align_y_axis(ax1, ax2, minresax1, minresax2):
""" Sets tick marks of twinx axes to line up with 7 total tick marks
ax1 and ax2 are matplotlib axes
Spacing between tick marks will be a factor of minresax1 and minresax2"""
ax1ylims = ax1.get_ybound()
ax2ylims = ax2.get_ybound()
ax1factor = minresax1 * 6
ax2factor = minresax2 * 6
ax1.set_yticks(np.linspace(ax1ylims[0],
ax1ylims[1]+(ax1factor -
(ax1ylims[1]-ax1ylims[0]) % ax1factor) %
ax1factor,
7))
ax2.set_yticks(np.linspace(ax2ylims[0],
ax2ylims[1]+(ax2factor -
(ax2ylims[1]-ax2ylims[0]) % ax2factor) %
ax2factor,
7))
It calculates and sets the ticks such that there are seven ticks. The lowest tick corresponds to the current lowest tick and increases the highest tick such that the separation between each tick is integer multiples of minrexax1 or minrexax2.
To make it general, you can set the total number of ticks you want by changing ever 7 you see to the total number of ticks, and change 6 to the total number of ticks minus 1.
I put a pull request in to incorporate some this into matplotlib.ticker.LinearLocator:
https://github.com/matplotlib/matplotlib/issues/6142
In the future (Matplotlib 2.0 perhaps?), try:
import matplotlib.ticker
nticks = 11
ax1.yaxis.set_major_locator(matplotlib.ticker.LinearLocator(nticks))
ax2.yaxis.set_major_locator(matplotlib.ticker.LinearLocator(nticks))
That should just work and choose convenient ticks for both y-axes.
I created a method to align the ticks of multiple y- axes (could be more than 2), with possibly different scales in different axes.
Below is an example figure:
There are 3 y- axes, one blue on the left, and a green and a red on the right. The 3 curves are plotted onto the y-axis with the corresponding color. Note that they all have very different order of magnitudes.
Left plot: No alignment.
Mid plot: Aligned at (approximately) the lower bound of each y axis.
Right plot: Aligned at specified values: 0 for blue, 2.2*1e8 for red, and 44 for green. Those are chosen arbitrarily.
What I'm doing is to scale each y array to be within the range of 1-100, then merge all scaled y-values into a single array, from which a new set of ticks is created using MaxNLocator. Then this new set of ticks is scaled back using the corresponding scaling factor to get the new ticks for each axis. If some specific alignment is required, y arrays are shifted before scaling, and shifted back afterwards.
Complete code here (the key function is alignYaxes()):
import matplotlib.pyplot as plt
import numpy as np
def make_patch_spines_invisible(ax):
'''Used for creating a 2nd twin-x axis on the right/left
E.g.
fig, ax=plt.subplots()
ax.plot(x, y)
tax1=ax.twinx()
tax1.plot(x, y1)
tax2=ax.twinx()
tax2.spines['right'].set_position(('axes',1.09))
make_patch_spines_invisible(tax2)
tax2.spines['right'].set_visible(True)
tax2.plot(x, y2)
'''
ax.set_frame_on(True)
ax.patch.set_visible(False)
for sp in ax.spines.values():
sp.set_visible(False)
def alignYaxes(axes, align_values=None):
'''Align the ticks of multiple y axes
Args:
axes (list): list of axes objects whose yaxis ticks are to be aligned.
Keyword Args:
align_values (None or list/tuple): if not None, should be a list/tuple
of floats with same length as <axes>. Values in <align_values>
define where the corresponding axes should be aligned up. E.g.
[0, 100, -22.5] means the 0 in axes[0], 100 in axes[1] and -22.5
in axes[2] would be aligned up. If None, align (approximately)
the lowest ticks in all axes.
Returns:
new_ticks (list): a list of new ticks for each axis in <axes>.
A new sets of ticks are computed for each axis in <axes> but with equal
length.
'''
from matplotlib.pyplot import MaxNLocator
nax=len(axes)
ticks=[aii.get_yticks() for aii in axes]
if align_values is None:
aligns=[ticks[ii][0] for ii in range(nax)]
else:
if len(align_values) != nax:
raise Exception("Length of <axes> doesn't equal that of <align_values>.")
aligns=align_values
bounds=[aii.get_ylim() for aii in axes]
# align at some points
ticks_align=[ticks[ii]-aligns[ii] for ii in range(nax)]
# scale the range to 1-100
ranges=[tii[-1]-tii[0] for tii in ticks]
lgs=[-np.log10(rii)+2. for rii in ranges]
igs=[np.floor(ii) for ii in lgs]
log_ticks=[ticks_align[ii]*(10.**igs[ii]) for ii in range(nax)]
# put all axes ticks into a single array, then compute new ticks for all
comb_ticks=np.concatenate(log_ticks)
comb_ticks.sort()
locator=MaxNLocator(nbins='auto', steps=[1, 2, 2.5, 3, 4, 5, 8, 10])
new_ticks=locator.tick_values(comb_ticks[0], comb_ticks[-1])
new_ticks=[new_ticks/10.**igs[ii] for ii in range(nax)]
new_ticks=[new_ticks[ii]+aligns[ii] for ii in range(nax)]
# find the lower bound
idx_l=0
for i in range(len(new_ticks[0])):
if any([new_ticks[jj][i] > bounds[jj][0] for jj in range(nax)]):
idx_l=i-1
break
# find the upper bound
idx_r=0
for i in range(len(new_ticks[0])):
if all([new_ticks[jj][i] > bounds[jj][1] for jj in range(nax)]):
idx_r=i
break
# trim tick lists by bounds
new_ticks=[tii[idx_l:idx_r+1] for tii in new_ticks]
# set ticks for each axis
for axii, tii in zip(axes, new_ticks):
axii.set_yticks(tii)
return new_ticks
def plotLines(x, y1, y2, y3, ax):
ax.plot(x, y1, 'b-')
ax.tick_params('y',colors='b')
tax1=ax.twinx()
tax1.plot(x, y2, 'r-')
tax1.tick_params('y',colors='r')
tax2=ax.twinx()
tax2.spines['right'].set_position(('axes',1.2))
make_patch_spines_invisible(tax2)
tax2.spines['right'].set_visible(True)
tax2.plot(x, y3, 'g-')
tax2.tick_params('y',colors='g')
ax.grid(True, axis='both')
return ax, tax1, tax2
#-------------Main---------------------------------
if __name__=='__main__':
# craft some data to plot
x=np.arange(20)
y1=np.sin(x)
y2=x/1000+np.exp(x)
y3=x+x**2/3.14
figure=plt.figure(figsize=(12,4),dpi=100)
ax1=figure.add_subplot(1, 3, 1)
axes1=plotLines(x, y1, y2, y3, ax1)
ax1.set_title('No alignment')
ax2=figure.add_subplot(1, 3, 2)
axes2=plotLines(x, y1, y2, y3, ax2)
alignYaxes(axes2)
ax2.set_title('Default alignment')
ax3=figure.add_subplot(1, 3, 3)
axes3=plotLines(x, y1, y2, y3, ax3)
alignYaxes(axes3, [0, 2.2*1e8, 44])
ax3.set_title('Specified alignment')
figure.tight_layout()
figure.show()
This code will ensure that grids from both axes align to each other, without having to hide gridlines from either set. In this example, it allows you to match whichever has the finer grid lines. This builds off of the idea from #Leo. Hope it helps!
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import pandas as pd
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax1.plot(pd.Series(np.random.uniform(0,1,size=10)))
ax2 = ax1.twinx()
ax2.plot(pd.Series(np.random.uniform(10,20,size=10)),color='r')
ax2.grid(None)
# Determine which plot has finer grid. Set pointers accordingly
l1 = len(ax1.get_yticks())
l2 = len(ax2.get_yticks())
if l1 > l2:
a = ax1
b = ax2
l = l1
else:
a = ax2
b = ax1
l = l2
# Respace grid of 'b' axis to match 'a' axis
b_ticks = np.linspace(b.get_yticks()[0],b.get_yticks()[-1],l)
b.set_yticks(b_ticks)
plt.show()
If you're using axis labels, Leo's solution can push them off the side, due to the precision of the numbers in the ticks.
So in addition to something like Leo's solution (repeated here),
ax2.set_yticks(np.linspace(ax2.get_yticks()[0],ax2.get_yticks()[-1],len(ax1.get_yticks())))
you can use the autolayout setting, as mentioned in this answer; e.g., earlier in your script you can update rcParams:
from matplotlib import rcParams
rcParams.update({'figure.autolayout': True})
In a few test cases, this appears to produce the expected result, with both lined-up ticks and labels fully contained in the output.
I had the same issue except this was for a secondary x axis. I solved by setting my secondary x axis equal to the limit of my primary axis.The example below is without setting the limit of the second axis equal to the first:ax2 = ax.twiny()
Once I set the limit of the second axis equal to the first ax2.set_xlim(ax.get_xlim()) here is my result:
fix the limits for both axis (from any number to any number)
divide both axis into same n parts
ax1.set_ylim(a,b)
ax1.set_yticks(np.linspace(a,b, n))
ax2.set_ylim(c,d)
ax2.set_yticks(np.linspace(c,d, n))

Categories