34
$\begingroup$

I wonder what is the best practice for interpolating curves? Usually, I'm using BSplineCurve and adjusting SplineWeights so it would fit better (and assigning more weight around the sharp edges to drag the curve closer to it). Or if I can guess what formula describes the points, I use FindFit. But often, I can't guess the formula and adjusting weights is very tedious, so it's easier to just manually draw the curve along the points. So what is the best way to join points in Mathematica?

Consider these five sets of points describing five resonance curves for example:

 data = {{{100.434, 0.}, {102.461, 0.0909091}, {104.392, 0.318182}, {105.321, 0.545455},
          {106.226, 1.}, {107.108, 0.545455}, {107.965, 0.318182}, {109.608, 0.136364},
          {111.154, 0.0909091}},
         {{100.434, 0.}, {102.461, 0.06}, {104.392, 0.22}, {105.321, 0.46}, {106.226, 1.},
          {107.108, 0.4}, {107.965, 0.12}, {111.154, 0.02}, {113.958, 0.}},
         {{100.434, 0.030303}, {102.461, 0.0505051}, {104.392, 0.0909091},
          {105.321, 0.272727}, {105.867, 0.494949}, {106.226, 1.}, {106.582, 0.636364},
          {107.108, 0.212121}, {107.965, 0.0505051}, {111.154, 0.}},
         {{100.434, 0.}, {102.461, 0.0555556}, {104.392, 0.333333}, {105.321, 0.611111},
          {105.867, 1.}, {106.226, 0.944444}, {106.405, 0.583333}, {106.582, 0.777778},
          {106.933, 1.}, {107.965, 0.444444}, {109.608, 0.166667}, {111.154, 0.0555556}},
         {{100.434, 0.0188679}, {102.461, 0.0566038}, {104.392, 0.}, {105.321, 0.54717},
          {105.867, 0.849057}, {106.226, 1.}, {106.405, 0.433962}, {106.582, 0.886792},
          {106.933, 0.924528}, {107.281, 0.660377}, {107.965, 0.320755},
          {111.154, 0.0566038}}}
