Put one curve exactly underneath the other in Matplotlib

Viewed 154

I have two curves in Matplotlib and I would like to have them exactly beneath each other. My naive approach was to just decrease the values of the one curve by a constand factor. Strangely in Matplotlib (opposed to Excel) the created curves have some deviations and are not always beneath each other.

Here is my code:

from matplotlib import pyplot as plt
from scipy.interpolate import interp1d
import numpy as np
load = [0.0, 0.1, 0.5, 0.7, 0.4, 0.5, 0.4, 0.3, 0.4, 0.5, 0.65, 0.75, 0.8, 0.65, 0.15, 0.55, 0.1, 0.4, 0.0, 0.25, 0.25, 0.35, 0.6, 0.25, 0.05]
#demand = [0.35, 0.25, 0.15, 0.1, 0.1, 0.2, 0.40, 0.50, 0.50, 0.40, 0.40, 0.47, 0.47, 0.40, 0.35, 0.35, 0.4, 0.5, 0.65, 0.7, 0.65, 0.5, 0.4, 0.35, 0.25]
help = 0.025;
demand = [0.0-help, 0.1-help, 0.5-help, 0.7-help, 0.4-help, 0.5-help, 0.4-help, 0.3-help, 0.4-help, 0.5-help, 0.65-help, 0.75-help, 0.8-help, 0.65-help, 0.15-help, 0.55-help, 0.1-help, 0.4-help, 0.0-help, 0.25-help, 0.25-help, 0.35-help, 0.6-help, 0.25-help, 0.05-help]
hours = list(range(25)) # [0, 1, 2, ... 22, 23, 24]
labels = [f'{h:02d}:00' for h in hours] # ["00:00", "01:00", ... "23:00", "24:00"]


f = interp1d(hours, load, kind='cubic')
f2 = interp1d(hours, demand, kind='cubic')


xnew = np.linspace(0, 24, num=500, endpoint=True)

plt.xticks(np.arange(0, 25, step=1))  # Set label locations.
plt.xticks(np.arange(25), labels)  # Set text labels.
plt.xticks(np.arange(25), labels, rotation=90)

plt.plot(hours, load, 'o', xnew, f(xnew), '-', xnew, f2(xnew), '--')
#plt.legend(['data', 'linear', 'cubic'], loc='best')
plt.ylabel("Electrical power in W", fontsize=14, labelpad=8)
plt.xlabel("Time of day", fontsize=14, labelpad=8)
plt.show()


plt.xticks(np.arange(0, 25, step=1))  # Set label locations.
plt.xticks(np.arange(25), labels)  # Set text labels.
plt.xticks(np.arange(25), labels, rotation=90)
plt.xlim(xmin=0.0)

xnew = np.linspace(0, 24, num=500, endpoint=True)
plt.xticks(np.arange(0, 25, step=1))  # Set label locations.
plt.xticks(np.arange(25), labels)  # Set text labels.
plt.xticks(np.arange(25), labels, rotation=90)


plt.plot(xnew, f(xnew), color="gold", linewidth=3)
plt.plot(xnew, f2(xnew), color="green", linewidth=3)
#plt.fill_between(xnew, f(xnew), color="gold", alpha=0.30, edgecolor=None)
#plt.fill_between(xnew, f2(xnew), color="red", alp_ha=0.30, edgecolor=None)

plt.legend( ['Electricity generation', 'Electricity demand'], bbox_to_anchor=(0.5, 1.2), loc='upper center' )
plt.ylabel("Electrical power", fontsize=14, labelpad=8)
plt.xlabel("Time of day", fontsize=14, labelpad=8)
plt.tick_params(labelleft=False)
plt.savefig('Generation_Demand_DSM.png', edgecolor='black', dpi=400, bbox_inches='tight')
plt.show()

And here you see the results. I illustratively marked some areas where the curves are either almost identical (and thus the one is not far away from the other) and areas where the deviations are too high.enter image description here Any ideas how I can do that in a 'clean' way? I'd appreciate every comment.

Update: As this seems to be a difficult task I'd like to know, if it is somehow possible to just get the curve with a transparent background in Matplotlib such that I can just create the curve and then manually place it under the original curve (by using e.g. Paint)?

So there is no option in Matplotlib to just create the curve with a transparent background?

1 Answers

As many people have already suggested in the comments when you subtract the y values only y-axis is shifted while x-axis remains the same, thus not creating the effect you want. This is mainly because of the varying slope in your curve. However, we can use the slope information to artificially create a shift.

The effect will need further refining and fine-tuning in order to get the exact displacement. I am sharing the code here as a starting point.

