Python polar bar chart - Remove degrees & color one ring - python-3.x

I have been trying to create a polar bar chart in python for quite some time. After some research I managed to get the results that I wanted. Well, almost. There're still a couple thing that I don't know how to do.
I include my code:
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.ticker import FixedLocator
from operator import add
#DATA MANIPULATION
dataset = pd.read_csv('Controls.csv', delimiter=";")
dataset.astype({'Rating':'float'})
#print(dataset.dtypes)
categories = dataset['Category'].drop_duplicates()
controls = dataset['Control'].drop_duplicates()
categ_avg = []
control_average = []
#Average for controls
for category in categories:
avg = 0
for index, item in dataset.iterrows():
if item['Category'] == category:
avg += item['Rating']
categ_avg.append(avg)
avg = 0
for control in controls:
avg = 0
for index, item in dataset.iterrows():
if item['Control'] == control:
avg += item['Rating']
control_average.append(avg)
avg = 0
average = [total / 5 for total in categ_avg]
avgdf = pd.DataFrame({
'Category' : categories,
#'Controls' : controls,
'Average' : average
})
#PLOTTING
#Compute pie slices which is the number of unique controls
N = len(controls)
#theta = np.linspace(0, 2 * np.pi, N, endpoint=False)
theta = [0]
for cat in categories:
if cat == 'CAT-A':
theta.append( theta[-1] + (2 * np.pi/N * 2) )
else:
theta.append( theta[-1] + (2*np.pi / N) )
print(theta)
#Compute the filling axis
mid_theta = []
for cat in categories:
if cat == 'CAT-A':
mid_theta.append( 2 * np.pi/N )
else:
mid_theta.append( 2 * np.pi / N / 2 )
mid_theta = list(map(add,theta, mid_theta))
print(mid_theta)
radii = avgdf['Average']
#width = theta[1] - theta[0]
width = []
for i in range(0, len(avgdf['Average'])):
width.append(theta[i+1] - theta[i])
fig = plt.figure()
fig.patch.set_facecolor('white')
fig.patch.set_alpha(0.5)
#Draw X labels
ax = fig.add_subplot(111, projection='polar')
ax.set_xticks(theta)
# Draw ylabels
ax.set_rlabel_position(0)
ax.set_yticks([1, 2, 3, 4, 5])
ax.set_yticklabels(["1", "2", "3", "4", "5"], color="black", size=8)
ax.set_ylim(0, 5)
#colors = plt.cm.hsv(theta/2/np.pi)
bars = ax.bar(mid_theta, radii, width=width, bottom=0.0)
#Labels
for bar, angle, label in zip(bars, mid_theta, avgdf["Category"]):
# Labels are rotated. Rotation must be specified in degrees :(
rotation = np.rad2deg(angle)
# Flip some labels upside down
alignment = ""
if angle >= np.pi/2 and angle < 3*np.pi/2:
alignment = "right"
rotation = rotation + 180
else:
alignment = "left"
# Finally add the labels
ax.text(
x=angle,
y=5.5,
s=label,
ha=alignment,
va='center')
#Use custom colors and opacity
for r, bar in zip(avgdf['Average'], bars):
bar.set_facecolor(plt.cm.viridis(r/5.))
bar.set_alpha(0.5)
plt.show()
When I execute it I obtain the following graph: Resulting graph
What I'm trying to achieve is:
I would like to color the ring number 4 in green.
I would like to remove the degrees from the outer ring. I only want to see my categories not the 0, 144ยบ...
I really appreciate the help.
Thanks you.

Create a list of colours with as many colours as you have polar bars.
c = ['blue', 'blue', 'blue', 'green', 'blue', 'blue']
bars = ax.bar(
x=angles,
height=heights,
width=width,
color=c,
linewidth=2,
edgecolor="white")

Related

Annotate Percentage of Group within a Seaborn CountPlot

The below code gets the percentage of all collisions. However, I want to get the percentage within a group. E.G. Mid-Block (not related to intersection) has 2 labels, a 1(red) or a 2(green/blue). Currently, the percentages next to those bars are percentages of the whole (bar count / all collisions), but I need to display the percentage within just one y-axis label. E.G. for Mid-block (not related to intersection), bar count / all collisions within mid-block (not related to intersection). I do not know how to do this, so if someone could point me in the right direction or give me some code that I could study to understand, I'd be very grateful.
Thank you so much for your time.
plt.style.use('ggplot')
plt.figure(figsize = (20, 15))
ax = sb.countplot(y = "JUNCTIONTYPE", hue = "SEVERITYCODE", data = dfm)
plt.title('Number of Persons vs. Number of Collisions by Severity', fontsize = 30)
plt.xlabel('Number of Collisions', fontsize = 24)
plt.ylabel('Number of Persons', fontsize = 24)
plt.tick_params(labelsize=18);
plt.legend(fontsize = 18, title = "Severity", loc = 'lower right')
plt.text(5, 6, "Figure 8: Number of persons plotted against the number of collisions grouped by severity", fontsize = 16)
# labels = [item.get_text() for item in ax.get_yticklabels()]
# labels[0] = 'No'
# labels[1] = 'Yes'
# ax.set_yticklabels(labels)
for p in ax.patches:
width = p.get_width()
height = p.get_height()
x, y = p.get_xy()
ax.annotate(int(width),
((x + width), y),
xytext = (30, -25),
fontsize = 18,
color = '#000000',
textcoords = 'offset points',
ha = 'right',
va = 'center')
for p in ax.patches:
width = p.get_width()
height = p.get_height()
x, y = p.get_xy()
totals = []
for i in ax.patches:
totals.append(i.get_width())
total = sum(totals)
ax.text(width + 0.3, y + 0.38,
str(
round((width/total) * 100, 2))
+ '%',
fontsize=18)
You could pre-calculate the per-group percentage points and use the order in which seaborn / matplotlib draws the bars to reference them.
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd
titanic = sns.load_dataset('titanic')
# prepare the dataset
df = (titanic
.groupby(["embark_town", "survived"])
.size()
.reset_index()
.replace({"survived": {0:"no", 1:"yes"}})
.rename(columns={0:"count"}))
# calculate survival % per town of embarkation
df["percent"] = (df
.groupby("embark_town")
.apply(lambda x: x["count"] / x["count"].sum()).values)
# sort the dataframe to match the drawing order
df.sort_values(by=["survived", "embark_town"], inplace=True)
# visualisation
plt.style.use('ggplot')
fig = sns.catplot(
x="count", y="embark_town", hue="survived",
kind="bar", data=df, height=4, aspect=2)
for i, bar in enumerate(fig.ax.patches):
height = bar.get_height()
fig.ax.annotate(
# reference the pre-calculated row in the dataframe
f"{df.iloc[i, 3] :.0%}",
xycoords="data",
xytext=(20, -15),
textcoords="offset points",
xy=(bar.get_width(), bar.get_y()),
ha='center', va='center')
# make space for annonations
plt.margins(x=0.2)
plt.show()

How to combine multiple spectrogram subplots to produce single plot?

I am visualizing four classes of the spectrogram. For a single class, My spectrogram code looks like this
Considering this as one image.
And the code to produce this, is
def spec(filename):
time_period = 0.5 # FFT time period (in seconds). Can comfortably process time frames from 0.05 seconds - 10 seconds
# ==============================================
fs_rate, signal_original = wavfile.read(filename)
total_time = int(np.floor(len(signal_original)/fs_rate))
sample_range = np.arange(0,total_time,time_period)
total_samples = len(sample_range)
print ("Frequency sampling", fs_rate)
print ("total time: ", total_time)
print ("sample time period: ", time_period)
print ("total samples: ", total_samples)
output_array = []
for i in sample_range:
# print ("Processing: %d / %d (%d%%)" % (i/time_period + 1, total_samples, (i/time_period + 1)*100/total_samples))
sample_start = int(i*fs_rate)
sample_end = int((i+time_period)*fs_rate)
signal = signal_original[sample_start:sample_end]
l_audio = len(signal.shape)
#print ("Channels", l_audio)
if l_audio == 2:
signal = signal.sum(axis=1) / 2
N = signal.shape[0]
#print ("Complete Samplings N", N)
secs = N / float(fs_rate)
# print ("secs", secs)
Ts = 1.0/fs_rate # sampling interval in time
#print ("Timestep between samples Ts", Ts)
t = scipy.arange(0, secs, Ts) # time vector as scipy arange field / numpy.ndarray
FFT = abs(scipy.fft(signal))
FFT_side = FFT[range(int(N/2))] # one side FFT range
freqs = scipy.fftpack.fftfreq(signal.size, t[1]-t[0])
fft_freqs = np.array(freqs)
freqs_side = freqs[range(int(N/2))] # one side frequency range
fft_freqs_side = np.array(freqs_side)
# Reduce to 0-5000 Hz
bucket_size = 5
buckets = 16
FFT_side = FFT_side[0:bucket_size*buckets]
fft_freqs_side = fft_freqs_side[0:bucket_size*buckets]
# Combine frequencies into buckets
FFT_side = np.array([int(sum(FFT_side[current: current+bucket_size])) for current in range(0, len(FFT_side), bucket_size)])
fft_freqs_side = np.array([int(sum(fft_freqs_side[current: current+bucket_size])) for current in range(0, len(fft_freqs_side), bucket_size)])
# FFT_side: Normalize (0-1)
max_value = max(FFT_side)
if (max_value != 0):
FFT_side_norm = FFT_side / max_value
# Append to output array
output_array.append(FFT_side_norm)
# ============================================
# Plotting
plt.figure(figsize=(4,7))
plt.subplot(411)
plt.subplots_adjust(hspace = 0.5)
plt.plot(t, signal, "g") # plotting the signal
plt.xlabel('Time')
plt.ylabel('Amplitude')
plt.subplot(412)
diff = np.diff(fft_freqs_side)
widths = np.hstack([diff, diff[-1]])
plt.bar(fft_freqs_side, abs(FFT_side_norm), width=widths) # plotting the positive fft spectrum
plt.xticks(fft_freqs_side, fft_freqs_side, rotation='vertical')
plt.xlabel('Frequency (Hz)')
plt.ylabel('Count single-sided')
FFT_side_norm_line = FFT_side_norm.copy()
FFT_side_norm_line.resize( (1,buckets) )
plt.subplot(413)
plt.imshow(FFT_side_norm_line)
plt.xlabel('Image Representation 1D')
plt.show()
print("\n\n\n\n\n\n")
How can I combine four images plot like this, and make a single output image. Something like this
I'd suggest using fig.subfigures and plt.subplot_mosaic.
The plot above was obtained using this simple script:
import matplotlib.pyplot as plt
fig = plt.figure(figsize = (8, 10), layout='constrained')
# next two lines make the trick
sfigs = fig.subfigures(2,2)
mosaics = [f.subplot_mosaic('t;t;t;f;f;f;i;.') for f in sfigs.flat]
# next, "how to" reference the subplots in subfigures
mosaics[0]['t'].plot(range(5), color='b')
mosaics[1]['t'].plot(range(5), color='k')
mosaics[2]['t'].plot(range(5), color='r')
mosaics[3]['t'].plot(range(5), color='g')
mosaics[0]['f'].plot(range(3), color='b')
mosaics[1]['f'].plot(range(3), color='k')
mosaics[2]['f'].plot(range(3), color='r')
mosaics[3]['f'].plot(range(3), color='g')
mosaics[0]['i'].imshow([range(10)]*2)
plt.show()
You can do it this way:
fig, axs = plt.subplots(2, 2)
axs[0, 0].plot(x, y)
axs[0, 0].set_title('Axis [0, 0]')
axs[0, 1].plot(x, y, 'tab:orange')
axs[0, 1].set_title('Axis [0, 1]')
axs[1, 0].plot(x, -y, 'tab:green')
axs[1, 0].set_title('Axis [1, 0]')
axs[1, 1].plot(x, -y, 'tab:red')
axs[1, 1].set_title('Axis [1, 1]')
for ax in axs.flat:
ax.set(xlabel='x-label', ylabel='y-label')
# Hide x labels and tick labels for top plots and y ticks for right plots.
for ax in axs.flat:
ax.label_outer()
The result will be like this:
Taken from https://matplotlib.org/stable/gallery/subplots_axes_and_figures/subplots_demo.html