$\endgroup$
11
  • $\begingroup$ Are you aware of Interpolation[]? $\endgroup$
    – VLC
    Commented Nov 2, 2012 at 16:53
  • $\begingroup$ @VLC It produces very not smooth curve. $\endgroup$
    – swish
    Commented Nov 2, 2012 at 17:00
  • $\begingroup$ @VLC With Method -> "Spline" it produces quite smooth curve but still not like I would join it manually. Is there a better way? $\endgroup$
    – swish
    Commented Nov 2, 2012 at 17:07
  • $\begingroup$ @PlatoManiac As I said it's five functions. $\endgroup$
    – swish
    Commented Nov 2, 2012 at 17:16
  • $\begingroup$ @swish Show[ListLinePlot[#, InterpolationOrder -> 3, Method -> "Spline", PlotRange -> All, PlotStyle -> Hue[RandomReal[]], Frame -> True, Axes -> None] & /@ data, Epilog -> {Red, PointSize[Medium], Point[Flatten[data, 1]]}] how do you find the interpolation? I guess this is quite a nice interpolation as the functions are at least twice differentiable. $\endgroup$ Commented Nov 2, 2012 at 17:29

2 Answers 2

46
$\begingroup$

So, what is the best way to join points in Mathematica?

There is no one "best way" (not only in Mathematica, but in general); an interpolation scheme that behaves nicely for data set A might be a crapshoot when applied to data set B. It depends on the configuration of your points, and impositions you have on the interpolant (e.g. $C^1$/$C^2$ continuity, preservation of monotonicity/convexity, etc.), with these impositions not always being achievable all at the same time.

Having said this, if you're fine with a $C^1$ interpolant (interpolant and first derivative are continuous), one possibility is to use Akima interpolation. It is not always guaranteed to preserve shape (unless your points are specially configured), but at least for your data set, it does a decent job:

AkimaInterpolation[data_] := Module[{dy}, dy = #2/#1 & @@@ Differences[data];
          Interpolation[Transpose[{List /@ data[[All, 1]], data[[All, -1]], 
                        With[{wp = Abs[#4 - #3], wm = Abs[#2 - #1]}, 
                             If[wp + wm == 0, (#2 + #3)/2, (wp #2 + wm #3)/(wp + wm)]] &
                        @@@ Partition[ArrayPad[dy, 2, "Extrapolated"], 4, 1]}], 
                        InterpolationOrder -> 3, Method -> "Hermite"]]

MapIndexed[(h[#2[[1]]] = AkimaInterpolation[#1]) &, data];

Partition[Table[Plot[{h[k][u]}, {u, 100.434, 111.154}, Axes -> None, 
                     Epilog -> {Directive[Red, AbsolutePointSize[4]], Point[data[[k]]]},
                     Frame -> True, PlotRange -> All],
                {k, 5}], 2, 2, 1, SpanFromBoth] // GraphicsGrid

Akima interpolants for resonance curves

Note that in the fifth plot, the Akima interpolant has a slight wiggle before hitting the third point; this, as I said, is due to the fact that Akima's scheme does not guarantee that it will respect the monotonicity of the data. If you want something that fits a bit more snugly, one scheme you can use is Steffen interpolation, which is also a $C^1$ interpolation method:

SteffenEnds[h1_, h2_, d1_, d2_] :=
       With[{p = d1 + h1 (d1 - d2)/(h1 + h2)}, (Sign[p] + Sign[d1]) Min[Abs[p]/2, Abs[d1]]]

SteffenInterpolation[data_?MatrixQ] := Module[{del, h, pp, xa, ya},
  {xa, ya} = Transpose[data]; del = Differences[ya]/(h = Differences[xa]);
  pp = MapThread[Reverse[#1].#2 &, Map[Partition[#, 2, 1] &, {h, del}]]/
       ListConvolve[{1, 1}, h];
  Interpolation[Transpose[{List /@ xa, ya, 
                Join[{SteffenEnds[h[[1]], h[[2]], del[[1]], del[[2]]]},
                     ListConvolve[{1, 1}, 2 UnitStep[del] - 1] *
                     MapThread[Min, {Partition[Abs[del], 2, 1], Abs[pp]/2}],
                     {SteffenEnds[h[[-1]], h[[-2]], del[[-1]], del[[-2]]]}]}], 
                InterpolationOrder -> 3, Method -> "Hermite"]]

MapIndexed[(w[#2[[1]]] = SteffenInterpolation[#1]) &, data]

Partition[Table[Plot[{w[k][u]}, {u, 100.434, 111.154}, Axes -> None, 
                     Epilog -> {Directive[Red, AbsolutePointSize[4]], Point[data[[k]]]},
                     Frame -> True, PlotRange -> All],
                {k, 5}], 2, 2, 1, SpanFromBoth] // GraphicsGrid

Steffen interpolants for resonance curves

Note that the interpolants from Steffen's method are a lot less wiggly, though the interpolant turns more sharply at its extrema. The advantage of using Steffen is that it is guaranteed to preserve the shape of the data, which might be more important than a smooth turn in some applications.

My point, now, is that sometimes, one must try a number of interpolation schemes to see what is most suitable for the data at hand, for which plotting the interpolant along with the data it is interpolating is paramount.

$\endgroup$
3
  • $\begingroup$ This is a fantastic answer! Thank you. $\endgroup$
    – Gleno
    Commented Sep 29, 2013 at 14:28
  • $\begingroup$ Well, I did my best. :) i'll note that the "Akima interpolation" implemented here is his older method; he has a newer method, but I haven't gotten around to writing a Mathematica implementation. $\endgroup$ Commented May 2, 2015 at 3:19
  • $\begingroup$ It's been almost two years now, and I have to thank you again - because this answer has been very influential on what I've been using (Akima, and Steffen interpolations mostly). I wrote some C code for it that my employer is using now. I'll check out this newer method as well, thanks. :) $\endgroup$
    – Gleno
    Commented May 2, 2015 at 23:21
24
$\begingroup$

May not turn out to be a very general method but here I will adapt the Interpolation function of MMA in a way so that smoother result can be obtained for your specific data set.

Interpolation vs. ListLinePlot

First lets see how the default Interpolation behaves compared to the interpolating function used when we call ListLinePlot with same interpolation specification. We use Method-> "Spline" in both cases and vary the InterpolationOrder from 1 to 4.

GraphicsGrid[{
 Table[ff =Interpolation[#, Method -> "Spline", InterpolationOrder -> i] & /@data;
       Plot[#[x] & /@ ff // Release, {x, 101, 111},Frame -> None, Axes -> True,
       Epilog -> {Red, PointSize[Medium], Point[Flatten[data, 1]]}],
      {i, 1, 4}],
 Table[Show[
       ListLinePlot[#, InterpolationOrder -> i, Method -> "Spline",PlotRange -> All,
       PlotStyle -> Hue[RandomReal[]],Frame -> None, Axes -> True] & /@ data, 
       Epilog -> {Red, PointSize[Medium], Point[Flatten[data, 1]]}],
      {i,1, 4}]
  }, Frame -> All, AspectRatio -> .5]

enter image description here

The plots in the first column are using default Interpolation function and they starts to oscillate quite naturally as from left to right higher order splines are used. We notice the plots using ListLinePlot are definitely more similar to what the OP is aspiring to achieve. We can get a less bumpy interpolation that still belongs to the $C^{3}(X)$ space of three times differentiable functions as follows.

Function I (ListPlotInterpolation):

Options[ListPlotInterpolation] = {InterpolationOrder -> 3,Method -> Automatic};
ListPlotInterpolation[data_, OptionsPattern[]] := Module[{pic, pts},
      pic = ListLinePlot[data, Method -> OptionValue[Method],
            InterpolationOrder -> OptionValue[InterpolationOrder],PlotRange -> All];
      Interpolation[First@Cases[pic, Line[pts : {{_, _} ..}] -> pts, Infinity], 
             InterpolationOrder -> OptionValue[InterpolationOrder], 
             Method -> OptionValue[Method]
       ]
 ];

Usage:

ListPlotInterpolation[data[[5]], Method -> "Spline",InterpolationOrder -> 3]

InterpolatingFunction[{{100.434,111.154}},<>]

In the following pic the plot in the left shows interpolation of the five given data sets using the above function and all of them are indeed less oscillating than the default Interpolation results with same options at the right . enter image description here

Taming the derivatives!

We can make the above situation a little better by taking the design advantage of the magic function Interpolation. We can always specify the derivatives numerically at the grid points to control the bumpiness of the interpolating function. We will first use ListPlotInterpolation'[] to estimate the derivative at the given grid points. Then we use the TotalVariationFilter to regularize the discrete gradient further on.

Function II (InterpolationReduced):

Options[InterpolationReduced] = {InterpolationOrder -> 3, Method -> Automatic};
InterpolationReduced[data_, OptionsPattern[]] := 
    Module[{fun, coords, value},
        fun = ListPlotInterpolation[data, Method -> OptionValue[Method], 
        InterpolationOrder -> OptionValue[InterpolationOrder]];
        coords = data // Transpose // First;
        value = Transpose@{data // Transpose // Last};
        Interpolation[Transpose[{Transpose@{coords}, value,
           (* You can change the filter parameter here *) 
           TotalVariationFilter[fun'[coords], 1, Method -> "Laplacian", 
           MaxIterations -> 200]}], 
           InterpolationOrder -> OptionValue[InterpolationOrder]
          ]
      ]

Usage: One can call the above function in similar fashion as the first one.

Now lets see how the gradients looks in case of three methods (default,ListLinePlot based and the TotalVariationFilter based). Each row of he following figure represent the methods respectively. From let to right each column represent InterpolationOrder of 1 to 4.

enter image description here

Compare!

Below in the last figure we show how all the three methods compare in terms of irregularity/bumpiness of their resulting interpolation output given the output needs to be a $C^{3}(X)$ class function. See the most irregular fifth data set separately. enter image description here enter image description here

BR

$\endgroup$
2
  • $\begingroup$ What a great answer! $\endgroup$
    – Lou
    Commented Nov 3, 2012 at 8:06
  • $\begingroup$ Your ListPlotInterpolation-Method and the Improvement with the TotalVariationFilter works wonders with my data Sets as well! Great and very detailed answer! $\endgroup$
    – Martin
    Commented May 28, 2014 at 6:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.