Python: mapping colors across surface plots - python

Is there a way to map the color-scheme from one surface plot onto another?
For example, let's say I have:
surf_1 = ax.plot_surface(X, Y, Z, cmap='summer')
and
surf_2 = ax.plot_surface(X, Y, Z-Q, cmap='summer')
Is there a way to map the colorscheme for the surface defined by Z-Q onto the surface defined by Z? In other words, I want to visualize surf_1, but I want its surface to take on the colors defined by surf_2.
For context, I am trying to visualize the colors of the fluctuations of a parameter (Z) around a variable height (Q), where Q is not necessarily equal to 0.
EDIT: Is there a way I could extract the colors in surf_2 as an array, and use those colors as input colors for surf_1? Any suggestions would be much appreciated!

You can use ScalarMappable() function to create all colors to use as facecolors in the two surface plots. Here is the runnable code that demonstrates the steps to achieve what you want.
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
fig, ax = plt.subplots(subplot_kw={'projection': '3d'})
fig.set_size_inches([10, 8])
# Make up data for 2 surfaces
X = np.logspace(0, np.log10(16), 50)
Y = np.linspace(3, 6, 50)
Z = np.linspace(-1, 1, 50)
# Convert to 2d arrays
Z = np.outer(Z.T, Z) # 50x50
X, Y = np.meshgrid(X, Y) # 50x50
# Make use of `ScalarMappable()` for custom color
# This use Z to get a colormap for plotting the surface
C = np.linspace(-1, 1, Z.size).reshape(Z.shape)
colormap = "summer" # 'inferno' 'plasma' 'viridis'
scmap = plt.cm.ScalarMappable(cmap=colormap)
# for clarity, 2 surfaces are separated by some z shift
zshift = 80
# Upper-surface
# Note: ax.plot_surface(X, Y, Z*X+zshift, cmap=colormap)
# is almost equivalent with this
ax.plot_surface(X, Y, Z*X+zshift, facecolors=scmap.to_rgba(Z*X+zshift), shade=False)
# `shade=False` is used to suppress 3D shading
# Lower-surface
# Also use `facecolors=scmap.to_rgba(Z*X+zshift)`
# thus, equivalent with taking color from previous surface
ax.plot_surface(X, Y, Z, facecolors=scmap.to_rgba(Z*X+zshift), shade=False)
plt.show()
The output plot:

Related

Displaying 4th variable as colormap on 3D plot in Python

Task
I am trying to generate a plot that would represent my data in 4 dimensions.
The option I am going for is a 3D plot where the 4th dimension is represented with a colormap.
My data has some NaN values.
My attempt
Using an arbitrary set of data, my 4 variables are X, Y, Z, C. Where I want X, Y and Z to lay on their respective axes of the 3D plot and C to define the colormap. Both Z and C have some matching NaN values.
import numpy as np
import matplotlib.pyplot as plt
# generate some data
X, Y = np.meshgrid(np.linspace(1, 10, 20), np.linspace(1, 10, 20)) # 20 by 20 grid
Z = np.linspace(0, 10, 400).reshape((20, 20)) # data to plot on the vertical axis
Z.ravel()[np.random.choice(Z.size, 10, replace=False)] = np.nan # some values are nan
C = np.cos(X) - np.sin(Y) # 4th variable, to be represented in 4th dimension using colormap
C[np.isnan(Z)] = np.nan # corresponding values are nan
I attempted to generate a 3D colormap of X, Y and C first, storing its colormap in variable fc. This code returns me the first colormap (unfortunately, StackOverflow is not letting me post images of the plots).
fig = plt.figure()
ax = fig.add_subplot(221, projection='3d')
# plot the surface for 4th dimension to get its colormap (facecolors)
surf = ax.plot_surface(X, Y, C, cmap='turbo')
fc = surf.get_facecolors()
Afterwards, I generated the desired 3D colormap of X, Y and Z, setting the colormap argument to fc (with some reshaping of fc on the way, as get_facecolors() returns a flat tuple of RGBA arrays), which returns me the second colormap.
# get_facecolors() returns a tuple of 1D arrays, but the plot_surface() facecolors argument requires a 3D array
fc = np.array(fc)
fc = fc.reshape((19, 19, 4))
ax = fig.add_subplot(222, projection='3d')
ax.plot_surface(X, Y, Z, facecolors=fc, cmap='turbo')
plt.show()
The problem
So as can be seen from the two plots, the colormap from the first plot gets mixed over before getting applied to the second plot. Reshaping in fc = fc.reshape((19, 19, 4)) is necessary as those are the required dimensions for the facecolors, but my guess it is this reshaping that causes mixing of colors.
I have attempted other solutions proposed in This question from a few years ago but no luck.
Any advice on how to get the colormap of one plot to translate to the other will be much appreciated! As well as any suggestions of easier/better options of representing 4 variables on a plot.
Thanks in advance!
You need to use Normalize and a color map to compute the facecolors, like this:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm
from matplotlib.colors import Normalize
# generate some data
X, Y = np.meshgrid(np.linspace(1, 10, 20), np.linspace(1, 10, 20)) # 20 by 20 grid
Z = np.linspace(0, 10, 400).reshape((20, 20)) # data to plot on the vertical axis
Z.ravel()[np.random.choice(Z.size, 10, replace=False)] = np.nan # some values are nan
C = np.cos(X) - np.sin(Y) # 4th variable, to be represented in 4th dimension using colormap
C[np.isnan(Z)] = np.nan # corresponding values are nan
cmap = cm.turbo
norm = Normalize(vmin=np.nanmin(C), vmax=np.nanmax(C))
fc = cmap(norm(C))
fig = plt.figure()
ax = fig.add_subplot(221, projection='3d')
# plot the surface for 4th dimension to get its colormap (facecolors)
surf = ax.plot_surface(X, Y, C, facecolors=fc)
ax = fig.add_subplot(222, projection='3d')
ax.plot_surface(X, Y, Z, facecolors=fc)

