Arduino测量误差数据的处理——莱特、格拉布斯准则剔除异常数据

    我们在用各种测量模块或设备进行测量时,或多或少会受到各种干扰的影响,使得到的数据与真实情况有所偏差,甚至与正确的数值差之千里。通常我们采用统计学的方法来获得相对正确的值,最基本的就是用平均值方法,即将大量数据进行累加再除以数据个数而获得均值。不过这个方法的前提是大量数据,而我们用Arduino这类工控设备进行动态测量和控制时,会受到采样频率和响应时间的限制,不可能进行数万次的采样后再进行处理。

    那如果我们采样几十个数据后就马上求取均值是不是可以呢?答案是肯定的,不过不能采用简单的直接求均值方法,因为在样本数据比较少的情况下,即使只有极少量的异常数据,都会造成均值与正确值的较大偏离。例如我们有10个人,其中一个是马云,另9人年薪在15万左右,结果一求平均年薪,我们每人的年薪都被平均到10亿了。在测量中,我们也会碰到这种情况,由于外界偶发的干扰以及测量设备内部产生的电子干扰等,都会出现异常的数据。那怎么办呢?我们自然想到先将这些异常数据剔除掉,然后再求均值。这种方法的基本思想是:给定一置信概率,确定相应的置信区间,凡超过置信区间的误差就认为是粗差,应予以剔除。用于粗大误差剔除的常见方法有莱特检验法和格拉布斯检验法。

    我们先简单的了解下莱特检验法和格拉布斯检验法。首先我们假定测量获得的数据呈正态分布,通俗点讲,就是测量获得的数据基本散布在正确值(真值)的附近,越靠近中心数据个数越多。这里我们先导出一个“残差”的概念,设第i次获得的数据为Xi,它与真值的差,我们称之为“残差”,不过我们根本不知道真值是多少,因此我们就用均值Xbar来代替真值,利用残差和数据量N,我们可以用贝塞尔公式求得σ(x),请参看下图。莱特准则就是残差|Vi|>3σ(x)时就作为粗大误差数据,予以剔除。在测量数据分布不能确定,对测量次数没有太大要求的情况下,通常取2更有适用性。看下图:

   我们再来看格拉布斯准则为:|Vi|>g(n,a)σ(x)时就作为粗差数据,格拉布斯准则中的g(n,a)是一个取决于测量次数n和置信概率a,置信概率就是测量数据落在这个区间中的概率,格拉布斯准则中最常用置信概率为95%和99%,即如果采用a=95%,意味着有95%的数据是落在可信的区间里。这里特别要注意的是,置信概率越大,则置信区间越大,似乎是大一点好,其实并不然,置信区间越大,最大偏差的范围也就越大,那异常数据落入这个区间的概率也越大,即置信概率大的情况下,异常数据就可能没有被排除掉,反而造成我们的平均值偏离了正确值。g(n,a)的值我们可以通过下面的表来查看:

    下面说下程序的实现,本程序建立了一个Detection,包含了莱特准则、格拉布斯95%、格拉布斯99%准则,还可以自定义贝塞尔公式前的系数(我称之为准则量),我们只需提供必要的数据后,调用该子程序就可以获得剔除粗差数据后的均值:

double Detection(double data[],double baddata[],int datanum,int badnum,int rule)

    Detection子程序返回的是剔除粗差数据后的平均值;参数data输入时为原始测量数据,返回时,前datanum个数据为有效数据(即剔除粗差数据后的数据);参数baddata无输入数据,输出为被剔除的数据;参数datanum输入为原始测量数据个数;参数datanum无输入数据,输出为剔除的数据个数;参数rule为莱特or格拉布斯准则选择,3为莱特准则,4为格拉布斯95%,5为格拉布斯99%,小于3为自定义准则量。

    程序采用了循环检测方式,即选定某个准则后,根据该准则剔除粗差数据后,再进行检测,如果还有粗差数据,再次剔除该粗差数据,依次循环,直到没有可剔除的粗差数据为止(或有效数据不足6个)。

    此外,子程序中的Serial.print和Serial.println语句是用于调试和观察粗差数据的处理过程用,在正式使用该子程序时,应该注解掉或直接删除这些Serial.print语句。

    本程序在Arduino UNO板上测试运行过,只需直接下载到Arduino UNO板即可,下面是完整的程序:

/*
本程序建立了一个Detection子程序,包含了莱特准则、格拉布斯95%、格拉布斯99%准则,
还可以自定义贝塞尔公式前的系数(我称之为准则量),
我们只需提供必要的数据后,调用该子程序就可以获得剔除粗差数据后的均值
 */
 
const int DATAN=15; //原始测试数据的个数
double dt[DATAN]={21.35,20.36,20.37,20.34,20.31,20.2,20.32,20.4,20.33,20.38,20.36,20.29,20.40,20.41,20.95};//原始测试数据
double bdt[DATAN];//存放被剔除的数据
int dn=DATAN;//数据个数
int bdn;//被剔除的数据个数

void setup() {
  Serial.begin(9600); //设置串口波特率9600
  Serial.println(Detection(dt,bdt,dn,bdn,4));//最后一个参数4,表示采用格拉布斯95%准则,可以用其他值进行测试
}

void loop() {
  // put your main code here, to run repeatedly:

}