from matplotlib import pyplot as plt
from scipy.interpolate import interp1d
import numpy as np
load = [0.0, 0.1, 0.5, 0.7, 0.4, 0.5, 0.4, 0.3, 0.4, 0.5, 0.65, 0.75, 0.8, 0.65, 0.15, 0.55, 0.1, 0.4, 0.0, 0.25, 0.25, 0.35, 0.6, 0.25, 0.05]
shift = 0.02;
demand = [l-shift for l in load]
hours = list(range(25)) # [0, 1, 2, ... 22, 23, 24]
labels = [f'{h:02d}:00' for h in hours] # ["00:00", "01:00", ... "23:00", "24:00"]

fig = plt.figure(figsize=(20,10))
f = interp1d(hours, load, kind='quadratic')
f2 = interp1d(hours, demand, kind='quadratic')


xnew = np.linspace(0, 24, num=500, endpoint=True)    

plt.xticks(np.arange(0, 25, step=1))  # Set label locations.
plt.xticks(np.arange(25), labels)  # Set text labels.
plt.xticks(np.arange(25), labels, rotation=90)
plt.xlim(xmin=0.0)

xnew = np.linspace(0, 24, num=500, endpoint=True)
plt.xticks(np.arange(0, 25, step=1))  # Set label locations.
plt.xticks(np.arange(25), labels)  # Set text labels.
plt.xticks(np.arange(25), labels, rotation=90)

xshift = xnew+np.concatenate(([0],np.diff(f(xnew))*7))

plt.plot(xnew, f(xnew), color="gold", linewidth=5)
plt.plot(xshift, f2(xnew), color="green", linewidth=5)

# plt.fill_between(xnew+np.concatenate(([0],np.diff(f(xnew))*7)), 
#                  f(xnew)-0.02, f(xnew), color="gold", alpha=0.30, edgecolor=None)
#plt.fill_between(xnew, f2(xnew), color="red", alp_ha=0.30, edgecolor=None)

plt.legend( ['Electricity generation', 'Electricity demand'], bbox_to_anchor=(0.5, 1.2), loc='upper center' )
plt.ylabel("Electrical power", fontsize=14, labelpad=8)
plt.xlabel("Time of day", fontsize=14, labelpad=8)
plt.tick_params(labelleft=False)
plt.savefig('Generation_Demand_DSM.png', edgecolor='black', dpi=400, bbox_inches='tight')
plt.show()

COMMENTS:

  1. I have also simplified your demand values assignment using list comprehension.
  2. The slope is obtained using np.diff The slope also takes care of +ve and -ve directions and hence can be directly used to shift the plot.
  3. A multiplier is used to adjust the spacing.
  4. To fine tune the plot you may use different multipliers for different directions and slope thresholds.

Here's the output obtained using the above code. HTH. Plot

UPDATE 1:

Saving plots using transparent background with matplotlib

As requested by the OP if you simply want to generate the plot without other plot elements you can do this with the following code change:

# plt.plot(xnew, f(xnew), color="gold", linewidth=5)
plt.plot(xshift, f2(xnew), color="green", linewidth=5)

# plt.fill_between(xnew+np.concatenate(([0],np.diff(f(xnew))*7)), 
#                  f(xnew)-0.02, f(xnew), color="gold", alpha=0.30, edgecolor=None)
#plt.fill_between(xnew, f2(xnew), color="red", alp_ha=0.30, edgecolor=None)

# plt.legend( ['Electricity generation', 'Electricity demand'], bbox_to_anchor=(0.5, 1.2), loc='upper center' )
# plt.ylabel("Electrical power", fontsize=14, labelpad=8)
# plt.xlabel("Time of day", fontsize=14, labelpad=8)
# plt.tick_params(labelleft=False)

plt.xticks([])
plt.yticks([])

plt.gca().spines['top'].set_visible(False)
plt.gca().spines['right'].set_visible(False)
plt.gca().spines['left'].set_visible(False)
plt.gca().spines['bottom'].set_visible(False)

plt.savefig('Generation_Demand_DSM_second.png', edgecolor='none', dpi=400, bbox_inches='tight', transparent=True)
plt.show()

Which generates an output like this (note it is rendered with white background here but the original png will retain transparency): Transparent plot

Now you can create the first curve using original settings and second curve as shown above. You can then superimpose the second curve on the first using paint (or similar tools) and manipulate as needed. HTH

UPDATE 2:

Editing with Inkscape If you are using inkscape the plot becomes significantly editable when you save figure as pdf. You can retain all your original settings and only change the savefig line as follows:

plt.savefig('Generation_Demand_DSM_second.pdf', bbox_inches='tight')

The pdf generated is fully editable in inkscape. You just need to open it like any other file (mostly default settings should work or you can choose poppler import in the dialog that opens). Each element can be selected individually as shown in the screenshot here:

inkscape screenshot

You can manipulate the output as required.

Related