How do I curve text in a polar plot?

Hello Matplotlib Experts,
How do I curve text in a matplotlib polar plot? In my attempt below, my code rotates each char individually, but doing so would remove the natural spacing of each font. Can somebody describe a solution for passing ax.text in matplotlib?
import numpy as np
import matplotlib as mpl
import matplotlib.pylab as plt
def curveText(text, height, minTheta, maxTheta, ax):
interval = np.arange(minTheta, maxTheta, .022)
if( maxTheta <= np.pi):
progression = interval[::-1]
rotation = interval[::-1] - np.arctan(np.tan(np.pi/2))
else:
progression = interval
rotation = interval - np.arctan(np.tan(np.pi/2)) - np.pi
## Render each letter individually
for i, rot, t in zip(progression, rotation, text):
ax.text(i, height, t, fontsize=11,rotation=np.degrees(rot), ha='center', va='center')
def buildCircularHeatMap( data=None, label=None, cmaps=None, categorymap=None, vmin=0, vmax=None ):
(xDim, yDim) = data.shape
if cmaps == None:
cmaps = [mpl.cm.get_cmap()] * yDim
BOTTOM = xDim / 100 * 120
#FONTSIZE = 1 if xDim/100*8 < 1 else xDim/100*8
theta = np.linspace(0.0, 2 * np.pi - 5 * np.pi/180, xDim, endpoint=False)
width = (2*np.pi - 5 * np.pi/180)/xDim
ax = plt.subplot(111, polar=True)
ax.grid(False)
ax.set_yticklabels([])
ax.set_xticklabels([])
categorysum = np.zeros(len(categorymap))
for x in label:
categorysum[int(float( x )) - 1] += 1
categorysum = categorysum/np.sum(categorysum)*2*np.pi
## Build Face Color Values
for i in range(yDim):
cmap_scalar = mpl.cm.ScalarMappable(cmap=cmaps[i])
cmap_scalar.set_clim(vmin=vmin, vmax=vmax)
facecolor = cmap_scalar.to_rgba(data[:,i])
_ = ax.text(2 * np.pi - 5 * np.pi/180, BOTTOM+i*10, str(i), fontsize=11, rotation=np.degrees(270))
bars = ax.bar(theta, np.ones(xDim)*10, width=width, bottom=BOTTOM+i*10)
for j, b in enumerate(bars):
b.set_facecolor( facecolor[j] )
## Build CCS Label
for th, l, bar in zip(theta, label, bars):
rot = np.arctan(np.tan(th))
ax.text(th,BOTTOM+yDim*10+bar.get_height()+5, l, rotation_mode='anchor',
rotation=np.degrees(rot), fontsize=11, ha='center', va='center')
## Build Category Label
categoryColor = np.asarray([int(float(c)) for c in label])
bars = ax.bar(theta, np.ones(xDim)*20, width=width, bottom=BOTTOM+yDim*10 + 30)
for j, b in enumerate(bars):
b.set_facecolor(np.asarray([0.0,0.0,0.0]))
if categoryColor[j] % 2 == 0:
b.set_alpha(0.07)
else:
b.set_alpha(0.0)
for i in range(len(categorymap)):
c = i + 1
t = theta[categoryColor==c]
mi = np.min(t)
ma = np.max(t)
rad = (ma-mi)/2+mi
curveText(categorymap[c], BOTTOM+yDim*10+40, mi, ma, ax)
if __name__ == "__main__":
categorymap={
1: "Infectious & parasitic dieases",
2: "Neoplasms",
3: "Endocrine; nutritional; and metabolic diseases and immunity disorders",
4: "Diseases of the blood and blood-forming organs",
5: "Mental Illness",
6: "Nervous system disorders",
7: "Circulatory disorders",
8: "Respiratory disorders",
9: "Digestive disorders",
10: "Genitourinary disorders",
11: "Complications of pregnancy; childbirth; and the puerperium",
12: "Skin and subcutaneous tissue disorder",
13: "Musculoskeletal system and connective tissue disorder",
14: "Congenital anomalies",
15: "Certain conditions originating in the perinatal period",
16: "Injury and poisoning",
17: "Ill-defined status",
18: "Unclassified"
}
data = np.random.standard_normal((180, 3))
colormaps = [mpl.cm.get_cmap("Reds"), mpl.cm.get_cmap("Oranges"), mpl.cm.get_cmap("Greens"), mpl.cm.get_cmap("Blues")]
labels = sorted([ '{:.2f}'.format(np.abs(i)) for i in np.random.random_sample(180) * 18 + 1 ])
fig = plt.figure(figsize=(11,11))
buildCircularHeatMap(data=data, label=labels, cmaps=colormaps, categorymap=categorymap)
plt.show()
In the link below, Thomas's answer seems only applicable for cartesian coordinates and my current attempt should be similar to Daan.
Curved text rendering in matplotlib
As #Makdous suggested above, Curved text rendering in matplotlib is a nice implementation of the problem. I read through the code, and you're right, it is in cartesian coordinates, but I think you could just modify it a bit and get it working using these formulas:
You can also use this one line function I wrote:
from typing import Tuple
from math import sqrt, degrees, atan2
def cartesian_to_polar(x: float, y: float)-> Tuple[float, float]:
return sqrt(x**2 + y ** 2), degrees(atan2(y,x))
Or, if you have polar coordinates and want to make it work with the script linked in the other response, you can use this:
from math import cos, sin, radians
def polar_to_cartesian(r: float, theta: float)-> Tuple[float, float]:
return r * cos(radians(theta)), r * sin(radians(theta))
Depending on how you implemented it, you could feed it in the coordinates you have, then convert it appropriately to arrive at cartesian coordinates and run the linked script, then convert the points back to polar coordinates and plot it.