//误差数据剔除程序,返回有效数据的平均值
//参数data输入为原始测量数据,返回时,前datanum个为有效数据
//参数baddata无输入数据,输出为被剔除的数据
//参数datanum输入为原始测量数据个数
//参数badnum无输入数据,输出为剔除的数据个数
//参数rule为莱特or格拉布斯准则选择,3为莱特准则,4为格拉布斯95%,5为格拉布斯99%,小于3为自定义准则量
double Detection(double data[],double baddata[],int datanum,int &badnum,int rule)
{
  double data_b[datanum];//临时存放保留的数据
  double v[datanum]; //残差
  double g95[]={1.15,1.46,1.67,1.82,1.94,2.03,2.11,2.18,2.23,2.29,2.33,2.37,2.41,2.44,2.47,2.50,2.53,2.56,2.58,2.60,2.62,2.64,2.66,2.74,2.81,2.87,2.96,3.17};//格拉布斯95%
  double g99[]={1.16,1.49,1.75,1.94,2.10,2.22,2.32,2.41,2.48,2.55,2.61,2.66,2.71,2.75,2.79,2.82,2.85,2.88,2.91,2.94,2.96,2.99,3.01,3.10,3.18,3.24,3.34,3.58};//格拉布斯99%
  double bsl; //贝塞尔公式结果
  double maxdev; //有效的莱特 or 格拉布斯准则的最大偏差
  double sum; //累加临时存储
  double average; //平均值
  int badindex;//某次剔除数据数
  int validNum=0;//有效数据数
  int proindex=0;//循环的次数
  double lg;//莱特 or 格拉布斯准则的系数
  int i;
  if (rule<=3) //当rule小于等于3时,直接用莱特系数3或自定义的rule值
    lg=rule;
  else if(rule>5) //当rule大于5时,强制设为莱特准则
    lg=3;
  badnum=0;
  while(1)
  {
    if(rule==4) //格拉布斯95%
    {
      if(datanum>=100) lg=g95[27];//数据个数大于100个时
      else if(datanum>=50) lg=g95[26];
      else if(datanum>=40) lg=g95[25];
      else if(datanum>=35) lg=g95[24];
      else if(datanum>=30) lg=g95[23];
      else if(datanum>=25) lg=g95[22];
      else lg=g95[datanum-3];
    }
    else if(rule==5)//格拉布斯99%
    {
      if(datanum>=100) lg=g99[27];
      else if(datanum>=50) lg=g99[26];
      else if(datanum>=40) lg=g99[25];
      else if(datanum>=35) lg=g99[24];
      else if(datanum>=30) lg=g99[23];
      else if(datanum>=25) lg=g99[22];
      else lg=g99[datanum-3];
    }
    proindex++;
    Serial.println("**It is the times to cal.**");
    Serial.println(proindex);
    sum=0;
    for(i=0;i<datanum;i++)
      sum+=data[i];
    average=sum/datanum; //计算平均值
    Serial.println("Your data are: ");
    sum=0;
    for(i=0;i<datanum;i++)
    {
      Serial.println(data[i]);
      v[i]=data[i]-average; //计算残差
      sum+=v[i]*v[i]; //计算残差平方和
    }
    Serial.println("the residual are:");
    for(i=0;i<datanum;i++)
      Serial.println(v[i]);
    bsl=sqrt(sum/(datanum-1)); //计算贝塞尔公式标准差
    maxdev=lg*bsl; //计算最大偏差
    //输出相关信息
    Serial.print("The average is: ");
    Serial.println(average);
    Serial.print("The Bessel Formula is: ");
    Serial.println(bsl);
    Serial.print("The Laite or Grubs level is:");
    Serial.println(lg);
    Serial.print("The Max deviation is: ");
    Serial.println(maxdev);
    //剔除坏值,即剔除粗差数据
    validNum=0;
    badindex=0;
    for(i=0;i<datanum;i++)
      if(fabs(v[i])>=maxdev && maxdev!=0) //当|Vi|>准则偏差值时
      {
        baddata[badnum++]=data[i];//将该Xi作为粗差数据,放入坏数据数组
        badindex++;
      }
      else data_b[validNum++]=data[i];//否则将效数数据暂存到data_b数组
        for(i=0;i<validNum;i++) //将暂存的效数数据送回数据数组data
          data[i]=data_b[i];
    datanum=validNum;//将当前有效数据个数作为数据个数
    if(datanum>5)//有效数据大于5个,则继续进行处理
    {
      if(badindex==0) //若没有可剔除的粗差数据
      {
        Serial.print("There is no baddata , end.");
        Serial.println(proindex);
        break;//跳出循环,即粗差数据处理完毕
      }
      else//否则,即有粗差数据,继续循环处理
      {
        Serial.print("You have got baddata in the times cal!!!  ");
        Serial.println(proindex);
        Serial.print("You have got baddata number is: ");
        Serial.println(badindex);
        Serial.println("the bad data is ");
        for(i=0;i<badnum;i++)
          Serial.println(baddata[i]);
      }
    }
    else break;//有效数据小于等于5个,直接跳出循环
  }
  //误差数据处理完毕
  Serial.println("**Summary**");
  //输出剩余数据
  Serial.println("the valid data are: ");
  for(i=0;i<validNum;i++)
    Serial.println(data_b[i]);
  //输出所有坏值
  Serial.println("the bad data are: ");
  for(i=0;i<badnum;i++)
    Serial.println(baddata[i]);
  Serial.print("The last average is: ");
  Serial.println(average);
  Serial.print("The last Max deviation is: ");
  Serial.println(maxdev);
  return average;//子程序返回有效数据的均值
}

猜你喜欢

转载自blog.csdn.net/m0_61543203/article/details/126780804