Is there a python 3d topographical plot that works with Pandas? - python

I am processing x, y, and z data to have a floor map with high and lows. Z being a displacement sensor. I need to plot a topographical map with gradients. I currently have a 3D scatter plot and a contour plot using matplotlib widgets. Those work great, but a wireframe map or topgraphical map would work best. Either 2D or 3D work as well. Thank you in advance!
Current outputs:
3D Scatter
3D Contour
Example of what I am trying to achieve:
Bokeh surface 3D plot
2D plot
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import holoviews as hv
from bokeh.models import ColumnDataSource
from mpl_toolkits.mplot3d import Axes3D
from holoviews import opts
hv.extension('bokeh', 'matplotlib')
%matplotlib widget
%matplotlib inline
%matplotlib nbagg
%matplotlib ipympl
plt.style.use('seaborn-white')
#Extend width of Jupyter Notebook
from IPython.core.display import display, HTML
display(HTML("<style>.container { width:100% !important; }</style>"))
#Read CSV
df = pd.read_csv('Floor Scan.csv')
clean_df = df.dropna(axis = 0, how ='any')
print(clean_df)
print('')
z_offset = (clean_df['Displacement (in)'].min())
z_offset_abs = abs(z_offset)
print("Minimum Z:" + str(z_offset))
#3D SCATTER
fig = plt.figure(figsize=(20,10))
ax = fig.add_subplot(111, projection='3d')
x = clean_df['fActualPosition_X (-)']
y = clean_df['fActualPosition_Y (-)']
z = clean_df['Displacement (in)']
ax.scatter(x, y, (z + z_offset_abs), c='b', marker='^')
plt.xlabel("fActualPosition_X (-)")
plt.ylabel("fActualPosition_Y (-)")
plt.show()
plt.savefig('Floor_Map_Scatter_3D.svg')
#3D CONTOUR
fig = plt.figure(figsize=(20,10))
ax = fig.add_subplot(111, projection='3d')
X = clean_df['fActualPosition_X (-)'].astype(np.uint8)
Y = clean_df['fActualPosition_Y (-)'].astype(np.uint8)
Z = clean_df['Displacement (in)'].astype(np.uint8)
flatX = np.asarray(clean_df['fActualPosition_X (-)'])
flatY = np.asarray(clean_df['fActualPosition_Y (-)'])
flatZ = np.asarray(clean_df['Displacement (in)'])
# flatX, flatY = np.meshgrid(X, Y)
# flatZ = function(flatX, flatY, Z)
# print(flatX)
# print('')
# print(flatY)
# print('')
# print(flatZ)
# print('')
plt.tricontourf(flatX, flatY, (flatZ+z_offset_abs),20)
plt.show();
plt.savefig('Floor_Map_Contour_3D.svg')

