2

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.

  • `plt.gca().set_axisbelow(False)` should put the grid on top. – JohanC Jan 31 '23 at 11:46
  • 1
    @JohanC it does bring the grid on top, overriding zorder, however the rendering of the gridlines did not change and still look different from that of the stackplot. I included dummy data and import statements. – Rokas Karabevičius Jan 31 '23 at 11:57

2 Answers2

1

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()
Tranbi
  • 11,407
  • 6
  • 16
  • 33
  • That seems to do the trick, at least for the blue plot. Although adding grid on the red plot remains tricky, as reducing alpha changes the shade. Say if I remove colour param, drop alpha to 0.5, it should look like the stackplot, but it doesn't. However this wasn't part of the original question, so yeah +1 for the answer. Thanks. – Rokas Karabevičius Jan 31 '23 at 17:01
  • Check out my edited answer. Also you can *accept* the answer if you consider the problem solved. – Tranbi Feb 01 '23 at 09:21
0

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").

Matt Pitkin
  • 3,989
  • 1
  • 18
  • 32
  • I tried - the output looks pretty much the same as before. I realised I did not add style params in the code, which has now been added for accurate reproduction of the problem at hand. I also found a hacky way to get what I want - by taking a difference between predicted and actual and using the stackplot, which renders everything beautifully out of the box. It bugs me however that I could not find a way to mimic the same exact look with a simple plot. – Rokas Karabevičius Jan 31 '23 at 14:16
  • When I run it it looks ok for me, but it might be a Matploltib version difference (I'm using the very latest 3.6.3) or maybe display differences. – Matt Pitkin Jan 31 '23 at 15:13