Friday, February 21, 2014

Arduino Linear Regression Function

Intro

I was a little surprised that no one had written a linear regression function and posted it on the web (perhaps I didn't look hard enough).  I spent a little time researching and wrote the code displayed below.

A few comments:

Because the Arduino language cannot pass arrays to sub-functions, the code passes pointers instead.  That's why the lrCoef variable is passed to the function but isn't passed back.  

Also, it is apparently difficult to find the length of an array in the Arduino language.  It's not impossible, but it appears to be easier to handle if the length is provided up front.  

The Linear Regression Code

// example
float y[4]={3, 4, 5, 6};
float x[4]={2.9875,3.9937,4.9925,5.9925};
float lrCoef[2]={0,0};

void setup(){
  Serial.begin(9600);
  delay(1000);
  
  // call simple linear regression algorithm
  simpLinReg(x, y, lrCoef, 4);
  
  Serial.print(lrCoef[0],8);
  Serial.print(" ");
  Serial.println(lrCoef[1],8);
}


void loop(){
}


void simpLinReg(float* x, float* y, float* lrCoef, int n){
  // pass x and y arrays (pointers), lrCoef pointer, and n.  The lrCoef array is comprised of the slope=lrCoef[0] and intercept=lrCoef[1].  n is length of the x and y arrays.
  // http://en.wikipedia.org/wiki/Simple_linear_regression

  // initialize variables
  float xbar=0;
  float ybar=0;
  float xybar=0;
  float xsqbar=0;
  
  // calculations required for linear regression
  for (int i=0; i<n; i++){
    xbar=xbar+x[i];
    ybar=ybar+y[i];
    xybar=xybar+x[i]*y[i];
    xsqbar=xsqbar+x[i]*x[i];
  }
  xbar=xbar/n;
  ybar=ybar/n;
  xybar=xybar/n;
  xsqbar=xsqbar/n;
  
  // simple linear regression algorithm
  lrCoef[0]=(xybar-xbar*ybar)/(xsqbar-xbar*xbar);
  lrCoef[1]=ybar-lrCoef[0]*xbar;
}

8 comments:

  1. Just what I was looking for :)
    (Doing some work with a robotic arm and potentiometer, and turns out it'd be better to calibrate the potentiometer at pre-set positions every once in a while. It can be done with a simple Excel-like program, but it's way more convenient for it to be inside the ardu to calibrate itself, a.k.a, get the Linear Regression Coefficients).

    i'd like to suggest a minor change to your code, though.


    if you need to do

    a = a + somethingtoadd ;

    it can be done simply as:

    a += somethingtoadd ;

    Shorter and a bit more elegant

    http://arduino.cc/en/Reference/IncrementCompound

    ReplyDelete
    Replies
    1. I'm glad you were able to make use of it. Thanks for the programming tip!

      Delete
  2. Hi John,
    Great code
    How can I use this code in real time to get the tendance of a potentiometer ?
    Thanks you

    ReplyDelete
  3. During compilation I have got an error:

    lin-regression_01.ino: In function 'void simpLinReg(float*, float*, float*, int)':
    lin-regression_01.ino:76:1: error: unable to find a register to spill in class 'POINTER_REGS'


    After long investigation I have moved the block:
    // initialize variables
    float xbar=0;
    float ybar=0;
    float xybar=0;
    float xsqbar=0;

    before void setup() section.
    And now the code is compiled without problems.

    ReplyDelete
  4. I have found errors in the code.
    According to
    http://en.wikipedia.org/wiki/Simple_linear_regression
    and compared to results from another math soft, the regression algorithm must be a bit changed.

    Complete improved code:
    // The Wikipedia example
    // http://en.wikipedia.org/wiki/Simple_linear_regression
    //
    float x[15]={1.47, 1.5, 1.52, 1.55, 1.57, 1.6, 1.63, 1.65, 1.68, 1.7, 1.73, 1.75, 1.78, 1.8, 1.83};
    float y[15]={52.21, 53.12, 54.48, 55.84, 57.20, 58.57, 59.93, 61.29, 63.11, 64.47, 66.28, 68.10, 69.92, 72.19, 74.46};

    float lrCoef[2]={0,0};

    // initialize variables
    float sum_x=0;
    float sum_y=0;
    float sum_xy=0;
    float sum_xx=0;



    void setup()
    {
    Serial.begin(9600);
    delay(200);

    // call simple linear regression algorithm
    simpLinReg(x, y, lrCoef, 15);
    Serial.println("Wikipedia example should give: beta= 61.272, alpha= -39.062");
    Serial.println();
    Serial.println("Calculated coefficients:");
    Serial.println(" BETA ALPHA");
    Serial.print("f(x)= ");
    //Serial.print("BETA: ");
    Serial.print(lrCoef[0], 4);

    //Serial.print(", ALPHA");
    if (lrCoef[1]>0)
    {
    Serial.print("*x + ");
    }
    else
    {
    Serial.print("*x ");
    }
    Serial.println(lrCoef[1], 4);
    }


    void loop()
    {

    }


    void simpLinReg(float* x, float* y, float* lrCoef, int n)
    {
    // pass x and y arrays (pointers), lrCoef pointer, and n. The lrCoef array is comprised of the slope=lrCoef[0] and intercept=lrCoef[1]. n is length of the x and y arrays.
    // http://en.wikipedia.org/wiki/Simple_linear_regression

    // calculations required for linear regression
    for (int i=0; i<n; i++)
    {
    sum_x = sum_x+x[i];
    sum_y = sum_y+y[i];
    sum_xy = sum_xy+x[i]*y[i];
    sum_xx = sum_xx+x[i]*x[i];
    }

    // simple linear regression algorithm
    lrCoef[0]=(n*sum_xy-sum_x*sum_y)/(n*sum_xx-sum_x*sum_x);
    lrCoef[1]=(sum_y/n)-((lrCoef[0]*sum_x)/n);
    }

    ReplyDelete
  5. hello, i have some nonlinear calibration points for which i need output 0 to 5 vdc linear . can you please give me code.

    ReplyDelete
  6. This comment has been removed by the author.

    ReplyDelete