Matplotlib - Recreating stackplot gridlines rendering in plot with fill_between

107 views Asked by At

I need to recreate a stackplot as a simple plot with fill_between (no stacking). The gridlines rendering appear to differ and I can't figure out how to make them look the same. Here's the code:

import pandas as pd
import numpy as np

mpl.rcParams.update(mpl.rcParamsDefault)
plt.style.use("ggplot")

_styles = {
'axes.edgecolor': '#bcbcbc',
'axes.facecolor': 'white',
'grid.color': '#b2b2b2',
'grid.linestyle': '--',
}

plt.rcParams.update(_styles)


def plot_a_plot(x_axis, actual, predicted, size=(10,4)):
    plt.figure(figsize=size)
    
    p1 = plt.plot(x_axis, actual, alpha=0.5, label=actual.name, lw=0.5)
    plt.fill_between(x_axis, actual.astype(np.float64), color='#F0A498', alpha=1, zorder=1)
    
    p2 = plt.plot(x_axis, predicted, alpha=0.5, label=predicted.name, lw=0.5)
    plt.fill_between(x_axis, predicted.astype(np.float64), color='C1', alpha=0.5, zorder=0)
    
    plt.grid(True, zorder=10)
    
    plt.title('Plot with fill_between')
    plt.show()

def plot_a_stackplot(x_axis, actual, predicted, size=(10,4)):
    y = np.vstack([actual.astype(float), predicted.astype(float)])
    plt.figure(figsize=size)
    plt.stackplot(x_axis, y, labels=[actual.name, predicted.name], alpha=0.5, edgecolors="face")
   
    plt.title('Stackplot')
    plt.show()

arr = np.random.rand(10)

data = {
  "actual": arr,
  "predicted": arr*2
}

df = pd.DataFrame(data)

x_axis = df.index
actual = df['actual']
predicted = df['predicted']

plot_a_plot(x_axis, actual, predicted)
plot_a_stackplot(x_axis, actual, predicted)

View example here

Changing zorder doesn't seem to have any effect, I also played with alpha levels etc - nothing seems to work. The gridlines on stackplot just look the way it's meant to look, and gridlines on simple plot look muddy.

2

There are 2 answers

2
Tranbi On BEST ANSWER

It seems that zorder doesn't work well with the value 0. In this demo, it is always chosen >=1.
The gird will look the same as with stackplot if you change your function to:

def plot_a_plot(x_axis, actual, predicted, size=(10,4)):
    plt.figure(figsize=size)
    
    p1 = plt.plot(x_axis, actual, alpha=0.5, label=actual.name, lw=0.5)
    plt.fill_between(x_axis, actual.astype(np.float64), color='#F0A498', alpha=1, zorder=2)
    
    p2 = plt.plot(x_axis, predicted, alpha=0.5, label=predicted.name, lw=0.5)
    plt.fill_between(x_axis, predicted.astype(np.float64), color='C1', alpha=0.5, zorder=1)
    
    plt.grid(True, zorder=10)
    
    plt.title('Plot with fill_between')
    plt.show()

Edit: if you want the bottom area (red) to look exactly like with stackplot, you should use the right color. You can find out colors with:

for color in plt.rcParams['axes.prop_cycle']:
    print(color)

The first one is the one you're looking for: #E24A33 (C0 would work too)

From there, the second call on fill_between should fill between actual and predicted, otherwise the fill areas will overlap:

def plot_a_plot(x_axis, actual, predicted, size=(10,4)):
    plt.figure(figsize=size)
    
    p1 = plt.plot(x_axis, actual, alpha=0.5, label=actual.name, lw=0.5)
    plt.fill_between(x_axis, actual.astype(np.float64), color='#E24A33', alpha=0.5, zorder=2)
    
    p2 = plt.plot(x_axis, predicted, alpha=0.5, label=predicted.name, lw=0.5)
    plt.fill_between(x_axis, actual, predicted, color='C1', alpha=0.5, zorder=1)

    plt.grid(True, zorder=10)

    plt.title('Plot with fill_between')
    plt.show()
2
Matt Pitkin On

Try the following (I've not added the labels in this example):

def plot_a_plot(x_axis, actual, predicted, size=(10, 4)):
    fig, ax = plt.subplots(figsize=size)
    ax.plot(x_axis, actual, lw=0.5, alpha=0.5)
    ax.fill_between(
        x_axis,
        actual,
        np.zeros_like(data["actual"]),  # explicitly set the "bottom" to be zero (this is the default so isn't actually required here)
        color='#F0A498',
        alpha=0.5,  # set alpha to 0.5
        zorder=1
    )
    
    ax.plot(x_axis, predicted, lw=0.5, alpha=0.5)
    ax.fill_between(
        x_axis,
        predicted,
        actual,  # explicitly set the "bottom" to be actual, so it doesn't cover actual
        color='C1',
        alpha=0.5,
        zorder=0
    )
    ax.grid(zorder=10)
    fig.show()

This is essentially what is being done by stackplot (although it obviously plots the cumulative "top" and "bottom").