9

Given a plot like

import matplotlib
import matplotlib.pyplot as plt
import numpy as np
t = np.arange(0.0, 2.0, 0.01)
s = 1 + np.sin(2 * np.pi * t)
fig, ax = plt.subplots()
ax.plot(s)
ax.set(xlabel='time (s)', ylabel='voltage (mV)', title='sine')
ax.grid()
plt.show()

How can I shade the vertical slices (from bottom to top) of the chart where the y-value of the plot is between (e.g.) 1.25 and 0.75 automatically?

the sine is just a sample here, the actual values of the plot are less regular.

I have seen FIll between two vertical lines in matplotlib, which looks similar to this question, but the answer there shades a region between fixed x values. I want the shaded region to be determined by the y values.

retorquere
  • 1,496
  • 1
  • 14
  • 27
  • 1
    Ah, but the marked duplicate shades a region between fixed x values. I want the shaded region to be determined by the y values. – retorquere Jun 06 '18 at 14:29

2 Answers2

13

You might be looking for ax.fill_between, which is pretty flexible (see the linked documentation).

For your specific case, if I understand correctly, this should be enough:

fig, ax = plt.subplots()
ax.plot(s)
ax.set(xlabel='time (s)', ylabel='voltage (mV)', title='sine')
ax.fill_between(range(len(s)), min(s), max(s), where=(s < 1.25) & (s > 0.75), alpha=0.5)
ax.grid()

enter image description here

heilala
  • 770
  • 8
  • 19
sacuL
  • 49,704
  • 8
  • 81
  • 106
  • I'm looking to shade the vertical bar though where the function is between those values. – retorquere Jun 06 '18 at 14:24
  • See my edit, I think that's what you want? – sacuL Jun 06 '18 at 14:31
  • Yes! I don't know why I can''t mark this as the answer, it clearly is. – retorquere Jun 06 '18 at 14:32
  • I'm trying to do the same with https://gist.github.com/ccfa18896996ce8128f13687f32acca8 but for some reason, I'm getting two colored columns when I try to shade using fill_between(df.index, ymin, ymax, where=(df.traffic_density > 18) & (df.traffic_density < 40)) even when (I think) there's only a single contiguous region where the value is between 18 and 40. Any idea? – retorquere Jun 12 '18 at 11:54
  • It works as expected for me (only one vertical bar for your region of interest). It's a datetime column on your x axis, so I changed that to numeric using `np.linspace`. i.e, I use `fill_between(x=np.linspace(min(plt.xlim()), max(plt.xlim()), len(df)), ymin, ....)`. Bit of a hack, and there is probably a better way to do it, but it works – sacuL Jun 12 '18 at 12:41
  • I'm fine with hacks (I just need the bloody graph to plot), but using "ax2.fill_between(np.linspace(min(plt.xlim()), max(plt.xlim()), len(df)), ymin, ymax, color=color, alpha=0.3, where=(df.traffic_density > 10) & (df.traffic_density < 40))", I'me getting the same 2 shaded regions. – retorquere Jun 14 '18 at 12:24
  • There are two separate regions where `traffic_density` is between 10 and 40 (you can see this if you do `np.diff(df.loc[(df.traffic_density > 10) & (df.traffic_density < 40)].index)`, they are separated by 501 rows). There is only one region where it is between 18 and 40 (which you were trying to do in your last comment), so only one bar is produced there – sacuL Jun 14 '18 at 13:02
  • 1
    Dammit, you're right. My apologies for abusing your time and expertise like this. There's data missing in that file which is potentially a significant problem for us - happy I caught it now rather than later! – retorquere Jun 14 '18 at 16:02
9

You can use ax.axvspan, which apparently does exactely what you want. For better results, usa an alpha value below 0.5, and optionally set color and edge-color/width.

fig, ax = plt.subplots()
ax.plot(s)
ax.set(xlabel='time (s)', ylabel='voltage (mV)', title='sine')
ax.axvspan(0.75, 1.25, alpha=0.2)
ax.grid()
plt.show()

If you want the shading to be in a different orientation (horizontal instead of vertical), there is also the ax.axhspan method.

heltonbiker
  • 26,657
  • 28
  • 137
  • 252
  • I've tried this, and while it gives no errors, I also don't see any shaded area. – retorquere Jun 06 '18 at 14:23
  • That's because, in your current plot sequence, there is no data, I guess. But you could set `ylim` explicitly, even if you don't plot any data. Does this comment make sense in your current situation? – heltonbiker Jun 06 '18 at 20:29
  • I need the shaded area to be dependent on the values of the plot. – retorquere Jun 11 '18 at 18:12