2

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?

VanessaF
  • 515
  • 11
  • 36
  • Thanks JohanC for your answers. What I basically want to have is the two curves under each other, nothing more and nothing less. So the green curve should be exactly like the yellow curve but just slightly shifted. In my posted example this is unfortunately not the case. So I'd like to tell Matplotlib to create a second curve that has all the coordinates like the first curve but just reduced by a small factor. – VanessaF Sep 18 '20 at 11:30
  • Thanks JohanC for your comment. Is there no way to get want I really want (which is definetely not what matplotlib creates). Just for every value on the x-axis the corresponding value on the y-axis should be slightly decreased. – VanessaF Sep 18 '20 at 12:23
  • Well, this is exactly what matplotlib created. This is how a displaced curve looks like. What you might want, is an offset curve mentioned in the linked post. [How to get the x,y coordinates of a offset spline from a x,y list of points and offset distance](https://stackoverflow.com/questions/32772638/python-how-to-get-the-x-y-coordinates-of-a-offset-spline-from-a-x-y-list-of-poi). I really doubt Excel creates something like that. See also [this wikipedia article about parallel curves](https://en.wikipedia.org/wiki/Parallel_curve) – JohanC Sep 18 '20 at 12:31
  • Yes I want to have a parallel curve and the current curve created by matplotlibi is NOT what I want – VanessaF Sep 18 '20 at 12:51
  • 4
    Matplotlib is doing the correct thing here. The green curve is exactly the same as the yellow but shifted downwards. Choose a larger offset or use a thinner line to minimize the optical illusion that they are not. also try setting your y limits to be larger. – Jody Klymak Sep 18 '20 at 14:44
  • look at second answer [here](https://stackoverflow.com/a/42190453/7570817) – scleronomic Sep 18 '20 at 14:48
  • Thanks sclernomic for your answer. Okay this seems to be a difficult task using Matplotlib (I tought this should have been an easy thing to do because at the end of the day you only have to shift a curve). – VanessaF Sep 18 '20 at 14:52
  • Is is somehow possible to just get the curve with a transparent background such that I can just created the curve and then manually place it under the original curve (by using e.g. Paint)? – VanessaF Sep 18 '20 at 14:53
  • @Jody Thanks for your answer. Basically I enlarged the image and I can tell you that it is definetely not an optimal illusion. The curves are not shifted correctly. At some points the differences between the curves is bigger compared to other points. – VanessaF Sep 18 '20 at 21:01
  • If the curves aren't shifted correctly, then you didn't shift them correctly. I'd suggest `load = np.array(load); demand = load - help;` to make sure you don't have a typo. If after that they still are not shifted "correctly" then its not clear what you mean by "correct". Rather than simply enlarging the image, you can also zoom using one of the GUI backends. – Jody Klymak Sep 18 '20 at 21:05
  • Thanks Jody for your comment. I checked it and I think I do not have a typo. Even from the posted picture you can see that the differences between the curves are not equal for every x-value. And this is what I mean by 'correctly' shifted. Just image a two curves that have exactly the same shape and at every x-position the perpendicular differnce is exactly the same which is not the case in my example – VanessaF Sep 18 '20 at 21:09
  • Again please plot with a much harder thinner line or zoom in x. Your thick line is touching when the curve have a large slope and look far apart when the curve is flat. But the appearance is only because the thickness of your line is similar to the vertical offset you provided. – Jody Klymak Sep 19 '20 at 03:32
  • Thanks Jody for your comment. I tried it with thinner lines and as expected the diagramm still looks not correct. It was clear for me because on the posted diagramm you can clearly see that the deviation between the two curves is NOT IDENTICAL. Using thinner lines surely does not change this fact. On some x-positions (as marked) the lines lie almost at each other (also with thinner lines) and on others, the difference is quite hight which makes it look weird as in the posted picture. – VanessaF Sep 19 '20 at 07:29
  • @Jody: I tried to use a larger y-axis. But - as expected - this also does not solve the problem. Maybe you should have a closer look again at the posted picture. I think you will realize that the differences between the curves are not identical at every position. So why should adjusting the thickness of the line or the scale of the y-axis change this? – VanessaF Sep 19 '20 at 07:43
  • Zoom in x, not in y. – Jody Klymak Sep 19 '20 at 14:37
  • Please make same plot in excel and demonstrate what excel does differently. – Jody Klymak Sep 19 '20 at 14:38
  • Thanks Jody for your comments. You are right about excel. It produces a similar plot as Matplotlib. Zooming in x (with e.g. plt.xlim(left = 20, right = 23)) does not seem to have any effect. – VanessaF Sep 20 '20 at 07:43
  • Again my question: Is is somehow possible to just get the curve with a transparent background such that I can just created the curve and then manually place it under the original curve (by using e.g. Paint)? – VanessaF Sep 20 '20 at 07:43
  • I don’t know about paint, but you can save the plot as a PDF or eps and open in illustrator and change the opacity of the stroke. You will need to save as vector graphics – Jody Klymak Sep 21 '20 at 14:16
  • Thanks Jody for your comment. I do not have illustrator so I can't do that. Is there no option in matplotlib to just save the curve with a transparent (not white background) and without the axis? – VanessaF Sep 21 '20 at 14:20
  • Drawing a parallel curve is not the same has shifting existing curve downwards. By shifting a curve only in one axis will not result in parallel curve. (It can happen for a line or very simple curves but not always) – Ajay Verma Sep 26 '20 at 09:02
  • Thanks Ajay for your answer. In my case just shifting the curve will do the thing as the curvers are identical. – VanessaF Sep 26 '20 at 09:04

1 Answers1

2

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.

DrSpill
  • 552
  • 4
  • 18
  • Thanks DrSpill for your answer. Bascially your plot looks definetely better than the previous one by me. But it is not the entirely desired output. So I can use that if I can't manage to do the other option and get a the single curve with a transparent background sucht that I can manually shift the curves. Do you know whether this is possible in Matplotlib? I'd appreciate further comments. – VanessaF Sep 26 '20 at 13:15
  • Yes it is possible. Do you use any image processing or vector editor such as GIMP, Inkscape? You can export the plot as pdf and extract each component as a layer. I can add some more info on that. I think there is also a transparent parameter you can send to savefig. – DrSpill Sep 26 '20 at 14:00
  • Thanks DrSpill for your answer and effort. Yes I use Inkscape from time to time (altough I am not the most experiences one). I'd be happy if you would mind adding more information on that. – VanessaF Sep 26 '20 at 14:17
  • I have included the method to edit with Inskcape. It is pretty straightforward and you don't need much expertise to work with it. – DrSpill Sep 26 '20 at 14:33