I have two pandas dataframe objects named 'freq' and 'freq1000', each being a vector of 5 numbers. I want to draw them on the same graph with plt.text on the graph to show the y-values. 'freq' and 'freq1000' are constructed from 'pr' and 'pr1000', both of which are generated by extremely lengthy procedures. To avoid overlapping of plt.text, I followed the link How to fix overlapping annotations / text and installed the package adjust_text. However it did not work as I want, and the graph I got still have overlapping for the figures on the graph, see the picture below:
My related imports and codes are included below
import pandas as pd
import matplotlib.pyplot as plt
from adjustText import adjust_text
freq = pd.DataFrame(np.average(pr, axis=0), index=pr.columns)
freq1000 = pd.DataFrame(np.average(pr1000, axis=0), index=pr1000.columns)
plt.plot(freq, label='n=500')
plt.plot(freq1000, label='n=1000')
plt.legend(loc='best')
plt.xlabel('x')
plt.ylabel('y')
plt.title('curve high vs low,gtype{}'.format(type_))
# plt.close()
texts=[]
for x, y in zip(freq.index, freq.values):
texts.append(plt.text(x, y,'%.3f' % y))
adjust_text(texts, only_move={'points':'y', 'texts':'xy'}, arrowprops=dict(arrowstyle="->", color='r', lw=0.5))
texts=[]
for x, y in zip(freq1000.index, freq1000.values):
texts.append(plt.text(x, y,'%.3f' % y))
adjust_text(texts, only_move={'points':'y', 'texts':'y'}, arrowprops=dict(arrowstyle="->", color='r', lw=0.5))
How should I fix the codes to solve the overlapping problem? Thanks!
This is an attempt at something with less overlap, but with some tuning required if things move around a lot. ax.annotate
does most of the work, but there are some padding parameters for extra adjustment. The main idea is to shift the blue labels left, and the orange ones right.
One of the orange labels overlaps with the line. You can manually force it off if required for a one-off plot, but I haven't done that in order to keep the answer more generalisable and robust to new cases.
from matplotlib import pyplot as plt
pr_x = [-1, -0.5, 0, 0.5, 1]
pr_y = [1, 0.683, 0.067, 0.5, 1]
color_pr = 'tab:blue'
pr1000_x = [-1, -0.5, 0, 0.5, 1]
pr1000_y = [1, 0.983, 0.05, 0.917, 1]
color_pr1000 = 'tab:orange'
f, ax = plt.subplots(figsize=(6, 3))
ax.plot(pr_x, pr_y, color=color_pr, marker='o', label='n=500')
ax.plot(pr1000_x, pr1000_y, color=color_pr1000, marker='o', label='n=1000')
ax.set(xlabel='x', ylabel='y', title='curve high vs low, gtype2')
ax.legend()
for x, y in zip(pr_x, pr_y):
ax.annotate(
f'{y:.3f}', (x, y),
xytext=(-0.6, 0), textcoords='offset fontsize', #shift blue pts left
horizontalalignment='right', verticalalignment='center',
weight='bold', fontsize=8, color=color_pr,
)
for x, y in zip(pr1000_x, pr1000_y):
ax.annotate(
f'{y:.3f}', (x, y),
xytext=(0.6, 0.2), textcoords='offset fontsize', #shift orange pts up, right
horizontalalignment='left', verticalalignment='bottom',
weight='bold', fontsize=8, color=color_pr1000,
# rotation=20
)
ax.set_xlim(ax.get_xlim()[0] * 1.15, ax.get_xlim()[1]) #make x axis wider
ax.spines[['right', 'top']].set_visible(False)
This is an alternative arrangement that has less potential for overlap. There's a visual guide for each plot, but the numbers are split across two plots.
I think seaborn
's FacetGrid
could do something similar and with less code if you were interested in exploring this sort of arrangement further.
f, axs = plt.subplots(ncols=2, nrows=1, figsize=(7, 3), sharey=True, layout='tight')
pr_data = (pr_x, pr_y, 'n=500')
pr1000_data = (pr1000_x, pr1000_y, 'n=1000')
for ax in axs:
fg_data = pr_data if ax==axs[0] else pr1000_data
bg_data = pr_data if ax==axs[1] else pr1000_data
#Foreground data
x, y, label = fg_data
ax.plot(x, y, 'tab:purple', marker='o', markersize=7, linewidth=3, label=label)
for x_i, y_i in zip(x, y):
ax.annotate(
f'{y_i:.3f}', (x_i, y_i), xytext=(1, 0.6), textcoords='offset fontsize',
horizontalalignment='left', verticalalignment='top',
color='midnightblue', weight='bold', size=8, rotation=0,
)
#Background data
x, y, label = bg_data
ax.plot(x, y, 'black', marker='o', markersize=6,
linewidth=4.5, linestyle='-', alpha=0.12,
label=label, zorder=0)
ax.set_xlabel('x')
ax.spines[['right', 'top']].set_visible(False)
ax.set_title(label, color='rebeccapurple', weight='bold')
# ax.legend()
axs[0].set_ylabel('y')