6

I have some x and y data, with which I would like to generate a 3D histogram, with a color gradient (bwr or whatever).

I have written a script which plot the interesting values, in between -2 and 2 for both x and y abscesses:

import numpy as np
import numpy.random
import matplotlib.pyplot as plt

# To generate some test data
x = np.random.randn(500)
y = np.random.randn(500)

XY = np.stack((x,y),axis=-1)

def selection(XY, limitXY=[[-2,+2],[-2,+2]]):
        XY_select = []
        for elt in XY:
            if elt[0] > limitXY[0][0] and elt[0] < limitXY[0][1] and elt[1] > limitXY[1][0] and elt[1] < limitXY[1][1]:
                XY_select.append(elt)

        return np.array(XY_select)

XY_select = selection(XY, limitXY=[[-2,+2],[-2,+2]])

heatmap, xedges, yedges = np.histogram2d(XY_select[:,0], XY_select[:,1], bins = 7, range = [[-2,2],[-2,2]])
extent = [xedges[0], xedges[-1], yedges[0], yedges[-1]]


plt.figure("Histogram")
#plt.clf()
plt.imshow(heatmap.T, extent=extent, origin='lower')
plt.show()

And give this correct result:

enter image description here

Now, I would like to turn this into a 3D histogram. Unfortunatly I don't success to plot it correctly with bar3d because it takes by default the length of x and y for abscisse.