Plot 4D Contour in Python (X,Y,Z + Data)

I have a large set of measurements that I want to visualize in 4D using matplotlib in Python.
Currently, my variables are arranged in this way:
x = np.array(range(0, v1))
y = np.array(range(0, v2))
z = np.array(range(0, v3))
I have C which is a 3D array containing measurement values for each combination of the previous variables. So it has a dimension of v1*v2*v3.
Currently, I visualize my measurements using contourf function and I plot that for each z value. This results in 3D contour plot i.e. 2D + color map for the values. Now, I want to combine all the variables and look at the measurements in 4D dimensions (x, y, z, and color corresponding to the measurement value). What is the most efficient way to do this in python?
Regarding to #Sameeresque answer, I think the question was about a 4D graph like this (three coordinates x, y, z and a color as the fourth coordinate):
import numpy as np
import matplotlib.pyplot as plt
# only for example, use your grid
z = np.linspace(0, 1, 15)
x = np.linspace(0, 1, 15)
y = np.linspace(0, 1, 15)
X, Y, Z = np.meshgrid(x, y, z)
# Your 4dimension, only for example (use yours)
U = np.exp(-(X/2) ** 2 - (Y/3) ** 2 - Z ** 2)
# Creating figure
fig = plt.figure()
ax = plt.axes(projection="3d")
# Creating plot
ax.scatter3D(X, Y, Z, c=U, alpha=0.7, marker='.')
plt.show()
A 4D plot with (x,y,z) on the axis and the fourth being color can be obtained like so:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x = np.array(range(0, 50))
y = np.array(range(0, 50))
z = np.array(range(0, 50))
colors = np.random.standard_normal(len(x))
img = ax.scatter(x, y, z, c=colors, cmap=plt.hot())
fig.colorbar(img)
plt.show()
A simple way to visualize your 4D function, call it W(x, y, z), could be producing a gif of the cross-section contour plots along the z-axis.
Package plot4d could help you do it. An example plotting an isotropic 4D function:
from plot4d import plotter
import numpy as np
plotter.plot4d(lambda x,y,z:x**2+y**2+z**2, np.linspace(0,1,20), wbounds=(0,3), fps=5)
The code above generates this gif:

Plot of 3D matrix with colour scale - Python

