title: Continuous Piecewise-Linear Fit in Python
body: <p>I have a number of short time-series ( maybe 30 - 100 time points ), and they have a general shape : they start high, come down quickly, may or may not plateau near zero, and then go back up. If they don't plateau, they look something like a simple quadratic, and if they do plateau, you may have a long series of zeros.</p>
  
  <p>I'm trying to use the <code>lmfit</code> module to fit a piecewise linear curve that is continuous. <strong>I'd like to infer where the line changes gradients</strong>, that is, I want to know where the curve "qualitatitively" changes gradients. I'd like to know when the gradient stops going down, and when it starts increasing again, in general terms. I'm having a few issues with it :</p>
  
  <ul>
  <li><code>lmfit</code> seems to require at least two parameters, so I'm having to pass <code>_</code>. </li>
  <li>I'm unsure how to constrain one parameter to be greater than another.</li>
  <li>I'm getting <code>could not broadcast input array from shape (something) into shape (something)
  </code> errors</li>
  </ul>
  
  <p>Here's some code. First, my objective function, to be minimised.</p>
  
  <pre><code>def piecewiselinear(params, data, _) :
  
      t1 = params["t1"].value
      t2 = params["t2"].value
      m1 = params["m1"].value
      m2 = params["m2"].value
      m3 = params["m3"].value
      c = params["c"].value
  
      # Construct continuous, piecewise-linear fit
      model = np.zeros_like(data)
      model[:t1] = c + m1 * np.arange(t1)
      model[t1:t2] = model[t1-1] + m2 * np.arange(t2 - t1)
      model[t2:] = model[t2-1] + m3 * np.arange(len(data) - t2)
  
      return model - data
  </code></pre>
  
  <p>I then call,</p>
  
  <pre><code>p = lmfit.Parameters()
  p.add("t1", value = len(data)/4, min = 1, max = len(data))
  p.add("t2", value = len(data)/4*3, min = 2, max = len(data))
  p.add("m1", value = -100., max=0)
  p.add("m2", value = 0.)
  p.add("m3", value = 20., min = 1.)
  p.add("c", min=0, value = 800.)
  
  result = lmfit.minimize(piecewiselinear, p, args = (data, _) )
  </code></pre>
  
  <p>The model is that, at some time t1, the gradient of the line changes, and the same happens at t2. Both of these parameters, as well as the gradients of the line segments ( and one intercept ), need to be inferred.</p>
  
  <p>I could do this using MCMC methods, but I have too many of these series, and it would take too long.</p>
  
  <p>Part of the traceback :</p>
  
  <pre><code>     15     model = np.zeros_like(data)
       16     model[:t1] = c + m1 * np.arange(t1)
  ---&gt; 17     model[t1:t2] = model[t1-1] + m2 * np.arange(t2-t1)
       18     model[t2:] = model[t2-1] + m3 * np.arange(len(data) - t2)
       19 
  
  ValueError: could not broadcast input array from shape (151) into shape (28)
  </code></pre>
  
  <p>A couple of examples of the time-series :
  <img src="https://i.stack.imgur.com/AgCGD.png" alt="No plateau : steady drop and rise">
  <img src="https://i.stack.imgur.com/ohw6Q.png" alt="Long, sharp plateau"></p>
  
  <p>Any and all suggestions welcome. Thank you very much.</p>