2

I would like to have a step-wise line plot in Matplotlib. The shape of the line should be similar to this one (see screenshot): Desired shape

This is my current code:

import pandas as pd
from matplotlib import pyplot as plt
%matplotlib inline


prices = [12.05, 17.69, 15.31, 12.75, 17.18, 25.05, 33.19, 38.56, 42.9, 38.29, 37.06, 38.94, 36.36, 39.45, 43.97, 46.14, 50.96, 51.04, 48.85, 45.6, 42.38, 39.83, 33.53, 30.03, 28.69
]


price_data = pd.DataFrame(prices, index=range(0, 25))
fig = plt.figure(linewidth=1, figsize=(7, 5))
ax = price_data.plot.line(ax=plt.gca(), color="green"  )
ax.set_facecolor("white")
ax.set_xlabel("Time of day", fontsize = 14, labelpad=8)
ax.set_ylabel("Price in €/MWh", fontsize = 14,labelpad=8)
ax.set_xlim(0, 24)
ax.set_ylim(0, 60)


plt.xticks(price_data.index, labels=[f'{h:02d}:00' for h in price_data.index], rotation=90)
plt.grid(axis='y', alpha=.4)
plt.tight_layout()
xticks = ['00:00', '01:00', '02:00' , '03:00', '04:00' , '05:00' , '06:00' , '07:00' , '08:00' , 
          '09:00' , '10:00' , '11:00' , '12:00' , '13:00' , '14:00' , '15:00' , '16:00' 
          , '17:00', '18:00', '19:00' , '20:00' , '21:00', '22:00' , '23:00' , '24:00'  ]
xvals = [0, 1*12, 2*12, 3*12, 4*12, 5*12, 6*12, 7*12, 8*12, 9*12, 10*12, 11*12, 12*12, 13*12, 14*12, 15*12, 16*12
        , 17*12, 18*12, 19*12, 20*12, 21*12, 22*12, 23*12, 24*12] 
ax.set(xticks=xvals, xticklabels=xticks)
ax.tick_params(axis='both', which='major', labelsize=14)
ax.legend(loc='center left', bbox_to_anchor=(0.03, 1.15), fontsize = 14, ncol=3)
plt.savefig('Prices.png', edgecolor='black', dpi=300, bbox_inches='tight')
plt.show()

