Filling in a grid in numpy - python

I want to achieve the following. I have a x-y plot with a function y dependent on x. The plot consists of a mesh of squares. When a function point is inside the function block, the block changes color. I have attached an example:
I want to make an simillar figure, but I want to be able to make the gridsize variable.
I know how to make a plot with plt.plot(), but I'm not familliar with making a grid and filling in that grid if the function point falls in a square. Can somebody refer to numpy or mathplotlib functions that can help?
Thanks

here is a piece of code that should work for graphs centered around (min(X)-max(X)) and (min(Y),max(Y)) :
import numpy as np
def grid_plot(X,Y,resx,resy) :
d_x=resx/(np.max(X)-np.min(X))
d_y=resy/(np.max(Y)-np.min(Y))
mat=np.zeros((resy,resx))
for i in range(len(X)) :
mat[int((Y[i]-np.min(Y))*d_y),resx-int((X[i]-np.min(X))*d_x)]=1
return mat

You can use np.histogram2d to create a 2D histogram and plot the locations with count greater than zero with imshow:
import numpy as np
import matplotlib.pyplot as plt
# Input data
x = np.linspace(-2, 2, 300)
y = np.sin(x)
# Plot limits
x_min, x_max = -3, 3
y_min, y_max = -2, 2
plt.figure(figsize=(8, 3))
# First plot
resolution = 25
xg = np.linspace(x_min, x_max, resolution)
yg = np.linspace(y_min, y_max, resolution)
h, _, _ = np.histogram2d(x, y, (xg, yg))
plt.subplot(121)
# Transpose because imshow swaps X and Y axes
plt.imshow(h.T > 0, origin='lower', extent=(xg[0], xg[-1], yg[0], yg[-1]))
# Show grid
plt.gca().set_xticks([], minor=False)
plt.gca().set_xticks(xg, minor=True)
plt.gca().set_yticks([], minor=False)
plt.gca().set_yticks(yg, minor=True)
plt.grid(True, 'minor')
# Second plot
resolution = 50
xg = np.linspace(x_min, x_max, resolution)
yg = np.linspace(y_min, y_max, resolution)
h, _, _ = np.histogram2d(x, y, (xg, yg))
plt.subplot(122)
plt.imshow(h.T > 0, origin='lower', extent=(xg[0], xg[-1], yg[0], yg[-1]))
plt.gca().set_xticks([], minor=False)
plt.gca().set_xticks(xg, minor=True)
plt.gca().set_yticks([], minor=False)
plt.gca().set_yticks(yg, minor=True)
plt.grid(True, 'minor')
# Show plot
plt.tight_layout()
plt.show()
Result:

Related

Extract plottable Matplotlib 2D axes from 3D plot?