Scaling a PDF to show 100% at peak

I'm displaying a histogram of my data, with an overlaid PDF. My plots all look something like this:
and I'm trying to scale the red curve to show 100% at the peak.
My following toy code is identical to what I'm actually using, apart from the lines in between the two %:
%
import pandas as pd
import matplotlib.pyplot as plt
import scipy.stats as stats
import numpy as np
my_randoms = np.random.normal(0.5, 1, 50000)
dictOne = {"delta z":my_randoms}
df = pd.DataFrame(dictOne)
df = df[df['delta z'] > -999]
%
fig, ax = plt.subplots()
h, edges, _ = ax.hist(df['delta z'], alpha = 1, density = False, bins = 100)
param = stats.norm.fit(df['delta z'].dropna()) # Fit a normal distribution to the data
pdf_fitted = stats.norm.pdf(df['delta z'], *param)
x = np.linspace(*df['delta z'].agg([min, max]), 100) # x-values
binwidth = np.diff(edges).mean()
ax.plot(x, stats.norm.pdf(x, *param)*h.sum()*binwidth, color = 'r')
# Decorations
graph_title = 'U-B'
plt.grid(which = 'both')
plt.title(r'$\Delta z$ distribution for %s'%graph_title, fontsize = 25)
plt.xlabel(r'$\Delta z = z_{spec} - z_{photo}$', fontsize = 25)
plt.ylabel('Number', fontsize = 25)
plt.xticks(fontsize = 25)
plt.yticks(fontsize = 25)
xmin, xmax = min(df['delta z']), max(df['delta z'])
plt.xlim(xmin, xmax)
plt.annotate(
r'''$\mu_{\Delta z}$ = %.3f
$\sigma_{\Delta z}$ = %.3f'''%(param[0], param[1]),
fontsize = 25, color = 'r', xy=(0.85, 0.85), xycoords='axes fraction')
How would I define another axes object from 0 to 100 on the right-hand side and map the PDF to that?
Or is there a better way to do it?
This is kind of a follow-up to my previous question.
You can use density=True in plotting the histogram.
You use .twinx():
fig = plt.figure(figsize=(10, 8), dpi=72.0)
n_rows = 2
n_cols = 2
ax1 = fig.add_subplot(n_rows, n_cols, 1)
ax2 = fig.add_subplot(n_rows, n_cols, 2)
ax3 = ax1.twinx()
https://matplotlib.org/gallery/api/two_scales.html

