Annotate bars with values on Pandas (on Seaborn factorplot bar plot) - python-3.x

I wrote some code to try and solve this question:
https://stackoverflow.com/questions/39477748/how-to-annotate-bars-with-values-on-pandas-on-seaborn-factorplot-bar-plot
I used part of the code that can be found here:
matplotlib advanced bar plot
Why is the graph so small? The code just tells to grab the accuracies from Pandas dataframe .
The code:
sns.set(style="white")
g = sns.factorplot(x="Stages", y="Accuracy", hue="Dataset", data=df, saturation = 5, size=4, aspect=2, kind="bar",
palette= myPalette, legend=False)
ax=g.ax
def annotateBars(row, ax=ax):
if row['Accuracy'] < 20:
color = 'white'
vertalign = 'bottom'
vertpad = 2
else:
color = 'black'
vertalign = 'top'
vertpad = -2
ax.text(row.name, row['Accuracy'] + vertpad, "{:.1f}%".format(row['Accuracy']),
zorder=10, rotation=90, color=color,
horizontalalignment='center',
verticalalignment=vertalign,
fontsize=12, weight='heavy')
junk = df.apply(annotateBars, ax=ax, axis=1)
This is code to annotate each bar, but ...with Pandas and Matplotlib. The only problem is that I do not know how to change colors and group the "x axis" :(
df = df.set_index('Stages')
ax = df.plot.bar(title="Accuracy")
ax.set_ylim(0, 120)
for p in ax.patches:
ax.annotate("%.2f" % p.get_height(), (p.get_x() + p.get_width() / 2., p.get_height()),
ha='center', va='center', rotation=90, xytext=(0, 20), textcoords='offset points') #vertical bars

#Seaborn --factorplot
colors = ["windows blue", "orange red", "grey", "amber"]
myPalette = sns.xkcd_palette(colors) #envío "colors" a la función xkcd_palette
sns.set(style="white") #fondo blanco
g = sns.factorplot(x="Stages", y="Accuracy", hue="Dataset", data=df, saturation=5, size=4, aspect=3, kind="bar",
palette= myPalette, legend=False) #se suprime la leyenda
g.set(ylim=(0, 140))
g.despine(right=False)
g.set_xlabels("")
g.set_ylabels("")
g.set_yticklabels("")
#Matplotlib --legend creation
myLegend=plt.legend(bbox_to_anchor=(0., 1.2, 1., .102), prop ={'size':10}, loc=10, ncol=4, #left, bottom, width, height
title=r'TOTAL ACCURACY AND PER STAGE-RANDOM FOREST')
myLegend.get_title().set_fontsize('24')
#Matplotlib --anotación de barras
ax=g.ax #annotate axis = seaborn axis
def annotateBars(row, ax=ax):
for p in ax.patches:
ax.annotate("%.2f" % p.get_height(), (p.get_x() + p.get_width() / 2., p.get_height()),
ha='center', va='center', fontsize=11, color='gray', rotation=90, xytext=(0, 20),
textcoords='offset points') verticales
plot = df.apply(annotateBars, ax=ax, axis=1)

This can now be plotted much more concisely
Axes.bar_label automatically labels bars [since matplotlib 3.4]
for container in ax.containers:
ax.bar_label(container)
Axes.legend includes fontsize and title_fontsize params [since matplotlib 3.0]
ax.legend(fontsize=10, title='ACCURACY', title_fontsize=24)
Also note that seaborn.factorplot has been renamed to seaborn.catplot [since seaborn 0.9]
Updated seaborn.catplot
colors = ['xkcd:windows blue', 'xkcd:orange red', 'xkcd:grey', 'xkcd:amber']
g = sns.catplot(x='Stages', y='Accuracy', hue='Dataset', data=df,
kind='bar', height=4, aspect=3, palette=colors, legend=False)
# auto-label bars
for container in g.ax.containers:
g.ax.bar_label(container, fmt='%.2f', padding=2, rotation=90)
# add legend with custom font sizes
ax.legend(bbox_to_anchor=(0, 1.2, 1, 0.102), loc=10, ncol=4, fontsize=10,
title='TOTAL ACCURACY AND PER STAGE-RANDOM FOREST', title_fontsize=24)
# redecorate
g.despine(right=False)
g.set_xlabels('')
g.set_ylabels('')
g.ax.set_yticklabels([])
Updated DataFrame.plot.bar
ax = df.pivot('Stages', 'Dataset', 'Accuracy').plot.bar(legend=False)
# auto-label bars
for container in ax.containers:
ax.bar_label(container, fmt='%.2f', padding=3, rotation=90, size='small')
# add legend with custom font sizes
ax.legend(bbox_to_anchor=(0, 1.1, 1, 0.102), loc=10, ncol=4, fontsize='small',
title='TOTAL ACCURACY AND PER STAGE-RANDOM FOREST', title_fontsize='xx-large')
# redecorate
sns.despine(right=False)
ax.set_yticklabels([])
plt.xticks(rotation=0)

Related

Do not plot gridlines/contourlines/coastlines outside of the canvas with cartopy

I'm plotting a map panel with cartopy in eps format. The resulting plot looks fine but has very broad margins when I insert it into my latex document. When checking the plot with adobe illustrator, it seems like the cartopy plots all the gridlines/contourlines/coastlines, even those outside of the canvas, which are hidden but do take up some spaces in the plot.
I tried to use constrained_layout and tight_layout, but they are incompatible with subplots_adjust which I use for adding the shared colorbar.
The code I use to plot is as follows:
proj2 = ccrs.LambertConformal(central_longitude=0, central_latitude=50)
proj_lonlat = ccrs.PlateCarree()
fig = plt.figure(figsize=(12, 9), constrained_layout=True)
# define a function to plot
def plot_era5_500z_MSLp(f500, fsurf, time, label, ax):
# read data
for i in np.arange(len(f500.time.values)):
if pd.to_datetime(f500.time.values[i]) == pd.to_datetime(time):
print('processing time: ' + time)
lons = f500.longitude.values # 1-d array
lats = f500.latitude.values # 1-d array
gph500 = f500.z.values[i,:,:]/98 # geopotential (m2 s-2) -> geopotential height (dagpm) [time = 72, lat = 241, lon = 561]
pmsl = fsurf.msl.values[i,:,:]/100 # mean sea level pressure Pa -> hPa
# create base map
ax.set_extent([-35, 30, 25, 70]) # x0, x1, y0, y1
gl = ax.gridlines(crs=proj_lonlat, draw_labels=True, xlocs=[-60,-40,-20,0,20,40,60], ylocs=[20,30,40,50,60],
x_inline=False, y_inline=False, color='k', alpha=0.5, linestyle='dotted')
gl.top_labels=False
gl.right_labels=False
gl.xlabel_style = {'size': 14, 'color': 'k'}
gl.ylabel_style = {'size': 14, 'color': 'k'}
gl.rotate_labels = False
ax.add_feature(cfeature.COASTLINE.with_scale('50m'), lw=0.4, alpha=0.9) # add coastline feature
# plot 500hPa geopotential height (zc: z contour)
z_levels = np.arange(500, 580+10, 8)
zc = ax.contour(lons, lats, gph500, transform=proj_lonlat,
levels=z_levels, extent='both', colors='mediumblue', linewidths=0.5)
ax.clabel(zc, inline=True, fontsize=10, fmt='%.0f')
# plot MSL pressure (mslps: MSL p shading; mslpc: MSL p contour)
levels = np.arange(960, 1057, 4)
mslps = ax.contourf(lons, lats, pmsl, levels=levels, cmap='Spectral_r', transform=proj_lonlat)
mslpc = ax.contour(lons, lats, pmsl, levels=levels, colors='k', linewidths=0.5, alpha=0.6, transform=proj_lonlat)
ax.set_title(label + ' ' + time, loc= 'left', pad=0.5, fontsize=14)
return mslps
# fig (a)
ax1 = plt.subplot(2, 2, 1, projection=proj2)
plot_era5_500z_MSLp(f500_2016nov, fsurf_2016nov, '2016-11-20 12:00', '(a)', ax1)
# fig (b)
ax2 = plt.subplot(2, 2, 2, projection=proj2)
plot_era5_500z_MSLp(f500_2016nov, fsurf_2016nov, '2016-11-24 00:00', '(b)', ax2)
# fig (c)
ax3 = plt.subplot(2, 2, 3, projection=proj2)
plot_era5_500z_MSLp(f500_2017feb, fsurf_2017feb, '2017-02-27 18:00', '(c)', ax3)
# fig (4)
ax4 = plt.subplot(2, 2, 4, projection=proj2)
mslps = plot_era5_500z_MSLp(f500_2017mar, fsurf_2017mar, '2017-03-04 06:00', '(d)', ax4) # only return mslps here for plotting the sharred colorbar
fig.subplots_adjust(right=0.8, wspace=0.2, hspace=0.000001)
cbar_ax = fig.add_axes([0.82, 0.2, 0.02, 0.55]) # left border, bottom border, width, height
cbar = fig.colorbar(mslps, cax=cbar_ax)
cbar.set_label(label='Mean sea level pressure (hPa)', size=16)
cbar.ax.tick_params(labelsize=14)
The resulting eps plot looks good, but in adobe illustrator, one can see the excess lines outside of the canvas:
Is there any way I can limit the plotting range of the data, or disable the lines outside of the canvas?

sns.histplot legend colors not matching the output

I am creating a combo boxplot\histplot.
Everything runs and I get the output I am expecting except for one thing:
The line colors in the legend do not match the output.
Code:
def boxhist(dfx, x):
variable = dfx[x].values
np.array(variable).mean()
np.median(variable)
f, (ax_box, ax_hist) = plt.subplots(2, sharex=True, gridspec_kw={"height_ratios": (0.5, 2)})
mean = np.array(variable).mean()
median = np.median(variable)
sns.boxplot(variable, ax=ax_box)
ax_box.axvline(mean, color='orange', linestyle='--')
ax_box.axvline(median, color='black', linestyle='-')
sns.histplot(data=variable, ax=ax_hist, kde=True, binwidth=2, facecolor='green').lines[0].set_color('red')
ax_hist.axvline(mean, color='orange', linestyle='--')
ax_hist.axvline(median, color='black', linestyle='-')
plt.title(x, fontsize=10, loc='right')
plt.legend({'Mean': mean, 'Median': median})
ax_box.set(xlabel='')
plt.tight_layout()
plt.show()
Output:
The mean should be orange.
The median should be black.
Why is the legend showing the mean as red and the median as orange?
I want the legend colors to match the plot output. mean\orange, median\black.
You need to add a label in ax_hist.axvline(mean, ...., label='Mean') (and similar for the median). Then matplotlib should automatically add them to the legend (when called without parameters).
Here is some example code:
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np
def boxhist(dfx, x):
variable = dfx[x].values
variable.mean()
np.median(variable)
f, (ax_box, ax_hist) = plt.subplots(2, sharex=True, gridspec_kw={"height_ratios": (0.5, 2)})
mean = variable.mean()
median = np.median(variable)
sns.boxplot(x=variable, ax=ax_box)
ax_box.axvline(mean, color='orange', linestyle='--')
ax_box.axvline(median, color='black', linestyle='-')
sns.histplot(x=variable, ax=ax_hist, kde=True, binwidth=2, facecolor='green')
ax_hist.lines[0].set_color('red')
ax_hist.axvline(mean, color='orange', linestyle='--', label='Mean')
ax_hist.axvline(median, color='black', linestyle='-', label='Median')
ax_hist.set_title(x, fontsize=10, loc='right')
ax_hist.legend()
# ax_box.set(xlabel='') # has no effect on shared x-axis
plt.tight_layout()
plt.show()
dfx = pd.DataFrame({'bmi': np.random.normal(30.2, 5, 100)})
boxhist(dfx, 'bmi')

How to show data points on top of matplotlib imshow contour plot?

I have made a contour plot using imshow. The value of z is calculated for a range of x and y values as can be seen from the code below. As a next step I want to plot some calculated z_new values for some random x_new and y_new on top of the contour image which may be marked by closed circles or something similar.
The code reads as:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import colors
x = np.linspace(-0.5, 2.0, 101)
y = np.linspace(-0.5, 2.0, 101)
z = np.zeros((101, 101))
E = 0.0
for i in range(len(x)):
for j in range(len(y)):
z[i,j] = -max(y[j]+0.2+E, 0.5-x[i], 0)
x_new = np.array([1.1168189, 0.8381589, 1.3312789, -0.2149011])
y_new = np.array([1.7571379, 1.5555579, 1.9138179, 0.7912879])
z_new = []
for k, l in zip(x_new, y_new):
#print (k, l, -max(l+0.2+E, 0.5-k, 0)) # z_new = -max(l+0.2+eU, 0.5-k, 0) calculated for some random x_new and y_new
z_new.append(-max(l+0.2+E, 0.5-k, 0)) # I would like to see these z_new points on the contour plot for corresponding x_new and y_new
fig, ax = plt.subplots()
ms = plt.imshow(z.T, cmap='plasma', vmin=-2.5, vmax=0, origin='lower', interpolation='none', extent=[-0.5,2.0,-0.5,2.0])
ax.set_xlabel('x', fontsize=16, fontname = "Helvetica")
ax.set_ylabel('y', fontsize=16, fontname = "Helvetica")
cbar = plt.colorbar(ms)
cbar.ax.tick_params(labelsize=10, direction='out')
cbar.set_label('z', fontsize=16, fontname = "Helvetica")
#plt.savefig('test.pdf')
plt.xticks(fontname = "Helvetica", fontsize=12)
plt.yticks(fontname = "Helvetica", fontsize=12)
plt.show()
You can add a scatter top on top of your heat map with the last line of the code below. The circles are hard to see without the edges, though when you spot them you can get a good feel for how their values compare to the heat map. You can add black edges to the circles by uncommenting the line above the plt.scatter command, which makes the circles easy to locate but the differences in color hard to see.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import colors
x = np.linspace(-0.5, 2.0, 101)
y = np.linspace(-0.5, 2.0, 101)
z = np.zeros((101, 101))
E = 0.0
for i in range(len(x)):
for j in range(len(y)):
z[i,j] = -max(y[j]+0.2+E, 0.5-x[i], 0)
x_new = np.array([1.1168189, 0.8381589, 1.3312789, -0.2149011])
y_new = np.array([1.7571379, 1.5555579, 1.9138179, 0.7912879])
z_new = []
for k, l in zip(x_new, y_new):
#print (k, l, -max(l+0.2+E, 0.5-k, 0)) # z_new = -max(l+0.2+eU, 0.5-k, 0) calculated for some random x_new and y_new
z_new.append(-max(l+0.2+E, 0.5-k, 0)) # I would like to see these z_new points on the contour plot for corresponding x_new and y_new
fig, ax = plt.subplots()
ms = plt.imshow(z.T, cmap='plasma', vmin=-2.5, vmax=0, origin='lower', interpolation='none', extent=[-0.5,2.0,-0.5,2.0])
ax.set_xlabel('x', fontsize=16, fontname = "Helvetica")
ax.set_ylabel('y', fontsize=16, fontname = "Helvetica")
cbar = plt.colorbar(ms)
cbar.ax.tick_params(labelsize=10, direction='out')
cbar.set_label('z', fontsize=16, fontname = "Helvetica")
#plt.savefig('test.pdf')
plt.xticks(fontname = "Helvetica", fontsize=12)
plt.yticks(fontname = "Helvetica", fontsize=12)
## New code ##
# plt.scatter(x_new, y_new, c=z_new, cmap='plasma', vmin=-2.5, vmax=0, edgecolors='black') # Uncomment this if you want the points circled in black
plt.scatter(x_new, y_new, c='green') # Uncomment if you want green circles
# plt.scatter(x_new, y_new, c=z_new, cmap='plasma', vmin=-2.5, vmax=0)
## End of new code ##
plt.show()

Matplotlib square major/minor grid for axes with different limits

I have a plot with a background grid. I need grid cells to be square (both major grid and minor grid cells) even though the limits of X and Y axes are different.
My current code is as follows:
import matplotlib.pyplot as plt
import matplotlib.ticker as plticker
import numpy as np
data = [0.014, 0.84, 0.95, -0.42, -0.79, 0.84, 0.98, 1.10, 0.56, -0.49]
fig, ax = plt.subplots(figsize=(20, 5))
ax.minorticks_on()
# Set major and minor grid lines on X
ax.set_xticks(np.arange(0, 10, 0.2))
ax.xaxis.set_minor_locator(plticker.MultipleLocator(base=0.2 / 5.))
for xmaj in ax.xaxis.get_majorticklocs():
ax.axvline(x=xmaj, ls='-', color='red', linewidth=0.8)
for xmin in ax.xaxis.get_minorticklocs():
ax.axvline(x=xmin, ls=':', color='red', linewidth=0.6)
# Set major and minor grid lines on Y
ylim = int(np.ceil(max(abs(min(data)), max(data))))
yticks = np.arange(-ylim, ylim + 0.5, 0.5)
ax.set_yticks(yticks)
ax.yaxis.set_minor_locator(plticker.MultipleLocator(base=0.5 / 5.))
for ymaj in ax.yaxis.get_majorticklocs():
ax.axhline(y=ymaj, ls='-', color='red', linewidth=0.8)
for ymin in ax.yaxis.get_minorticklocs():
ax.axhline(y=ymin, ls=':', color='red', linewidth=0.6)
ax.axis([0, 10, -ylim, ylim])
fig.tight_layout()
# Plot
ax.plot(data)
# Set equal aspect ratio NOT WORKING
plt.gca().set_aspect('equal', adjustable='box')
plt.show()
Which generates the following plot:
Large grid cells contain 5 smaller cells each. However, the aspect ratio of large grid is not 1.
Question: How can I make sure that large grid is square?
EDIT
Current approach is to set same tick locations as suggested by #ImportanceOfBeingErnest, but change Y labels:
ylim = int(np.ceil(max(abs(min(data)), max(data))))
yticks = np.arange(-ylim, ylim + 0.2, 0.2)
ax.set_yticks(yticks)
labels = ['{:.1f}'.format(v if abs(v) < 1e-3 else (1 if v > 0 else -1)*((0.5 - abs(v)%0.5) + abs(v)))
if i%2==0 else "" for i, v in enumerate(np.arange(-ylim, ylim, 0.2))]
ax.set_yticklabels(labels)
Result: seems too hacky.
When using equal aspect ratio and aiming for a square grid you would need to use the same tickspacing for both axes. This can be achieved with a MultipleLocator where the interval needs to be the same for x and y axis.
In general, grids can be created with the grid command.
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
import numpy as np
data = [0.014, 0.84, 0.95, -0.42, -0.79, 0.84, 0.98, 1.10, 0.56, -0.49]
fig, ax = plt.subplots(figsize=(20, 5))
ax.minorticks_on()
# Set major and minor grid lines on X
ax.xaxis.set_major_locator(mticker.MultipleLocator(base=.5))
ax.xaxis.set_minor_locator(mticker.MultipleLocator(base=0.5 / 5.))
ax.yaxis.set_major_locator(mticker.MultipleLocator(base=.5))
ax.yaxis.set_minor_locator(mticker.MultipleLocator(base=0.5 / 5.))
ax.grid(ls='-', color='red', linewidth=0.8)
ax.grid(which="minor", ls=':', color='red', linewidth=0.6)
## Set limits
ylim = int(np.ceil(max(abs(min(data)), max(data))))
ax.axis([0, 10, -ylim, ylim])
plt.gca().set_aspect('equal', adjustable='box')
fig.tight_layout()
# Plot
ax.plot(data)
plt.show()
If you instead want to have different tick spacings with square major cells in the grid, you would need to give up the equal aspect ratio and instead set it to the quotient of the tick spacings.
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
import numpy as np
data = [0.014, 0.84, 0.95, -0.42, -0.79, 0.84, 0.98, 1.10, 0.56, -0.49]
fig, ax = plt.subplots(figsize=(20, 5))
ax.minorticks_on()
xm = 0.2
ym = 0.25
# Set major and minor grid lines on X
ax.xaxis.set_major_locator(mticker.MultipleLocator(base=xm))
ax.xaxis.set_minor_locator(mticker.MultipleLocator(base=xm / 5.))
ax.yaxis.set_major_locator(mticker.MultipleLocator(base=ym))
ax.yaxis.set_minor_locator(mticker.MultipleLocator(base=ym / 5.))
ax.grid(ls='-', color='red', linewidth=0.8)
ax.grid(which="minor", ls=':', color='red', linewidth=0.6)
## Set limits
ylim = int(np.ceil(max(abs(min(data)), max(data))))
ax.axis([0, 10, -ylim, ylim])
plt.gca().set_aspect(xm/ym, adjustable='box')
fig.tight_layout()
# Plot
ax.plot(data)
plt.show()
To then get rid of every second ticklabel, an option is
fmt = lambda x,p: "%.2f" % x if not x%(2*ym) else ""
ax.yaxis.set_major_formatter(mticker.FuncFormatter(fmt))
You should be able to achieve this by using the same locator for the both axis. However matplotlib has a limitation currently, so here's a workaround:
# matplotlib doesnt (currently) allow two axis to share the same locator
# so make two wrapper locators and combine their view intervals
def share_locator(locator):
class _SharedLocator(matplotlib.ticker.Locator):
def tick_values(self, vmin, vmax):
return locator.tick_values(vmin, vmax)
def __call__(self):
min0, max0 = shared_locators[0].axis.get_view_interval()
min1, max1 = shared_locators[1].axis.get_view_interval()
return self.tick_values(min(min0, min1), max(max0, max1))
shared_locators = (_SharedLocator(), _SharedLocator())
return shared_locators
Use like:
lx, ly = share_locator(matplotlib.ticker.AutoLocator()) # or any other locator
ax.xaxis.set_major_locator(lx)
ax.yaxis.set_major_locator(ly)

How to change the line formate in animation? [duplicate]

This question already has answers here:
Animate points with labels with matplotlib
(3 answers)
Closed 5 years ago.
I made this animation using matplotlib and it is working properly, however, i need to add some animated labels to be moving with its corresponding points.
The first label to be referring to the intersection point between the circle and the horizontal line from the centre of the ellipse and the other text label is to be in the middle of the inclined line annotating its length.
I tried some ideas but nothing worked properly. Any ideas?
screenshot
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig = plt.figure()
ax = fig.add_subplot(111, autoscale_on=True, xlim=(-6, 6), ylim=(-7, 17))
# ax.grid()
line, = ax.plot([], [], 'k', lw=1)
line2, = ax.plot([], [], 'k--', lw=1)
a,b = 3,2
x,y = list(),list()
x1 =np.array([item/10 for item in range(-30,31)])
y1 = np.sqrt(b**2 * (1-(x1**2 / a**2)))
x =list(x1)+[-item for item in list(x1)]
y =list(y1)+[-item for item in list(y1)]
plt.plot(x, y, 'k:')
plt.plot((0,0), (0,15), 'k--')
ax.annotate('$A$', xy=(0,15), xytext=(-10, 10),color='b',
textcoords='offset points')
ax.annotate('$O$', xy=(0,-1), xytext=(-10, 10),color='b',ha='center',
textcoords='offset points')
ax.annotate('$4a$', xy=(0,7), xytext=(-10, 10),color='b',ha='center',
textcoords='offset points', family='sans serif')
def animate(i):
thisx = [0, x[i]]
thisy = [15, y[i]]
xx = [x[i], 0]
yy = [y[i], 0]
line.set_data(thisx, thisy)
line2.set_data(xx, yy)
return line, line2
ani = animation.FuncAnimation(fig, animate, np.arange(0, len(x)), interval=20, blit=False)
ax.annotate('$P$', xy=(3,0), xytext=(0, 0),color='b',ha='center',
textcoords='offset points', family='sans serif', style='italic')
plt.show()
# ani.save('circular_motion.mp4', fps=20)
#
plt.close()
You can alter the annotation properties in the same way that you alter the line properties. Just store the Annotation object that is returned from the ax.annotation command and then update its position in the animate function. Note that the function set_position does not work properly, as was also noted in here, therefore you have to use the xy attribute.
Furthermore, I noticed that your animation runs faster when you y values are close to zero. You can fix that (if it needs fixing) by defining a vector of angles and computing the xy coordinates from that. I took the freedom to alter your code to show what I mean.
About the length of the inclined line, I annotated it here as L, as you don't state what the length of the distance OP is, but I guess that you can fill that in yourself.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig = plt.figure()
ax = fig.add_subplot(111, autoscale_on=True, xlim=(-6, 6), ylim=(-7, 17))
# ax.grid()
line, = ax.plot([], [], 'k', lw=1)
line2, = ax.plot([], [], 'k--', lw=1)
a,b = 3,2
x,y = list(),list()
z = 15
phi = np.linspace(0,2*np.pi,100)
x = a * np.cos(phi)
y = -b * np.sin(phi)
plt.plot(x, y, 'k:')
plt.plot((0,0), (0,z), 'k--')
ax.annotate('$A$', xy=(0,15), xytext=(-10, 10),color='b',
textcoords='offset points')
ax.annotate('$O$', xy=(0,-1), xytext=(-10, 10),color='b',ha='center',
textcoords='offset points')
ax.annotate('$4a$', xy=(0,7), xytext=(-10, 10),color='b',ha='center',
textcoords='offset points', family='sans serif')
def animate(i):
thisx = [0, x[i]]
thisy = [z, y[i]]
xx = [x[i], 0]
yy = [y[i], 0]
line.set_data(thisx, thisy)
line2.set_data(xx, yy)
P.xy = (x[i]*1.05,y[i])
L.xy = ((x[i]/2)*1.05, z/2+y[i]/2)
return line, line2, P, L
ani = animation.FuncAnimation(fig, animate, np.arange(0, len(x)), interval=20, blit=False)
P = ax.annotate('$P$', xy=(a,0), xytext=(0, 0),color='b',ha='center',
textcoords='offset points', family='sans serif', style='italic')
L = ax.annotate('$L$', xy=(a/2,z/2), xytext=(0, 0),color='b',ha='center',
textcoords='offset points', family='sans serif', style='italic')
plt.show()
# ani.save('circular_motion.mp4', fps=20)
#
plt.close()
Hope this helps.

Resources