I would like to plot a 3D matrix - essentially a box of numbers, each labelled by an x, y, z triad of coordinates- by assigning a different colour to each of the x, y, z point, according to its magnitude (for example, bigger numbers in red and smaller numbers in blue).
I cannot plot sections of the matrix, I rather need to plot the whole matrix together.
If we call matrix3D my matrix, its elements are built this way:
matrix3D[x][y][z] = np.exp(-(x**2+y**2+z**2))
How can I obtain the desired plot?
EDIT: Using Mayavi2 Contour3D(), I have tried to write the following:
from mayavi import mlab
X = np.arange(0, n_x, 1)
Y = np.arange(0, n_z, 1)
Z = np.arange(0, n_z, 1)
X, Y, Z = np.meshgrid(X, Y, Z)
obj = mlab.contour3d(X, Y, Z, matrix3D, contours=4, transparent=True)
where n_x, n_y, n_z are the dimension of the 3 axes. How can I actually see and/or save the image now?
If you need to plot the whole thing I think you're best taking a look at mayavi. This will let you plot a volume and you should be able to get the results you need.
I know you said you need to plot the whole thing at once, but this might still be of some use. You can use countourf to plot like this:
import numpy as np
import matplotlib.pyplot as plt
matrix3D = np.empty((10, 10, 10))
x = np.arange(10)
y = np.arange(10)
z = np.arange(10)
matrix3D[x][y][z] = np.exp(-(x**2+y**2+z**2))
fig = plt.figure()
ax = fig.add_subplot(plt.subplot(1, 1, 1))
ax.contourf(x, y, matrix3D[:, :, 3])
plt.show()
This gives you a slice of the 3D matrix (in this example the 4th slice).

Matplotlib discrete colorbar

