plotting of 3-d softmax function using matplotlib - python-3.x

I would like to illustrate a detailed function similar to the softmax function.
The formula is as shown in the image.
I wrote it in python based on the following blog. Sorry, written in Japanese.
https://www.anarchive-beta.com/entry/2020/06/07/180000
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
import matplotlib
from mpl_toolkits.mplot3d.axes3d import Axes3D
%matplotlib notebook
def softmax(x, a, b, d):
x = x - np.max(x, axis=-1, keepdims=True)
return (np.exp(a * (x - d))*b / np.sum(np.exp(a * (x - d)), axis=-1, keepdims=True))
# input
x_vals = np.arange(0, 10, 0.01)
x2_vals = np.arange(0, 10, 0.01)
X0_vals, X1_vals = np.meshgrid(x_vals, x2_vals)
X_vals = np.array([
X0_vals.flatten(),
X1_vals.flatten()
]).T
print(X_vals)
print(X_vals[:5])
print(X_vals.shape)
input_shape = X0_vals.shape
print(input_shape)
Y_vals = softmax(X_vals, 12, 0.8, [10,10])
print(np.round(Y_vals[:5], 3))
print(np.sum(Y_vals[:5], axis=1))
Y0_vals = np.array(Y_vals[:, 0]).reshape(input_shape)
fig = plt.figure(figsize=(5, 5))
ax = Axes3D(fig)
ax.plot_wireframe(X0_vals, X1_vals, Y0_vals, label='$y_0$')
ax.set_xlabel('$x_0$')
ax.set_ylabel('$x_1$')
ax.set_zlabel('$y_0$')
ax.set_title('Softmax Function', fontsize=20)
ax.legend()
ax.set_zlim(0, 1)
ax.view_init(elev=20, azim=240)
plt.show()
Y_vals = softmax(X_vals, 12, 0.8, 0)
print(np.round(Y_vals[:5], 3))
print(np.sum(Y_vals[:5], axis=1))
Y0_vals = np.array(Y_vals[:, 0]).reshape(input_shape)
fig = plt.figure(figsize=(5, 5))
ax = Axes3D(fig)
ax.plot_wireframe(X0_vals, X1_vals, Y0_vals, label='$y_0$')
ax.set_xlabel('$x_0$')
ax.set_ylabel('$x_1$')
ax.set_zlabel('$y_0$')
ax.set_title('Softmax Function', fontsize=20)
ax.legend()
ax.set_zlim(0, 1)
ax.view_init(elev=20, azim=240)
plt.show()
(1)
I try to plot the picture with a_1 = a_2 = 12, b = 0.8, c_1 = c_2 = 12. However, I could not find the differences with a_1 = a_2 = 12, b = 0.8, c_1 = c_2 = 0.
How should I write the code?
(2)
I have no idea to plot when a_1 is not equal to a_2, or c_1 is not equal to c_2.
(3)
I would like to overlap a scatterplot of any point on a function, but it does not overlap properly.
There might be similar questions, but I'm not so familiar with the 3-D plot, so I would be glad to show me the details.

Related

Can't seem to get Scipy curve_fit Sigmoid to work in Python