I am quite sure that there is a very easy way to plot this in 3D with imshow. Like an unknow option...

Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
Agape Gal'lo
  • 687
  • 4
  • 9
  • 23
  • 1
    Unless you use [mplot3d](https://matplotlib.org/api/toolkits/index.html#mplot3d), matplotlib does not feature 3d plotting. – Pierre de Buyl Sep 18 '18 at 11:25
  • 2
    Have you checked the official [docs](https://matplotlib.org/examples/mplot3d/hist3d_demo.html)? There is a simple example for that. – norok2 Sep 18 '18 at 11:29
  • 1
    I don't understand what this x,y data is required for. I guess you have a equispaced grid and each square on the grid should get some random value. – kanayamalakar Sep 18 '18 at 11:31
  • To norok2: yes, I have looked but the examples (like yours) does not fit with what I was looking for... – Agape Gal'lo Sep 18 '18 at 13:25
  • To kanayamalakar: I am doing data treatment, and I have points with x and y coordinates. Then, I want to recreate an histogram to see which events are more populated. The first I do is to filter them , to keep the ones insides the bins. – Agape Gal'lo Sep 18 '18 at 13:28
  • 1
    The linked example seems to be directly applicable. If that is "not what I was looking for", you would want to tell *in how far* it isn't, else one cannot know what's wrong with that. (If you want to notify someone, use @username, else they will not see it) – ImportanceOfBeingErnest Sep 18 '18 at 13:35

2 Answers2

13

I finaly succeded in doing it. I am almost sure there is a better way to do it, but at leat it works:

import numpy as np
import numpy.random
import matplotlib.pyplot as plt

# To generate some test data
x = np.random.randn(500)
y = np.random.randn(500)

XY = np.stack((x,y),axis=-1)

def selection(XY, limitXY=[[-2,+2],[-2,+2]]):
        XY_select = []
        for elt in XY:
            if elt[0] > limitXY[0][0] and elt[0] < limitXY[0][1] and elt[1] > limitXY[1][0] and elt[1] < limitXY[1][1]:
                XY_select.append(elt)

        return np.array(XY_select)

XY_select = selection(XY, limitXY=[[-2,+2],[-2,+2]])


xAmplitudes = np.array(XY_select)[:,0]#your data here
yAmplitudes = np.array(XY_select)[:,1]#your other data here


fig = plt.figure() #create a canvas, tell matplotlib it's 3d
ax = fig.add_subplot(111, projection='3d')


hist, xedges, yedges = np.histogram2d(x, y, bins=(7,7), range = [[-2,+2],[-2,+2]]) # you can change your bins, and the range on which to take data
# hist is a 7X7 matrix, with the populations for each of the subspace parts.
xpos, ypos = np.meshgrid(xedges[:-1]+xedges[1:], yedges[:-1]+yedges[1:]) -(xedges[1]-xedges[0])


xpos = xpos.flatten()*1./2
ypos = ypos.flatten()*1./2
zpos = np.zeros_like (xpos)

dx = xedges [1] - xedges [0]
dy = yedges [1] - yedges [0]
dz = hist.flatten()

cmap = cm.get_cmap('jet') # Get desired colormap - you can change this!
max_height = np.max(dz)   # get range of colorbars so we can normalize
min_height = np.min(dz)
# scale each z to [0,1], and get their rgb values
rgba = [cmap((k-min_height)/max_height) for k in dz] 

ax.bar3d(xpos, ypos, zpos, dx, dy, dz, color=rgba, zsort='average')
plt.title("X vs. Y Amplitudes for ____ Data")
plt.xlabel("My X data source")
plt.ylabel("My Y data source")
plt.savefig("Your_title_goes_here")
plt.show()

I use this example, but I modified it, because it introduced an offset. The result is this:

enter image description here

Agape Gal'lo
  • 687
  • 4
  • 9
  • 23
  • 1
    Well done ! I was having trouble with applying a color scheme. Thanks for sharing – kanayamalakar Sep 19 '18 at 10:11
  • @kanayamalakar but there is a problem in the renormalisation for the colors... If after this line 'hist, xedges, yedges = np.histogram2d(x, y, bins=(7,7), range = [[-2,+2],[-2,+2]]) # you can change your bins, and the range on which to take data' you replace hist by an other 7X7 matrix, the colors are not anymore well scale. I don't understand why ! There should a better way to get this color gradiant. – Agape Gal'lo Sep 19 '18 at 12:37
  • 1
    Perhaps you can add "import matplotlib.cm as cm" – Daniel González Cortés Feb 15 '21 at 12:36
5

You can generate the same result using something as simple as the following:

import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(-2, 2, 7)
y = np.linspace(-2, 2, 7)

xx, yy = np.meshgrid(x, y)

z = xx*0+yy*0+ np.random.random(size=[7,7])

plt.imshow(z, interpolation='nearest', cmap=plt.cm.viridis, extent=[-2,2,2,2])
plt.show()

from mpl_toolkits.mplot3d import Axes3D
ax = Axes3D(plt.figure())

ax.plot_surface(xx, yy, z, cmap=plt.cm.viridis, cstride=1, rstride=1)
plt.show()

The results are given below: enter image description here

Surface plot

kanayamalakar
  • 564
  • 1
  • 11
  • 27
  • Sorry, but it would be better if you use the same x, y and heatmap.T datas. You change the exep – Agape Gal'lo Sep 18 '18 at 12:06
  • Thanks! But sorry, but it would be better if you use the same x, y and heatmap.T datas. You change the example a bit. Also, could we have some flat levels instead of the tilts surfaces ? – Agape Gal'lo Sep 18 '18 at 12:12
  • And also, there is a problem I had not notice before: There are 7x7 delimitations for the 2D colored histogram, and 6 for the other one ! Which make it dangerous to observe for data analysis... What would be great is a 7x7 flat square levels, in 3D. @kanayamalakar – Agape Gal'lo Sep 18 '18 at 17:56
  • @AgapeGal'lo, could you show me a picture of how you want the 3D plot to be - maybe just a representative one ? Because I don't seem to get what you mean by flat square levels in 3D. – kanayamalakar Sep 19 '18 at 05:45
  • You can look at this link https://matplotlib.org/examples/mplot3d/hist3d_demo.html#mplot3d-example-code-hist3d-demo-py – kanayamalakar Sep 19 '18 at 06:06
  • 1
    The difference in the 2D and 3D plots are : In 2D, the data point lies at the center of each box. Whereas in 3D the data points lie at the corner of the boxes. The line segments join adjacent data points and form grids. Hence for 7x7 data points you get (7-1)x(7-1) sized grid. – kanayamalakar Sep 19 '18 at 06:09
  • Yes, I had understood, but it's not what you expect in a 3 histogram... – Agape Gal'lo Sep 19 '18 at 08:45