And this is my current output (see screenshot): [![

PeterBe
  • 700
  • 1
  • 17
  • 37
  • 1
    What is `wind_data`? – Guimoute Mar 11 '20 at 09:38
  • Thanks for your comment Guimoute. This was a mistake by me. It should be price_data. Still the plot looks not as desired – PeterBe Mar 11 '20 at 09:47
  • 2
    Multiplying all the hours by 12 doesn't make any sense. Just leave out `ax.set(xticks=xvals, xticklabels=xticks)` because it puts all the ticks on the wrong locations. The ticks are already put correctly via `plt.xticks(....)`. – JohanC Mar 11 '20 at 09:51
  • Thanks for your comment JohanC. In the answer below from user10455554 the hours are also multiplied – PeterBe Mar 11 '20 at 09:56
  • 1
    @PeterBe Do not bother typing sequences yourself. For example, `labels=[f'{h:02d}:00' for h in price_data.index]` was great. You could do `xvals = list(range(0, 25))`. Now JohanC is right you don't need `xvals` or `xticks` anyway. – Guimoute Mar 11 '20 at 10:01
  • Thanks Guimoute for your answer. What does `"xvals = list(range(0, 25))" basically do? I was told to type the sequences on my own for an other example (see https://stackoverflow.com/questions/60621751/reduce-x-axis-entries-in-an-area-plot-in-matplotlib) and it was better than the automatic one – PeterBe Mar 11 '20 at 10:06
  • It's better to type them yourself if you have like 5 values. 24 is a big excessive. `list(range(0, 25))` is `[0, 1, 2, 3, ..., 22, 23, 24]`. – Guimoute Mar 11 '20 at 10:07

2 Answers2

2

Using @JohanC's great comments, here is a solution.

As he said, get rid of xticks, xvals, etc. You already defined your ticks properly with plt.xticks(price_data.index, labels=[f'{h:02d}:00' for h in price_data.index], rotation=90), plus you don't have to type 24 different values yourself.

To get a step-like plot, you simply need to add the argument drawstyle = "steps-pre" or "steps-post" (or other options found in the documentation) to your plotting function.

from matplotlib import pyplot as plt
%matplotlib inline

prices = [12.05, 17.69, 15.31, 12.75, 17.18, 25.05, 33.19, 38.56, 42.9, 38.29, 37.06, 38.94, 36.36, 39.45, 43.97, 46.14, 50.96, 51.04, 48.85, 45.6, 42.38, 39.83, 33.53, 30.03, 28.69]    
hours = list(range(25)) # [0, 1, 2, ... 22, 23, 24]
labels = [f'{h:02d}:00' for h in hours] # ["00:00", "01:00", ... "23:00", "24:00"]

fig = plt.figure(linewidth=1, figsize=(7, 5))
ax = plt.gca()

ax.plot(hours, prices, color="green", drawstyle="steps-post") # <- drawstyle argument.
ax.set_xlabel("Time of day", fontsize=14, labelpad=8)
ax.set_ylabel("Price in €/MWh", fontsize=14, labelpad=8)
ax.set_xlim(0, 24)
ax.set_ylim(0, 60)    
plt.xticks(hours, labels=labels, rotation=90)
plt.grid(axis='y', alpha=.4)
ax.tick_params(axis='both', which='major', labelsize=14)
# (Optional) ax.legend(loc='center left', bbox_to_anchor=(0.03, 1.15), fontsize = 14, ncol=3)
plt.tight_layout() # This must be called last, after all elements (plot and legend) are ready.
plt.savefig('Prices.png', edgecolor='black', dpi=300, bbox_inches='tight')
plt.show()

enter image description here

Guimoute
  • 4,407
  • 3
  • 12
  • 28
  • Thanks for your answer Guimoute. How can I get rid of the legend? Just removing "ax.legend(loc='center left', bbox_to_anchor=(0.03, 1.15), fontsize = 14, ncol=3)" just leads to the legend placed on the right upper corner – PeterBe Mar 11 '20 at 10:38
  • 1
    That might be due to how Panda handles things. If we use `ax.plot(x, y)` and remove `ax.legend(...)` it does disappear. I'll edit. – Guimoute Mar 11 '20 at 13:28
  • Thaks Guimoute for your comment. Unfortunately I get an error when I use your edited code "TypeError: plot got an unexpected keyword argument 'x'" – PeterBe Mar 11 '20 at 14:08
  • 1
    @PeterBe Ah I guess `x=` and `y=` are not even needed actually! – Guimoute Mar 11 '20 at 14:57
  • Thanks a lot for your help and effort Guimoute – PeterBe Mar 11 '20 at 15:07
1

You only need to use the step function on your plot with your x- and y-values like plt.step(xvals, prices) Just add this line after you defined the xvals should give you a good starting point.

For more details see the step plot example:

import pandas as pd
from matplotlib import pyplot as plt

prices = [12.05, 17.69, 15.31, 12.75, 17.18, 25.05, 33.19, 38.56, 42.9, 38.29, 37.06, 38.94, 36.36, 39.45, 43.97, 46.14, 50.96, 51.04, 48.85, 45.6, 42.38, 39.83, 33.53, 30.03, 28.69]

xvals = [0, 1*12, 2*12, 3*12, 4*12, 5*12, 6*12, 7*12, 8*12, 9*12, 10*12, 11*12, 12*12, 13*12, 14*12, 15*12, 16*12, 17*12, 18*12, 19*12, 20*12, 21*12, 22*12, 23*12, 24*12] 

price_data = pd.DataFrame(prices, index=range(0, 25))
fig = plt.figure(linewidth=1, figsize=(7, 5))
ax = price_data.plot.line(ax=plt.gca(), color="green"  )
ax.set_facecolor("white")
ax.set_xlabel("Time of day", fontsize = 14, labelpad=8)
ax.set_ylabel("Price in €/MWh", fontsize = 14,labelpad=8)
ax.set_xlim(0, 288)
ax.set_ylim(0, 60)


plt.xticks(price_data.index, labels=[f'{h:02d}:00' for h in price_data.index], rotation=90)
plt.grid(axis='y', alpha=.4)
plt.tight_layout()
xticks = ['00:00', '01:00', '02:00' , '03:00', '04:00' , '05:00' , '06:00' , '07:00' , '08:00' , 
          '09:00' , '10:00' , '11:00' , '12:00' , '13:00' , '14:00' , '15:00' , '16:00' 
          , '17:00', '18:00', '19:00' , '20:00' , '21:00', '22:00' , '23:00' , '24:00'  ]

plt.step(xvals, prices)
ax.set(xticks=xvals, xticklabels=xticks)
ax.tick_params(axis='both', which='major', labelsize=14)
ax.legend(loc='center left', bbox_to_anchor=(0.03, 1.15), fontsize = 14, ncol=3)
plt.savefig('Prices.png', edgecolor='black', dpi=300, bbox_inches='tight')
plt.show()

result

user10455554
  • 403
  • 7
  • 14
  • Thanks user10455554 for your answer and your help. The plot looks good. But how can I get rid of the green line and the legend above? – PeterBe Mar 11 '20 at 09:54
  • 1
    Ok Guimoute was faster, the green line was the plot from pandas.dataframe `price_data.plot.line(ax=plt.gca(), color="green" )` I added another line with the matplotlib step function instead of using the `drawstyle` parameter, which is the better solution. – user10455554 Mar 11 '20 at 10:15