我们在用各种测量模块或设备进行测量时,或多或少会受到各种干扰的影响,使得到的数据与真实情况有所偏差,甚至与正确的数值差之千里。通常我们采用统计学的方法来获得相对正确的值,最基本的就是用平均值方法,即将大量数据进行累加再除以数据个数而获得均值。不过这个方法的前提是大量数据,而我们用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;//子程序返回有效数据的均值
}
今天的文章随机误差的消除方法_matlab剔除误差较大数据「建议收藏」分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/87516.html