How to smooth zig-zag?

How to smooth a zig-zag-line with only a few points?
I have read the following topics here in this forum:
smoothing-interpolation-for-tachometer-data
smoothing-data-with-dates
But I am still struggling with my sort of zig-zag-lines. I achieved the best results so far with the package bsplinekit, but the result is still not what I am looking for.
Any ideas? Here my BSplineKit.jl-example:

begin
    using PlotlyJS
    using BSplineKit, LinearAlgebra, SparseArrays
    # ---
    case_ = 1
    if case_ == 1 # real data
        x_in = [0.114, 0.112, 0.11, 0.107, 0.105, 0.103, 0.102, 0.0998, 0.0983, 0.097, 0.0969, 0.094, 0.0935, 0.0928, 
            0.0928, 0.0917, 0.093, 0.0918, 0.092, ]
        y_in = [-0.0126, -0.0116, -0.0119, -0.0113, -0.0106, -0.00912, -0.00981, -0.0089, -0.00685, -0.00702, -0.00574, 
            -0.00603, -0.00516, -0.00431, -0.00328, -0.00135, -0.000356, -0.000367, 0.00181,]
    elseif case_ == 2 # synthetic data
        x_in = collect(1:1.0:10000)
        y_in = [x^1.35 + x*rand() for x in 1:length(x_in)] 
    else
        error("Case not defined!")
    end
end
# ---
begin
    # --- code taken from:
    # --- https://discourse.julialang.org/t/smoothing-interpolation-for-tachometer-data/56117/20
    n_breakpoints = 4
    spline_order  = 3 # order = 4 is equal to cubic splines
    # Create B-spline basis of "spline_order" order (spline_order = 4 <=> cubic splines) with chosen breakpoints
    xbreaks = LinearAlgebra.LinRange(extrema(x_in)..., n_breakpoints)
    B_ = BSplineBasis(BSplineOrder(spline_order), xbreaks)
    # Construct spline from the given data by @jipolanco
    C_ = BSplineKit.collocation_matrix(B_, x_in, SparseMatrixCSC{Float64})  # evaluates basis functions at data points
    coefs = LinearAlgebra.qr(C_) \ y_in       # spline coefficients
    fspline = BSplineKit.Spline(B_, coefs)
    y_smoothed = fspline.(x_in)
    # --- plot:
    fn_ = joinpath(raw"C:\tmp\plt", string("smoothing_spline_order_", spline_order, ".svg"))
    fig_hdl = PlotlyJS.Plot([PlotlyJS.scatter(; x= x_in, y= y_in, name= "data", mode= "markers+lines"), 
        PlotlyJS.scatter(; x= x_in, y= y_smoothed, name= "smoothed")], 
        PlotlyJS.Layout(;title_text = string("Data Points: ", length(x_in), ", Break Points: ", n_breakpoints, 
        ", Spline Order: ", spline_order))        )
    PlotlyJS.savefig(fig_hdl, fn_)
end

You want the number of B-spline breakpoints (n_breakpoints) to be smaller than the number of data points, otherwise you won’t get a smoothing spline. This is not the case in your case 1.

Also, I’m not sure if this is intentional, but your x_in data is not monotonously increasing, which may cause some issues. Maybe you intended to fit a parametric spline instead?

PS: for next time, It would be easier to help if you showed us the resulting plot.

1 Like

Thanks for your comment, indeed with 5 breakpoints and 19 data points I can clearly see the smoothing effect and if I reduce the spline order to 3, it looks really nice:

Here my proposal for a smoothing function, comments are welcome!

using BSplineKit, LinearAlgebra, SparseArrays
# ---
function _MyLibSmoothing(_x_vec::Vector{<:Number}, _y_vec::Vector{<:Number}, _x_smooth::Union{Nothing, <:Number, Vector{<:Number}}=nothing; 
    _spline_order::Int=3, _n_breakpts::Int=0)
    if length(_x_vec) == length(_y_vec)
        _n_pts = length(_x_vec)
        if _n_pts < 5
            error("Input vectors x and y too short, min values are 5 points!")
        end
    else
        error("Input vectors x and y must have the same length!")
    end
    if _n_breakpts == 0
        if _n_pts < 10
            _n_breakpts = floor(Int, _n_pts / 2)
        elseif _n_pts < 50
            _n_breakpts = floor(Int, _n_pts / 4)
        else
            _n_breakpts = floor(Int, _n_pts / 10)
        end
    else
        if _n_breakpts > _n_pts
            error("Reduce braekpoints! - num Breakpoints: ", _n_breakpts, " - num data points: ", _n_pts, "!")
        elseif _n_breakpts > floor(Int, _n_pts / 2)
            @warn("Too many breakpoints for significant smoothing!")
        end
    end
    if _spline_order != 3
        if _spline_order < 2 || _spline_order > 4
            error("Spline Order must be in the range of [2 .. 4], specified is: ", _spline_order, "!")
        end
    end
    if isnothing(_x_smooth)
        _x_smooth = _x_vec
    else
        if minimum(_x_smooth) < minimum(_x_vec)
            error("Only interpolation allowed: X-values for smoothing below range!")
        end
        if maximum(_x_smooth) > maximum(_x_vec)
            error("Only interpolation allowed: X-values for smoothing above range!")
        end
    end 
    # ---
    _xbreaks        = LinearAlgebra.LinRange(extrema(_x_vec)..., _n_breakpts)
    _BSpl_base      = BSplineBasis(BSplineOrder(_spline_order), _xbreaks)
    # --- Construct spline from the given data by @jipolanco
    _colloc_matrix  = BSplineKit.collocation_matrix(_BSpl_base, _x_vec, SparseMatrixCSC{Float64})  # evaluates basis functions at data points
    _spline_coeffs  = LinearAlgebra.qr(_colloc_matrix) \ _y_vec       # spline coefficients
    _fspline        = BSplineKit.Spline(_BSpl_base, _spline_coeffs)
    # ---
    return _x_smooth, _fspline.(_x_smooth)
end