I'm trying to plot the output of a continuous wavelet transform of a signal that I have. The signal is just a cosine wave whose frequency decrease with time. I'm just using it to test the plotting function.
I have a function that plots the scalogram of the CWT but I'm struggling on how to pick the correct levels range to use for my contour lines.
What i would like to know:
Is increasing the amount of levels basically just making the difference between color regions smaller? With more levels the difference between adjacent levels is smaller?
If you look at the below plots you notice how there appears to be more distinct regions in the first plot (with more levels) So I would think this is a good thing. In the bottom plot the use of less levels we see that whole large dark red section as one color instead of a few different shades.
I appreciate any feedback/comments/answers or if you see any mistakes. Thank you so much!
the function I'm using to create the plots:
def plot_wavelet(ax, time2, signal, scales, waveletname = 'cmor',
cmap =plt.cm.seismic, title = '', ylabel = '', xlabel = ''):
dt=time2
coefficients, frequencies = pywt.cwt(signal, scales, waveletname, dt)
print ("coeff shape is:",coefficients.shape)
print ("frequency shape is:", frequencies.shape)
power = (abs(coefficients)) ** 2
period = frequencies
# different level lists to test
levels = [0.015625, 0.03125, 0.0625, 0.125, 0.25, 0.5, 1, 2, 4, 8, 16] #option 1
#levels = [0.015625, 0.03125, 0.0625, 0.125, 0.25, 0.5, 1] #option 2
contourlevels = np.log2(levels) #convert to log2 for plotting
time=range(2048) # Sampling frequency is 2048, so this is a 1 second sample
im = ax.contourf(time, np.log2(period), np.log2(power), contourlevels, extend='both',cmap=cmap)
ax.set_title(title, fontsize=20)
ax.set_ylabel(ylabel, fontsize=18)
ax.set_xlabel(xlabel, fontsize=18)
yticks = 2**np.arange(np.ceil(np.log2(period.min())), np.ceil(np.log2(period.max())))
ax.set_yticks(np.log2(yticks)) #original
ax.set_yticklabels(yticks) #original
ax.invert_yaxis()
ylim = ax.get_ylim()
cbar_ax = fig.add_axes([0.95, 0.5, 0.03, 0.25])
fig.colorbar(im, cax=cbar_ax, orientation="vertical")
return yticks, ylim
The only difference between the two plots below are the levels used, everything else is the same:
This is the plot with levels = [0.015625, 0.03125, 0.0625, 0.125, 0.25, 0.5, 1, 2, 4, 8, 16]
This is the plot with levels = [0.015625, 0.03125, 0.0625, 0.125, 0.25, 0.5, 1]
Related
I am trying to plot some 336 data points and am encountering an issue with my use of pythons plt.hist() function. I would like to use more than eight bins for my data, but when I do a lot of whitespace is introduced. For example, here is a plot with bins = 8
and with bins = 24
Does anyone know why this is and how I can best represent my data with more bins? Many thanks, ~S.
Sample code:
tumbles = np.array(df['Tumbles'])
fig, axs = plt.subplots(1, 1,
tight_layout = True)
N, bins, patches = axs.hist(tumbles, bins = 24, edgecolor= "black")
axs.grid(b = True, color ='grey',
linestyle ='-.', linewidth = 0.5,
alpha = 0.6)
plt.xlabel("Time (s)", size = 14)
plt.ylabel("Frequency", size = 14)
plt.title('Histogram ofTimes', size = 18)
plt.show()
I feel like your data is distributed in a way that the empty space between bars are simply bars with height 0 (a lack of samples). In such a case you just don't need more bins.
Please include your code
With this setup I get the same problem:
import matplotlib.pyplot as plt
plt.hist([1, 2, 2, 3, 4, 5, 5, 5, 5, 5, 6, 7, 9], bins=20)
plt.show()
It would be a bit more effort, but if you want a bit more control over the number of bins and the range of each bin, you might set up the bin parameter in your histogram definition as a list. This was alluded to above, but here is a snippet of code illustrating that.
import matplotlib.pyplot as plt
data = [0.02, 0.02, 0.02, 0.27, 0.27, 0.03, 0.03, 0.04, 0.044, 0.044, 0.05, 0.05, 0.06, 0.07, 0.08, 0.08, 0.08, 0,10, 0.10, 0.11, 0.12, 0.13, 0.13, 0.14, 0.15, 0.17, 0,18, 0.19, 0.20, 0.20, 0.22, 0.23, 0.23, 0.23, 0.23, 0.24, 0.26, 0.26, 0.28, 0.29, 0.30, 0.32]
fig, ax = plt.subplots()
N, bins, values = ax.hist(data, [0.000,0.015,0.030,0.045,0.060,0.075,0.090,0.105,0.120,0.135,0.150,0.165,0.180,0.195,0.210,0.225,0.240,0.255,0.270,0.285,0.300,0.315,0.330,0.345], linewidth=1)
plt.bar_label(values)
plt.xlabel("Time (s)", size = 14)
plt.ylabel("Frequency", size = 14)
plt.title('Histogram of Times', size = 18)
plt.show()
The data is just a small subset to establish some data points to produce a histogram. Following was the histogram created in this fashion.
You might give that a try adjusting for the range each bin should have.
I am trying to rotate the radial tick labels on the attached plot.
Why does matplotlib not rotate them when I have the 'rotation' command specified?
I would then like to shift the labels in the radial direction. Is there an equivalent of the 'pad' command with the polar charts?
import numpy as np
import matplotlib.pyplot as plt
import math
Graph_title = "Radar Plot"
def radarplot():
ax = plt.subplot(111, polar=True)
# INPUT DATA
n_directions = 12
angles = [n / float(n_directions) * 2 * math.pi for n in range(n_directions)]
data = [3.0, 3.0, 3.0, 3.0, 2.0, 2.5, 2.5, 2.5, 2.75, 2.75, 3.0, 3.0]
# Add the last element of the list to the list. This is necessary or the line from 330 deg to 0 degree does not join up on the plot.
angles = np.append(angles, angles[:1])
data = np.append(data, data[:1])
ax.plot(angles, data, linewidth=2, linestyle='solid', color = 'red')
# Radial tick parameters
radial_ticks = [0.00, 0.50, 1.00, 1.50, 2.00, 2.50, 3.00]
ax.set_rlabel_position(45)
ax.set_rorigin(0)
plt.yticks(radial_ticks, color='black', size=8)
ax.set_yticklabels(radial_ticks, rotation = 45, zorder = 500)
# X Tick parameters
plt.xticks(angles, color='black', size=10, zorder = 5)
ax.tick_params(axis='x', which='major', pad=3)
ax.set_theta_zero_location("N") # Sets the labels initial position to 0 degrees
ax.set_theta_direction("clockwise") # Set the labels to rotate clockwise
plt.savefig(Graph_title +".png", figsize = [6.4, 5], dpi=1000)
plt.show()
plt.close()
radarplot()
Recently I wanted to achieve the same thing as you and here is the solution that I came up with.
Suppress the automatic r tick labels using the command ax.set_yticklabels([])
For each radial tick define a tick list, a position list, and an alignment list.
Using the text command write the values in the tick list at the radial locations specified by the position list with alignment specified by the alignment list.
Essentially the r ticks can be moved along the radial direction by changing the values in the position list.
Make sure that the text command is specified with transform=ax.transData option.
import numpy as np
import matplotlib.pyplot as plt
import math
Graph_title = "Radar Plot"
def radarplot():
ax = plt.subplot(111, polar=True)
# INPUT DATA
n_directions = 12
angles = [n / float(n_directions) * 2 * math.pi for n in range(n_directions)]
data = [3.0, 3.0, 3.0, 3.0, 2.0, 2.5, 2.5, 2.5, 2.75, 2.75, 3.0, 3.0]
# Add the last element of the list to the list. This is necessary or the line from 330 deg to 0 degree does not join up on the plot.
angles = np.append(angles, angles[:1])
data = np.append(data, data[:1])
ax.plot(angles, data, linewidth=2, linestyle='solid', color = 'red')
r_ticks = [0.00, 0.50, 1.00, 1.50, 2.00, 2.50, 3.00] #tick list
r_ticks_pos = [0.20, 0.65, 1.15, 1.65, 2.15, 2.65, 3.25] #radial position list (CHANGE THESE VALUES TO MOVE EACH TICK RADIALLY INDIVIDUALLY)
r_ticks_h_align = ['center','center','center','center','center','center','center'] #horizontal alignment list
r_ticks_v_align = ['center','center','center','center','center','center','center'] #vertical alignment list
r_label_angle = 45 #theta angle
# Radial tick parameters
ax.set_rlabel_position(r_label_angle)
ax.set_rorigin(0)
ax.set_yticklabels([])
ax.set_rticks(r_ticks)
ax.set_rlabel_position(r_label_angle)
#write the ticks using the text command
for rtick, rtick_pos, rtick_ha, rtick_va in zip(r_ticks, r_ticks_pos, r_ticks_h_align, r_ticks_v_align):
plt.text(np.radians(r_label_angle), rtick_pos, r'$'+str(rtick)+'$', ha=rtick_ha, va=rtick_va, transform=ax.transData, rotation=-45, fontsize=8)
# X Tick parameters
plt.xticks(angles, color='black', size=10, zorder = 5)
ax.tick_params(axis='x', which='major', pad=3)
ax.set_theta_zero_location("N") # Sets the labels initial position to 0 degrees
ax.set_theta_direction("clockwise") # Set the labels to rotate clockwise
plt.savefig(Graph_title +".png", figsize = [6.4, 5], dpi=1000)
plt.show()
plt.close()
radarplot()
And the result:
I meet the same problem, and I only solved the rotation problem. axis='x' failed to control the rotation, only axis='both' can work.
ax.tick_params(
axis='both',
labelrotation=-45.,
)
I am trying to solve the pad problem.
I am trying to find the indices all within a certain bin of the data binned liked this:
import numpy as np
x=np.random.random(1000)
y=np.random.random(1000)
#The bins are not evenly spaced and not the same number in x and y.
xedges=np.array(0.1,0.2, 0.4, 0.5, 0.55, 0.6, 0.8, 0.9)
yedges=np.arange(0.1,0.2, 0.4, 0.5, 0.55, 0.6, 0.8, 0.9)
h=np.histogram2d(x,y, bins=[xedges,yedges])
I want to find the indices (then plot them etc) contained in each bin that is greater than some threshold number of counts. So each bin with a count greater than the threshold is a "cluster" and I want to know all the datapoints (x,y) in that cluster.
I wrote in pseudocode how I think it would work.
thres=5
mask=(h>5)
for i in mask:
# for each bin with count > thres
# get bin edges for x and y directions
# find (rightEdge < x < leftEdge) and (rightEdge < y < leftEdge)
# return indices for each True in mask
plt.plot(x[indices], y[indicies])
I tried reading the documentation for functions such as scipy.stats.binned_statistic2d and pandas.DataFrame.groupby but I couldn't figure out how to apply it to my data. For the binned_statistic2d they ask for an argument values :
The data on which the statistic will be computed. This must be the same shape as x, or a set of sequences - each the same shape as x.
And I wasn't sure how to input the data I wanted it to be computed on.
Thank you for any help you can provide on this issue.
If I understand correctly, you want to build a mask on the original points indicating that the point belongs to a bin with more than 5 points.
To construct such a mask, np.histogram2d returns the counts for each bin, but does not indicate which point goes into which bin.
You can construct such a mask by iterating over each bin that fulfills the condition, and add all corresponding point indices to the mask.
To visualize the result of np.histogram2d, plt.pcolormesh can be used. Drawing the mesh with h > 5 will show all the True values with the highest color (red) and the False values with the lowest color (blue).
from matplotlib import pyplot as plt
import numpy as np
x = np.random.uniform(0, 2, 500)
y = np.random.uniform(0, 1, x.shape)
xedges = np.array([0.1, 0.2, 0.5, 0.55, 0.6, 0.8, 1.0, 1.3, 1.5, 1.9])
yedges = np.array([0.1, 0.2, 0.4, 0.5, 0.55, 0.6, 0.8, 0.9])
hist, _xedges, _yedges = np.histogram2d(x, y, bins=[xedges, yedges])
h = hist.T # np.histogram2d transposes x and y, therefore, transpose the resulting array
thres = 5
desired = h > thres
plt.pcolormesh(xedges, yedges, desired, cmap='coolwarm', ec='white', lw=2)
mask = np.zeros_like(x, dtype=np.bool) # start with mask all False
for i in range(len(xedges) - 1):
for j in range(len(yedges) - 1):
if desired[j, i]:
# print(f'x from {xedges[i]} to {xedges[i + 1]} y from {yedges[j]} to {yedges[j + 1]}')
mask = np.logical_or(mask, (x >= xedges[i]) & (x < xedges[i + 1]) & (y >= yedges[j]) & (y < yedges[j + 1]))
# plt.scatter(np.random.uniform(xedges[i], xedges[i+1], 100), np.random.uniform(yedges[j], yedges[j+1], 100),
# marker='o', color='g', alpha=0.3)
plt.scatter(x, y, marker='o', color='gold', label='initial points')
plt.scatter(x[mask], y[mask], marker='.', color='green', label='filtered points')
plt.legend(bbox_to_anchor=(1.02, 1), loc='upper left')
plt.tight_layout()
plt.show()
Note that in the given example the edges don't cover the complete range of points. The points outside the given edges will not be taken into account. To include these points, just extend the edges.
I am trying to plot a performance diagram and I would like to have certain sections of it be different colors based on the CSI value.
I am able to plot a performance diagram with an all white background so far.
line_label=[]
line_str=[]
line_label2=[]
line_str2=[]
ax = fig.add_axes([0.1, 0.1, 0.8, 0.8])
x = np.arange(0,1.01,0.01)
y = np.arange(0,1.01,0.01)
xi,yi = np.meshgrid(x, y)
#Calculate bias and CSI; set contour levels
bias = yi/xi
blevs = [0.1, 0.25, 0.5, 0.75, 1, 1.25, 2.5, 5, 10]
csi = 1/( (1/xi) + (1/yi) - 1 )
csilevs = np.arange(0.1,1,0.1)
#Axis labels, tickmarks
ax.set_xlabel('Success Ratio (1 - False Alarm Ratio)',fontsize=16,fontweight='bold',labelpad=30)
ax.set_ylabel('Probability of Detection',fontsize=16,fontweight='bold')
ax.set_xticks(np.arange(0,1.1,0.1))
ax.set_yticks(np.arange(0,1.1,0.1))
plt.setp(ax.get_xticklabels(),fontsize=13)
plt.setp(ax.get_yticklabels(),fontsize=13)
#Second y-axis for bias values < 1
ax2 = ax.twinx()
ax2.set_yticks(blevs[0:5])
plt.setp(ax2.get_yticklabels(),fontsize=13)
#Axis labels for bias values > 1
ax.text(0.1,1.015,'10',fontsize=13,va='center',ha='center')
ax.text(0.2,1.015,'5',fontsize=13,va='center',ha='center')
ax.text(0.4,1.015,'2.5',fontsize=13,va='center',ha='center')
ax.text(0.8,1.015,'1.25',fontsize=13,va='center',ha='center')
#Plot bias and CSI lines at specified contour intervals
cbias = ax.contour(x,y,bias,blevs,colors='black',linewidths=1,linestyles='--')
ccsi = ax.contour(x,y,csi,csilevs,colors='gray',linewidths=1,linestyles='-')
plt.clabel(ccsi,csilevs,inline=True,fmt='%.1f',fontsize=14,fontweight='bold')
This is current result
https://imgur.com/a/Uojy2Ja.
I want different colors between the gray, curved lines that go from 0, 0.1, 0.2, 0.3, etc.
Add
ax.contourf(x,y,csi, np.r_[0, csilevs, 1],linestyles='-')
before cbias = ...
The np._r adds levels at 0 and 1 so they are filled too
I am trying to make four sets of plots in a 2x2 or 1x4 grid. Each set then has three more panels, say, a scatter plot with histograms of the x- and y-axes on the sides.
Instead of setting the axes for all 12 plots, I'd like to divide my canvas into 4 parts, and then divide each one individually. For example,
def plot_subset():
# these coords are normalized to this subset of plots
pos_axScatter=[0.10, 0.10, 0.65, 0.65]
pos_axHistx = [0.10, 0.75, 0.65, 0.20]
pos_axHisty = [0.75, 0.10, 0.20, 0.20]
axScatter = plt.axes(pos_axScatter)
axHistx = plt.axes(pos_axHistx)
axHisty = plt.axes(pos_axHisty)
def main():
# need to divide the canvas to a 2x2 grid
plot_subset(1)
plot_subset(2)
plot_subset(3)
plot_subset(4)
plt.show()
I have tried GridSpec and subplots but cannot find a way to make plot_subset() work in the normalized space. Any help would be much appreciated!
You can use BboxTransformTo() to do this:
from matplotlib import transforms
fig = plt.figure(figsize=(16, 4))
fig.subplots_adjust(0.05, 0.05, 0.95, 0.95, 0.04, 0.04)
gs1 = plt.GridSpec(1, 4)
gs2 = plt.GridSpec(4, 4)
for i in range(4):
bbox = gs1[0, i].get_position(fig)
t = transforms.BboxTransformTo(bbox)
fig.add_axes(t.transform_bbox(gs2[:3, :3].get_position(fig)))
fig.add_axes(t.transform_bbox(gs2[3, :3].get_position(fig)))
fig.add_axes(t.transform_bbox(gs2[:3, 3].get_position(fig)))
the output: