I want to plot a 3d bar use matplotlib.
I have a dataframe like this
In[1]: mf
Out[1]: 1 2 4
0
6N 45.238806 104.102564 16.804965
12S 25.597015 95.128205 13.156028
18S 29.689055 76.730769 17.078014
7S 0.000000 156.602564 20.106383
12S 25.597015 95.128205 13.156028
25S 0.000000 151.217949 16.929078
2S 4.962687 49.358974 32.517730
14N 0.000000 0.000000 33.386525
24S 10.447761 71.346154 25.343972
I want to plot a 3d bar in the dataframe corresponding position.
My code like this:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax1 = fig.add_subplot(111, projection='3d')
xpos = [1,1,1,2,2,2,3,3,3,4,4,4,5,5,5,6,6,6,7,7,7,8,8,8,9,9,9]
ypos = [3,2,1,3,2,1,3,2,1,3,2,1,3,2,1,3,2,1,3,2,1,3,2,1,3,2,1]
zpos = np.zeros(27)
dx = np.ones(27)
dy = np.ones(27)
# to reshape my dataframe to a np vector
nf = mf.values
dz = np.reshape(nf,(1,27))
ax1.bar3d(xpos, ypos, zpos, dx,dy,dz, color="#00ceaa")
but I get this error:
15 dz = np.reshape(nf,(1,27))
16 dz
---> 17 ax1.bar3d(xpos, ypos, zpos, dx,dy,dz, color="#00ceaa")
bar3d(self, x, y, z, dx, dy, dz, color, zsort, shade, *args, **kwargs)
2526
2527 if shade:
-> 2528 normals = self._generate_normals(polys)
2529 sfacecolors = self._shade_colors(facecolors, normals)
in _generate_normals(self, polygons)
1771 v1 = np.array(verts[0]) - np.array(verts[1])
1772 v2 = np.array(verts[2]) - np.array(verts[0])
-> 1773 normals.append(np.cross(v1, v2))
1774 return normals
in cross(a, b, axisa, axisb, axisc, axis)
1716 "(dimension must be 2 or 3)")
1717 if a.shape[-1] not in (2, 3) or b.shape[-1] not in (2, 3):
-> 1718 raise ValueError(msg)
1719
1720 # Create the output array
ValueError: incompatible dimensions for cross product
(dimension must be 2 or 3)
Where is my code wrong I did not have thinks, thanks a lot.
You need to reshape your df.values like this:
dz = np.reshape(nf,(27))
such that all arrays have the same shape (i.e. (27,), check dx.shape, dy.shape,z.shape,...).
Also note that (while not required) it's good practice to declare both your xpos and ypos lists as np.array like:
xpos = np.array([1,1,1,2,2,2,3,3,3,4,4,4,5,5,5,6,6,6,7,7,7,8,8,8,9,9,9])
Related
I used below code to generate the colorbar plot of an image:
plt.imshow(distance)
cb = plt.colorbar()
plt.savefig(generate_filename("test_images.png"))
cb.remove()
The image looks likes this:
I want to draw a single contour line on this image where the signed distance value is equal to 0. I checked the doc of pyplot.contour but it needs a X and Y vector that represents the coordinates and a Z that represents heights. Is there a method to generate X, Y, and Z? Or is there a better function to achieve this? Thanks!
If you leave out X and Y, by default, plt.contour uses the array indices (in this case the range 0-1023 in both x and y).
To only draw a contour line at a given level, you can use levels=[0]. The colors= parameter can fix one or more colors. Optionally, you can draw a line on the colorbar to indicate the value of the level.
import matplotlib.pyplot as plt
import numpy as np
from scipy import ndimage # to smooth a test image
# create a test image with similar properties as the given one
np.random.seed(20221230)
distance = np.pad(np.random.randn(1001, 1001), (11, 11), constant_values=-0.02)
distance = ndimage.filters.gaussian_filter(distance, 100)
distance -= distance.min()
distance = distance / distance.max() * 0.78 - 0.73
plt.imshow(distance)
cbar = plt.colorbar()
level = 0
color = 'red'
plt.contour(distance, levels=[level], colors=color)
cbar.ax.axhline(level, color=color) # show the level on the colorbar
plt.show()
Reference: https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.contour.html
You can accomplish this by setting the [levels] parameter in contour([X, Y,] Z, [levels], **kwargs).
You can draw contour lines at the specified levels by giving an array that is in increasing order.
import matplotlib.pyplot as plt
import numpy as np
x = y = np.arange(-3.0, 3.0, 0.02)
X, Y = np.meshgrid(x, y)
Z1 = np.exp(-X ** 2 - Y ** 2)
Z2 = np.exp(-(X - 1) ** 2 - (Y - 1) ** 2)
Z3 = np.exp(-(X + 1) ** 2 - (Y + 1) ** 2)
Z = (Z1 - Z2 - Z3) * 2
fig, ax = plt.subplots()
im = ax.imshow(Z, interpolation='gaussian',
origin='lower', extent=[-4, 4, -4, 4],
vmax=abs(Z).max(), vmin=-abs(Z).max())
plt.colorbar(im)
CS = ax.contour(X, Y, Z, levels=[0.9], colors='black')
ax.clabel(CS, fmt='%1.1f', fontsize=12)
plt.show()
Result (levels=[0.9]):
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 ])
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()
I am reading CSV file:
Notation Level RFResult PRIResult PDResult Total Result
AAA 1 1.23 0 2 3.23
AAA 1 3.4 1 0 4.4
BBB 2 0.26 1 1.42 2.68
BBB 2 0.73 1 1.3 3.03
CCC 3 0.30 0 2.73 3.03
DDD 4 0.25 1 1.50 2.75
AAA 5 0.25 1 1.50 2.75
FFF 6 0.26 1 1.42 2.68
...
...
Here is the code
import pandas as pd
import matplotlib.pyplot as plt
df = pd.rad_csv('home\NewFiles\Files.csv')
Notation = df['Notation']
Level = df['Level']
RFResult = df['RFResult']
PRIResult = df['PRIResult']
PDResult = df['PDResult']
fig, axes = plt.subplots(nrows=7, ncols=1)
ax1, ax2, ax3, ax4, ax5, ax6, ax7 = axes.flatten()
n_bins = 13
ax1.hist(data['Total'], n_bins, histtype='bar') #Current this shows all Total Results in one plot
plt.show()
I want to show each Level Total Result in each different axes like as follow:
ax1 will show Level 1 Total Result
ax2 will show Level 2 Total Result
ax3 will show Level 3 Total Result
ax4 will show Level 4 Total Result
ax5 will show Level 5 Total Result
ax6 will show Level 6 Total Result
ax7 will show Level 7 Total Result
You can select a filtered part of a dataframe just by indexing: df[df['Level'] == level]['Total']. You can loop through the axes using for ax in axes.flatten(). To also get the index, use for ind, ax in enumerate(axes.flatten()). Note that Python normally starts counting from 1, so adding 1 to the index would be a good choice to indicate the level.
Note that when you have backslashes in a string, you can escape them using an r-string: r'home\NewFiles\Files.csv'.
The default ylim is from 0 to the maximum bar height, plus some padding. This can be changed for each ax separately. In the example below a list of ymax values is used to show the principle.
ax.grid(True, axis='both) sets the grid on for that ax. Instead of 'both', also 'x' or 'y' can be used to only set the grid for that axis. A grid line is drawn for each tick value. (The example below tries to use little space, so only a few gridlines are visible.)
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
N = 1000
df = pd.DataFrame({'Level': np.random.randint(1, 6, N), 'Total': np.random.uniform(1, 5, N)})
fig, axes = plt.subplots(nrows=5, ncols=1, sharex=True)
ymax_per_level = [27, 29, 28, 26, 27]
for ind, (ax, lev_ymax) in enumerate(zip(axes.flatten(), ymax_per_level)):
level = ind + 1
n_bins = 13
ax.hist(df[df['Level'] == level]['Total'], bins=n_bins, histtype='bar')
ax.set_ylabel(f'TL={level}') # to add the level in the ylabel
ax.set_ylim(0, lev_ymax)
ax.grid(True, axis='both')
plt.show()
PS: A stacked histogram with custom legend and custom vertical lines could be created as:
import matplotlib.pyplot as plt
from matplotlib.patches import Patch
import pandas as pd
import numpy as np
N = 1000
df = pd.DataFrame({'Level': np.random.randint(1, 6, N),
'RFResult': np.random.uniform(1, 5, N),
'PRIResult': np.random.uniform(1, 5, N),
'PDResult': np.random.uniform(1, 5, N)})
df['Total'] = df['RFResult'] + df['PRIResult'] + df['PDResult']
fig, axes = plt.subplots(nrows=5, ncols=1, sharex=True)
colors = ['crimson', 'limegreen', 'dodgerblue']
column_names = ['RFResult', 'PRIResult', 'PDResult']
level_vertical_line = [1, 2, 3, 4, 5]
for level, (ax, vertical_line) in enumerate(zip(axes.flatten(), level_vertical_line), start=1):
n_bins = 13
level_data = df[df['Level'] == level][column_names].to_numpy()
# vertical_line = level_data.mean()
ax.hist(level_data, bins=n_bins,
histtype='bar', stacked=True, color=colors)
ax.axvline(vertical_line, color='gold', ls=':', lw=2)
ax.set_ylabel(f'TL={level}') # to add the level in the ylabel
ax.margins(x=0.01)
ax.grid(True, axis='both')
legend_handles = [Patch(color=color) for color in colors]
axes[0].legend(legend_handles, column_names, ncol=len(column_names), loc='lower center', bbox_to_anchor=(0.5, 1.02))
plt.show()
I would like to find the intersection between (eq1, eq2) and (eq1, eq3) and show that point with the dotted line on each axis. This code does not give me the exact point but just an approximation. I do not understand where am I doing mistake.
import matplotlib.pyplot as plt
import numpy as np
f = []
h = []
j = []
point = []
for x in range(25):
eq1 = x * 185 * 3
eq2 = 11930 - (12502 / 6) + (x * 185) / 6
eq3 = 11930 - (12502 / 3) + (x * 185) / 6
point.append(x)
f.append(eq1)
h.append(eq2)
j.append(eq3)
plt.plot(point, f)
plt.plot(point, h)
plt.plot(point, j)
plt.legend(loc='lower right', fontsize=10)
idx1 = np.argwhere(np.diff(np.sign(np.array(f) - np.array(h)))).flatten()
idx2 = idx = np.argwhere(np.diff(np.sign(np.array(f) - np.array(j)))).flatten()
plt.plot(np.array(point)[idx1+1], np.array(h)[idx1+1], 'ro')
plt.plot(np.array(point)[idx2+1], np.array(j)[idx2+1], 'ro')
plt.show()
Several issues here:
Firstly, your code is unnecessarily long. Make use of NumPy arrays to simplify things. Since NumPy is a dependency of matplotlib, you are not overkilling by importing NumPy.
You need to make a very dense mesh of points between 0 and 25 to get more accurate intersection points. Use linspace with 1000 points for example.
As you can see, with arrays, you don't need to use for loop, neither you need to initialize empty lists and then append values one by one.
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 25, 1000)
f = x * 185 * 3
h = 11930 - (12502 / 6) + (x * 185) / 6
j = 11930 - (12502 / 3) + (x * 185) / 6
plt.plot(x, f, label='f')
plt.plot(x, h, label='h')
plt.plot(x, j, label='j')
plt.legend(loc='lower right', fontsize=12)
idx1 = np.argwhere(np.diff(np.sign(np.array(f) - np.array(h)))).flatten()
idx2 = idx = np.argwhere(np.diff(np.sign(np.array(f) - np.array(j)))).flatten()
plt.plot(x[idx1+1], h[idx1+1], 'ro')
plt.plot(x[idx2+1], j[idx2+1], 'ro')
plt.vlines(x[idx1+1], 0, h[idx1+1], linestyle='--')
plt.vlines(x[idx2+1], 0, j[idx2+1], linestyle='--')
plt.hlines(h[idx1+1], 0, x[idx1+1], linestyle='--')
plt.hlines(j[idx2+1], 0, x[idx2+1], linestyle='--')
plt.xlim(0, None)
plt.ylim(0, None)
plt.show()