It sounds like your original data is in the form of isolated points (from a range-measuring device like LIDAR?), and what you want is not simply to plot those points, but first to infer or interpolate a surface from those points and then plot that surface. The two desired examples both take an already calculated grid of values and plot them either as a surface or as an image, so first you need to make such a grid, which is not strictly a plotting problem but one of data processing.
One typical way of creating the grid is to aggregate the values into Cartesian coordinates, basically just counting the average value of the scatter points per grid cell. Another is to connect up all the points into a triangular mesh, which may or may not actually form a surface (a function mapping from x,y -> z).
You can use our library Datashader to aggregate just about any set of data into a regular grid, and can then display it as images or contours using hvPlot (https://hvplot.holoviz.org/user_guide/Gridded_Data.html) or as a surface or wireframe using HoloViews (http://holoviews.org/reference/elements/plotly/Surface.html#elements-plotly-gallery-surface).
If you want an unstructured grid, you can use scipy.spatial to compute a triangulation, then HoloViews to visualize it (http://holoviews.org/reference/elements/bokeh/TriMesh.html#elements-bokeh-gallery-trimesh).

Related

matplotlib logarithmic colormap for logarithmic surface plot

I'm using python to create a 3D surface map, I have an array of data I'm trying to plot as a 3D surface, the issue is that I have logged the Z axis (necessary to show peaks in data) which means the default colormap doesn't work (displays one continous color). I've tried using the LogNorm to normalise the colormap but again this produces one continous color. I'm not sure whether I should be using the logged values to normalise the map, but if i do this the max is negative and produces an error?
fig=plt.figure(figsize=(10,10))
ax=plt.axes(projection='3d')
def log_tick_formatter(val, pos=None):
return "{:.2e}".format(10**val)
ax.zaxis.set_major_formatter(mticker.FuncFormatter(log_tick_formatter))
X=np.arange(0,2,1)
Y=np.arange(0,3,1)
X,Y=np.meshgrid(X,Y)
Z=[[1.2e-11,1.3e-11,-1.8e-11],[6e-13,1.3e-13,2e-15]]
Z_min=np.amin(Z)
Z_max=np.amax(Z)
norm = colors.LogNorm(vmin=1e-15,vmax=(Z_max),clip=False)
ax.plot_surface(X,Y,np.transpose(np.log10(Z)),norm=norm,cmap='rainbow')
Just an example of the logarithmic colors and logarithmic data:
#!/usr/bin/env ipython
import numpy as np
import matplotlib as mpl
import matplotlib.pylab as plt
import matplotlib.colors as colors
# ------------------
X=np.arange(0,401,1);nx= np.size(X)
Y=np.arange(40,200,1);ny = np.size(Y)
X,Y=np.meshgrid(X,Y)
Z = 10000*np.random.random((ny,nx))
Z=np.array(Z)
# ------------------------------------------------------------
Z_min=np.amin(Z)
Z_max=np.amax(Z)
# ------------------------------------------------------------
norm = colors.LogNorm(vmin=np.nanmin(Z),vmax=np.nanmax(Z),clip=False)
# ------------------------------------------------------------
fig = plt.figure(figsize=(15,5));axs = [fig.add_subplot(131),fig.add_subplot(132),fig.add_subplot(133)]
p0 = axs[0].pcolormesh(X,Y,np.log10(Z),cmap='rainbow',norm=norm);plt.colorbar(p0,ax=axs[0]);
axs[0].set_title('Original method: NOT TO DO!')
p1 = axs[1].pcolormesh(X,Y,Z,cmap='rainbow',norm=norm);plt.colorbar(p1,ax=axs[1])
axs[1].set_title('Normalized colorbar, original data')
p2 = axs[2].pcolormesh(X,Y,np.log10(Z),cmap='rainbow');plt.colorbar(p2,ax=axs[2])
axs[2].set_title('Logarithmic data, original colormap')
plt.savefig('test.png',bbox_inches='tight')
# --------------------------------------------------------------
So the result is like this:
In the first case, we have used logarithmic colormap and also taken the logarithm of the data, so the colorbar does not work anymore as the values on the map are small and we have used large limits for the colorbar.
In the middle image, we use the normalized colorbar or logarithmic colorbar so that it is quite natively understood what is on the image and what are the values. The third case is when we take the logarithm from the data and the colorbar is just showing the power of the 10th we have to use in order to interpret the coloured value on the plot.
So, in the end, I would suggest the middle method: use the logarithmic colorbar and original data.
Edit: to solve your problem you are taking the log of the data then you are taking it again when calculating the norm, simply remove the norm and apply vmin and vmax directly to the drawing function
ax.plot_surface(X, Y, np.transpose(np.log10(Z)), cmap='rainbow',vmin=np.log10(1e-15),vmax=np.log10(Z_max))
you can use the facecolor argument of plot_surface to define color for each face independent of z, here's a simplified example
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
x = np.linspace(0,10,100)
y = np.linspace(0,10,100)
x,y = np.meshgrid(x,y)
z = np.sin(x+y)
fig, ax = plt.subplots(subplot_kw={"projection": "3d"})
cmap = matplotlib.cm.get_cmap('rainbow')
def rescale_0_to_1(item):
max_z = np.amax(item)
min_z = np.amin(item)
return (item - min_z)/(max_z-min_z)
rgba = cmap(rescale_0_to_1(z)) # some values of z to calculate color with
real_z = np.log(z+1) # real values of z to draw
surf = ax.plot_surface(x, y, real_z, cmap='rainbow', facecolors=rgba)
plt.show()
you can modify it to calculate colors based on x or y or something completely unrelated.

How do I use Matplotlib pyplot pcolor to provide distinct color to each grid in my plot

I have built some fairly simple logarithmic matplotlib scatter plots. I am happy with the results thus far, but I need to change the color of each grid area.
Current Plot:
I would like to understand how I could do the following:
I appreciate any help. Thanks.
Plot a small image underneath the scatter plot, with an integer indicating the tile color. You can then use pcolor to plot the image, with edgecolors to define the borders. The code below does this, with cell color defined as the maximum of cell index i, j, which happens to match your grid.
import numpy as np
import matplotlib.pyplot as plt
# define grid
nx, ny = 6, 5
x, y = np.arange(nx), np.arange(ny)
xx, yy = np.meshgrid(x, y)
z = np.maximum(xx, yy)
# create random points
npoints = 30
data_x = 10**(np.random.rand(npoints)*nx)
data_y = 10**(np.random.rand(npoints)*ny-1)
# plot grid then points
plt.pcolor(10.**x, 10.**(y-1), z, edgecolors='k')
plt.loglog(data_x, data_y, '.w')
plt.axis([1,10**5,0.1,10**3])
plt.show()
Note that you could also use zorder=n to force the scatter plot above the image.

python: scatter does not display

I have some trouble using scatter in python. I have 2 vectors of size 100 of latitudes and longitudes, and a vector 100 of corresponding data of values between 1 and 2.
I am trying to display these data on a north polar stereographic projected map, but nothing appears on the figure. Here is my code:
import numpy as np
import matplotlib as mpl
from matplotlib import pyplot as plt
from matplotlib import colors as mpl_colors
from mpl_toolkits.basemap import Basemap`
lon=np.linspace(-180,180,num=100)
lat=np.linspace(75,75,num=100)
data=1+np.random.rand(100)
cmap = plt.cm.jet
norm_color = mpl_colors.Normalize(vmin=None, vmax=None, clip=False)
fig=plt.figure()
m = Basemap(projection='npstere',boundinglat=65,lon_0=310,resolution='l')
x,y = m(lon,lat)
m.scatter(x, y,marker='+',c=data,cmap=cmap, norm=norm_color,edgecolor='none')
plt.show()
When I just scatter the coordinates: m.scatter(x, y,marker='+') it works fine (I have "+" at the corresponding coordinates, i.e on the 75° latitude projected circle). But when I want to add the data, nothing is displayed.
Where am I not using scatter right ?
I found what was the problem.
It is the simultaneous use of the options marker='+' and edgecolor='None'.
The latter allows to change the color of the edge of the marker. With the marker '+', the edge is the marker itself: that is why it was not displayed.
In replacement of the line m.scatter(x, y,marker='+',c=data,cmap=cmap, norm=norm_color,edgecolor='none') the following work:
m.scatter(x, y,marker='+',c=data,cmap=cmap, norm=norm_color)
or
m.scatter(x, y,marker='o',c=data,cmap=cmap, norm=norm_color,edgecolor='none')

Create a stack of polar plots using Matplotlib/Python

I need to generate a stack of 2D polar plots (a 3D cylindrical plot) so that I can view a distorted cylinder. I want to use matplotlib since I already have it installed and want to distribute my code to others who only have matplotlib. For example, say I have a bunch of 2-D arrays. Is there any way I can do this without having to download an external package? Here's my code.
#!usr/bin/env python
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(-180.0,190.0,10)
theta = (np.pi/180.0 )*x # in radians
A0 = 55.0
offset = 60.0
R = [116.225,115.105,114.697,115.008,115.908,117.184,118.61,119.998,121.224,122.216,\
122.93,123.323,123.343,122.948,122.134,120.963,119.575,118.165,116.941,116.074,115.66\
,115.706,116.154,116.913,117.894,119.029,120.261,121.518,122.684,123.594,124.059,\
123.917,123.096,121.661,119.821,117.894,116.225]
fig = plt.figure()
ax = fig.add_axes([0.1,0.1,0.8,0.8],polar=True) # Polar plot
ax.plot(theta,R,lw=2.5)
ax.set_rmax(1.5*(A0)+offset)
plt.show()
I have 10 more similar 2D polar plots and I want to stack them up nicely. If there's any better way to visualize a distorted cylinder in 3D, I'm totally open to suggestions. Any help would be appreciated. Thanks!
If you want to stack polar charts using matplotlib, one approach is to use the Axes3D module. You'll notice that I used polar coordinates first and then converted them back to Cartesian when I was ready to plot them.
from numpy import *
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
n = 1000
fig = plt.figure()
ax = fig.gca(projection='3d')
for k in linspace(0, 5, 5):
THETA = linspace(0, 2*pi, n)
R = ones(THETA.shape)*cos(THETA*k)
# Convert to Cartesian coordinates
X = R*cos(THETA)
Y = R*sin(THETA)
ax.plot(X, Y, k-2)
plt.show()
If you play with the last argument of ax.plot, it controls the height of each slice. For example, if you want to project all of your data down to a single axis you would use ax.plot(X, Y, 0). For a more exotic example, you can map the height of the data onto a function, say a saddle ax.plot(X, Y, -X**2+Y**2 ). By playing with the colors as well, you could in theory represent multiple 4 dimensional datasets (though I'm not sure how clear this would be). Examples below:

how do you radially 'sweep out' a 1D array to plot 3d figure in python? (to represent a wavefunction)

effectively I have a large 1D array of heights. As a small example consider:
u=array([0,1,2,1,0,2,4,6,4,2,1])
and a 1D array, the same size as u, of radial values which the heights correspond to, e.g.:
r=array([0,1,2,3,4,5,6,7,8,9,10])
Obviously plotting these with:
pylab.plot(r,u)
gives a nice 2D plot.
How can one sweep this out around 360 degrees, to give a 3D contour/surface plot?
If you can imagine it should look like a series of concentric, circular ridges, like for the wavefunction of an atom.
any help would be much appreciated!
You're better off with something more 3D oriented than matplotlib, in this case...
Here's a quick example using mayavi:
from enthought.mayavi import mlab
import numpy as np
# Generate some random data along a straight line in the x-direction
num = 100
x = np.arange(num)
y, z = np.ones(num), np.ones(num)
s = np.cumsum(np.random.random(num) - 0.5)
# Plot using mayavi's mlab api
fig = mlab.figure()
# First we need to make a line source from our data
line = mlab.pipeline.line_source(x,y,z,s)
# Then we apply the "tube" filter to it, and vary the radius by "s"
tube = mlab.pipeline.tube(line, tube_sides=20, tube_radius=1.0)
tube.filter.vary_radius = 'vary_radius_by_scalar'
# Now we display the tube as a surface
mlab.pipeline.surface(tube)
# And finally visualize the result
mlab.show()
#!/usr/bin/python
from mpl_toolkits.mplot3d import Axes3D
import matplotlib
import numpy as np
from scipy.interpolate import interp1d
from matplotlib import cm
from matplotlib import pyplot as plt
step = 0.04
maxval = 1.0
fig = plt.figure()
ax = Axes3D(fig)
u=np.array([0,1,2,1,0,2,4,6,4,2,1])
r=np.array([0,1,2,3,4,5,6,7,8,9,10])
f=interp1d(r,u)
# walk along the circle
p = np.linspace(0,2*np.pi,50)
R,P = np.meshgrid(r,p)
# transform them to cartesian system
X,Y = R*np.cos(P),R*np.sin(P)
Z=f(R)
ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.jet)
ax.set_xticks([])
plt.show()

Categories