There are a few posts about this and normally the answer is to have a good initial guess and bounds. I've played around with it for a while and cannot find a configuration that produces any sort of curve.
import numpy as np
array1 = np.array(column1).astype(float)
array2 = np.array(column2).astype(float)
print(array1)
print(array2)
Output:
[18.7327 9.3784 6.6293 20.8361 11.2603 19.3706 5.4302 10.1293 13.7516
8.0567 16.8688 4.969 3.94 19.4793 11.7527 13.2811 13.338 0.5944
7.4406 11.2338 6.2283 3.4818 10.1056 16.2689 22.442 18.7345 5.2605
5.6405 12.7186 18.2497 5.4315 14.2651 16.7544 12.9192 13.5955 10.9256
5.7798 8.4485 8.5229 11.879 6.5271 10.3376 7.781 31.4558 8.0236
2.3527 10.8926 16.1995 11.1924 25.8071 13.9692 20.7791 10.3045 12.2833
7.4066 15.9807 11.4462 15.1504 5.9021 19.1184]
[83.85 52.45 41.2 92.59 62.65 86.77 30.63 53.78 73.34 48.55 82.53 28.3
23.87 90.99 62.95 68.82 71.06 20.74 45.25 60.65 39.07 21.93 53.35 79.61
93.27 85.88 28.95 32.73 65.89 83.51 30.74 75.22 79.8 67.43 71.12 58.41
35.83 49.61 50.72 63.49 40.67 55.75 46.49 96.22 47.62 21.8 56.23 76.97
59.07 94.67 74.9 92.52 55.61 63.51 41.34 76.8 62.81 75.99 36.34 85.96]
import pylab
from scipy.optimize import curve_fit
def sigmoid(x, a, b):
y = 1 / (1 + np.exp(-b*(x-a)))
return y
popt, pcov = curve_fit(sigmoid, array1, array2, p0 = [5,20], method='dogbox', bounds=([0, 20],[40, 100]))
print(popt)
x = np.linspace(0, 35, 50)
y = sigmoid(x, *popt)
pylab.plot(array1, array2, 'o', label='data')
pylab.plot(x,y, label='fit')
pylab.ylim(0, 100)
pylab.legend(loc='best')
pylab.show()
Output:
Graph
As you can see it just not doing anything at all. Would really appreciate any help on this to get a rough sigmoid curve. Doesn't need to be super accurate.
Many Thanks.
In your case, the problem wasn't a good initial guess, but an inappropriate model. Note how your sigmoid cannot be larger than 1, yet your data is in the range of ~10 - 100.
xs = np.linspace(0, 15)
as_ = np.linspace(0, 5, num=10)
bs_ = np.linspace(0, 5, num=10)
for a in as_:
for b in bs_:
plt.plot(xs, sigmoid(xs, a, b))
Therefore, you either have to modify your model to accept a scaling parameter, or scale down your data to a range your model can fit. Here's the two solutions:
Preamble
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
import pandas as pd
array1 = np.array([18.7327,9.3784,6.6293,20.8361,11.2603,19.3706,5.4302,10.1293,13.7516,8.0567,16.8688,4.969,3.94,19.4793,11.7527,13.2811,13.338,0.5944,7.4406,11.2338,6.2283,3.4818,10.1056,16.2689,22.442,18.7345,5.2605,5.6405,12.7186,18.2497,5.4315,14.2651,16.7544,12.9192,13.5955,10.9256,5.7798,8.4485,8.5229,11.879,6.5271,10.3376,7.781,31.4558,8.0236,2.3527,10.8926,16.1995,11.1924,25.8071,13.9692,20.7791,10.3045,12.2833,7.4066,15.9807,11.4462,15.1504,5.9021,19.1184])
array2 = np.array([83.85,52.45,41.2,92.59,62.65,86.77,30.63,53.78,73.34,48.55,82.53,28.3,23.87,90.99,62.95,68.82,71.06,20.74,45.25,60.65,39.07,21.93,53.35,79.61,93.27,85.88,28.95,32.73,65.89,83.51,30.74,75.22,79.8,67.43,71.12,58.41,35.83,49.61,50.72,63.49,40.67,55.75,46.49,96.22,47.62,21.8,56.23,76.97,59.07,94.67,74.9,92.52,55.61,63.51,41.34,76.8,62.81,75.99,36.34,85.96])
df = pd.DataFrame({'x':array1, 'y':array2})
df = df.sort_values('x')
Scaling data to match parameter
def sigmoid(x, a, b):
y = 1 / (1 + np.exp(-b*(x-a)))
return y
popt, pcov = curve_fit(sigmoid, df['x'], df['y'] / df['y'].max(), p0 = [5,20], method='dogbox', bounds=([0, 0],[40, 100]))
plt.plot(df['x'], df['y'] / df['y'].max(), label='data')
plt.plot(df['x'], sigmoid(df['x'], *popt))
popt is [8.56754823 0.20609918]
Adding new parameter to function
def sigmoid2(x, a, b, scale):
y = scale / (1 + np.exp(-b*(x-a)))
return y
popt, pcov = curve_fit(sigmoid2, df['x'], df['y'], p0 = [5,20, 100], method='dogbox', bounds=([0, 0, 0],[40, 100, 1E5]))
plt.plot(df['x'], df['y'], label='data')
plt.plot(df['x'], sigmoid2(df['x'], *popt))
popt is array([ 8.81708442, 0.19749557, 98.357044 ])

Heat map for Irregularly Spaced Data with No Interpolation