So, this is my problem - I use a matplotlib-based library that accepts 2D Axes ax as input, and uses ax.imshow, which makes the assumption that the ax passed to it is a 2D one. I'd like to use this library, but to plot its result on the xy plane at z=0 on a 3D matplotlib plot.
As far as I can see from:
Plotting a imshow() image in 3d in matplotlib
Creating intersecting images in matplotlib with imshow or other function
... I have to basically use ax.plot_surface to have the equivalent of ax.imshow in 3D. However, that involves rewriting/hacking the library, so all corresponding calls are replaced.
So, I tried to come up with this simple example, to see what can be achieved by using imshow in a 3D context:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm
fig = plt.figure()
ax3d = fig.add_subplot(projection='3d')
x = np.linspace(0, 1, 100)
y = np.sin(x * 2 * np.pi) / 2 + 0.5
#ax3d.plot(x, y, zs=0, zdir='z', label='curve in (x, y)') # works
# syntax as for 2d plot:
ax3d.plot(x, y, label='curve in (x, y)') # works
# https://matplotlib.org/stable/gallery/images_contours_and_fields/image_demo.html
delta = 0.025
xa = ya = np.arange(0.0, 1.0, delta)
Xa, Ya = np.meshgrid(xa, ya)
Z1a = np.exp(-Xa**2 - Ya**2)
Z2a = np.exp(-(Xa - 1)**2 - (Ya - 1)**2)
Za = (Z1a - Z2a) * 2
# imshow causes NotImplementedError: Axes3D currently only supports the aspect argument 'auto'. You passed in 'equal'.
ax3d.set_aspect('auto') # does not help
im = ax3d.imshow(Za, interpolation='bilinear', cmap=cm.RdYlGn,
origin='lower', extent=[0, 1, 0, 1],
vmax=abs(Za).max(), vmin=-abs(Za).max(),
aspect='auto' # makes imshow pass and draw - but the drawing is not connected to 3d rotation
)
ax3d.set_xlim(0, 1)
ax3d.set_ylim(0, 1)
ax3d.set_zlim(0, 1)
ax3d.view_init(elev=20., azim=-35)
plt.show()
... so, syntactically, it can be "coaxed" - unfortunately, the result is not a "part" of the 3D plot, in the sense that it is not on the xy plane at z=0, and it does not rotate with the 3D view as the rest of the plot:
So, I was thinking - is there a way/a "hack" of sorts, so that I could "extract" 2D Axes matplotlib object for the xy plane at z=0 of the 3D plot, - and then use that Axes object to pass as input to the library, which will proceed as usual (but the ultimate results of its plot will be a part of the 3D plot)? Basically, as in the following pseudocode:
...
ax2dxy = ax3d.get_2daxes('x', 'y', z=0) # PSEUDO
im = ax2dxy.imshow(Za, interpolation='bilinear', cmap=cm.RdYlGn,
origin='lower', extent=[0, 1, 0, 1],
vmax=abs(Za).max(), vmin=-abs(Za).max(),
)
...
Not quite an answer to the question - and likely, it is not easily possible to "extract" "plottable" 2D axes from 3D ones - but in the below example I've attempted it, and I couldn't get it to do much.
However, I also tried plotting 2D imshow on "virtual" 2D axes - and reusing that data for 3D plot surface - and it seems to work:
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import inspect
fig = plt.figure()
ax3d = fig.add_subplot(projection='3d')
#~ ax3d = fig.add_subplot()
x = np.linspace(0, 1, 100)
y = np.sin(x * 2 * np.pi) / 2 + 0.5
#ax3d.plot(x, y, zs=0, zdir='z', label='curve in (x, y)') # works
# syntax as for 2d plot:
ax3d.plot(x, y, label='curve in (x, y)') # works
# https://matplotlib.org/stable/gallery/images_contours_and_fields/image_demo.html
delta = 0.025
xa = ya = np.arange(0.0, 1.0, delta)
Xa, Ya = np.meshgrid(xa, ya)
Z1a = np.exp(-Xa**2 - Ya**2)
Z2a = np.exp(-(Xa - 1)**2 - (Ya - 1)**2)
Za = (Z1a - Z2a) * 2
#print(inspect.getsourcefile(ax3d.plot3D)) # /mingw64/lib/python3.8/site-packages/mpl_toolkits/mplot3d/axes3d.py
#print(inspect.getsource(ax3d.plot3D)) # def plot! plot3D = plot
#print(inspect.getsourcefile(matplotlib.axes.Axes)) # /mingw64/lib/python3.8/site-packages/matplotlib/axes/_axes.py
# imshow in /mingw64/lib/python3.8/site-packages/matplotlib/axes/_axes.py
print( ax3d.xaxis, ax3d.yaxis , ax3d._position ) # ax3d._position is rect, Bbox(x0=0.125, y0=0.10999999999999999, x1=0.9, y1=0.88)
newax = matplotlib.axes.Axes(fig, (0,0,1,1) )
print(newax._position) # Bbox(x0=0.0, y0=0.0, x1=1.0, y1=1.0)
newax.xaxis = ax3d.xaxis
newax.yaxis = ax3d.yaxis
# imshow causes NotImplementedError: Axes3D currently only supports the aspect argument 'auto'. You passed in 'equal'.
ax3d.set_aspect('auto') # does not help
#im = ax3d.imshow(Za, interpolation='bilinear', cmap=cm.RdYlGn,
# origin='lower', extent=[0, 1, 0, 1],
# vmax=abs(Za).max(), vmin=-abs(Za).max(),
# aspect='auto' # makes imshow pass and draw - but the drawing is not connected to 3d rotation
# )
imB = newax.imshow(Za, interpolation='bilinear', cmap=cm.RdYlGn,
origin='lower', extent=[0, 1, 0, 1],
vmax=abs(Za).max(), vmin=-abs(Za).max(),
) # passes, but does not show anything
#imB.axes = ax3d # ValueError: Can not reset the axes. You are probably trying to re-use an artist in more than one Axes which is not supported
#~ print(newax.images) # [<matplotlib.image.AxesImage object at 0x000002505f06f670>]
#~ print(imB._A) # is there
fig.canvas.draw() # call to create fig.canvas.renderer
im, l, b, trans = imB.make_image(fig.canvas.renderer, unsampled=True)
#print(im)
ax3d.set_axisbelow(False)
ax3d.plot_surface(Xa, Ya, np.zeros(Xa.shape), rstride=1, cstride=1, facecolors=np.divide(im, 255.0), shade=False, zorder=-100)
# ax3d.grid(True, which='major')
ax3d.set_xlim(0, 1)
ax3d.set_ylim(0, 1)
ax3d.set_zlim(0, 1)
ax3d.view_init(elev=20., azim=-35)
plt.show()
The code above produces:
... which looks decent ...
Now I just wish I knew how I could control the z-order (plot_surface below, gridlines and sinusoid on top of it) - but I posted a separate Q for that ( How to draw Axes3D grid lines over plot_surface() in Matplotlib? )