Matplotlib Line Rotation or Animation

I have created a polar plot and would like to mimic a doppler. This includes a 360 degree sweep around the circle (polar plot). Once the sweep gets to 360 degrees, it needs to go back to zero and continue the sweep.
How do I animate or rotate this line to constantly sweep around this circle? I only want one line to constantly sweep around this plot.
I have looked at several different examples, however, none that create this rotation.
import numpy as np
import math
import matplotlib.pyplot as plt
import pylab
import time
r = 90 * (math.pi/180)
t = 50000
az = 90
el = 5
fig = pylab.figure(figsize = [5.0, 5.0])
ax = fig.gca(projection = 'polar')
fig.canvas.set_window_title('Doppler')
ax.plot(r, t, color ='b', marker = 'o', markersize = '3')
ax.set_theta_zero_location('N')
ax.set_theta_direction(-1)
currTime = time.time()
prevTime = currTime - 1
deltaTime = currTime - prevTime
outer_border_width = 1
screen_width = 500
screen_height = 500
midpoint = [int(screen_width/2), int(screen_height/2)]
radius = (midpoint[0])
sweep_length = radius - outer_border_width
angle = 50
sweep_interval = 10
sweep_speed = sweep_interval
x = sweep_length * math.sin(angle) + int(screen_width/2)
y = sweep_length * math.cos(angle) + int(screen_height/2)
az = az + ((360.0 / sweep_interval ) * deltaTime)
line1 = (midpoint, [50000, 50000])
#line2 = (midpoint, [20000, 20000])
ax.plot(line1, color = 'b', linewidth = 1)
#Increase the angle by 0.05 radians
angle = angle - sweep_speed
#Reset the angle to 0
if angle > 2 * math.pi:
angle = angle - 2 * math.pi
#ax.plot(line2, color = 'r', linewidth = 1)
#ax.lines.pop(0)
plt.show()
Below is a picture of what it currently looks like for reference:
Many thanks!
I do not understand much of your code, but in order to produce an animation you can use matplotlib.animation.FuncAnimation. Here, you'd give an array of angles to an updating function, which sets the data of the line for each frame.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation
r = 90 * (np.pi/180)
t = 50000
fig = plt.figure()
ax = fig.gca(projection = 'polar')
fig.canvas.set_window_title('Doppler')
ax.plot(r, t, color ='b', marker = 'o', markersize = '3')
ax.set_theta_zero_location('N')
ax.set_theta_direction(-1)
ax.set_ylim(0,1.02*t)
line1, = ax.plot([0, 0],[0,t], color = 'b', linewidth = 1)
def update(angle):
line1.set_data([angle, angle],[0,t])
return line1,
frames = np.linspace(0,2*np.pi,120)
fig.canvas.draw()
ani = matplotlib.animation.FuncAnimation(fig, update, frames=frames, blit=True, interval=10)
plt.show()

Resources