I am trying to make a discrete colorbar for a scatterplot in matplotlib
I have my x, y data and for each point an integer tag value which I want to be represented with a unique colour, e.g.
plt.scatter(x, y, c=tag)
typically tag will be an integer ranging from 0-20, but the exact range may change
so far I have just used the default settings, e.g.
plt.colorbar()
which gives a continuous range of colours. Ideally i would like a set of n discrete colours (n=20 in this example). Even better would be to get a tag value of 0 to produce a gray colour and 1-20 be colourful.
I have found some 'cookbook' scripts but they are very complicated and I cannot think they are the right way to solve a seemingly simple problem
You can create a custom discrete colorbar quite easily by using a BoundaryNorm as normalizer for your scatter. The quirky bit (in my method) is making 0 showup as grey.
For images i often use the cmap.set_bad() and convert my data to a numpy masked array. That would be much easier to make 0 grey, but i couldnt get this to work with the scatter or the custom cmap.
As an alternative you can make your own cmap from scratch, or read-out an existing one and override just some specific entries.
import numpy as np
import matplotlib as mpl
import matplotlib.pylab as plt
fig, ax = plt.subplots(1, 1, figsize=(6, 6)) # setup the plot
x = np.random.rand(20) # define the data
y = np.random.rand(20) # define the data
tag = np.random.randint(0, 20, 20)
tag[10:12] = 0 # make sure there are some 0 values to show up as grey
cmap = plt.cm.jet # define the colormap
# extract all colors from the .jet map
cmaplist = [cmap(i) for i in range(cmap.N)]
# force the first color entry to be grey
cmaplist[0] = (.5, .5, .5, 1.0)
# create the new map
cmap = mpl.colors.LinearSegmentedColormap.from_list(
'Custom cmap', cmaplist, cmap.N)
# define the bins and normalize
bounds = np.linspace(0, 20, 21)
norm = mpl.colors.BoundaryNorm(bounds, cmap.N)
# make the scatter
scat = ax.scatter(x, y, c=tag, s=np.random.randint(100, 500, 20),
cmap=cmap, norm=norm)
# create a second axes for the colorbar
ax2 = fig.add_axes([0.95, 0.1, 0.03, 0.8])
cb = plt.colorbar.ColorbarBase(ax2, cmap=cmap, norm=norm,
spacing='proportional', ticks=bounds, boundaries=bounds, format='%1i')
ax.set_title('Well defined discrete colors')
ax2.set_ylabel('Very custom cbar [-]', size=12)
I personally think that with 20 different colors its a bit hard to read the specific value, but thats up to you of course.
You could follow this example below or the newly added example in the documentation
#!/usr/bin/env python
"""
Use a pcolor or imshow with a custom colormap to make a contour plot.
Since this example was initially written, a proper contour routine was
added to matplotlib - see contour_demo.py and
http://matplotlib.sf.net/matplotlib.pylab.html#-contour.
"""
from pylab import *
delta = 0.01
x = arange(-3.0, 3.0, delta)
y = arange(-3.0, 3.0, delta)
X,Y = meshgrid(x, y)
Z1 = bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0)
Z2 = bivariate_normal(X, Y, 1.5, 0.5, 1, 1)
Z = Z2 - Z1 # difference of Gaussians
cmap = cm.get_cmap('PiYG', 11) # 11 discrete colors
im = imshow(Z, cmap=cmap, interpolation='bilinear',
vmax=abs(Z).max(), vmin=-abs(Z).max())
axis('off')
colorbar()
show()
which produces the following image:
The above answers are good, except they don't have proper tick placement on the colorbar. I like having the ticks in the middle of the color so that the number -> color mapping is more clear. You can solve this problem by changing the limits of the matshow call:
import matplotlib.pyplot as plt
import numpy as np
def discrete_matshow(data):
# get discrete colormap
cmap = plt.get_cmap('RdBu', np.max(data) - np.min(data) + 1)
# set limits .5 outside true range
mat = plt.matshow(data, cmap=cmap, vmin=np.min(data) - 0.5,
vmax=np.max(data) + 0.5)
# tell the colorbar to tick at integers
cax = plt.colorbar(mat, ticks=np.arange(np.min(data), np.max(data) + 1))
# generate data
a = np.random.randint(1, 9, size=(10, 10))
discrete_matshow(a)
To set a values above or below the range of the colormap, you'll want to use the set_over and set_under methods of the colormap. If you want to flag a particular value, mask it (i.e. create a masked array), and use the set_bad method. (Have a look at the documentation for the base colormap class: http://matplotlib.org/api/colors_api.html#matplotlib.colors.Colormap )
It sounds like you want something like this:
import matplotlib.pyplot as plt
import numpy as np
# Generate some data
x, y, z = np.random.random((3, 30))
z = z * 20 + 0.1
# Set some values in z to 0...
z[:5] = 0
cmap = plt.get_cmap('jet', 20)
cmap.set_under('gray')
fig, ax = plt.subplots()
cax = ax.scatter(x, y, c=z, s=100, cmap=cmap, vmin=0.1, vmax=z.max())
fig.colorbar(cax, extend='min')
plt.show()
This topic is well covered already but I wanted to add something more specific : I wanted to be sure that a certain value would be mapped to that color (not to any color).
It is not complicated but as it took me some time, it might help others not lossing as much time as I did :)
import matplotlib
from matplotlib.colors import ListedColormap
# Let's design a dummy land use field
A = np.reshape([7,2,13,7,2,2], (2,3))
vals = np.unique(A)
# Let's also design our color mapping: 1s should be plotted in blue, 2s in red, etc...
col_dict={1:"blue",
2:"red",
13:"orange",
7:"green"}
# We create a colormar from our list of colors
cm = ListedColormap([col_dict[x] for x in col_dict.keys()])
# Let's also define the description of each category : 1 (blue) is Sea; 2 (red) is burnt, etc... Order should be respected here ! Or using another dict maybe could help.
labels = np.array(["Sea","City","Sand","Forest"])
len_lab = len(labels)
# prepare normalizer
## Prepare bins for the normalizer
norm_bins = np.sort([*col_dict.keys()]) + 0.5
norm_bins = np.insert(norm_bins, 0, np.min(norm_bins) - 1.0)
print(norm_bins)
## Make normalizer and formatter
norm = matplotlib.colors.BoundaryNorm(norm_bins, len_lab, clip=True)
fmt = matplotlib.ticker.FuncFormatter(lambda x, pos: labels[norm(x)])
# Plot our figure
fig,ax = plt.subplots()
im = ax.imshow(A, cmap=cm, norm=norm)
diff = norm_bins[1:] - norm_bins[:-1]
tickz = norm_bins[:-1] + diff / 2
cb = fig.colorbar(im, format=fmt, ticks=tickz)
fig.savefig("example_landuse.png")
plt.show()
I have been investigating these ideas and here is my five cents worth. It avoids calling BoundaryNorm as well as specifying norm as an argument to scatter and colorbar. However I have found no way of eliminating the rather long-winded call to matplotlib.colors.LinearSegmentedColormap.from_list.
Some background is that matplotlib provides so-called qualitative colormaps, intended to use with discrete data. Set1, e.g., has 9 easily distinguishable colors, and tab20 could be used for 20 colors. With these maps it could be natural to use their first n colors to color scatter plots with n categories, as the following example does. The example also produces a colorbar with n discrete colors approprately labelled.
import matplotlib, numpy as np, matplotlib.pyplot as plt
n = 5
from_list = matplotlib.colors.LinearSegmentedColormap.from_list
cm = from_list(None, plt.cm.Set1(range(0,n)), n)
x = np.arange(99)
y = x % 11
z = x % n
plt.scatter(x, y, c=z, cmap=cm)
plt.clim(-0.5, n-0.5)
cb = plt.colorbar(ticks=range(0,n), label='Group')
cb.ax.tick_params(length=0)
which produces the image below. The n in the call to Set1 specifies
the first n colors of that colormap, and the last n in the call to from_list
specifies to construct a map with n colors (the default being 256). In order to set cm as the default colormap with plt.set_cmap, I found it to be necessary to give it a name and register it, viz:
cm = from_list('Set15', plt.cm.Set1(range(0,n)), n)
plt.cm.register_cmap(None, cm)
plt.set_cmap(cm)
...
plt.scatter(x, y, c=z)
I think you'd want to look at colors.ListedColormap to generate your colormap, or if you just need a static colormap I've been working on an app that might help.