Python: Filling colors between curves and axes & to regionalize the areas

I have a set of x,y values for two curves on excel sheets.
Using xlrd module, I have been able to plot them as below:
Question:
How do I shade the three areas with different fill colors? Had tried with fill_between but been unsuccessful due to not knowing how to associate with the x and y axes. The end in mind is as diagram below.
Here is my code:
import xlrd
import numpy as np
import matplotlib.pyplot as plt
workbook = xlrd.open_workbook('data.xls')
sheet = workbook.sheet_by_name('p1')
rowcount = sheet.nrows
colcount = sheet.ncols
result_data_p1 =[]
for row in range(1, rowcount):
row_data = []
for column in range(0, colcount):
data = sheet.cell_value(row, column)
row_data.append(data)
#print(row_data)
result_data_p1.append(row_data)
sheet = workbook.sheet_by_name('p2')
rowcount = sheet.nrows
colcount = sheet.ncols
result_data_p2 =[]
for row in range(1, rowcount):
row_data = []
for column in range(0, colcount):
data = sheet.cell_value(row, column)
row_data.append(data)
result_data_p2.append(row_data)
x1 = []
y1 = []
for i,k in result_data_p1:
cx1,cy1 = i,k
x1.append(cx1)
y1.append(cy1)
x2 = []
y2 = []
for m,n in result_data_p2:
cx2,cy2 = m,n
x2.append(cx2)
y2.append(cy2)
plt.subplot(1,1,1)
plt.yscale('log')
plt.plot(x1, y1, label = "Warm", color = 'red')
plt.plot(x2, y2, label = "Blue", color = 'blue')
plt.xlabel('Color Temperature (K)')
plt.ylabel('Illuminance (lm)')
plt.title('Kruithof Curve')
plt.legend()
plt.xlim(xmin=2000,xmax=7000)
plt.ylim(ymin=10,ymax=50000)
plt.show()
Please guide or lead to other references, if any.
Thank you.
Here is a way to recreate the curves and the gradients. It resulted very complicated to draw the background using the logscale. Therefore, the background is created in linear space and put on a separate y-axis. There were some problems getting the background behind the rest of the plot if it were drawn on the twin axis. Therefore, the background is drawn on the main axis, and the plot on the second axis. Afterwards, that second y-axis is placed again at the left.
To draw the curves, a spline is interpolated using six points. As the interpolation didn't give acceptable results using the plain coordinates, everything was interpolated in logspace.
The background is created column by column, checking where the two curves are for each x position. The red curve is extended artificially to have a consistent area.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
from scipy import interpolate
xmin, xmax = 2000, 7000
ymin, ymax = 10, 50000
# a grid of 6 x,y coordinates for both curves
x_grid = np.array([2000, 3000, 4000, 5000, 6000, 7000])
y_blue_grid = np.array([15, 100, 200, 300, 400, 500])
y_red_grid = np.array([20, 400, 10000, 500000, 500000, 500000])
# create interpolating curves in logspace
tck_red = interpolate.splrep(x_grid, np.log(y_red_grid), s=0)
tck_blue = interpolate.splrep(x_grid, np.log(y_blue_grid), s=0)
x = np.linspace(xmin, xmax)
yr = np.exp(interpolate.splev(x, tck_red, der=0))
yb = np.exp(interpolate.splev(x, tck_blue, der=0))
# create the background image; it is created fully in logspace
# the background (z) is zero between the curves, negative in the blue zone and positive in the red zone
# the values are close to zero near the curves, gradually increasing when they are further
xbg = np.linspace(xmin, xmax, 50)
ybg = np.linspace(np.log(ymin), np.log(ymax), 50)
z = np.zeros((len(ybg), len(xbg)), dtype=float)
for i, xi in enumerate(xbg):
yi_r = interpolate.splev(xi, tck_red, der=0)
yi_b = interpolate.splev(xi, tck_blue, der=0)
for j, yj in enumerate(ybg):
if yi_b >= yj:
z[j][i] = (yj - yi_b)
elif yi_r <= yj:
z[j][i] = (yj - yi_r)
fig, ax2 = plt.subplots(figsize=(8, 8))
# draw the background image, set vmax and vmin to get the desired range of colors;
# vmin should be -vmax to get the white at zero
ax2.imshow(z, origin='lower', extent=[xmin, xmax, np.log(ymin), np.log(ymax)], aspect='auto', cmap='bwr', vmin=-12, vmax=12, interpolation='bilinear', zorder=-2)
ax2.set_ylim(ymin=np.log(ymin), ymax=np.log(ymax)) # the image fills the complete background
ax2.set_yticks([]) # remove the y ticks of the background image, they are confusing
ax = ax2.twinx() # draw the main plot using the twin y-axis
ax.set_yscale('log')
ax.plot(x, yr, label="Warm", color='crimson')
ax.plot(x, yb, label="Blue", color='dodgerblue')
ax2.set_xlabel('Color Temperature')
ax.set_ylabel('Illuminance (lm)')
ax.set_title('Kruithof Curve')
ax.legend()
ax.set_xlim(xmin=xmin, xmax=xmax)
ax.set_ylim(ymin=ymin, ymax=ymax)
ax.grid(True, which='major', axis='y')
ax.grid(True, which='minor', axis='y', ls=':')
ax.yaxis.tick_left() # switch the twin axis to the left
ax.yaxis.set_label_position('left')
ax2.grid(True, which='major', axis='x')
ax2.xaxis.set_major_formatter(mticker.StrMethodFormatter('{x:.0f} K')) # show x-axis in Kelvin
ax.text(5000, 2000, 'Pleasing', fontsize=16)
ax.text(5000, 20, 'Appears bluish', fontsize=16)
ax.text(2300, 15000, 'Appears reddish', fontsize=16)
plt.show()

