Matplotlib - Recreating stackplot gridlines rendering in plot with fill_between - matplotlib

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.

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()

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

Related

How to make the plot's shape round?

I have created a plot, which is working just fine.
But I really want to change its shape to a circle.
This is my current plotting code:
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(5,5), dpi=300)
ax = fig.add_axes([0,0,1,1])
ax.plot(30, 80, marker="o", markersize=20, markeredgecolor="#ed6033", markerfacecolor="#ed6033")
ax.spines['left'].set_position('center')
ax.spines['bottom'].set_position('center')
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
ax.set_facecolor('#8cc9e2')
ax.margins(0.1)
plt.setp(ax.get_xticklabels()[4], visible=False)
plt.xlim(10, 90)
plt.ylim(10, 90)
plt.grid(color='white')
plt.show()
and this is the output I get:
eventually, this is my desired output:
You can clip the path of artists including the background patch using the path of another artist.
Add this snippet before the plt.show() call:
clip_path = plt.Circle(
(0.5, 0.5), 0.5, transform=ax.transAxes, # circle coordinates defined in axes fractions
fill=None, linewidth=0 # makes circle invisible
)
ax.add_patch(clip_path)
ax.patch.set_clip_path(clip_path)

How to access second plot axis parameters?

I am trying to access the ax parameters of the second subplot figure, ax1. I am trying to put a title and remove the overlapping ticks but can't manage to get to them.
Here is the code and figure I have made :
fig, (ax0, ax1)= plt.subplots(nrows=1,
ncols=2,
sharey=True,
tight_layout = True,
gridspec_kw={'width_ratios': [3, 24],'wspace': 0})
ax1=librosa.display.specshow(data=df.iloc[i,2],
sr=fe,
x_axis='time',
y_axis='mel',
htk=False,
x_coords=np.linspace(0,duree[i],df.iloc[i,2].shape[1]),
hop_length=1000,
cmap=plt.get_cmap("magma"),
fmin=0, fmax=fe//2,
vmin=40, vmax=140)
ax0.plot(df.loc[Names[i], "DSP"], df.loc[Names[i], "f_dsp"],
linewidth=1, label=Names[i]) # ,color='grey')
ax0.set_title('Subplot 1')
ax0.set_xlim([20, 100])
ax0.set_ylim([0, fe//2])
ax0.set_ylabel('Fréquence [Hz]')
ax0.set_xlabel('Amplitude [dB ref 1µPa²]')
ax0.grid(visible=True)
ax0.grid(b=True, which='minor', color='k', linestyle=':', lw=0.5)
yticks=np.array([10,100,1000,10000,100000],dtype=int)
yticks_hz = np.unique(np.concatenate((np.array([fe//2],dtype=int),
np.array([250*2**n for n in range(0,int(np.log2(fe//(2*250))))]))))
ax0.set_yticks(yticks, minor=False)
ax0.set_yticks(yticks_hz, minor=True)
ax0.set_yticklabels(yticks,
minor=False,
fontdict={'fontweight':'demibold','fontsize':8})
ax0.set_yticklabels(yticks_hz,
minor=True,
fontdict={'fontsize':8})
# =============================================================================
# Doesnt work :(
# =============================================================================
ax1.set_title("Subplot 2")
ax1.get_yaxislabels().set_visible(False)
ax1.get_xaxislabels()[0].set_visible(False)

Not getting legend in figure/plot

I am sharing Y-axis in two subplots, with the following codes but both shared plots are missing legends in them.
projectDir = r'/media/DATA/banikr_D_drive/model/2021-04-28-01-18-15_5_fold_114sub'
logPath = os.path.join(projectDir, '2021-04-28-01-18-15_fold_1_Mixed_loss_dice.bin')
with open(logPath, 'rb') as pfile:
h = pickle.load(pfile)
print(h.keys())
fig, ax = plt.subplots(2, figsize=(20, 20), dpi=100)
ax[0].plot(h['dice_sub_train'], color='tab:cyan', linewidth=2.0, label="Train")
ax[0].plot(smooth_curve(h['dice_sub_train']), color='tab:purple')
ax[0].set_xlabel('Epoch/iterations', fontsize=20)
ax[0].set_ylabel('Dice Score', fontsize=20)
ax[0].legend(loc='lower right', fontsize=20)#, frameon=False)
ax1 = ax[0].twiny()
ax1.plot(h['dice_sub_valid'], color='tab:orange', linewidth=2.0, alpha=0.9, label="Validation" )
ax1.plot(smooth_curve(h['dice_sub_valid']), color='tab:red')
# , bbox_to_anchor = (0.816, 0.85)
ax[1].plot(h['loss_sub_train'], color='tab:cyan', linewidth=2.0, label="Train")
ax[1].plot(smooth_curve(h['loss_sub_train']), color='tab:purple')
ax2 = ax[1].twiny()
ax2.plot(h['loss_sub_valid'], color='tab:orange', linewidth=2.0, label="Validation", alpha=0.6)
ax2.plot(smooth_curve(h['loss_sub_valid']), color='tab:red')
ax[1].set_xlabel('Epoch/iterations', fontsize=20)
ax[1].set_ylabel('loss(a.u.)', fontsize=20)
ax[1].legend(loc='upper right', fontsize=20)
# ,bbox_to_anchor = (0.8, 0.9)
plt.suptitle('Subject wise dice score and loss', fontsize=30)
plt.setp(ax[0].get_xticklabels(), fontsize=20, fontweight="normal", horizontalalignment="center") #fontweight="bold"
plt.setp(ax[0].get_yticklabels(), fontsize=20, fontweight='normal', horizontalalignment="right")
plt.setp(ax[1].get_xticklabels(), fontsize=20, fontweight="normal", horizontalalignment="center")
plt.setp(ax[1].get_yticklabels(), fontsize=20, fontweight="normal", horizontalalignment="right")
plt.show()
Any idea how to solve the issue?
[1]: https://i.stack.imgur.com/kg7PY.png
ax1 has a twin y-axis with ax[0], but they are two separate axes. That's why ax[0].legend() does not know about the Validation line of ax1.
To have Train and Validation on the same legend, plot empty lines on the main axes ax[0] and ax[1] with the desired color and label. This will generate dummy Validation entries on the main legend:
...
ax[0].plot([], [], color='tab:orange', label="Validation")
ax[0].legend(loc='lower right', fontsize=20)
...
ax[1].plot([], [], color='tab:orange', label="Validation")
ax[1].legend(loc='upper right', fontsize=20)
...

Matplotlib make color map have no transparency

def visualize(goal_x, goal_y, goal_z, epoch_arr):
# %% Create Color Map
colormap = plt.get_cmap("binary")
norm = matplotlib.colors.Normalize(vmin=min(epoch_arr), vmax=max(epoch_arr))
# %% 3D Plot
fig = plt.figure()
ax3D = fig.add_subplot(111, projection='3d')
ax3D.set_facecolor('xkcd:salmon')
ax3D.scatter(goal_x, goal_y, goal_z, s=100, c=colormap(norm(epoch_arr.values)), marker='o')
plt.show()
The above code produces the following picture:
However, as you can see there is a point in the right side that is clearly still not 100% opaque. You can see the grid lines through the point. How do I make the scatter plot points 100% opaque, no transparency?
Some tricks will help. Here I plot all the markers in white first, then plot again on top using the intended color.
import matplotlib
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# make-up some data
goal_x = list(range(10))
goal_y = list(range(10))
goal_z = list(range(10))
epoch_arr = np.linspace(0,1,10)
fig = plt.figure(figsize=(8,8))
ax3D = fig.add_subplot(111, projection='3d')
ax3D.set_facecolor('xkcd:salmon')
# First plot: all markers are in white color
ax3D.scatter(goal_x, goal_y, goal_z, s=500, c='w', marker='o', alpha=1.0, zorder=10)
colormap = plt.get_cmap("binary")
norm = matplotlib.colors.Normalize(vmin=min(epoch_arr), vmax=max(epoch_arr))
#ax3D.scatter(goal_x, goal_y, goal_z, s=100, c=colormap(norm(epoch_arr.values)), marker='o')
# Second plot: use intended colormap
ax3D.scatter(goal_x, goal_y, goal_z, s=500, c='b', marker='o', zorder=11)
plt.show()
The resulting plot:
Setting alpha=1 should be enough.
ax3D.scatter(..., alpha=1)
Alternatively set depthshade=False
ax3D.scatter(..., depthshade=False)
The result will be the same in both cases.

Align ylabel with yticks

The code below draws a plot that looks almost exactly the way I want it to be. However, I'd like the ylabel to be horizontal and left-aligned with the yticks. Currently, the ylabel is placed left relative to the yticks which looks ugly (the image below shows the upper left corner of the plot). Does someone know how to fix this?
import matplotlib.pyplot as plt
import numpy as np
xvals = range(0,10);
yvals = lambda s: [ x*x*s for x in xvals ]
# setting the x=... option does NOT help
yprops = dict(rotation=0, y=1.05, horizontalalignment='left')
plt.subplot(111,axisbg='#BBBBBB',alpha=0.1)
plt.grid(color='white', alpha=0.5, linewidth=2, linestyle='-', axis='y')
for spine_name in ['top', 'left', 'right']:
plt.gca().spines[spine_name].set_color('none')
plt.ylabel('y label', **yprops)
plt.xlabel('x label')
plt.gca().tick_params(direction='out', length=0, color='k')
plt.plot(xvals, yvals(1), 'bo-', linewidth=2)
plt.gca().set_axisbelow(True)
plt.show()
You can adjust the coordinates using ax.yaxis.set_label_coords like in this example.
With your data:
import matplotlib.pyplot as plt
import numpy as np
xvals = range(0,10);
yvals = lambda s: [ x*x*s for x in xvals ]
yprops = dict(rotation=0, x=0, y=1.05)
fig, ax = plt.subplots(1, 1, figsize=(5,3))
ax.set_ylabel('y label', **yprops )
ax.set_xlabel('x label')
ax.plot(xvals, yvals(1), 'bo-', linewidth=2)
print(ax.get_position())
ax.yaxis.set_label_coords(-0.1,1.05)
fig.savefig('cucu.png')
plt.show()
Note that if you go further away, the label will be placed outside the figure. If that is the case, you can adjust the margins before:
fig, ax = plt.subplots(1, 1, figsize=(5,3))
ax.set_ylabel('y label', **yprops )
ax.set_xlabel('x label')
ax.plot(xvals, yvals(1), 'bo-', linewidth=2)
fig.subplots_adjust(left=0.2, bottom=0.2, right=0.8, top=0.8)
ax.yaxis.set_label_coords(-0.2,1.2)
fig.savefig('cucu2.png')
plt.show()
See also this answer