Plotting a two variable function with two parameters in python - python

I am trying to plot a 2 variable function with additional parameters which can be changed. Below is the function-
f(x,y) = (x - a*y)/(b+y)
I want to plot it in 3d and would like to see the change in the plot by changing the values of a and b, i.e. when a=1 and b=0, etc.
I can plot it for specific a and b cases, below is the code that works for a=1 and b=0. Is there any way where I don't need to map separately for different cases of a and b?
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
import numpy as np
x = np.linspace(30,7000,10000)
y = np.linspace(1,11000, 10000)
def delCAD(x,y):
return (x-y)/(y) # the function when a=1 and b=0
fig = plt.figure(figsize=(12,8))
ax = Axes3D(fig)
ax = fig.gca(projection = "3d")
surf = ax.plot_trisurf(x, y, delCAD(x,y), cmap = cm.coolwarm)
fig.colorbar(surf, shrink=0.5, aspect=5)
plt.show()

I generally use IPython or Jupyter for that sort of thing — maybe that's an option for you? For example, using ipywidgets.interact():
import matplotlib.pyplot as plt
import numpy as np
from ipywidgets import interact
x = np.linspace(1, 20, 50)
y = np.linspace(1, 20, 50)
y, x = np.meshgrid(y, x)
def delCAD(x, y, a=1, b=0):
return (x - a * y) / (b + y)
#interact(a=(1, 10), b=(0, 10))
def plot(a, b):
fig, ax = plt.subplots(subplot_kw={"projection": "3d"}, figsize=(12, 6))
surf = ax.plot_trisurf(x.flat, y.flat, delCAD(x, y, a, b).flat, cmap='coolwarm')
fig.colorbar(surf, ax=ax, shrink=0.5, aspect=5)
ax.view_init(elev=30, azim=160)
plt.show()
return
Produces this:
As well as the interact wrapper, I introduced the meshgrid line to compute all the locations in the grid, and I changed some of your parameters a bit so you can see more going on in the function. I hope you can unpick the various pieces to fit your needs.

Related

Make 3d Python plot as beautiful as Matlab

