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;
    }
复制代码

 

posted @   高富贵  阅读(2310)  评论(11编辑  收藏  举报
相关博文:
阅读排行:
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
点击右上角即可分享
微信分享提示