最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

python - plotting vertical lines on pandas line plot with multiindex x axis - Stack Overflow

programmeradmin3浏览0评论

I have a dataframe whose index is a multiindex where axes[0] is the date, and axis[1] is the rank. Rank starts with 1 and ends at 100, but there can be a variable number of ranks in between as below. Here are the ranks

dx = pd.DataFrame({
    "date": [
        pd.to_datetime('2025-02-24'), pd.to_datetime('2025-02-24'), pd.to_datetime('2025-02-24'), pd.to_datetime('2025-02-24'),
        pd.to_datetime('2025-02-25'), pd.to_datetime('2025-02-25'), pd.to_datetime('2025-02-25'), 
        pd.to_datetime('2025-02-26'), pd.to_datetime('2025-02-26'), pd.to_datetime('2025-02-26'), pd.to_datetime('2025-02-26'), pd.to_datetime('2025-02-26')
    ],
     "rank": [0.0,1.0,2.0,100.0,0.0,1.0,100.0,0.0,1.0,2.0,3.0,100.0],
    "value": [2.3, 2.5, 2.4, 2.36, 2.165, 2.54, 2.34, 2.12, 2.32, 2.43, 2.4, 2.3]
})

dx.set_index(["date", "rank"], inplace=True)

I want to plot this df, and df.plot() works fine creating a reasonable x-axis. However, I want to add a grid or vertical lines at all the rank=1, and all the rank=100(different color).

I tried this :


fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(30, 5))

dx.plot(ax=axes[0])
axes[0].tick_params('x', labelrotation=90)
xs = [x for x in dx.index if x[1]==0]

for xc in xs:
    axes[0].axvline(x=xc, color='blue', linestyle='-')

but get this error:

ConversionError: Failed to convert value(s) to axis units: (Timestamp('2025-02-24 00:00:00'), 0.0)

I also want to only show x labels for rank=0, and not all of them. Currently, if i set label rotation to 90, it results in that but not sure if this is the best way to ensure that.

axes[0].tick_params('x', labelrotation=90)

So looking for 2 answers

  1. How to set vertical lines at specific points with this type of multiindex
  2. How to ensure only certain x labels show on the chart

I have a dataframe whose index is a multiindex where axes[0] is the date, and axis[1] is the rank. Rank starts with 1 and ends at 100, but there can be a variable number of ranks in between as below. Here are the ranks

dx = pd.DataFrame({
    "date": [
        pd.to_datetime('2025-02-24'), pd.to_datetime('2025-02-24'), pd.to_datetime('2025-02-24'), pd.to_datetime('2025-02-24'),
        pd.to_datetime('2025-02-25'), pd.to_datetime('2025-02-25'), pd.to_datetime('2025-02-25'), 
        pd.to_datetime('2025-02-26'), pd.to_datetime('2025-02-26'), pd.to_datetime('2025-02-26'), pd.to_datetime('2025-02-26'), pd.to_datetime('2025-02-26')
    ],
     "rank": [0.0,1.0,2.0,100.0,0.0,1.0,100.0,0.0,1.0,2.0,3.0,100.0],
    "value": [2.3, 2.5, 2.4, 2.36, 2.165, 2.54, 2.34, 2.12, 2.32, 2.43, 2.4, 2.3]
})

dx.set_index(["date", "rank"], inplace=True)

I want to plot this df, and df.plot() works fine creating a reasonable x-axis. However, I want to add a grid or vertical lines at all the rank=1, and all the rank=100(different color).

I tried this :


fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(30, 5))

dx.plot(ax=axes[0])
axes[0].tick_params('x', labelrotation=90)
xs = [x for x in dx.index if x[1]==0]

for xc in xs:
    axes[0].axvline(x=xc, color='blue', linestyle='-')

but get this error:

ConversionError: Failed to convert value(s) to axis units: (Timestamp('2025-02-24 00:00:00'), 0.0)

I also want to only show x labels for rank=0, and not all of them. Currently, if i set label rotation to 90, it results in that but not sure if this is the best way to ensure that.

axes[0].tick_params('x', labelrotation=90)

So looking for 2 answers

  1. How to set vertical lines at specific points with this type of multiindex
  2. How to ensure only certain x labels show on the chart
Share Improve this question edited Mar 9 at 16:30 dayum asked Mar 9 at 15:24 dayumdayum 1,10515 silver badges33 bronze badges 4
  • always put full error message because there are other useful information. – furas Commented Mar 9 at 15:46
  • 1 maybe create minimal working code with example data directly in code as DataFrame(...) so we could simply copy and test it. – furas Commented Mar 9 at 15:48
  • Just a minor comment. Suggest adding plt.tight_layout() before plt.show() for plots like this one with long labels. – Bill Commented Mar 9 at 19:04
  • I think the reason this is not working is that the x-axis of this plot is categorical, (i.e. discrete values not continuous). To make this more obvious you could use a bar plot instead dx.plot.bar(ax=axes[0]). I'm not sure how the position of axvline works with a categorical x-axis. – Bill Commented Mar 9 at 19:12
Add a comment  | 

1 Answer 1

Reset to default 1

With a categorical axis, plt will use an integer index "under the hood". Here, since you are using a lineplot, it tries to come up with a reasonable step:

dx.plot(ax=axes[0])
axes[0].get_xticks()

# array([-2.,  0.,  2.,  4.,  6.,  8., 10., 12.])

With a barplot, you would get the more logical:

dx.plot.bar(ax=axes[0])
axes[0].get_xticks()

# array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])

You can use Axes.set_xticks and Axes.set_xticklabels to fix this. E.g.,

ticks = range(len(dx))

# only label at rank 0
labels = [f"{x[0].strftime('%Y-%m-%d')}, {int(x[1])}" 
          if x[1] == 0 else '' for x in dx.index]

axes[0].set_xticks(ticks=ticks)
axes[0].set_xticklabels(labels=labels, rotation=90)

It's easier to see now that we need the appropriate index matches for Axes.axvline. We can apply np.nonzero to Index.get_level_values and then add the lines in a loop:

def add_vlines(ax, rank, color):
    indices = np.nonzero(dx.index.get_level_values('rank') == rank)[0]
    for index in indices:
        ax.axvline(x=index, color=color, linestyle='dotted')

add_vlines(axes[0], 1, 'blue')
add_vlines(axes[0], 100, 'red')

Output:

发布评论

评论列表(0)

  1. 暂无评论