Note:
This is not a conversion question. It is meant to see if Python has the capability to produce 3D plot like Matlab.
I have created a Matlab plot as follows:
I tried to plot it using Python but I could not get it as good as Matlab. Is there any packages that can plot the above as good as the original one? If it is please convert my code to a Python version. Here is my Matlab code.
set(groot,'defaultAxesTickLabelInterpreter','latex');
set(groot,'defaulttextinterpreter','latex');
set(groot,'defaultLegendInterpreter','latex');
x0=0;
y0=0;
width=3000;
height=2000;
set(gcf,'position',[x0,y0,width,height])
[X,Y] = meshgrid(-1:.01:1);
a = 3;
b = 2;
Z = a*X.^2 + b*Y.^2;
subplot(1,3,1)
s = surf(X,Y,Z,'FaceColor','r', 'FaceAlpha',0.5, 'EdgeColor','none');
s.EdgeColor = 'none';
xlabel('$x_1$','Interpreter','latex','FontSize', 15)
ylabel('$x_2$','Interpreter','latex','FontSize', 15)
zlabel('$f(\mathbf{x};\mathbf{\theta})$','Interpreter','latex','FontSize', 15)
legend({'$f([x_1, x_2]^\top; [\theta_1=3,\theta_2=2]^\top)=3x_1^2+2x_2^2$'},'interpreter','latex','FontSize', 10)
subplot(1,3,2)
Z2 = a*X.^2 ;
s2 = surf(X,Y,Z2,'FaceColor','b', 'FaceAlpha',0.5, 'EdgeColor','none');
s2.EdgeColor = 'none';
xlabel('$x_1$','Interpreter','latex','FontSize', 15)
ylabel('$x_2$','Interpreter','latex','FontSize', 15)
zlabel('$f(\mathbf{x};\mathbf{\theta})$','Interpreter','latex','FontSize', 15)
legend({'$f([x_1, x_2]^\top; [\theta_1=3,\theta_2=0]^\top)=3x_1^2$'},'interpreter','latex','FontSize', 10)
subplot(1,3,3)
s3 = surf(X,Y,Z,'FaceColor','r', 'FaceAlpha',0.5, 'EdgeColor','none');
s3.EdgeColor = 'none';
hold
s4 = surf(X,Y,Z2,'FaceColor','b', 'FaceAlpha',0.5, 'EdgeColor','none');
s4.EdgeColor = 'none';
xlabel('$x_1$','Interpreter','latex','FontSize', 15)
ylabel('$x_2$','Interpreter','latex','FontSize', 15)
zlabel('$f(\mathbf{x};\mathbf{\theta})$','Interpreter','latex','FontSize', 15)
legend({'$f(\mathbf{x};\mathbf{\theta})=3x_1^2+2x_2^2$', '$f(\mathbf{x};\mathbf{\theta})=3x_1^2$'},'interpreter','latex','FontSize', 10)
Yes.
numpy + plotly is an effective Matlab replacement - you may recognize some of the code :). As a benefit, the plots render as html, which means they are highly portable, save as a single file, and can be embedded in a webpage. There may be small details that are different (I don't know the current status of latex axis labels), but, provided you have python, numpy and plotly installed, the following is a good replacement of your first plot:
import plotly.graph_objects as go
import numpy as np
x = np.arange(-1,1,.01)
y = np.arange(-1,1,.01)
X,Y = np.meshgrid(x,y)
a = 3
b = 2
Z = a*X**2 + b*Y**2
fig = go.Figure(
data=[go.Surface(z=Z, x=x, y=y, colorscale="Reds", opacity=0.5)])
fig.update_layout(
title='My title',
autosize=False,
width=500,
height=500,
margin=dict(l=65, r=50, b=65, t=90),
scene_aspectmode='cube'
)
fig.show()
Notice that the go-to plotting package in python is Matplotlib. IMO, it inherited all the worst parts of Matlab's plotting and none of the good (performant rendering). Plotly superior from a performance (esp 3D rendering), interactivity, and API standpoint.
For 3D charting in Python I've had the best results with matplotlib.pyplot.
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.axes3d import Axes3D, get_test_data
from matplotlib import cm
import numpy as np
import random
X_k_list = range(1, 100, 10)
Y_p_list = [ float(x)/100.0 for x in range(1, 100, 10) ]
# set up a figure twice as wide as it is tall
fig = plt.figure(figsize=plt.figaspect(0.5))
# set up the axes for the first plot
ax = fig.add_subplot(1, 1, 1, projection='3d')
# plot a 3D surface like in the example mplot3d/surface3d_demo
X, Y = np.meshgrid(X_k_list, Y_p_list)
def critical_function(b, c):
num = random.uniform(0, 1) * 10.0
return num + (b * c)
Z_accuracy = X.copy()
Z_accuracy = Z_accuracy.astype(np.float32)
for i in range(len(X_k_list)):
for j in range(len(Y_p_list)):
Z_accuracy[j][i] = critical_function(Y_p_list[j], X_k_list[i])
surf = ax.plot_surface(X, Y, Z_accuracy,
rstride=1, cstride=1, cmap=cm.coolwarm,
linewidth=0, antialiased=False)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
fig.colorbar(surf, shrink=0.5, aspect=10)
plt.show()
https://www.python-graph-gallery.com/371-surface-plot
You can increase the smoothness of the chart by adding more datapoints, rotate the graph along the x,y,z axis, with the mouse and you can add a title, legend and other eye candy.
matplotlib.mplot3d looks like it does euclidian continuous surfaces
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
from matplotlib import cm
ax = plt.figure().add_subplot(projection='3d')
X, Y, Z = axes3d.get_test_data(0.05)
cset = ax.contour(X, Y, Z, extend3d=True, cmap=cm.coolwarm)
ax.clabel(cset, fontsize=9, inline=True)
plt.show()
https://matplotlib.org/stable/gallery/mplot3d/contour3d_2.html#sphx-glr-gallery-mplot3d-contour3d-2-py
You're using matlab's meshgrid(...) tool to generate x,y,z data. Python can achieve the same results with numpy.meshgrid fed into matplotlib.pyplot thustly.
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import numpy as np
import matplotlib.pyplot as plt
def f(x, y):
return np.sin(np.sqrt(x ** 2 + y ** 2))
x = np.linspace(-6, 6, 30)
y = np.linspace(-6, 6, 30)
X, Y = np.meshgrid(x, y)
Z = f(X, Y)
fig = plt.figure()
ax = plt.axes(projection='3d')
ax.contour3D(X, Y, Z, 50, cmap='binary')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
plt.show()
https://jakevdp.github.io/PythonDataScienceHandbook/04.12-three-dimensional-plotting.html

Using drawstyle "steps-mid" together with x-log-scale causes step points to be non-centered

Matplotlib offers various options for the drawstyle. steps-mid does the following:
The steps variants connect the points with step-like lines, i.e. horizontal lines with vertical steps. [...]
'steps-mid': The step is halfway between the points.
This works fine when the x-scale is linear however when using a log-scale it still seems to compute the step points by averaging in data-space rather than log-space. This leads to data points not being centered between the steps.
import matplotlib.pyplot as plt
import numpy as np
x = np.logspace(0, 10, num=10)
y = np.arange(x.size) % 2
fig, ax = plt.subplots()
ax.set_xscale('log')
ax.plot(x, y, drawstyle='steps-mid', marker='s')
Is there a way to use step-like plotting together with x-log-scale such that the steps are centered between data points in log-space?
I don't know of a way other than building the steps correctly in log space yourself:
import matplotlib.pyplot as plt
import numpy as np
x = np.logspace(0, 10, num=10)
y = np.arange(x.size) % 2
def log_steps_mid(x, y, **kwargs):
x_log = np.log10(x)
x_log_mid = x_log[:-1] + np.diff(x_log)/2
x_mid = 10 ** x_log_mid
x_mid = np.hstack([x[0],
np.repeat(x_mid, 2),
x[-1]])
y_mid = np.repeat(y, 2)
ax.plot(x_mid, y_mid, **kwargs)
fig, ax = plt.subplots()
ax.set_xscale('log')
ax.plot(x, y, ls='', marker='s', color='b')
log_steps_mid(x, y, color='b')

Matplotlib plot contourf on 3d surface

I am trying to use the colormap feature of a 3d-surface plot in matplotlib to color the surface based on values from another array instead of the z-values.
The surface plot is created and displayed as follows:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
def gauss(x, y, w_0):
r = np.sqrt(x**2 + y**2)
return np.exp(-2*r**2 / w_0**2)
x = np.linspace(-100, 100, 100)
y = np.linspace(-100, 100, 100)
X, Y = np.meshgrid(x, y)
Z = gauss(X, Y, 50)
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
ax.plot_surface(X, Y, Z, cmap='jet')
Now instead of coloring based on elevation of the 3d-surface, I am looking to supply the color data for the surface in form of another array, here as an example a random one:
color_data = np.random.uniform(0, 1, size=(Z.shape))
However, I did not find a solution to colorize the 3d-surface based on those values. Ideally, it would look like a contourf plot in 3d, just on the 3d surface.
You can use matplotlib.colors.from_levels_and_colors to obtain a colormap and normalization, then apply those to the values to be colormapped.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.colors
x = np.linspace(-100, 100, 101)
y = np.linspace(-100, 100, 101)
X, Y = np.meshgrid(x, y)
Z = np.exp(-2*np.sqrt(X**2 + Y**2)**2 / 50**2)
c = X+50*np.cos(Y/20) # values to be colormapped
N = 11 # Number of level (edges)
levels = np.linspace(-150,150,N)
colors = plt.cm.get_cmap("RdYlGn", N-1)(np.arange(N-1))
cmap, norm = matplotlib.colors.from_levels_and_colors(levels, colors)
color_vals = cmap(norm(c))
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
ax.plot_surface(X, Y, Z, facecolors=color_vals, rstride=1, cstride=1)
plt.show()

convert a scatter plot into a contour plot in matplotllib [duplicate]

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()

Contour graph in python

How would I make a countour grid in python using matplotlib.pyplot, where the grid is one colour where the z variable is below zero and another when z is equal to or larger than zero? I'm not very familiar with matplotlib so if anyone can give me a simple way of doing this, that would be great.
So far I have:
x= np.arange(0,361)
y= np.arange(0,91)
X,Y = np.meshgrid(x,y)
area = funcarea(L,D,H,W,X,Y) #L,D,H and W are all constants defined elsewhere.
plt.figure()
plt.contourf(X,Y,area)
plt.show()
You can do this using the levels keyword in contourf.
import numpy as np
import matplotlib.pyplot as plt
fig, axs = plt.subplots(1,2)
x = np.linspace(0, 1, 100)
X, Y = np.meshgrid(x, x)
Z = np.sin(X)*np.sin(Y)
levels = np.linspace(-1, 1, 40)
zdata = np.sin(8*X)*np.sin(8*Y)
cs = axs[0].contourf(X, Y, zdata, levels=levels)
fig.colorbar(cs, ax=axs[0], format="%.2f")
cs = axs[1].contourf(X, Y, zdata, levels=[-1,0,1])
fig.colorbar(cs, ax=axs[1])
plt.show()
You can change the colors by choosing and different colormap; using vmin, vmax; etc.

Categories

Resources