I would like to plot a heatmap where the input data is not in the typical rectangularly spaced grid. Here is some sample data:
import numpy as np
xmin = 6
xmax= 12
ymin = 0
x = np.linspace(xmin, xmax, 100)
ymax = x**2
final = []
for i in range(len(ymax)):
yrange = np.linspace(0, ymax[i], 100)
for j in range(len(yrange)):
intensity = np.random.rand()
final.append([x[i], yrange[j], intensity])
data_for_plotting = np.asarray(final) # (10000, 3) shaped array
I would like to plot intensity (in the colorbar) as a function of (x,y) which represents the position and I would like to do this without interpolation.
Here is my solution which uses matplotlib's griddata and linear interpolation.
import matplotlib.pyplot as plt
from matplotlib.mlab import griddata
total_length = 100
x1 = np.linspace(min(data_for_plotting[:,0]), max(data_for_plotting[:,0]), total_length)
y1 = np.linspace(min(data_for_plotting[:,1]), max(data_for_plotting[:,1]), total_length)
z1 = griddata(data_for_plotting[:,0], data_for_plotting[:,1], data_for_plotting[:,2], x1, y1, interp='linear')
p=plt.pcolormesh(x1, y1, z1, vmin = 0. , vmax=1.0, cmap='viridis')
clb = plt.colorbar(p)
plt.show()
I am looking for an alternate solution without interpolation as I would like to see the smallest unit of measurement in my x and y position (pixel size/rectangle). Based on the sample data given above I expect the height of the pixel to increase for large values of x.
I'm unsure what matplotlib.mlab.griddata is about. Maybe some very old version?
You could use scipy.interpolate.griddata which needs its parameters in a slightly different format. method='nearest' switches off the interpolation (default method='linear').
Here is how it could look with your test data (see griddata's documentation for more explanation and examples):
import matplotlib.pyplot as plt
from scipy.interpolate import griddata
import numpy as np
xmin = 6
xmax = 12
ymin = 0
x = np.linspace(xmin, xmax, 100)
ymax = x ** 2
final = []
for i in range(len(ymax)):
yrange = np.linspace(0, ymax[i], 100)
for j in range(len(yrange)):
intensity = np.random.rand()
final.append([x[i], yrange[j], intensity])
data_for_plotting = np.asarray(final) # (10000, 3) shaped array
total_length = 100
x1 = np.linspace(min(data_for_plotting[:, 0]), max(data_for_plotting[:, 0]), total_length)
y1 = np.linspace(min(data_for_plotting[:, 1]), max(data_for_plotting[:, 1]), total_length)
grid_x, grid_y = np.meshgrid(x1, y1)
z1 = griddata(data_for_plotting[:, :2], data_for_plotting[:, 2], (grid_x, grid_y), method='nearest')
img = plt.imshow(z1, extent=[x1[0], x1[-1], y1[0], y1[-1]], origin='lower',
vmin=0, vmax=1, cmap='inferno', aspect='auto')
cbar = plt.colorbar(img)
plt.show()
An alernative, is to create one rectangle for each of the prolonged pixels. Beware that this can be a rather slow operation. If really needed, one could create a pcolormesh for each column.
import matplotlib.pyplot as plt
from matplotlib.cm import ScalarMappable
import numpy as np
# ... create x and data_for_plotting as before
fig, ax = plt.subplots()
cmap = plt.get_cmap('inferno')
norm = plt.Normalize(0, 1)
x_step = x[1] - x[0]
y_step = 0
for i, (xi, yi, intensity_i) in enumerate(data_for_plotting):
if i + 1 < len(data_for_plotting) and data_for_plotting[i + 1, 0] == xi: # when False, the last y_step is reused
y_step = data_for_plotting[i + 1, 1] - yi
ax.add_artist(plt.Rectangle((xi, yi), x_step, y_step, color=cmap(norm(intensity_i))))
cbar = plt.colorbar(ScalarMappable(cmap=cmap, norm=norm))
ax.set_xlim(x[0], x[-1])
ax.set_ylim(0, data_for_plotting[:, 1].max())
plt.tight_layout()
plt.show()

How to color space between two lines with a colour transition?

How to colour space between two lines with a colour transition? For instance, with grey colour - the dark grey from the upper line should become lighter as proceeding to the lower line. Thank you
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
plt.rcParams["figure.figsize"] = [8, 8]
x = np.linspace(0, 1, 100)
y = 0.3*x
ax.set_ylim(-0.2, 0.6)
ax.plot(x, y)
width_l = ax.get_ylim()[1] - ax.get_ylim()[0]
ax.plot(x, y - 0.1*width_l)
plt.show()
Edit
And this, please? How to make the width of the coloured part the same?
import matplotlib.pyplot as plt
import numpy as np
import pywt
fig, ax = plt.subplots()
plt.rcParams["figure.figsize"] = [8, 8]
wavelet = pywt.ContinuousWavelet('morl')
psi, x = wavelet.wavefun(level=2)
cmap = plt.get_cmap('Greys_r')
ax.plot(x, psi)
ax.set_xlim(ax.get_xlim()[0], ax.get_xlim()[1])
y_a = ax.get_ylim()
ax.set_ylim(y_a[0],y_a[1]*1.3)
width_l = ax.get_ylim()[1] - ax.get_ylim()[0]
x_range = ax.get_xlim()[1] - ax.get_xlim()[0]
x_shift = x_range * 0.1
ax.plot([x[0]+x_shift, x[1]+x_shift], [psi[0], psi[1]])
ax.plot([x[2]-x_shift, x[3]-x_shift], [psi[2], psi[3]])
ax.plot([x[1], x[2]], [psi[1]-width_l*0.1, psi[2]-width_l*0.1])
for t in np.linspace(0, 1, 40):
ax.plot(x, psi - t * 0.1 * width_l, color=cmap(t/2 + 0.25))
plt.show()
You could draw a lot of parallel lines (or curves) using a color from a gray-scale colormap. The example code below uses a transformation u = t/2 + 0.25, so when t goes from 0 to 1, u would just go between 0.25 and 0.75 to select of specific range from the colormap, avoiding the very dark and very light parts.
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
plt.rcParams["figure.figsize"] = [8, 8]
x = np.linspace(0, 1, 100)
y = 0.3 * x
width_l = ax.get_ylim()[1] - ax.get_ylim()[0]
ax.set_ylim(-0.2, 0.6)
cmap = plt.get_cmap('Greys_r')
for t in np.linspace(0, 1, 40):
u = t/2 + 0.25
ax.plot(x, y - t * 0.1 * width_l, color=cmap(u))
ax.plot(x, y)
ax.plot(x, y - 0.1 * width_l)
plt.show()

Plotly surface plot with unequal length x and y axes

I have a 10x12 array that I would like to plot using plotly. The length of x-axis is 10 and y-axis is 12. I expect the plot to have a range of 10 in the x-axis and 12 in the y-axis. But that is not the case. I get 10 on both axes. I have included a MWE to reproduce the issue.
import numpy as np
import plotly.graph_objects as go
data_array = np.empty((10, 12))
for i in range(10):
for j in range(12):
data_array[i, j] = i+j
x = np.linspace(1, 10, 10)
y = np.linspace(1, 12, 12)
fig = go.Figure(data=[go.Surface(z=data_array, x=x, y=y)])
fig.show()
The plot that it generates is
I have tried setting the range via update_layout which doesn't solve the problem.
import numpy as np
import plotly.graph_objects as go
data_array = np.empty((10, 12))
for i in range(10):
for j in range(12):
data_array[i, j] = i+j
x = np.linspace(1, 10, 10)
y = np.linspace(1, 12, 12)
fig = go.Figure(data=[go.Surface(z=data_array, x=x, y=y)])
fig.update_layout(scene=dict(xaxis=dict(range=[0, 10]),
yaxis=dict(range=[0, 12])))
fig.show()
Although the range of y-axis in now till 12, there is nothing plotted in that part of the graph.
How can I get this to work? Thanks.
Based on the answer below, I have changed the code.
import numpy as np
import plotly.graph_objects as go
data_array = np.empty((10, 12))
for idx1, i in enumerate(range(0, 50, 5)):
for idx2, j in enumerate(range(0, 24, 2)):
data_array[idx1, idx2] = (i+j) / (50+24)
x = np.linspace(0, 45, 10)
y = np.linspace(0, 22, 12)
fig = go.Figure(data=[go.Surface(z=data_array.T)])
fig.show()
But the axes now have no correspondence with the actual x and y values. The values in the plot go from 0 to 9 and 0 to 11 for x and y axes respectively. How do I set the calibration? Thanks.
The matrix data_array should be transposed (x along columns, y along rows). The limit can be explicitly set by passing the x, y arguments. The code is as follows:
import numpy as np
import plotly.graph_objects as go
data_array = np.empty((10, 12))
for idx1, i in enumerate(range(0, 50, 5)):
for idx2, j in enumerate(range(0, 24, 2)):
data_array[idx1, idx2] = (i+j) / (50+24)
x = np.linspace(0, 45, 10)
y = np.linspace(0, 22, 12)
fig = go.Figure(data=[go.Surface(z=data_array.T, x=x, y=y)])
fig.show()
I am currently unable to save the image with the tag on the last position. The image obtained is as follows:
Documentation: https://plotly.com/python/3d-surface-plots/

Draw curves with triple colors and width by using matplotlib and LineCollection [duplicate]

The figure above is a great artwork showing the wind speed, wind direction and temperature simultaneously. detailedly:
The X axes represent the date
The Y axes shows the wind direction(Southern, western, etc)
The variant widths of the line were stand for the wind speed through timeseries
The variant colors of the line were stand for the atmospheric temperature
This simple figure visualized 3 different attribute without redundancy.
So, I really want to reproduce similar plot in matplotlib.
My attempt now
## Reference 1 http://stackoverflow.com/questions/19390895/matplotlib-plot-with-variable-line-width
## Reference 2 http://stackoverflow.com/questions/17240694/python-how-to-plot-one-line-in-different-colors
def plot_colourline(x,y,c):
c = plt.cm.jet((c-np.min(c))/(np.max(c)-np.min(c)))
lwidths=1+x[:-1]
ax = plt.gca()
for i in np.arange(len(x)-1):
ax.plot([x[i],x[i+1]], [y[i],y[i+1]], c=c[i],linewidth = lwidths[i])# = lwidths[i])
return
x=np.linspace(0,4*math.pi,100)
y=np.cos(x)
lwidths=1+x[:-1]
fig = plt.figure(1, figsize=(5,5))
ax = fig.add_subplot(111)
plot_colourline(x,y,prop)
ax.set_xlim(0,4*math.pi)
ax.set_ylim(-1.1,1.1)
Does someone has a more interested way to achieve this? Any advice would be appreciate!
Using as inspiration another question.
One option would be to use fill_between. But perhaps not in the way it was intended. Instead of using it to create your line, use it to mask everything that is not the line. Under it you can have a pcolormesh or contourf (for example) to map color any way you want.
Look, for instance, at this example:
import matplotlib.pyplot as plt
import numpy as np
from scipy.interpolate import interp1d
def windline(x,y,deviation,color):
y1 = y-deviation/2
y2 = y+deviation/2
tol = (y2.max()-y1.min())*0.05
X, Y = np.meshgrid(np.linspace(x.min(), x.max(), 100), np.linspace(y1.min()-tol, y2.max()+tol, 100))
Z = X.copy()
for i in range(Z.shape[0]):
Z[i,:] = c
#plt.pcolormesh(X, Y, Z)
plt.contourf(X, Y, Z, cmap='seismic')
plt.fill_between(x, y2, y2=np.ones(x.shape)*(y2.max()+tol), color='w')
plt.fill_between(x, np.ones(x.shape) * (y1.min() - tol), y2=y1, color='w')
plt.xlim(x.min(), x.max())
plt.ylim(y1.min()-tol, y2.max()+tol)
plt.show()
x = np.arange(100)
yo = np.random.randint(20, 60, 21)
y = interp1d(np.arange(0, 101, 5), yo, kind='cubic')(x)
dv = np.random.randint(2, 10, 21)
d = interp1d(np.arange(0, 101, 5), dv, kind='cubic')(x)
co = np.random.randint(20, 60, 21)
c = interp1d(np.arange(0, 101, 5), co, kind='cubic')(x)
windline(x, y, d, c)
, which results in this:
The function windline accepts as arguments numpy arrays with x, y , a deviation (like a thickness value per x value), and color array for color mapping. I think it can be greatly improved by messing around with other details but the principle, although not perfect, should be solid.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
x = np.linspace(0,4*np.pi,10000) # x data
y = np.cos(x) # y data
r = np.piecewise(x, [x < 2*np.pi, x >= 2*np.pi], [lambda x: 1-x/(2*np.pi), 0]) # red
g = np.piecewise(x, [x < 2*np.pi, x >= 2*np.pi], [lambda x: x/(2*np.pi), lambda x: -x/(2*np.pi)+2]) # green
b = np.piecewise(x, [x < 2*np.pi, x >= 2*np.pi], [0, lambda x: x/(2*np.pi)-1]) # blue
a = np.ones(10000) # alpha
w = x # width
fig, ax = plt.subplots(2)
ax[0].plot(x, r, color='r')
ax[0].plot(x, g, color='g')
ax[0].plot(x, b, color='b')
# mysterious parts
points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
# mysterious parts
rgba = list(zip(r,g,b,a))
lc = LineCollection(segments, linewidths=w, colors=rgba)
ax[1].add_collection(lc)
ax[1].set_xlim(0,4*np.pi)
ax[1].set_ylim(-1.1,1.1)
fig.show()
I notice this is what I suffered.

Resources