Get coordinates from points density with KDE

I have a set of points (> 1k) in this form:
y,x
173.549,308.176
173.549,313.328
213.26,419.588
Using KDE, i can plot points density with pcolormesh and contourf. This is an example result, plotting points too:
This is the code i used to have the plot:
import matplotlib.pyplot as plt
import matplotlib
import numpy as np
from scipy.stats.kde import gaussian_kde
x, y = np.genfromtxt('terzinoSX.csv', delimiter=',', unpack=True)
y = y[np.logical_not(np.isnan(y))]
x = x[np.logical_not(np.isnan(x))]
k = gaussian_kde(np.vstack([x, y]))
xi, yi = np.mgrid[x.min():x.max():x.size**0.5*1j,y.min():y.max():y.size**0.5*1j]
zi = k(np.vstack([xi.flatten(), yi.flatten()]))
fig = plt.figure(figsize=(7,4))
ax2 = fig.add_subplot(111)
#alpha=0.5 will make the plots semitransparent
#ax1.pcolormesh(yi, xi, zi.reshape(xi.shape), alpha=0.5)
ax2.contourf(yi, xi, zi.reshape(xi.shape), alpha=0.5)
plt.axis('off')
ax2.plot(y,x, "o")
ax2.set_xlim(0, 740)
ax2.set_ylim(515, 0)
#overlay soccer field
im = plt.imread('statszone_football_pitch.png')
ax2.imshow(im, extent=[0, 740, 0, 515], aspect='auto')
fig.savefig('test.png', bbox_inches='tight')
I would like to have one point representing coordinates of most populated zone (middle point for example), like a middle point over the "red" zone. Is it possible in some way?
I solved this by adding these lines that calculate the point in the most populated area:
xy = np.vstack([x,y])
kde = stats.gaussian_kde(xy)
density = kde(xy)
pts = xy.T[np.argmax(density)]
You can use np.argmax to get the coordinates of the maximum. For example:
kde = compute_my_kde() # Returns a two-dimensional array
y, x = np.argmax(kde) # x and y are swapped because matplotlib displays images as a matrix (first index is rows, second index is colums)
plt.imshow(kde) # Show the kde
plt.scatter(x, y) # Show the maximum point

