I think your specific logic to calculate local maximums needs to be defined rather than just using the "clock" metaphor, but once you define that logic with scipi
, pandas
or another library, you can create a dataframe as I have. From there, you should be able to produce the result from below.
If you are okay with seaborn
(built on top of matplotlib
), I think it is a little bit easier, because you can pass the hue
parameter to create all of the lines for each category in one line of code. You need to create a new dataframe with these lines
that you want to plot. I do this by sorting the values and getting the tail value per group. See reproducible examples below.
Example 1 (plotting a local maximum):
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import seaborn as sns
fig, ax = plt.subplots()
plt.style.use('classic')
df = pd.DataFrame(np.random.randn(50, 4),
index=pd.date_range('1/1/2000', periods=50), columns=list('ABCD'))
df = df.cumsum()
df = df.melt(ignore_index=False).reset_index()
sns.lineplot(data=df, x="index", y="value", hue="variable", ax=ax)
lines_max = (df.sort_values('value').groupby("variable").tail(1)).sort_values('variable')
lines_local_max = df[((df['variable'] == lines_max['variable'].iloc[0]) & (df['index'] > lines_max['index'].iloc[0]))
| ((df['variable'] == lines_max['variable'].iloc[1]) & (df['index'] > lines_max['index'].iloc[1]))
| ((df['variable'] == lines_max['variable'].iloc[2]) & (df['index'] > lines_max['index'].iloc[2]))
| ((df['variable'] == lines_max['variable'].iloc[3]) & (df['index'] > lines_max['index'].iloc[3]))]
lines_local_max = (lines_local_max.sort_values(['variable', 'value']).groupby("variable").tail(1))
lines = lines_max.append(lines_local_max).sort_values('variable')
lines
sns.lineplot(data=lines, x="index", y="value", hue="variable", marker="o",
style='variable', dashes=[(2, 2), (2, 2), (2, 2), (2, 2)], legend=False, ax=ax)
x_dates = pd.to_datetime(df['index'].unique())
plt.xticks(x_dates[0::7], rotation=45, ha='center')
ax.xaxis.set_major_formatter(mdates.DateFormatter('%b-%d-%Y'))

Example 2 (just drawing a line to the end and not defining local max - purpose is just to show you haow to draw a line from the max point to another defined point):
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
plt.style.use('classic')
fmri = sns.load_dataset("fmri")
fmri = fmri.groupby(['event', 'timepoint'], as_index=False)['signal'].mean()
sns.lineplot(data=fmri, x="timepoint", y="signal", hue="event")
lines_max = (fmri.sort_values('signal').groupby("event").tail(1))
lines_last = (fmri.sort_values('timepoint').groupby("event").tail(1))
lines = lines_max.append(lines_last)
sns.lineplot(data=lines, x="timepoint", y="signal", hue="event", marker="o", style='event', dashes=[(2, 2), (2, 2)])

Example 3 (another example drawing a line with the data you have provided to the end and not defining local max - purpose is just to show you how to draw a line from the max point to another defined point): example with the data you have provided:
import pandas as pd
import numpy as np
fig, ax = plt.subplots()
df = pd.DataFrame(np.random.randn(50, 4),
index=pd.date_range('1/1/2000', periods=50), columns=list('ABCD'))
df = df.cumsum()
df = df.melt(ignore_index=False).reset_index()
sns.lineplot(data=df, x="index", y="value", hue="variable", ax=ax)
lines_max = (df.sort_values('value').groupby("variable").tail(1))
lines_last = (df.sort_values('index').groupby("variable").tail(1))
lines = lines_max.append(lines_last).sort_values('variable')
sns.lineplot(data=lines, x="index", y="value", hue="variable", marker="o",
style='variable', dashes=[(2, 2), (2, 2), (2, 2), (2, 2)], legend=False, ax=ax)
x_dates = df['index'].dt.strftime('%Y-%m-%d').sort_values().unique()
ax.set_xticklabels(labels=x_dates, rotation=45, ha='center')
