I am trying to 3d plot below data, with height being the respecitive joint probability from probability mass function. The idea is to visualize covariance. I had to go 3D because, the probabilities varies for different combinations of sample. The bars or boxes overlap each other in weird ways that I am unable to infer a proper 3d perspective in different angles. If you look at below gif you will know (box suddenly grows over each other at few angles out of nowhere). Kindly help how to resolve this issue. Also alpha is not working.
Issues:
1. Weird 3d boxes rendering
2. Alpha also not working
Problematic output:
MWE (jupyter):
%matplotlib inline
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from itertools import product
from mpl_toolkits.mplot3d import Axes3D
X , Y = [100,250], [0,100,200]
xb, yb = 175, 125
import pandas as pd
matrix = np.array([
[0.20, 0.10, 0.20],
[0.05, 0.15, 0.30]
])
df = pd.DataFrame(matrix, columns=Y)
df.index = [100, 250]
top = 1
fig = plt.figure(figsize=(15,5))
ax1 = fig.add_subplot(121)
for xy in product(X,Y):
x,y = xy[0], xy[1]
z = df.loc[x,y]
d1, d2 = xb - x, yb - y
color = 'green' if d1*d2 > 0 else 'red'
ax1.add_patch(patches.Rectangle((x, y), d1, d2, alpha=z, facecolor=color))
ax1.scatter(x,y,color='black')
ax1.axvline(x=Xb, ls=':', color='blue')
ax1.axhline(y=Yb, ls=':', color='blue')
ax1.set_xticks(X)
ax1.set_yticks(Y)
ax1.set_xlim([min(X)-50,max(X)+50])
ax1.set_ylim([min(Y)-50,max(Y)+50])
ax2 = fig.add_subplot(122, projection='3d')
ax2.view_init(elev=30., azim=-50)
for xy in product(X,Y):
x ,y = xy[0], xy[1]
z = df.loc[x,y]
# print(x, y, z)
width = x - 175
depth = y - 125
pro = width*depth
top = z
bottom = np.zeros_like(top)
if pro > 0: #positive
color='#B9F6CA'
else:
color='#EF9A9A'
ax2.bar3d(x, y, bottom, -width, -depth, top, color=color)
ax2.scatter(x, y, z, color='blue')
def rotate(angle):
ax2.view_init(azim=angle)
from matplotlib import animation
ani = animation.FuncAnimation(fig, rotate, frames=np.arange(0,362,2),interval=100)
from IPython.display import HTML
plt.close()
HTML(ani.to_jshtml())
Related math problem:
Related
Basically I have a surface plot consisting of a set of time series, and I would like to add a section plan at a specific height, to better understand the period of the year when values are higher than the selected threshold.
From this:
where the plan is shown but not as a section
To This:
Any suggestion?
Plying with alpha and camera elevation did not solve the issue
the plan still seems to be in front of the figure, not as a section
Drawing in 3 steps
As others pointed out, matplotlib's 3D capabilities are somewhat limited. To hide objects behind other objects, it uses the painter's algorithm. So, the objects are simply drawn back to front, and no objects are put partly before and partly behind some plane. Matplotlib calculates some average depth of each object to define the order. You can overwrite this order via ax.computed_zorder = False, as the automatic calculation is not always what is wished.
You could draw the "layers" yourself:
the 3D surface
then the plane
then the part of the 3D surface that should be visible on top
An example:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
from scipy.ndimage.filters import gaussian_filter
x = np.linspace(-10, 10, 51)
y = np.linspace(-10, 10, 51)
X, Y = np.meshgrid(x, y)
np.random.seed(20220201)
Z = np.random.rand(*X.shape) ** 5
Z[X ** 2 + Y ** 2 > 30] = 0
Z = gaussian_filter(Z, sigma=2) * 100
fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")
ax.computed_zorder = False
ax.plot_surface(X, Y, Z, cmap='turbo')
special_z = 16
ax.plot_surface(X, Y, np.full_like(Z, special_z), color='blue', alpha=0.4)
ax.plot_surface(X, Y, np.where(Z >= special_z, Z, np.nan), cmap='turbo', vmin=0)
plt.show()
Drawing layer by layer
An alternative way could be to draw the surface one layer at a time.
The example at the left shows the surface divided into 30 layers, the example at the right stops at a given height, visualizing the intersection.
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
from scipy.ndimage.filters import gaussian_filter
x = np.linspace(-10, 10, 51)
y = np.linspace(-10, 10, 51)
X, Y = np.meshgrid(x, y)
np.random.seed(20220201)
Z = np.random.rand(*X.shape) ** 5
Z[X ** 2 + Y ** 2 > 30] = 0
Z = gaussian_filter(Z, sigma=2) * 100
fig = plt.figure()
for which in ['left', 'right']:
ax = fig.add_subplot(121 + (which == 'right'), projection="3d")
ax.computed_zorder = False
layers = np.linspace(Z.min(), Z.max(), 32)[1:-1]
colors = plt.get_cmap('turbo', len(layers)).colors
special_z = 16
plane_drawn = False
for layer, color in zip(layers, colors):
if layer >= special_z and not plane_drawn:
ax.plot_surface(X, Y, np.full_like(Z, special_z), color='blue', alpha=0.5, zorder=2)
plane_drawn = True
ax.contour(X, Y, Z, levels=[layer], offset=layer, colors=[color])
if plane_drawn and which == 'right':
break
plt.show()
I want to draw some circles using `ax3.scatter(x1, y1, s=r1 , facecolors='none', edgecolors='r'), where:
x1 and y1 are the coordinates of these circles
r1 is the radius of these circles
I thought typing s = r1 I would get the correct radius, but that's not the case.
How can I fix this?
If you change the value of 'r' (now 5) to your desired radius, it works. This is adapted from the matplotlib.org website, "Scatter Plots With a Legend". Should be scatter plots with attitude!
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(19680801)
fig, ax = plt.subplots()
for color in ['tab:blue', 'tab:orange', 'tab:green']:
r = 5 #radius
n = 750 #number of circles
x, y = np.random.rand(2, n)
#scale = 200.0 * np.random.rand(n)
scale = 3.14159 * r**2 #CHANGE r
ax.scatter(x, y, c=color, s=scale, label=color,
alpha=0.3, edgecolors='none')
ax.legend()
ax.grid(True)
plt.show()
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.
I'd like to make a scatter plot where each point is colored by the spatial density of nearby points.
I've come across a very similar question, which shows an example of this using R:
R Scatter Plot: symbol color represents number of overlapping points
What's the best way to accomplish something similar in python using matplotlib?
In addition to hist2d or hexbin as #askewchan suggested, you can use the same method that the accepted answer in the question you linked to uses.
If you want to do that:
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import gaussian_kde
# Generate fake data
x = np.random.normal(size=1000)
y = x * 3 + np.random.normal(size=1000)
# Calculate the point density
xy = np.vstack([x,y])
z = gaussian_kde(xy)(xy)
fig, ax = plt.subplots()
ax.scatter(x, y, c=z, s=100)
plt.show()
If you'd like the points to be plotted in order of density so that the densest points are always on top (similar to the linked example), just sort them by the z-values. I'm also going to use a smaller marker size here as it looks a bit better:
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import gaussian_kde
# Generate fake data
x = np.random.normal(size=1000)
y = x * 3 + np.random.normal(size=1000)
# Calculate the point density
xy = np.vstack([x,y])
z = gaussian_kde(xy)(xy)
# Sort the points by density, so that the densest points are plotted last
idx = z.argsort()
x, y, z = x[idx], y[idx], z[idx]
fig, ax = plt.subplots()
ax.scatter(x, y, c=z, s=50)
plt.show()
Plotting >100k data points?
The accepted answer, using gaussian_kde() will take a lot of time. On my machine, 100k rows took about 11 minutes. Here I will add two alternative methods (mpl-scatter-density and datashader) and compare the given answers with same dataset.
In the following, I used a test data set of 100k rows:
import matplotlib.pyplot as plt
import numpy as np
# Fake data for testing
x = np.random.normal(size=100000)
y = x * 3 + np.random.normal(size=100000)
Output & computation time comparison
Below is a comparison of different methods.
1: mpl-scatter-density
Installation
pip install mpl-scatter-density
Example code
import mpl_scatter_density # adds projection='scatter_density'
from matplotlib.colors import LinearSegmentedColormap
# "Viridis-like" colormap with white background
white_viridis = LinearSegmentedColormap.from_list('white_viridis', [
(0, '#ffffff'),
(1e-20, '#440053'),
(0.2, '#404388'),
(0.4, '#2a788e'),
(0.6, '#21a784'),
(0.8, '#78d151'),
(1, '#fde624'),
], N=256)
def using_mpl_scatter_density(fig, x, y):
ax = fig.add_subplot(1, 1, 1, projection='scatter_density')
density = ax.scatter_density(x, y, cmap=white_viridis)
fig.colorbar(density, label='Number of points per pixel')
fig = plt.figure()
using_mpl_scatter_density(fig, x, y)
plt.show()
Drawing this took 0.05 seconds:
And the zoom-in looks quite nice:
2: datashader
Datashader is an interesting project. It has added support for matplotlib in datashader 0.12.
Installation
pip install datashader
Code (source & parameterer listing for dsshow):
import datashader as ds
from datashader.mpl_ext import dsshow
import pandas as pd
def using_datashader(ax, x, y):
df = pd.DataFrame(dict(x=x, y=y))
dsartist = dsshow(
df,
ds.Point("x", "y"),
ds.count(),
vmin=0,
vmax=35,
norm="linear",
aspect="auto",
ax=ax,
)
plt.colorbar(dsartist)
fig, ax = plt.subplots()
using_datashader(ax, x, y)
plt.show()
It took 0.83 s to draw this:
There is also possibility to colorize by third variable. The third parameter for dsshow controls the coloring. See more examples here and the source for dsshow here.
3: scatter_with_gaussian_kde
def scatter_with_gaussian_kde(ax, x, y):
# https://stackoverflow.com/a/20107592/3015186
# Answer by Joel Kington
xy = np.vstack([x, y])
z = gaussian_kde(xy)(xy)
ax.scatter(x, y, c=z, s=100, edgecolor='')
It took 11 minutes to draw this:
4: using_hist2d
import matplotlib.pyplot as plt
def using_hist2d(ax, x, y, bins=(50, 50)):
# https://stackoverflow.com/a/20105673/3015186
# Answer by askewchan
ax.hist2d(x, y, bins, cmap=plt.cm.jet)
It took 0.021 s to draw this bins=(50,50):
It took 0.173 s to draw this bins=(1000,1000):
Cons: The zoomed-in data does not look as good as in with mpl-scatter-density or datashader. Also you have to determine the number of bins yourself.
5: density_scatter
The code is as in the answer by Guillaume.
It took 0.073 s to draw this with bins=(50,50):
It took 0.368 s to draw this with bins=(1000,1000):
Also, if the number of point makes KDE calculation too slow, color can be interpolated in np.histogram2d [Update in response to comments: If you wish to show the colorbar, use plt.scatter() instead of ax.scatter() followed by plt.colorbar()]:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from matplotlib.colors import Normalize
from scipy.interpolate import interpn
def density_scatter( x , y, ax = None, sort = True, bins = 20, **kwargs ) :
"""
Scatter plot colored by 2d histogram
"""
if ax is None :
fig , ax = plt.subplots()
data , x_e, y_e = np.histogram2d( x, y, bins = bins, density = True )
z = interpn( ( 0.5*(x_e[1:] + x_e[:-1]) , 0.5*(y_e[1:]+y_e[:-1]) ) , data , np.vstack([x,y]).T , method = "splinef2d", bounds_error = False)
#To be sure to plot all data
z[np.where(np.isnan(z))] = 0.0
# Sort the points by density, so that the densest points are plotted last
if sort :
idx = z.argsort()
x, y, z = x[idx], y[idx], z[idx]
ax.scatter( x, y, c=z, **kwargs )
norm = Normalize(vmin = np.min(z), vmax = np.max(z))
cbar = fig.colorbar(cm.ScalarMappable(norm = norm), ax=ax)
cbar.ax.set_ylabel('Density')
return ax
if "__main__" == __name__ :
x = np.random.normal(size=100000)
y = x * 3 + np.random.normal(size=100000)
density_scatter( x, y, bins = [30,30] )
You could make a histogram:
import numpy as np
import matplotlib.pyplot as plt
# fake data:
a = np.random.normal(size=1000)
b = a*3 + np.random.normal(size=1000)
plt.hist2d(a, b, (50, 50), cmap=plt.cm.jet)
plt.colorbar()
I want to plot a donut and my script is
import numpy as np
import matplotlib.pyplot as plt
pi,sin,cos = np.pi,np.sin,np.cos
r1 = 1
r2 = 2
theta = np.linspace(0,2*pi,36)
x1 = r1*cos(theta)
y1 = r1*sin(theta)
x2 = r2*cos(theta)
y2 = r2*sin(theta)
How to get a donut with red filled area ?
You can traverse the boundaries of the area in closed curve, and use fill method to fill the area inside this closed area:
import numpy as np
import matplotlib.pyplot as plt
n, radii = 50, [.7, .95]
theta = np.linspace(0, 2*np.pi, n, endpoint=True)
xs = np.outer(radii, np.cos(theta))
ys = np.outer(radii, np.sin(theta))
# in order to have a closed area, the circles
# should be traversed in opposite directions
xs[1,:] = xs[1,::-1]
ys[1,:] = ys[1,::-1]
ax = plt.subplot(111, aspect='equal')
ax.fill(np.ravel(xs), np.ravel(ys), edgecolor='#348ABD')
plt.show()
This can easily be applied to any shape, for example, a pentagon inside or outside of a circle:
You can do this by plotting the top and bottom halves separately:
import numpy as np
import matplotlib.pyplot as plt
inner = 5.
outer = 10.
x = np.linspace(-outer, outer, 1000, endpoint=True)
yO = outer*np.sin(np.arccos(x/outer)) # x-axis values -> outer circle
yI = inner*np.sin(np.arccos(x/inner)) # x-axis values -> inner circle (with nan's beyond circle)
yI[np.isnan(yI)] = 0. # yI now looks like a boulder hat, meeting yO at the outer points
ax = plt.subplot(111)
ax.fill_between(x, yI, yO, color="red")
ax.fill_between(x, -yO, -yI, color="red")
plt.show()
Or you can use polar coordinates, though whether this is beneficial depends on the broader context:
import numpy as np
import matplotlib.pyplot as plt
theta = np.linspace(0., 2.*np.pi, 80, endpoint=True)
ax = plt.subplot(111, polar=True)
ax.fill_between(theta, 5., 10., color="red")
plt.show()
It's a bit of a hack but the following works:
import numpy as np
import matplotlib.pyplot as plt
pi,sin,cos = np.pi,np.sin,np.cos
r1 = 1
r2 = 2
theta = np.linspace(0,2*pi,36)
x1 = r1*cos(theta)
y1 = r1*sin(theta)
x2 = r2*cos(theta)
y2 = r2*sin(theta)
fig, ax = plt.subplots()
ax.fill_between(x2, -y2, y2, color='red')
ax.fill_between(x1, y1, -y1, color='white')
plt.show()
It plots the whole area of your donut in red and then plots the central "hole" in white.
The answer given by tom10 is ten ;)
But if you want to define the circle (donut) origin is simple, just add the position x,y in the x, yI, yO and -yO and -yI, like this:
...
pos = [4,2]
ax.fill_between(x+pos[0], yI+pos[1], yO+pos[1], color=color)
ax.fill_between(x+pos[0], -yO+pos[1], -yI+pos[1], color=color)
...
REF Example: https://pastebin.com/8Ew4Vthb