JAVA代码实现SPC中八大判异标准逻辑
网上找了很久SPC八大判异标准的代码实现,没找到JAVA的,自己写了一套,做个备忘。
最终输出异常数据的坐标集,判异标准K值可编辑。效果如下:

/** * Xbar判异标准 * Created by GaoYuman on 2021/12/29 15:57 */ public class XbarAbnormalJudgeEnums { public enum type { ONE(1, "一个点,距离中心线大于K个标准差"), TWO(2, "连续K个点在中心线同一侧"), THREE(3, "连续K个点, 全部递增或全部递减"), FOUR(4, "连续K个点,上下交接"), FIVE(5, "K+1个点中有K个点,距离中心线(同侧)大于2个标准差"), SIX(6, "K+1个点中有K个点,距离中心线(同侧)大于1个标准差"), SEVEN(7, "连续K个点,距离中心线(任一侧)1个标准差以内"), EIGHT(8, "连续K个点,距离中心线(任一侧)大于1个标准差"), ; private final Integer code; private final String desc; type(Integer code, String desc) { this.code = code; this.desc = desc; } public Integer getCode() { return code; } public String getDesc() { return desc; } public static type getByCode(Integer code) { if (code == null) { return null; } return Arrays.stream(type.values()).filter(o -> code.equals(o.getCode())).findFirst().get(); } } /** * 是否选中 */ public enum selectFlag { YES(10, "是"), NO(20, "否") ; private final Integer code; private final String desc; selectFlag(Integer code, String desc) { this.code = code; this.desc = desc; } public Integer getCode() { return code; } public String getDesc() { return desc; } } }
@Data public class SpcXbarAbnormalJudgment implements Serializable { /** | 表字段 id */ private String id; /** | 表字段 spc_id */ private String spcId; /** 标准序号 | 表字段 standard_code */ private String standardCode; /** 标准描述 | 表字段 standard_desc */ private String standardDesc; /** K值 | 表字段 k_value */ private Integer kValue; /** 是否选中 10是 20否 | 表字段 select_flag */ private Integer selectFlag; /** | 表字段 create_time */ private Date createTime; }
/**
* Xbar-R计算工具类
*/
public class XbarRUtils {
/**
* 计算平均值
*
* @param datas
* @return
*/
public static BigDecimal average(List<BigDecimal> datas) {
BigDecimal sum = datas.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal average = divide(sum, BigDecimal.valueOf(datas.size()));
return average;
}
/**
* 计算极差
*
* @param datas
* @return
*/
public static BigDecimal extremeDifference(List<BigDecimal> datas) {
BigDecimal max = datas.stream().max((x1, x2) -> x1.compareTo(x2)).get();
BigDecimal min = datas.stream().min((x1, x2) -> x1.compareTo(x2)).get();
return max.subtract(min);
}
/**
* 计算中位数
*
* @param datas
* @return
*/
public static BigDecimal median(List<BigDecimal> datas) {
BigDecimal median;
Collections.sort(datas);
int size = datas.size();
if (size % 2 == 1) {
median = datas.get((size - 1) / 2);
} else {
median = divide(datas.get(size / 2 - 1).add(datas.get(size / 2)), new BigDecimal(2));
}
return median;
}
public static BigDecimal divide(BigDecimal a, BigDecimal b) {
if (b.compareTo(BigDecimal.ZERO) == 0) {
return BigDecimal.ZERO;
}
return a.divide(b, 6, BigDecimal.ROUND_HALF_UP);
}
/**
* 计算标准差σ=sqrt(s^2)
*
* @return
*/
public static BigDecimal standardDiviation(List<BigDecimal> datas) {
int n = datas.size();
BigDecimal sum = datas.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal avg = sum.divide(new BigDecimal(n), 6, BigDecimal.ROUND_HALF_UP);
//方差s^2=[(x1-x)^2 +...(xn-x)^2]/n 或者s^2=[(x1-x)^2 +...(xn-x)^2]/(n-1)
BigDecimal dVar = BigDecimal.ZERO;
for (int i = 0; i < n; i++) {
dVar = dVar.add((datas.get(i).subtract(avg)).multiply(datas.get(i).subtract(avg)));
}
BigDecimal v = dVar.divide(new BigDecimal(n - 1), 6, BigDecimal.ROUND_HALF_UP);
if (v.compareTo(BigDecimal.ZERO) == 0) {
return BigDecimal.ZERO;
}
return sqrt(v);
}
// BigDecimal的开方
private static BigDecimal sqrt(BigDecimal num) {
if (num.compareTo(BigDecimal.ZERO) < 0) {
return BigDecimal.ZERO;
}
BigDecimal x = num.divide(new BigDecimal("2"), MathContext.DECIMAL128);
while (x.subtract(x = sqrtIteration(x, num)).abs().compareTo(new BigDecimal("0.0000000000000000000001")) > 0) ;
return x;
}
private static BigDecimal sqrtIteration(BigDecimal x, BigDecimal n) {
return x.add(n.divide(x, MathContext.DECIMAL128)).divide(new BigDecimal("2"), MathContext.DECIMAL128);
}
}
/** * 判异 * @param datas * @param spcXbarAbnormalJudgments * @return */ public List<Integer> abnormalJudgment(List<BigDecimal> datas, List<SpcXbarAbnormalJudgment> spcXbarAbnormalJudgments) { List<Integer> abnormalDataIndexs = Lists.newArrayList();// 用于存储异常数据的下标 // 基础数据 BigDecimal avgX = XbarRUtils.average(datas); //中心线-总均值 BigDecimal standardDiviation = XbarRUtils.standardDiviation(datas); //标准差 spcXbarAbnormalJudgments = spcXbarAbnormalJudgments.stream().filter( spcXbarAbnormalJudgment -> XbarAbnormalJudgeEnums.selectFlag.YES.getCode().equals(spcXbarAbnormalJudgment.getSelectFlag()) ).collect(Collectors.toList()); // 数据计算容器 int twoDirection = 1; // 1:中心线上方。-1:中心线下方 (标准2) List<Integer> twoAbnormalIndex = Lists.newArrayList(); // 累计异常数据下标(标准2) int threeDirection = 1; // 1:上升 -1:下降 (标准3) List<Integer> threeAbnormalIndex = Lists.newArrayList(); // 累计异常数据下标(标准3) int fourDirection = 1; // 1:上升 -1:下降 (标准4) List<Integer> fourAbnormalIndex = Lists.newArrayList(); // 累计异常数据下标(标准4) List<Integer> sevenAbnormalIndex = Lists.newArrayList(); // 累计异常数据下标(标准7) List<Integer> eightAbnormalIndex = Lists.newArrayList(); // 累计异常数据下标(标准8) for(int i=0; i<datas.size(); i++){ BigDecimal data = datas.get(i); for (SpcXbarAbnormalJudgment abnormalJudgment : spcXbarAbnormalJudgments) { BigDecimal k = new BigDecimal(abnormalJudgment.getkValue()); switch (XbarAbnormalJudgeEnums.type.getByCode(Integer.parseInt(abnormalJudgment.getStandardCode()))) { // 一个点,距离中心线大于K个标准差 case ONE: if (data.compareTo(avgX.add(k.multiply(standardDiviation))) > 0 || data.compareTo(avgX.subtract(k.multiply(standardDiviation))) < 0) { abnormalDataIndexs.add(i); } break; // 连续K个点在中心线同一侧 case TWO: if(k.intValue() > 1){ if(i==0){ twoDirection = data.compareTo(avgX) > 0 ? 1 : -1; twoAbnormalIndex.add(i); } else { // 跟上个数据在同一侧,累加 if ((data.compareTo(avgX) > 0 && twoDirection > 0) || (data.compareTo(avgX) < 0 && twoDirection < 0)) { twoAbnormalIndex.add(i); if(twoAbnormalIndex.size() == k.intValue()) { //第一次到达阈值,把前面符合条件的数据都存入警告集合; abnormalDataIndexs.addAll(twoAbnormalIndex); } else if(twoAbnormalIndex.size() > k.intValue()) { abnormalDataIndexs.add(i); } } // 不符合报警条件,归零重新计算 else { twoDirection = data.compareTo(avgX) > 0 ? 1 : -1; twoAbnormalIndex.clear(); twoAbnormalIndex.add(i); } } } break; // 连续K个点, 全部递增或全部递减 case THREE: if(k.intValue() > 1){ if(i<2){ threeAbnormalIndex.add(i); // 判断方向至少需要两个点,初始值默认要存两个,从第三个开始判断是否递增递减; } else { // 跟上个数据同个趋势,累加 if ((data.compareTo(datas.get(i-1)) > 0 && threeDirection > 0) || (data.compareTo(datas.get(i-1)) < 0 && threeDirection < 0)) { threeAbnormalIndex.add(i); if(threeAbnormalIndex.size() == k.intValue()) { //第一次到达阈值,把前面符合条件的数据都存入警告集合; abnormalDataIndexs.addAll(threeAbnormalIndex); } else if(threeAbnormalIndex.size() > k.intValue()) { abnormalDataIndexs.add(i); } } // 不符合报警条件,归零重新计算 else { threeDirection = data.compareTo(datas.get(i-1)) > 0 ? 1 : -1; threeAbnormalIndex.clear(); threeAbnormalIndex.add(i-1); threeAbnormalIndex.add(i); } } } break; // 连续K个点,上下交接 case FOUR: if(k.intValue() > 1){ if(i<2){ fourAbnormalIndex.add(i); // 判断方向至少需要两个点,初始值默认要存两个,从第三个开始判断是否递增递减; } else { // 跟上个数据不同趋势,累加 if ((data.compareTo(datas.get(i-1)) > 0 && fourDirection < 0) || (data.compareTo(datas.get(i-1)) < 0 && fourDirection > 0)) { fourDirection = data.compareTo(datas.get(i-1)) > 0 ? 1 : -1; // 方向重置 fourAbnormalIndex.add(i); if(fourAbnormalIndex.size() == k.intValue()) { //第一次到达阈值,把前面符合条件的数据都存入警告集合; abnormalDataIndexs.addAll(fourAbnormalIndex); } else if(fourAbnormalIndex.size() > k.intValue()) { abnormalDataIndexs.add(i); } } // 不符合报警条件,归零重新计算 else { fourDirection = data.compareTo(datas.get(i-1)) > 0 ? 1 : -1; fourAbnormalIndex.clear(); fourAbnormalIndex.add(i-1); fourAbnormalIndex.add(i); } } } break; // K+1个点中有K个点,距离中心线(同侧)大于2个标准差 case FIVE: List<Integer> fiveUpAbnormalIndex = Lists.newArrayList(); // 中心线上方累计异常数据下标(标准5) List<Integer> fiveDownAbnormalIndex = Lists.newArrayList(); // 中心线下方累计异常数据下标(标准5) // 从第K个值开始统计,往前遍历是否有K-1个值符合条件 if (i >= k.intValue() - 1) { for (int j = 0; j < k.intValue(); j++) { if (datas.get(i-j).compareTo(avgX.add(new BigDecimal(2).multiply(standardDiviation))) > 0) { fiveUpAbnormalIndex.add(i); } if (datas.get(i-j).compareTo(avgX.subtract(new BigDecimal(2).multiply(standardDiviation))) < 0) { fiveDownAbnormalIndex.add(i); } } if (fiveUpAbnormalIndex.size() >= k.intValue()-1) { abnormalDataIndexs.addAll(fiveUpAbnormalIndex); } if (fiveDownAbnormalIndex.size() >= k.intValue()-1) { abnormalDataIndexs.addAll(fiveDownAbnormalIndex); } } break; // K+1个点中有K个点,距离中心线(同侧)大于1个标准差 case SIX: List<Integer> sixUpAbnormalIndex = Lists.newArrayList(); // 中心线上方累计异常数据下标(标准6) List<Integer> sixDownAbnormalIndex = Lists.newArrayList(); // 中心线下方累计异常数据下标(标准6) // 从第K个值开始统计,往前遍历是否有K-1个值符合条件 if (i >= k.intValue() - 1) { for (int j = 0; j < k.intValue(); j++) { if (datas.get(i-j).compareTo(avgX.add(standardDiviation)) > 0) { sixUpAbnormalIndex.add(i); } if (datas.get(i-j).compareTo(avgX.subtract(standardDiviation)) < 0) { sixDownAbnormalIndex.add(i); } } if (sixUpAbnormalIndex.size() >= k.intValue()-1) { abnormalDataIndexs.addAll(sixUpAbnormalIndex); } if (sixDownAbnormalIndex.size() >= k.intValue()-1) { abnormalDataIndexs.addAll(sixDownAbnormalIndex); } } break; // 连续K个点,距离中心线(任一侧)1个标准差以内 case SEVEN: if (data.compareTo(avgX.add(standardDiviation)) < 0 || data.compareTo(avgX.subtract(standardDiviation)) > 0) { sevenAbnormalIndex.add(i); if(sevenAbnormalIndex.size() == k.intValue()) { //第一次到达阈值,把前面符合条件的数据都存入警告集合; abnormalDataIndexs.addAll(sevenAbnormalIndex); } else if(sevenAbnormalIndex.size() > k.intValue()) { abnormalDataIndexs.add(i); } } // 不符合报警条件,归零重新计算 else { sevenAbnormalIndex.clear(); } break; // 连续K个点,距离中心线(任一侧)大于1个标准差 case EIGHT: if (data.compareTo(avgX.add(standardDiviation)) > 0 || data.compareTo(avgX.subtract(standardDiviation)) < 0) { eightAbnormalIndex.add(i); if(eightAbnormalIndex.size() == k.intValue()) { //第一次到达阈值,把前面符合条件的数据都存入警告集合; abnormalDataIndexs.addAll(eightAbnormalIndex); } else if(eightAbnormalIndex.size() > k.intValue()) { abnormalDataIndexs.add(i); } } // 不符合报警条件,归零重新计算 else { eightAbnormalIndex.clear(); } break; } } } abnormalDataIndexs = abnormalDataIndexs.stream().distinct().collect(Collectors.toList()); Collections.sort(abnormalDataIndexs); return abnormalDataIndexs; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探