I have three data distributions:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LogNorm
a = np.load('A.npy')
b = np.load('B.npy')
c = np.load('C.npy')
plt.figure(figsize=(12,4))
plt.subplot(131)
plt.hist2d(a,b,bins=300,norm=LogNorm())
plt.xlabel('A')
plt.ylabel('B')
plt.subplot(132)
plt.hist2d(a,c,bins=300,norm=LogNorm())
plt.xlabel('A')
plt.ylabel('C')
plt.subplot(133)
plt.hist2d(b,c,bins=300,norm=LogNorm())
plt.xlabel('B')
plt.ylabel('C')
plt.show()
And here is the result:
Now, I want to represent all three plots on a radar plot to look something like this:
Any ideas?
First, the easier part, the plotting: I've used 3 times the same random data, shrinked (0..2pi -> 0..2/3pi) and shifted (0, 2/3pi, 4/3pi) them to get 3 big pizza parts:
import numpy as np
import matplotlib.pyplot as plt
parts = 3
ax = plt.subplot(111, polar=True)
shrink = 1./parts
for i in range(3):
# beginning and end angle of this part
start = i * 2/parts * np.pi
end = (i + 1) * 2/parts * np.pi
# Generate random data:
N = 10000
r = .5 + np.random.normal(size=N, scale=.2)
theta = (np.pi / 2 + np.random.normal(size=N, scale=.1))
# shift the data counterclockwise so that it fills the n-th part
theta += i * 2.*np.pi / parts
# Histogramming
nr = 50
ntheta = 200
r_edges = np.linspace(0, 1, nr + 1)
theta_edges = np.linspace(start, end, ntheta + 1)
H, _, _ = np.histogram2d(r, theta, [r_edges, theta_edges])
# Plot
Theta, R = np.meshgrid(theta_edges, r_edges)
ax.pcolormesh(Theta, R, H)
plt.show()
Now the harder part: You will still have to convert your points into radial values, I'm not sure how you define this for your coordinates, as a point has 3 dimensions but you want to map to 2d. I hope this helps!
My code is based on this 2d heatmap.
I have this so far:
x,y,z = data.nonzero()
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(x, y, z, zdir='z', c= 'red')
plt.savefig("plot.png")
Which creates:
What I'd like to do is stretch this out to make the Z axis 9 times taller and keep X and Y the same. I'd like to keep the same coordinates though.
So far I tried this guy:
fig = plt.figure(figsize=(4.,35.))
But that just stretches out the plot.png image.
The code example below provides a way to scale each axis relative to the others. However, to do so you need to modify the Axes3D.get_proj function. Below is an example based on the example provided by matplot lib: http://matplotlib.org/1.4.0/mpl_toolkits/mplot3d/tutorial.html#line-plots
(There is a shorter version at the end of this answer)
from mpl_toolkits.mplot3d.axes3d import Axes3D
from mpl_toolkits.mplot3d import proj3d
import matplotlib as mpl
import numpy as np
import matplotlib.pyplot as plt
#Make sure these are floating point values:
scale_x = 1.0
scale_y = 2.0
scale_z = 3.0
#Axes are scaled down to fit in scene
max_scale=max(scale_x, scale_y, scale_z)
scale_x=scale_x/max_scale
scale_y=scale_y/max_scale
scale_z=scale_z/max_scale
#Create scaling matrix
scale = np.array([[scale_x,0,0,0],
[0,scale_y,0,0],
[0,0,scale_z,0],
[0,0,0,1]])
print scale
def get_proj_scale(self):
"""
Create the projection matrix from the current viewing position.
elev stores the elevation angle in the z plane
azim stores the azimuth angle in the x,y plane
dist is the distance of the eye viewing point from the object
point.
"""
relev, razim = np.pi * self.elev/180, np.pi * self.azim/180
xmin, xmax = self.get_xlim3d()
ymin, ymax = self.get_ylim3d()
zmin, zmax = self.get_zlim3d()
# transform to uniform world coordinates 0-1.0,0-1.0,0-1.0
worldM = proj3d.world_transformation(
xmin, xmax,
ymin, ymax,
zmin, zmax)
# look into the middle of the new coordinates
R = np.array([0.5, 0.5, 0.5])
xp = R[0] + np.cos(razim) * np.cos(relev) * self.dist
yp = R[1] + np.sin(razim) * np.cos(relev) * self.dist
zp = R[2] + np.sin(relev) * self.dist
E = np.array((xp, yp, zp))
self.eye = E
self.vvec = R - E
self.vvec = self.vvec / proj3d.mod(self.vvec)
if abs(relev) > np.pi/2:
# upside down
V = np.array((0, 0, -1))
else:
V = np.array((0, 0, 1))
zfront, zback = -self.dist, self.dist
viewM = proj3d.view_transformation(E, R, V)
perspM = proj3d.persp_transformation(zfront, zback)
M0 = np.dot(viewM, worldM)
M = np.dot(perspM, M0)
return np.dot(M, scale);
Axes3D.get_proj=get_proj_scale
"""
You need to include all the code above.
From here on you should be able to plot as usual.
"""
mpl.rcParams['legend.fontsize'] = 10
fig = plt.figure(figsize=(5,5))
ax = fig.gca(projection='3d')
theta = np.linspace(-4 * np.pi, 4 * np.pi, 100)
z = np.linspace(-2, 2, 100)
r = z**2 + 1
x = r * np.sin(theta)
y = r * np.cos(theta)
ax.plot(x, y, z, label='parametric curve')
ax.legend()
plt.show()
Standard output:
Scaled by (1, 2, 3):
Scaled by (1, 1, 3):
The reason I particularly like this method,
Swap z and x, scale by (3, 1, 1):
Below is a shorter version of the code.
from mpl_toolkits.mplot3d.axes3d import Axes3D
from mpl_toolkits.mplot3d import proj3d
import matplotlib as mpl
import numpy as np
import matplotlib.pyplot as plt
mpl.rcParams['legend.fontsize'] = 10
fig = plt.figure(figsize=(5,5))
ax = fig.gca(projection='3d')
theta = np.linspace(-4 * np.pi, 4 * np.pi, 100)
z = np.linspace(-2, 2, 100)
r = z**2 + 1
x = r * np.sin(theta)
y = r * np.cos(theta)
"""
Scaling is done from here...
"""
x_scale=1
y_scale=1
z_scale=2
scale=np.diag([x_scale, y_scale, z_scale, 1.0])
scale=scale*(1.0/scale.max())
scale[3,3]=1.0
def short_proj():
return np.dot(Axes3D.get_proj(ax), scale)
ax.get_proj=short_proj
"""
to here
"""
ax.plot(z, y, x, label='parametric curve')
ax.legend()
plt.show()
Please note that the answer below simplifies the patch, but uses the same underlying principle as the answer by #ChristianSarofeen.
Solution
As already indicated in other answers, it is not a feature that is currently implemented in matplotlib. However, since what you are requesting is simply a 3D transformation that can be applied to the existing projection matrix used by matplotlib, and thanks to the wonderful features of Python, this problem can be solved with a simple oneliner:
ax.get_proj = lambda: np.dot(Axes3D.get_proj(ax), np.diag([scale_x, scale_y, scale_z, 1]))
where scale_x, scale_y and scale_z are values from 0 to 1 that will re-scale your plot along each of the axes accordingly. ax is simply the 3D axes which can be obtained with ax = fig.gca(projection='3d')
Explanation
To explain, the function get_proj of Axes3D generates the projection matrix from the current viewing position. Multiplying it by a scaling matrix:
scale_x, 0, 0
0, scale_y, 0
0, 0, scale_z
0, 0, 1
includes the scaling into the projection used by the renderer. So, what we are doing here is substituting the original get_proj function with an expression taking the result of the original get_proj and multiplying it by the scaling matrix.
Example
To illustrate the result with the standard parametric function example:
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.gca(projection='3d')
theta = np.linspace(-4 * np.pi, 4 * np.pi, 100)
z = np.linspace(-2, 2, 100)
r = z ** 2 + 1
x = r * np.sin(theta)
y = r * np.cos(theta)
# OUR ONE LINER ADDED HERE:
ax.get_proj = lambda: np.dot(Axes3D.get_proj(ax), np.diag([0.5, 0.5, 1, 1]))
ax.plot(x, y, z)
plt.show()
for values 0.5, 0.5, 1, we get:
while for values 0.2, 1.0, 0.2, we get:
In my case I wanted to stretch z-axis 2 times for better point visibility
from mpl_toolkits import mplot3d
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
# plt.rcParams["figure.figsize"] = (10,200)
# plt.rcParams["figure.autolayout"] = True
ax = plt.axes(projection='3d')
ax.set_box_aspect(aspect = (1,1,2))
ax.plot(dataX,dataY,dataZ)
I looks like by default, mplot3d will leave quite a bit of room at the top and bottom of a very tall plot. But, you can trick it into filling that space using fig.subplots_adjust, and extending the top and bottom out of the normal plotting area (i.e. top > 1 and bottom < 0). Some trial and error here is probably needed for your particular plot.
I've created some random arrays for x, y, and z with limits similar to your plot, and have found the parameters below (bottom=-0.15, top = 1.2) seem to work ok.
You might also want to change ax.view_init to set a nice viewing angle.
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
from numpy import random
# Make some random data with similar limits to the OP's example
x,y,z=random.rand(3,100)
z*=250
y*=800
y+=900
x*=350
x+=1200
fig=plt.figure(figsize=(4,35))
# Set the bottom and top outside the actual figure limits,
# to stretch the 3D axis
fig.subplots_adjust(bottom=-0.15,top=1.2)
ax = fig.add_subplot(111, projection='3d')
# Change the viewing angle to an agreeable one
ax.view_init(2,None)
ax.scatter(x, y, z, zdir='z', c= 'red')
plt.savefig("plot.png")
Sounds like you're trying to adjust the scale of the plot. I don't think there's a way to stretch a linear scale to user specifications, but you can use set_yscale(), set_xscale(), set_zscale() to alter the scales with respect to each other.
Intuitively, set_yscale(log), set_xscale(log), set_zscale(linear) might solve your problems.
A likely better option: specify a stretch, set them all to symlog with the same log base and then specify the Z-axis's symlog scale with the linscalex/linscaley kwargs to your specifications.
More here:
http://matplotlib.org/mpl_toolkits/mplot3d/api.html
I found this while searching on a similar problem. After experimenting a bit, perhaps I can share some of my prelim findings here..matplotlib library is VAST!! (am a newcomer). Note that quite akin to this question, all i wanted was to 'visually' stretch the chart without distorting it.
Background story (only key code snippets are shown to avoid unnecessary clutter for those who know the library, and if you want a run-able code please drop a comment):
I have three 1-d ndarrays representing the X,Y and Z data points respectively. Clearly I can't use plot_surface (as it requires 2d ndarrays for each dim) so I went for the extremely useful plot_trisurf:
fig = plt.figure()
ax = Axes3D(fig)
3d_surf_obj = ax.plot_trisurf(X, Y, Z_defl, cmap=cm.jet,linewidth=0,antialiased=True)
You can think of the plot like a floating barge deforming in waves...As you can see, the axes stretch make it pretty deceiving visually (note that x is supposed to be at x6 times longer than y and >>>>> z). While the plot points are correct, I wanted something more visually 'stretched' at the very least. Was looking for A QUICK FIX, if I may. Long story cut short, I found a bit of success with...'figure.figsize' general setting (see snippet below).
matplotlib.rcParams.update({'font.serif': 'Times New Roman',
'font.size': 10.0,
'axes.labelsize': 'Medium',
'axes.labelweight': 'normal',
'axes.linewidth': 0.8,
###########################################
# THIS IS THE IMPORTANT ONE FOR STRETCHING
# default is [6,4] but...i changed it to
'figure.figsize':[15,5] # THIS ONE #
})
For [15,5] I got something like...
Pretty neat!!
So I started to push it.... and got up to [20,6] before deciding to settle there..
If you want to try for visually stretching the vertical axis, try with ratios like... [7,10], which in this case gives me ...
Not too shabby !
Should do it for visual prowess.
Multiply all your z values by 9,
ax.scatter(x, y, 9*z, zdir='z', c= 'red')
And then give the z-axis custom plot labels and spacing.
ax.ZTick = [0,-9*50, -9*100, -9*150, -9*200];
ax.ZTickLabel = {'0','-50','-100','-150','-200'};
I have a 3d plot made using matplotlib. I now want to fill the vertical space between the drawn line and the x,y axis to highlight the height of the line on the z axis. On a 2d plot this would be done with fill_between but there does not seem to be anything similar for a 3d plot. Can anyone help?
here is my current code
from stravalib import Client
import matplotlib as mpl
import numpy as np
import matplotlib.pyplot as plt
... code to get the data ....
mpl.rcParams['legend.fontsize'] = 10
fig = plt.figure()
ax = fig.gca(projection='3d')
zi = alt
x = df['x'].tolist()
y = df['y'].tolist()
ax.plot(x, y, zi, label='line')
ax.legend()
plt.show()
and the current plot
just to be clear I want a vertical fill to the x,y axis intersection NOT this...
You're right. It seems that there is no equivalent in 3D plot for the 2D plot function fill_between. The solution I propose is to convert your data in 3D polygons. Here is the corresponding code:
import math as mt
import matplotlib.pyplot as pl
import numpy as np
import random as rd
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
# Parameter (reference height)
h = 0.0
# Code to generate the data
n = 200
alpha = 0.75 * mt.pi
theta = [alpha + 2.0 * mt.pi * (float(k) / float(n)) for k in range(0, n + 1)]
xs = [1.0 * mt.cos(k) for k in theta]
ys = [1.0 * mt.sin(k) for k in theta]
zs = [abs(k - alpha - mt.pi) * rd.random() for k in theta]
# Code to convert data in 3D polygons
v = []
for k in range(0, len(xs) - 1):
x = [xs[k], xs[k+1], xs[k+1], xs[k]]
y = [ys[k], ys[k+1], ys[k+1], ys[k]]
z = [zs[k], zs[k+1], h, h]
#list is necessary in python 3/remove for python 2
v.append(list(zip(x, y, z)))
poly3dCollection = Poly3DCollection(v)
# Code to plot the 3D polygons
fig = pl.figure()
ax = Axes3D(fig)
ax.add_collection3d(poly3dCollection)
ax.set_xlim([min(xs), max(xs)])
ax.set_ylim([min(ys), max(ys)])
ax.set_zlim([min(zs), max(zs)])
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_zlabel("z")
pl.show()
It produces the following figure:
I hope this will help you.
I would like to add a fourth dimension to the scatter plot by defining the ellipticity of the markers depending on a variable. Is that possible somehow ?
EDIT:
I would like to avoid a 3D-plot. In my opinion these plots are usually not very informative.
You can place Ellipse patches directly onto your axes, as demonstrated in this matplotlib example. To adapt it to use eccentricity as your "third dimension") keeping the marker area constant:
from pylab import figure, show, rand
from matplotlib.patches import Ellipse
import numpy as np
import matplotlib.pyplot as plt
N = 25
# ellipse centers
xy = np.random.rand(N, 2)*10
# ellipse eccentrities
eccs = np.random.rand(N) * 0.8 + 0.1
fig = plt.figure()
ax = fig.add_subplot(111, aspect='equal')
A = 0.1
for pos, e in zip(xy, eccs):
# semi-minor, semi-major axes, b and a:
b = np.sqrt(A/np.pi * np.sqrt(1-e**2))
a = A / np.pi / b
ellipse = Ellipse(xy=pos, width=2*a, height=2*b)
ax.add_artist(ellipse)
ax.set_xlim(0, 10)
ax.set_ylim(0, 10)
show()
Of course, you need to scale your marker area to your x-, y- values in this case.
You can use colorbar as the 4th dimension to your 3D plot. One example is as shown below:
import matplotlib.cm as cmx
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import matplotlib
import numpy as np
def scatter3d(x,y,z, cs, colorsMap='jet'):
cm = plt.get_cmap(colorsMap)
cNorm = matplotlib.colors.Normalize(vmin=min(cs), vmax=max(cs))
scalarMap = cmx.ScalarMappable(norm=cNorm, cmap=cm)
fig = plt.figure()
ax = Axes3D(fig)
ax.scatter(x, y, z, c=scalarMap.to_rgba(cs))
scalarMap.set_array(cs)
fig.colorbar(scalarMap,label='Test')
plt.show()
x = np.random.uniform(0,1,50)
y = np.random.uniform(0,1,50)
z = np.random.uniform(0,1,50)
so scatter3D(x,y,z,x+y) produces:
with x+y being the 4th dimension shown in color. You can add your calculated ellipticity depending on your specific variable instead of x+y to get what you want.
To change the ellipticity of the markers you will have to create them manually as such a feature is not implemented yet. However, I believe you can show 4 dimensions with a 2D scatter plot by using color and size as additional dimensions. You will have to take care of the scaling from data to marker size yourself. I added a simple function to handle that in the example below:
import matplotlib.pyplot as plt
import numpy as np
data = np.random.rand(60,4)
def scale_size(data, data_min=None, data_max=None, size_min=10, size_max=60):
# if the data limits are set to None we will just infer them from the data
if data_min is None:
data_min = data.min()
if data_max is None:
data_max = data.max()
size_range = size_max - size_min
data_range = data_max - data_min
return ((data - data_min) * size_range / data_range) + size_min
plt.scatter(data[:,0], data[:,1], c=data[:,2], s=scale_size(data[:,3]))
plt.colorbar()
plt.show()
Result:
I am trying to make a polar plot that goes 180 degrees instead of 360 in Matplotlib similar to http://www.mathworks.com/matlabcentral/fileexchange/27230-half-polar-coordinates-figure-plot-function-halfpolar in MATLAB. Any ideas?
The following works in matplotlib 2.1 or higher. There is also an example on the matplotlib page.
You may use a usual polar plot, ax = fig.add_subplot(111, polar=True) and confine the theta range. For a half polar plot
ax.set_thetamin(0)
ax.set_thetamax(180)
or for a quarter polar plot
ax.set_thetamin(0)
ax.set_thetamax(90)
Complete example:
import matplotlib.pyplot as plt
import numpy as np
theta = np.linspace(0,np.pi)
r = np.sin(theta)
fig = plt.figure()
ax = fig.add_subplot(111, polar=True)
c = ax.scatter(theta, r, c=r, s=10, cmap='hsv', alpha=0.75)
ax.set_thetamin(0)
ax.set_thetamax(180)
plt.show()
The example code in official matplotlib documentation may obscure things a little bit if someone just needs a simple quarter of half plot.
I wrote a code snippet that may help someone who is not that familiar with AxisArtists here.
"""
Reference:
1. https://gist.github.com/ycopin/3342888
2. http://matplotlib.org/mpl_toolkits/axes_grid/users/overview.html#axisartist
"""
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.projections import PolarAxes
from mpl_toolkits.axisartist.floating_axes import GridHelperCurveLinear, FloatingSubplot
import mpl_toolkits.axisartist.grid_finder as gf
def generate_polar_axes():
polar_trans = PolarAxes.PolarTransform()
# Setup the axis, here we map angles in degrees to angles in radius
phi_degree = np.arange(0, 90, 10)
tlocs = phi_degree * np.pi / 180
gl1 = gf.FixedLocator(tlocs) # Positions
tf1 = gf.DictFormatter(dict(zip(tlocs, map(str, phi_degree))))
# Standard deviation axis extent
radius_min = 0
radius_max = 1
# Set up the axes range in the parameter "extremes"
ghelper = GridHelperCurveLinear(polar_trans, extremes=(0, np.pi / 2, # 1st quadrant
radius_min, radius_max),
grid_locator1=gl1,
tick_formatter1=tf1,
)
figure = plt.figure()
floating_ax = FloatingSubplot(figure, 111, grid_helper=ghelper)
figure.add_subplot(floating_ax)
# Adjust axes
floating_ax.axis["top"].set_axis_direction("bottom") # "Angle axis"
floating_ax.axis["top"].toggle(ticklabels=True, label=True)
floating_ax.axis["top"].major_ticklabels.set_axis_direction("top")
floating_ax.axis["top"].label.set_axis_direction("top")
floating_ax.axis["top"].label.set_text("angle (deg)")
floating_ax.axis["left"].set_axis_direction("bottom") # "X axis"
floating_ax.axis["left"].label.set_text("radius")
floating_ax.axis["right"].set_axis_direction("top") # "Y axis"
floating_ax.axis["right"].toggle(ticklabels=True)
floating_ax.axis["right"].major_ticklabels.set_axis_direction("left")
floating_ax.axis["bottom"].set_visible(False) # Useless
# Contours along standard deviations
floating_ax.grid(True)
floating_ax.set_title("Quarter polar plot")
data_ax = floating_ax.get_aux_axes(polar_trans) # return the axes that can be plotted on
return figure, data_ax
if __name__ == "__main__":
# Plot data onto the defined polar axes
fig, ax = generate_polar_axes()
theta = np.random.rand(10) * np.pi / 2
radius = np.random.rand(10)
ax.scatter(theta, radius)
fig.savefig("test.png")