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;
    }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM