We are working on a project in which we would like to construct 3D plots. Python is our main language, and therefore naturally chose to use matplotlib as our plotting library. Various tutorials (here, here and here) have teached us how to perform 3D plotting using the mplot3d functionality of matplotlib. Consequently, various StackOverflow answers helped us to move the origin of each of the axes to different locations (here and here).
After searching for a couple of hours we have a hard time finding an answer to our next question, however. We would like to have a positive and negative side for our Z-axis (see the picture below, orange part). This would mean that data points with Z>0 are above origin, and with Z<0 are below origin. We tried several things, but our Z-axis origin always ends up at the most negative value of our dataset.
With great help of the community here, we've come to a minimal example showcasing what I want. The code I used is:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure(figsize=(10, 10))
ax = fig.gca(projection='3d')
# Some settings
sn = 2 #limits in x,y,z
n = 50 #number of sample points
x1, x2 = 0, sn
y1, y2 = 0, sn
z1, z2 = -sn, sn
# Data for points
xs = (x2 - x1)*np.random.rand(n) + x1
ys = (y2 - y1)*np.random.rand(n) + y1
zs = (z2 - z1)*np.random.rand(n) + z1
# Points with z >= 0, plotted in green
ax.scatter(xs[zs>=0], ys[zs>=0], zs[zs>=0], color='green')
# Points with z < 0, plotted in red
ax.scatter(xs[zs<0], ys[zs<0], zs[zs<0], color='red')
# Data for plotting plane x|y|z=0 within the domain
tmp = np.linspace(0, sn, 8)
x, y = np.meshgrid(tmp, tmp)
z = 0*x
# Plot grid lines
ax.plot([0, sn], [0, 0], [0, 0], color='black')
ax.plot([0, 0], [0, sn], [0, 0], color='black')
ax.plot([0, 0], [0, 0], [-sn, sn], color='black')
# Maximum tick labels for X, Y, and Z (x3)
ax.plot([sn, sn], [0, 0], [-.05, .02], color='black')
ax.plot([0, 0], [sn, sn], [-.05, .02], color='black')
ax.plot([-.05, .02], [-.05, .02], [sn, sn], color='black')
ax.plot([-.05, .02], [-.05, .02], [-sn, -sn], color='black')
ax.plot([-.05, .02], [-.05, .02], [0, 0], color='black')
# Label texts
ax.text(sn/2, 0, -.2*sn, 'xlabel', 'x', ha='center')
ax.text(0, sn/2, -.2*sn, 'ylabel', 'y', ha='center')
ax.text(-.1*sn, 0, 0, 'zlabel', 'z', ha='center')
# Maximum limit text for X, Y and Z (x3)
ax.text(sn, 0, -.1*sn, f'{sn}', 'x', ha='center')
ax.text(0, sn, -.1*sn, f'{sn}', 'y', ha='center')
ax.text(-.05*sn, -.05*sn, 0, '0', 'x', ha='center')
ax.text(-.05*sn, -.05*sn, sn, f'{sn}', 'x', ha='right')
ax.text(-.05*sn, -.05*sn, -sn, f'{-sn}', 'x', ha='center')
# Set limits of the 3D display
ax.set_xlim3d([-sn, sn])
ax.set_ylim3d([-sn, sn])
ax.set_zlim3d([-sn, sn])
ax.set_axis_off()
plt.show()
This results in the graph below:
Although I am very happy with the outcome, this is still kind of 'hacky' solution with manually drawing the axis, ticks and labels. If anybody would have a solution in which we can re-design the axis from the mplot3d API that would be very helpful.
(Swatchai creates this as a community wiki):
Sometime, discussion without some runnable code to play/experiment with is not the best approach to get a solution. Here I propose this code to use for further discussion.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure(figsize=(10, 10))
ax = fig.gca(projection='3d')
# Quivers for axes x,y,z from (0,0,0)
quiver1 = ax.quiver([0],[0],[0],[2],[0],[0], colors='r')
quiver2 = ax.quiver([0],[0],[0],[0],[2],[0], colors='g')
quiver3 = ax.quiver([0],[0],[0],[0],[0],[2], colors='b')
# Some settings
sn = 2 #limits in x,y,z
n = 50 #number of sample points
x1, x2 = -sn, sn
y1, y2 = -sn, sn
z1, z2 = -sn, sn
# Data for points
xs = (x2 - x1)*np.random.rand(n) + x1
ys = (y2 - y1)*np.random.rand(n) + y1
zs = (z2 - z1)*np.random.rand(n) + z1
# Points with z >= 0, plotted in green
ax.scatter(xs[zs>=0], ys[zs>=0], zs[zs>=0], color='green')
# Points with z < 0, plotted in red
ax.scatter(xs[zs<0], ys[zs<0], zs[zs<0], color='red')
# Data for plotting plane x|y|z=0 within the domain
tmp = np.linspace(0, sn, 8)
x,y = np.meshgrid(tmp,tmp)
z = 0*x
ax.plot_surface(z,x,y, alpha=0.15, color='red') # plot the plane x=0
ax.plot_surface(x,z,y, alpha=0.15, color='green') # plot the plane y=0
ax.plot_surface(x,y,z, alpha=0.15, color='blue') # plot the plane z=0
# Set limits of the 3D display
ax.set_xlim3d([-sn, sn])
ax.set_ylim3d([-sn, sn])
ax.set_zlim3d([-sn, sn])
# Set labels at the 3d box/frame
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
plt.show()
Output plot:
Related
I'm trying to use fill_between to fill different regions of a plot, but I get gaps between the regions I'm trying to fill.
I've tried using interpolate=True, but this results in non rectangular shapes...
`
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
x = np.arange(0, 4 * np.pi, 0.01)
y = np.sin(x)
ax.plot(x, y, color='black')
threshold = 0.75
ax.axhline(threshold, color='green', lw=2, alpha=0.7)
ax.fill_between(x, 0, 1, where=y > threshold,
facecolor=(0.5,0,0,0.5), ec=None,transform=ax.get_xaxis_transform())
ax.fill_between(x, 0, 1, where=y <= threshold,
facecolor=(0,0.5,0,0.5), ec=None, transform=ax.get_xaxis_transform())
`
I've attched a zoomed in screenshot of the plot.
You could do one or both of the following:
use finer-grainded x values, e.g.x = np.arange(0, 4 * np.pi, 0.0001). This will remove the white stripes at full view, but if you zoom in they will re-appear at a certain zoom level.
first draw the green background without a where condition over the full x range and then plot the red sections at the required sections. In case of non-opaque colors as in the example you'll need to manually re-calculate the semitransparent color on the default white background to a fully opaque color:
x = np.arange(0, 4 * np.pi, 0.001)
# ...
ax.fill_between(x, 0, 1, facecolor=(0, 0.5, 0, 0.5), ec=None,
transform=ax.get_xaxis_transform())
ax.fill_between(x, 0, 1, where=y>threshold, facecolor=(0.75, 0.5, 0.5),
ec=None, transform=ax.get_xaxis_transform())
I found an alternative way of solving this problem, by using pcolormesh where the color array is 1xn:
C = np.reshape(np.array(trnsys_out["LCG_state"][:-1].values), (-1, 1)).T
x = trnsys_out.index
y = [Pmin, Pmax]
ctrl = ax2.pcolormesh(x, y, C, shading="flat", cmap="binary", alpha=0.5, vmin=0, vmax=5)
I am plotting my pandas data using matplotlib, My plot looks like this:
There are four classes in the dataset. I want to color the backgroud area for each class, something like this
My matplotlib code looks like this:
import pandas as pd
df = pd.read_csv('normalized.csv')
fig = plt.figure(figsize=(8,8))
plt.scatter(df['p1'], df['p2'], c= list(df['cs']), alpha=0.9)
plt.show()
I also tried sns for this:
import pandas as pd
df = pd.read_csv('normalized.csv')
sn.FacetGrid(df, hue="cs", size = 8).map(plt.scatter, "p1", "p2").add_legend()
plt.show()
How I can fill the backgroud area for four classes in any of module?
A filled contour could serve as background:
import numpy as np
import matplotlib.pyplot as plt
N = 100
M = 4
points = np.random.normal(np.tile(np.random.uniform(1, 10, 2 * M), N)).reshape(-1, 2)
group = np.tile(np.arange(M), N)
fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(14, 5), sharey=True, sharex=True)
cmap = plt.cm.get_cmap('tab10', 4)
ax1.scatter(points[:, 0], points[:, 1], c=group, cmap=cmap)
ax2.scatter(points[:, 0], points[:, 1], c=group, cmap=cmap)
ax2.tricontourf(points[:, 0], points[:, 1], group, levels=np.arange(-0.5, 4), zorder=0, cmap=cmap, alpha=0.3)
plt.show()
Note that the contour plot also creates some narrow zones of inbetween values, because it only looks at numeric values and supposes that between a zone 0 and a zone 2 there must exist some small zone 1.
A bit more involved approach uses a nearest neighbor fit:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
from sklearn import neighbors
N = 100
M = 4
points = np.random.normal(np.tile(np.random.uniform(1, 10, 2 * M), N)).reshape(-1, 2)
groups = np.tile(np.arange(M), N)
fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(14, 5), sharey=True, sharex=True)
cmap = ListedColormap(['orange', 'cyan', 'cornflowerblue', 'crimson'])
ax1.scatter(points[:, 0], points[:, 1], c=groups, cmap=cmap)
ax2.scatter(points[:, 0], points[:, 1], c=groups, cmap=cmap)
clf = neighbors.KNeighborsClassifier(10)
clf.fit(points, groups)
x_min, x_max = points[:, 0].min() - 1, points[:, 0].max() + 1
y_min, y_max = points[:, 1].min() - 1, points[:, 1].max() + 1
xx, yy = np.meshgrid(np.linspace(x_min, x_max, 50),
np.linspace(y_min, y_max, 50))
Z = clf.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape)
ax2.imshow(Z, extent=[x_min, x_max, y_min, y_max], cmap=cmap, alpha=0.3, aspect='auto', origin='lower')
plt.show()
If you don't need to fill the space and do not bother about areas overlap (your data points show some overlap) then you can try to fill out the convex hull defined by each subset.
import matplotlib.pyplot as plt
import numpy as np
from scipy.spatial import ConvexHull
N = 100
points = [np.random.normal(np.tile(np.random.uniform(1, 5, 2), N)).reshape(-1, 2) for i in range(4)]
colors = ['r', 'g', 'b', 'k']
for k in range(4):
hull = ConvexHull(points[k])
plt.plot(points[k][:,0], points[k][:,1], '.', color = colors[k])
plt.fill(points[k][hull.vertices,0], points[k][hull.vertices,1], color = colors[k], alpha=0.3)
stack.imgur.com/2562R.png
I am scatter ploting data points with a very small marker (see screengrab below). When I use the very small marker ',' the legend is very hard to read (example code taken from here).
(Python 3, Jupyter lab)
How can I increase the size of the marker in the legend. The two versions shown on the above mentioned site do not work:
legend = ax.legend(frameon=True)
for legend_handle in legend.legendHandles:
legend_handle._legmarker.set_markersize(9)
and
ax.legend(markerscale=6)
The two solutions do however work when the marker is set to '.'.
How can I show bigger makers in the legend?
Sample Code from intoli.com:
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(12)
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
for i in range(5):
mean = [np.random.random()*10, np.random.random()*10]
covariance = [ [1 + np.random.random(), np.random.random() - 1], [0, 1 + np.random.random()], ]
covariance[1][0] = covariance[0][1] # must be symmetric
x, y = np.random.multivariate_normal(mean, covariance, 3000).T
plt.plot(x, y, ',', label=f'Cluster {i + 1}')
ax.legend(markerscale=12)
fig.tight_layout()
plt.show()
You can get 1 pixel sized markers for a plot by setting the markersize to 1 pixel. This would look like
plt.plot(x, y, marker='s', markersize=72./fig.dpi, mec="None", ls="None")
What the above does is set the marker to a square, set the markersize to the ppi (points per inch) divided by dpi (dots per inch) == dots == pixels, and removes lines and edges.
Then the solution you tried using markerscale in the legend works nicely.
Complete example:
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(12)
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
for i in range(5):
mean = [np.random.random()*10, np.random.random()*10]
covariance = [ [1 + np.random.random(), np.random.random() - 1], [0, 1 + np.random.random()], ]
covariance[1][0] = covariance[0][1] # must be symmetric
x, y = np.random.multivariate_normal(mean, covariance, 3000).T
plt.plot(x, y, marker='s', markersize=72./fig.dpi, mec="None", ls="None",
label=f'Cluster {i + 1}')
ax.legend(markerscale=12)
fig.tight_layout()
plt.show()
According to this discussion, the markersize has no effect when using pixels (,) as marker. How about generating a custom legend instead? For example, by adapting the first example in this tutorial, one can get a pretty decent legend:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
np.random.seed(12)
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
for i in range(5):
mean = [np.random.random()*10, np.random.random()*10]
covariance = [ [1 + np.random.random(), np.random.random() - 1], [0, 1 + np.random.random()], ]
covariance[1][0] = covariance[0][1] # must be symmetric
x, y = np.random.multivariate_normal(mean, covariance, 3000).T
plt.plot(x, y, ',', label=f'Cluster {i + 1}')
##generating custom legend
handles, labels = ax.get_legend_handles_labels()
patches = []
for handle, label in zip(handles, labels):
patches.append(mpatches.Patch(color=handle.get_color(), label=label))
legend = ax.legend(handles=patches)
fig.tight_layout()
plt.show()
The output would look like this:
This is the plot I get from the code below:
aa = np.zeros(len(self.depthrange))
bb = np.zeros(len(self.depthrange))
for i in range(0, self.n):
goodin = ~np.isnan(self.u[:, i])
bb[i] = self.u[goodin, i].mean()
aa[i] = self.v[goodin, i].mean()
speed = np.sqrt(bb**2 + aa**2)
dirt = np.arctan2(bb, aa) * 180 / np.pi
dirt[dirt < 360] += 360
dirt[dirt > 360] -= 360
binrange = -np.mean(self.variables.depth) + self.binrange[1, :]
aa = speed * np.cos(dirt * np.pi / 180)
bb = speed * np.sin(dirt * np.pi / 180)
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.quiver(0, 0, binrange, aa, bb, np.zeros(len(bb)), pivot='tail', arrow_length_ratio=0.3, length=0.15, color='tomato', lw='2')
ax.text(0, -0.025, -15, 'W', color='k', fontsize=15)
ax.text(-0.025, 0, -15, 'S', color='k', fontsize=15)
ax.text(0.025, 0, -15, 'N', color='k', fontsize=15)
ax.text(0, 0.025, -15, 'E', color='k', fontsize=15)
ax.set_zlim(-15, 0)
ax.set_xlim(-0.25, 0.25)
ax.set_ylim(-0.25, 0.25)
plt.gca().invert_xaxis()
#plt.gca().invert_yaxis()
ax.view_init(elev=18, azim=30)
ax.dist = 8
ax.set_xlabel('m/s')
ax.set_ylabel('m/s')
ax.set_zlabel('Depth (m)')
ax.set_title('Mean Current Vector')
ax.plot([0, 0], [0, 0], zs=[-15, 0], lw=2, color='grey')
for i in range(0, self.n):
ax.plot([-0.1*0.25, 0.1*0.25], [0, 0], zs=[binrange[i], binrange[i]], lw=2, color='grey')
ax.plot([0, 0], [-0.1*0.25, 0.1*0.25], zs=[binrange[i], binrange[i]], lw=2, color='grey')
plt.show()
There are arrow heads present as you can see a difference in the colour of the line however they do not look like arrows, does anyone know how I get the arrows to show properly?
Many thanks,
The arrow heads (in red) are there. I believe this to be a bug in the matplotlib library, but in any case the problem is with the components (U,V,W). Try to rotate them slightly in the axis of the shaft and you should see the heads appear. Here is a minimal example:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = fig.gca(projection='3d')
X = (0, 0, 0)
Y = (0, 1, 2)
Z = (0,0,0)
ax.quiver(X,Y,Z,(0,2,1),(1,1,1),(1,3,1),length=0.05,arrow_length_ratio=0.3)
plt.show()
, the result is this:
Notice how one the arrows suffers from the same problem as yours. The others are fine. The only difference between them is the components.
I think you should try providing the data (you're using a class that we can't see so your plot remains with difficult reproduction) along with the code, in case you'll have problems in setting up the correct components.
Four-way logarithmic plot is a very often used graph for vibration control and earthquake protection. I am quite interesting in how this plot can be plotted in Matplotlib instead of adding axes in Inkscape. A sample of Four-way logarithmic plot is here.
A quick and dirty Python code can generate main part of the figure, but I cannot add the two axes onto the figure. http://matplotlib.org/examples/axes_grid/demo_curvelinear_grid.html provides an example of adding axes, but I fails to make it working. Anyone has similar experience on adding axes to Matplotlib figure?
from pylab import *
from mpl_toolkits.axisartist.grid_helper_curvelinear import GridHelperCurveLinear
from mpl_toolkits.axisartist import Subplot
beta=logspace(-1,1,500)
Rd={}
for zeta in [0.01,0.1,0.2,0.7,1]:
Rd[zeta]=beta/sqrt((1-beta*beta)**2+(2*beta*zeta)**2)
loglog(beta,Rd[zeta])
ylim([0.1,10])
xlim([0.1,10])
grid('on',which='minor')
Update: Thank you all! I use Inkscape to modify the figure above. I think the result is just fine. However, I am still looking for methods to draw this figure in Matplotlib.
Here is a partial solution. I am still working on how to do all of this in a natural loglog() plot rather than scaling the data. (To complete this example you would have to define custom tick-lables so that they display 10**x rather than x.)
%matplotlib inline # I am doing this in an IPython notebook.
from matplotlib import pyplot as plt
import numpy as np
from numpy import log10
# Generate the data
beta = np.logspace(-1, 1, 500)[:, None]
zeta = np.array([0.01,0.1,0.2,0.7,1])[None, :]
Rd = beta/np.sqrt((1 - beta*beta)**2 + (2*beta*zeta)**2)
def draw(beta=beta, Rd=Rd):
plt.plot(log10(beta), log10(Rd))
plt.ylim([log10(0.1), log10(10)])
plt.xlim([log10(0.1), log10(10)])
plt.grid('on',which='minor')
ax = plt.gca()
ax.set_aspect(1)
from mpl_toolkits.axisartist import GridHelperCurveLinear
from matplotlib.transforms import Affine2D
from mpl_toolkits.axisartist import SubplotHost
from mpl_toolkits.axisartist import Subplot
#tr = Affine2D().rotate(-np.pi/2)
#inv_tr = Affine2D().rotate(np.pi/2)
class Transform(object):
"""Provides transforms to go to and from rotated grid.
Parameters
----------
ilim : (xmin, xmax, ymin, ymax)
The limits of the displayed axes (in physical units)
olim : (xmin, xmax, ymin, ymax)
The limits of the rotated axes (in physical units)
"""
def __init__(self, ilim, olim):
# Convert each to a 3x3 matrix and compute the transform
# [x1, y1, 1] = A*[x0, y0, 1]
x0, x1, y0, y1 = np.log10(ilim)
I = np.array([[x0, x0, x1],
[y0, y1, y1],
[ 1, 1, 1]])
x0, x1, y0, y1 = np.log10(olim)
x_mid = (x0 + x1)/2
y_mid = (y0 + y1)/2
O = np.array([[ x0, x_mid, x1],
[y_mid, y1, y_mid],
[ 1, 1, 1]])
self.A = np.dot(O, np.linalg.inv(I))
self.Ainv = np.linalg.inv(self.A)
def tr(self, x, y):
"""From "curved" (rotated) coords to rectlinear coords"""
x, y = map(np.asarray, (x, y))
return np.dot(self.A, np.asarray([x, y, 1]))[:2]
def inv_tr(self, x, y):
"""From rectlinear coords to "curved" (rotated) coords"""
x, y = map(np.asarray, (x, y))
return np.dot(self.Ainv, np.asarray([x, y, 1]))[:2]
ilim = (0.1, 10)
olim = (0.01, 100)
tr = Transform(ilim + ilim, olim + olim)
grid_helper = GridHelperCurveLinear((tr.tr, tr.inv_tr))
fig = plt.gcf()
ax0 = Subplot(fig, 1, 1, 1)
ax1 = Subplot(fig, 1, 1, 1, grid_helper=grid_helper, frameon=False)
ax1.set_xlim(*np.log10(olim))
ax1.set_ylim(*np.log10(olim))
ax1.axis["left"] = ax1.new_floating_axis(0, 0.)
ax1.axis["bottom"] = ax1.new_floating_axis(1, 0.0)
fig.add_subplot(ax0)
fig.add_subplot(ax1)
ax0.grid('on', which='both')
ax1.grid('on', which='both')
plt.plot(log10(beta), log10(Rd))
plt.ylim(np.log10(ilim))
plt.xlim(np.log10(ilim))
This seems to be a bit tricker than it should. There are ways to center the spines (axis lines), and ways to rotate them, but those do not work together. Adding a normal axis on a line (a la mpl demos) results in a curved axis (because it is logarithmic). Here is a [poor] example of how to draw -- as in, like you would with Inkscape something to look like an additional pair of axis spines with the example data.
import matplotlib.pyplot as plt
import numpy as np
#data
b = np.logspace(-1, 1, 500)
Rd = {}
for zeta in [0.01, 0.1, 0.2, 0.7, 1]:
Rd[zeta] = b / np.sqrt((1 - b * b) ** 2 + (2 * b * zeta) ** 2)
#plot
fig = plt.figure()
ax1 = fig.add_subplot(111)
for z in Rd:
ax1.loglog(b, Rd[z])
ax1.set_xlim([0.1, 10])
ax1.set_ylim([0.1, 10])
ax1.set_aspect(1.)
#draw lines to look like diagonal spines (axes)
xmin, xmax = ax1.get_xlim() # xlim == ylim
a = np.log10(xmin)
b = np.log10(xmax)
span = b - a
period_points = 3 # number of points/ticks per decade
npts = (span * period_points) + 1 # +1 for even powers of 10
x1 = np.logspace(a, b, num=npts)
x2 = np.logspace(b, a, num=npts)
ax1.plot(x1, x1, color='k', marker='x', ms='9')
ax1.plot(x1, x2, color='k', marker='x', ms='9')
#NOTE: v1.2.1 lacks 'TICKUP' and similar - these may be
# a better choice in v1.3x and beyond
ax1.text(0.97, 0.9,
"axis label: A",
size='large',
horizontalalignment='right',
verticalalignment='top',
rotation=45,
transform=ax1.transAxes,
#bbox={'facecolor': 'white', 'alpha': 0.5, 'pad': 10},
)
ax1.text(0.03, 0.9,
"axis label: B",
size='large',
horizontalalignment='left',
verticalalignment='top',
rotation=-45,
transform=ax1.transAxes,
#bbox={'facecolor': 'white', 'alpha': 0.5, 'pad': 10},
)
plt.savefig("example.pdf")