3D histograms and Contour plots Python

I have a problem with contourf function of matplotlib. I have a txt data file from which I am importing my data. I have columns of data (pm1 and pm2) and I am performing a 2D histogram. I want to plot this data as a 3D histogram and as a contour plot to see where is located the maximum values.
This is my code:
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
rows = np.arange(200,1300,10)
hist, xedges, yedges = np.histogram2d (pm1_n, pm2_n, bins = (rows, rows) )
elements = (len(xedges) - 1) * (len(yedges) - 1)
xpos, ypos = np.meshgrid(xedges[:-1], yedges[:-1])
xpos = xpos.flatten()
ypos = ypos.flatten()
zpos = np.zeros(elements)
dx = 0.1 * np.ones_like(zpos)
dy = dx.copy()
dz = hist.flatten()
#####The problem is here#####
#ax.contourf(xpos,ypos,hist)
#ax.bar3d(xpos, ypos, zpos, dx, dy, dz, zsort='average')
plt.show()
I can plot the 3d bar graph but I am not able to plot the contour one, If I place hist in the contourf function I get the error: Length of x must be number of columns in z and if I place dz I get Input z must be a 2D array
I also have tried using xedges and yexges but this doesn't solve the problem.
I think that the problem is related with the shape of the return of the function histogram2D. But I don't know how to solve it.
I would also like to perform a 3D bar plot with a colorcode changing form the minimum to the maximum value. Is there anyway to make this?
Thank you
Perhaps I don't understand what exactly you are trying to do since I don't know what your data looks like, but it seems wrong to have your contourf plot sharing the same axis as your bar3d plot. If you add an axis without the 3D projection to a new figure, you should be able to make a contourf plot just fine using hist. An example using data from a random, normal distribution:
import numpy as np
import matplotlib.pyplot as plt
n_points = 1000
x = np.random.normal(0, 2, n_points)
y = np.random.normal(0, 2, n_points)
hist, xedges, yedges = np.histogram2d(x, y, bins=np.sqrt(n_points))
fig2D = plt.figure()
ax2D = fig2D.add_subplot(111)
ax2D.contourf(hist, interpolation='nearest',
extent=(xedges[0], xedges[-1], yedges[0], yedges[-1]))
plt.show()
returns an image like this.
As for your second question, regarding a color-coded 3D bar plot, how about this (using the same data as above but with 1/10 the size):
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
import matplotlib.colors as colors
n_points = 100
x = np.random.normal(0, 2, n_points)
y = np.random.normal(0, 2, n_points)
hist, xedges, yedges = np.histogram2d(x, y, bins=np.sqrt(n_points))
# Following your data reduction process
xpos, ypos = np.meshgrid(xedges[:-1], yedges[:-1])
length, width = 0.4, 0.4
xpos = xpos.flatten()
ypos = ypos.flatten()
zpos = np.zeros(n_points)
dx = np.ones(n_points) * length
dy = np.ones(n_points) * width
dz = hist.flatten()
# This is where the colorbar customization comes in
dz_normed = dz / dz.max()
normed_cbar = colors.Normalize(dz_normed.min(), dz_normed.max())
# Using jet, but should work with any colorbar
color = cm.jet(normed_cbar(dz_normed))
fig3D = plt.figure()
ax3D = fig3D.add_subplot(111, projection='3d')
ax3D.bar3d(xpos, ypos, zpos, dx, dy, dz, color=color)
plt.show()
I get this image.

