matplotlibfillthreshold

Fill between two horizontal lines thresholds in matplotlib


Dears, I need to make a fill between two thresholds in my chart I have tried with my code but it does not display, could you please tell me what I am doing wrong? I would like my figure like contour red like figure 2

fig = plt.figure(figsize=(15, 6))
ax = fig.add_subplot(111)

ax = lacebita['Prob'].plot(figsize=(15, 7), )
xtime = np.linspace(1990,2021,384)


ax.plot(xtime, lacebita['Prob'], 'black', alpha=1.00, linewidth=2, label = 'Deciles')
ax.fill_between(xtime, 0., lacebita['Prob'], lacebita['Prob']< 30., color='red',  alpha=.75)
ax.axhline(50, linestyle='--', color='black',label='Percentile 50')
ax.axhline(33, linestyle='--', color='orange', label='Percentile 33')

ax.set_xlim(1990, 2021)
ax.set_ylim(0, 100, 10)

plt.grid(True)
plt.legend(loc = "upper left")
#ax.autoscale_view()

ax.set_title('Deciles para 12-Meses La Cebita(1990-2020)', fontsize=16)
ax.set_xlim(lacebita.index.min(), lacebita.index.max())

plt.savefig('deciles_12_lacebita.jpg')

enter image description here


Solution

  • There are a couple of ways to go about it. One approach is to fill the space in between the two horizontal threshold lines:

    # Make a fake function
    t = 20
    fs = 10
    samples = np.linspace(0, t, int(fs*t), endpoint=False)
    wave_y = np.sin(samples)
    time_x = np.arange(0, len(wave_y))
    
    # Set upper and lower thresholds where horizontal lines will go and fill in between
    upper_th = 0.5
    lower_th = -0.5
    
    # Plot function
    fig, ax = plt.subplots()
    ax.plot(time_x, wave_y)
    ax.grid()
    ax.set_ylim([-1.25, 1.25])
    ax.set_ylabel('y label')
    ax.set_xlim([0, 125])
    ax.set_xlabel('x label')
    
    # Fill in area under the curve and the horizontal lines
    ax.fill_between(x=time_x, y1=upper_th, y2=lower_th, color='red',  interpolate=True, alpha=.75)
    
    # Horizontal lines
    ax.axhline(upper_th, linestyle='--', color='black', label="upper_th: 0.5")
    ax.axhline(lower_th, linestyle='--', color='orange', label='lower_th: - 0.5')
    ax.legend()
    plt.show()
    

    fill figure

    Or if you change y1 or y2, for example to y1=0, you can play around with where exactly the fill is.

    enter image description here

    Another method is to fill in between the curve and the horizontal dashed lines. To do that you could modify the original data so that the values that go above the upper threshold and below the lower threshold become the threshold values. In other words, we want to make a new y curve that includes the threshold points by eliminating the points that go above/below the threshold so that matplotlib understands that the horizontal lines are part of the y curve.

    # Copy original data, we are now going to modify
    new_wave_y = np.copy(wave_y)
    # Change values outside thresholds to threshold value for fill in
    new_wave_y[new_wave_y < lower_th] = lower_th
    new_wave_y[new_wave_y > upper_th] = upper_th
    

    This way we can use where in fill between to point out where exactly under the curve, including under/above the horizontal lines, matplotlib needs to fill in the area. The full script:

    # Make a fake function
    t = 20
    fs = 10
    samples = np.linspace(0, t, int(fs*t), endpoint=False)
    wave_y = np.sin(samples)
    time_x = np.arange(0, len(wave_y))
    
    # Set upper and lower thresholds where horizontal lines will go and fill in between
    upper_th = 0.5
    lower_th = -0.5
    
    # Copy original data, we are now going to modify
    new_wave_y = np.copy(wave_y)
    # Change values outside thresholds to threshold value for fill in
    new_wave_y[new_wave_y < lower_th] = lower_th
    new_wave_y[new_wave_y > upper_th] = upper_th
    
    # Plot function
    fig, ax = plt.subplots()
    ax.plot(time_x, wave_y)
    ax.grid()
    ax.set_ylim([-1.25, 1.25])
    ax.set_ylabel('y label')
    ax.set_xlim([0, 125])
    ax.set_xlabel('x label')
    
    # Fill in area under the curve and the horizontal lines
    ax.fill_between(x=time_x, y1=new_wave_y, where=(lower_th < new_wave_y), color='red',  interpolate=True, alpha=.75)
    ax.fill_between(x=time_x, y1=new_wave_y, where=(new_wave_y < upper_th), color='red',  interpolate=True, alpha=.75)
    
    # Horizontal lines
    ax.axhline(upper_th, linestyle='--', color='black', label="upper_th: 0.5")
    ax.axhline(lower_th, linestyle='--', color='orange', label='lower_th: - 0.5')
    ax.legend()
    plt.show()
    

    fill_area

    You can get some more information in the Matplotlib fill between demo and the fill between docs.

    Edit:

    If you want to fill in below or above the threshold line, for example fill in below the lower threshold, you can modify the y curve so that the values above the threshold become the threshold value (same as before but reverse) and change the values in fill_between . The full script:

    # Make a fake function
    t = 20
    fs = 10
    samples = np.linspace(0, t, int(fs*t), endpoint=False)
    wave_y = np.sin(samples)
    time_x = np.arange(0, len(wave_y))
    
    # Set upper and lower thresholds where horizontal lines will go and fill in between
    upper_th = 0.5
    lower_th = -0.5
    
    # Copy original data, we are now going to modify
    new_wave_y = np.copy(wave_y)
    # Change values outside thresholds to threshold value for fill in
    new_wave_y[new_wave_y > lower_th] = lower_th
    
    # Plot function
    fig, ax = plt.subplots()
    ax.plot(time_x, wave_y)
    ax.grid()
    ax.set_ylim([-1.25, 1.25])
    ax.set_ylabel('y label')
    ax.set_xlim([0, 125])
    ax.set_xlabel('x label')
    
    # Fill in area under the curve and the horizontal lines
    ax.fill_between(x=time_x, y1=new_wave_y, y2=lower_th, where=(new_wave_y < lower_th), color='red',  interpolate=True, alpha=.75)
    
    # Horizontal lines
    ax.axhline(upper_th, linestyle='--', color='black', label="upper_th: 0.5")
    ax.axhline(lower_th, linestyle='--', color='orange', label='lower_th: - 0.5')
    ax.legend()
    
    plt.show()
    

    fig_fill