Plot 4D graph in python2.7

I want to plot red, blue and green colors on the three axis and an array which stores the value corresoding to each combination of color in python2.7....when i run my program either becomes unresponsive for 24 hours or it gives me memory error. Here is my code:
import pylab
import math
from itertools import product
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
import matplotlib.pyplot as plt
import numpy as np
N=[]
p=np.zeros((256,256,256))
S=[]
fig=plt.figure()
ax=fig.gca(projection='3d')
X=np.arange(0,256,1) #for one of the features either red, blue or green
Y=np.arange(0,256,1)
X,Y = np.meshgrid(X,Y)
R=np.sqrt(X**2 + Y**2)
Z=R/np.sqrt(2)
N=p.flatten();
N=(p[i,j,k] for k in Z)
surf=ax.plot_surface(X,Y,Z, rstride=1, cstride=1,
facecolors=cm.jet(N),
linewidth=0, antialiased=False, shade=False)
plt.show()
Please help. I have read the previous posts, and have used them, still I am getting memory error. Here p is a containing values of combinations of red, green and blue. For simplicity I have initialized it to zero...it is giving the following error..colset.append(fcolors[rs][cs])
IndexError: index out of bounds
First, your program is slow because you're doing a lot of unnecessary work building N. You're building a 70 MB list a few bytes at a time (256*256*256=16,777,216 appends!). A better (faster, memory efficient) way to build p is to use numpy's array broadcasting, and then reuse p to make N:
import numpy as np
a = np.arange(256)
p = a[:,np.newaxis,np.newaxis] * a[np.newaxis,:,np.newaxis] * a[np.newaxis,np.newaxis,:]
N = p.flatten()
Second and more importantly, you're not using plot_surface() correctly. According to the docs, X, Y and Z should be 2D arrays. X and Y lay down a 2D grid and Z provides the "height" for each point on that 2D grid. If you want to manually set the facecolor, it should also be a 2D array. You should look at the example in the docs for a working example.
EDIT:
I'm not sure what your plot is intended to look like, so lets walk through the MPL demo.
Make the necessary imports and create an axis object (yours does this correctly):
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.gca(projection='3d')
Next, make an X/Y grid and corresponding Z. In your program, X, Y and Z are 1D. They describe a line in 3D space, not a surface.
X = np.arange(-5, 5, 0.25)
Y = np.arange(-5, 5, 0.25)
X, Y = np.meshgrid(X, Y) # <-- returns a 2D grid from initial 1D arrays
R = np.sqrt(X**2 + Y**2)
Z = np.sin(R)
Lets first plot the simplest thing possible. No colors, default anti-aliasing, lines, etc.
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1)
plt.show()
Now add a colors. Note that the color comes from the Z component.
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.jet)
plt.show()
Now manually control the colors (MPL inspiration).
colortuple = ('y', 'k') # only use two colors: yellow and black
xlen, ylen = X.shape # get length of
colors = np.empty(X.shape, dtype=str) # make a 2D array of strings
for i in range(xlen):
for j in range(ylen):
index = (i + j) % 2 # alternating 0's and 1's
colors[i,j] = colortuple[index]
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1,
facecolors=colors)
If you want to color based on some other metric, you can create your own colormap. There are many answered questions on how to do that.
Edit 2:
Colors can also be specified as RGB sequences. For something like your red on X, green on Y description you could do this:
xlen, ylen = X.shape
colors = np.zeros((xlen,ylen,3))
jspan = np.linspace(0., 1., ylen)
ispan = np.linspace(0., 1., xlen)
for i in range(xlen):
colors[i,:,0] = jspan
for j in range(ylen):
colors[:,j,1] = ispan
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, facecolors=colors,)

Categories