arraysmatplotlibplotcurve

Why can I not get a continous curve when manually splitting a fitted curve into two parts?


I aim to fit the following function to my data:

def P(t, CP, Pan, tau):
    P_val = CP + Pan / (1 + t / tau)
    P_val = np.where(t >= 900, P_val - 0.8 * np.log(t / 900), P_val)
    return P_val

and then plot this function. However, I would like to use two different colours for the cases t<900 and t>=900. Thus, I decided to plot the two parts of the curve separately, as shown below. Nevertheless, this resulted in two curves with a thin space between them, even when trying to explicitly force the incorporation of t=900 into the time arrays used for plotting the two parts of the curve. Somewhat like here:

If forcing on the inclusion of t=900, the gap issue can be solved, but the rest falls apart, and I get two plotted curves: enter image description here

How should I solve this issue, or is there a workaround/another approach (e.g., multicolored lines) for getting a continous curve with two different colors, which I could use here? According to the Matplotlib documentation, LineCollection might be useful but I am not comfortable with matplotlib.collections, unfortunately.

Here is my complete code:

import numpy as np
from scipy.optimize import curve_fit
import matplotlib.pyplot as plt

# Data
t2 = np.array([
    80,
    160,
    200,
    320,
    400,
    640,
    800,
    900,
    1000,
    1280,
    2000,
])

P_t2 = np.array([
    4.64,
    3.97,
    3.79,
    3.48,
    3.36,
    3.18,
    3.11,
    3.08,
    3.06,
    3.01,
    2.94,
])

t_min = 0 
t_cutoff = 7500  

mask = (t2 >= t_min) & (t2 <= t_cutoff)

t_filtered = t2[mask]
P_t_filtered = P_t2[mask]

def P(t, CP, Pan, tau):
    P_val = CP + Pan / (1 + t / tau)
    P_val = np.where(t >= 900, P_val - 0.8 * np.log(t / 900), P_val)
    return P_val

initial_guesses = [3.0, 4.0, 50.0]
bounds = ([1.35, 0, 0], [np.inf, np.inf, np.inf])

popt2, pcov2 = curve_fit(P, t_filtered, P_t_filtered, p0=initial_guesses, bounds=bounds, maxfev=5000)


t_fit2 = np.linspace(1, max(t2), 500)
P_fit2 = P(t_fit2, *popt2)


t_fit2_middle = t_fit2[(t_fit2 >= 80) & (t_fit2 <= 900)]
t_fit2_middle = np.append(t_fit2_middle, 900)               # include 900 explicitly

t_fit2_over = t_fit2[t_fit2 >= 900]
#t_fit2_over = np.append(t_fit2_over, 900)               # include 900 explicitly

P_fit2_middle = P(t_fit2_middle, *popt2)
P_fit2_over1 = P(t_fit2_over, *popt2)

#plotting
plt.plot(t_fit2_middle, P_fit2_middle, color='green', linewidth=2)
plt.plot(t_fit2_over, P_fit2_over1, color='red', linewidth=2)

plt.fill_between(t_fit2_middle, 0, P_fit2_middle, color='green', alpha=0.3, hatch='//')
plt.fill_between(t_fit2_over, P_fit2_over1, color='red', alpha=0.3, hatch='//')

plt.xlim(1, 1250)
plt.ylim(0, 8)
plt.minorticks_on()

ax = plt.gca()
ax.tick_params(labelbottom=False, labelleft=False)

plt.tight_layout()
plt.show()

Solution

  • The issue occurs in the following line of code:

    t_fit2_over = np.append(t_fit2_over, 900)
    

    This causes the data point corresponding to 900 to appear at the end of the second part instead of the beginning, resulting in the graph "wrapping back" to the x coordinate 900 at the final position. The problem can be resolved by modifying the code to:

    t_fit2_over = np.insert(t_fit2_over, 0, 900)
    

    The effect is shown in the figure below:

    Corrected Plot