Grid Lines below the contour in Matplotlib

I have one question about the grid lines matplotlib.
I am not sure if this is possible to do or not.
I am plotting the following graph as shown in the image.
I won't give the entire code, since it is involving reading of files.
However the important part of code is here -
X, Y = np.meshgrid(smallX, smallY)
Z = np.zeros((len(X),len(X[0])))
plt.contourf(X, Y, Z, levels, cmap=cm.gray_r, zorder = 1)
plt.colorbar()
...
# Set Border width zero
[i.set_linewidth(0) for i in ax.spines.itervalues()]
gridLineWidth=0.1
ax.set_axisbelow(False)
gridlines = ax.get_xgridlines()+ax.get_ygridlines()
#ax.set_axisbelow(True)
plt.setp(gridlines, 'zorder', 5)
ax.yaxis.grid(True, linewidth=gridLineWidth, linestyle='-', color='0.6')
ax.xaxis.grid(False)
ax.xaxis.set_ticks_position('none')
ax.yaxis.set_ticks_position('none')
Now, my questions is like this -
If I put the grid lines below the contour, they disappear since they are below it.
If I put the grid line above the contour, they looks like what they are looking now.
However, what I would like to have is the grid lines should be visible, but should be below the black portion of the contour. I am not sure if that is possible.
Thank You !
In addition to specifying the z-order of the contours and the gridlines, you could also try masking the zero values of your contoured data.
Here's a small example:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(-2*np.pi, 2*np.pi, 0.1)
y = np.arange(-2*np.pi, 2*np.pi, 0.1)
X, Y = np.meshgrid(x, y)
Z = np.sin(X) - np.cos(Y)
Z = np.ma.masked_less(Z, 0) # you use mask_equal(yourData, yourMagicValue)
fig, ax = plt.subplots()
ax.contourf(Z, zorder=5, cmap=plt.cm.coolwarm)
ax.xaxis.grid(True, zorder=0)
ax.yaxis.grid(True, zorder=0)
And the output:

Categories

Resources