8

My first question here at Stackoverflow... hope my question is specific enough.

I have an array in Swift with measurements at certain dates. Like:

var myArray:[(day: Int, mW: Double)] = []
myArray.append(day:0, mW: 31.98)
myArray.append(day:1, mW: 31.89)
myArray.append(day:2, mW: 31.77)
myArray.append(day:4, mW: 31.58)
myArray.append(day:6, mW: 31.46)

Some days are missing, I just didn't take a measurement... All measurements should be on a line, more or less. So I thought about linear regression. I found the Accelerate framework, but the documentation is missing and I can't find examples.

For the missing measurements I would like to have a function, with as input a missing day and as output a best guess, based on the other measurements.

func bG(day: Int) -> Double {
    return // return best guess for measurement
}

Thanks for helping out. Jan

timbo
  • 13,244
  • 8
  • 51
  • 71
arakweker
  • 1,535
  • 4
  • 18
  • 40

3 Answers3

18

My answer doesn't specifically talk about the Accelerate Framework, however I thought the question was interesting and thought I'd give it a stab. From what I gather you're basically looking to create a line of best fit and interpolate or extrapolate more values of mW from that. To do that I used the Least Square Method, detailed here: http://hotmath.com/hotmath_help/topics/line-of-best-fit.html and implemented this in Playgrounds using Swift:

//  The typealias allows us to use '$X.day' and '$X.mW',
//  instead of '$X.0' and '$X.1' in the following closures.
typealias PointTuple = (day: Double, mW: Double)

//  The days are the values on the x-axis.
//  mW is the value on the y-axis.
let points: [PointTuple] = [(0.0, 31.98),
                            (1.0, 31.89),
                            (2.0, 31.77),
                            (4.0, 31.58),
                            (6.0, 31.46)]

// When using reduce, $0 is the current total.
let meanDays = points.reduce(0) { $0 + $1.day } / Double(points.count)
let meanMW   = points.reduce(0) { $0 + $1.mW  } / Double(points.count)

let a = points.reduce(0) { $0 + ($1.day - meanDays) * ($1.mW - meanMW) }
let b = points.reduce(0) { $0 + pow($1.day - meanDays, 2) }

// The equation of a straight line is: y = mx + c
// Where m is the gradient and c is the y intercept.
let m = a / b
let c = meanMW - m * meanDays

In the code above a and b refer to the following formula from the website:

a: enter image description here

b:enter image description here

Now you can create the function which uses the line of best fit to interpolate/extrapolate mW:

func bG(day: Double) -> Double {
    return m * day + c
}

And use it like so:

bG(3) // 31.70
bG(5) // 31.52
bG(7) // 31.35
ABakerSmith
  • 22,759
  • 9
  • 68
  • 78
  • Thanks a lot for your code (and your edits to my question), ABakerSmith! I'm very pleased with it... Btw, as I said I'm new to Stackoverflow... am I supposed to vote for another badge for you? Would be happy to... :-) – arakweker Apr 16 '15 at 22:25
  • Awesome, I'm glad it helped! If you feel an answer solved your problem you can mark it as correct by pressing the check mark on the left. You can also up vote answers to questions, but I believe you need 15 points for that. – ABakerSmith Apr 16 '15 at 22:29
  • Welcome to Stack Overflow by the way! – ABakerSmith Apr 16 '15 at 22:32
  • Can the same be achieved for a polynomial regression with nth degree in Swift? – user13138159 Nov 01 '20 at 03:14
4

If you want to do fast linear regressions in Swift, I suggest using the Upsurge framework. It provides a number of simple functions that wrap the Accelerate library and so you get the benefits of SIMD on either iOS or OSX without having to worry about the complexity of vDSP calls.

To do a linear regression with base Upsurge functions is simply:

let meanx = mean(x)
let meany = mean(y)
let meanxy = mean(x * y)
let meanx_sqr = measq(x)

let slope = (meanx * meany - meanxy) / (meanx * meanx - meanx_sqr)
let intercept = meany - slope * meanx

This is essentially what is implemented in the linregress function.

You can use it with an array of [Double], other classes such as RealArray (comes with Upsurge) or your own objects if they can expose contiguous memory.

So a script to meet your needs would look like:

#!/usr/bin/env cato

import Upsurge

typealias PointTuple = (day: Double, mW:Double)

var myArray:[PointTuple] = []

myArray.append((0, 31.98))
myArray.append((1, 31.89))
myArray.append((2, 31.77))
myArray.append((4, 31.58))
myArray.append((6, 31.46))

let x = myArray.map { $0.day }
let y = myArray.map { $0.mW }

let (slope, intercept) = Upsurge.linregress(x, y)

func bG(day: Double) -> Double {
    return slope * day + intercept
}

(I left in the appends rather than using literals as you are likely programmatically adding to your array if it is of significant length)

and full disclaimer: I contributed the linregress code. I hope to also add the co-efficient of determination at some point in the future.

timbo
  • 13,244
  • 8
  • 51
  • 71
0

To estimate the values between different points, you can also use SKKeyframeSequence from SpriteKit https://developer.apple.com/documentation/spritekit/skinterpolationmode/spline

import SpriteKit

let sequence = SKKeyframeSequence(keyframeValues: [0, 20, 40, 60, 80, 100], times: [64, 128, 256, 512, 1024, 2048])
sequence.interpolationMode = .spline // .linear, .step

let estimatedValue = sequence.sample(atTime: CGFloat(1500)) as! Double // 1500 is the value you want to estimate
print(estimatedValue)
Senocico Stelian
  • 1,378
  • 15
  • 23