diff --git a/jeecg-module-data-analyze/src/main/java/org/jeecg/util/DistributionAnalysisToolkit.java b/jeecg-module-data-analyze/src/main/java/org/jeecg/util/DistributionAnalysisToolkit.java index fc599ca..e85cadf 100644 --- a/jeecg-module-data-analyze/src/main/java/org/jeecg/util/DistributionAnalysisToolkit.java +++ b/jeecg-module-data-analyze/src/main/java/org/jeecg/util/DistributionAnalysisToolkit.java @@ -1,11 +1,12 @@ package org.jeecg.util; import lombok.Data; +import lombok.extern.slf4j.Slf4j; import org.jeecg.entity.NuclideActConcIntvl; import java.util.*; import java.util.stream.Collectors; - +@Slf4j public class DistributionAnalysisToolkit { /** * 区间统计结果 @@ -16,25 +17,26 @@ public class DistributionAnalysisToolkit { private final int count; private final List values; private final Map levelDistribution; - public IntervalStat(String interval, List nuclideData) { - values=new ArrayList<>(); - levelDistribution=new TreeMap<>(); + + public IntervalStat(String interval, List nuclideData) { + values = new ArrayList<>(); + levelDistribution = new TreeMap<>(); this.interval = interval; this.count = nuclideData.size(); - for (NuclideActConcIntvl nuclide : nuclideData) { - //获取浓度值 - Double conc = nuclide.getConc(); - if (conc != null) { // 过滤 conc null - values.add(conc); - } - Integer category = nuclide.getCategory(); - if (category != null) { // 跳过 null - levelDistribution.merge(category, 1, Integer::sum); - } else { - // 数据库中存在记录 null 级别,用特殊 key 如 -1 - // levelDistribution.merge(-1, 1, Integer::sum); - } - } + for (NuclideActConcIntvl nuclide : nuclideData) { + //获取浓度值 + Double conc = nuclide.getConc(); + if (conc != null) { // 过滤 conc null + values.add(conc); + } + Integer category = nuclide.getCategory(); + if (category != null) { // 跳过 null + levelDistribution.merge(category, 1, Integer::sum); + } else { + // 数据库中存在记录 null 级别,用特殊 key 如 -1 + // levelDistribution.merge(-1, 1, Integer::sum); + } + } } } @@ -50,8 +52,13 @@ public class DistributionAnalysisToolkit { this.density = density; } - public double getX() { return x; } - public double getDensity() { return density; } + public double getX() { + return x; + } + + public double getDensity() { + return density; + } } /** @@ -66,15 +73,19 @@ public class DistributionAnalysisToolkit { this.cumulativeProb = cumulativeProb; } - public double getValue() { return value; } - public double getCumulativeProb() { return cumulativeProb; } + public double getValue() { + return value; + } + + public double getCumulativeProb() { + return cumulativeProb; + } } + /** * 数据区间统计 * * @param nuclideData 原始数据 - * @param start 起始值 - * @param step 区间宽度 * @return 区间统计结果列表 */ //region @@ -111,50 +122,80 @@ public class DistributionAnalysisToolkit { // return stats; // } // - //endregion - - public static List calculateIntervalStats(List nuclideData, double start, double step) { + //endregion + public static List calculateIntervalStats(List nuclideData) { if (nuclideData == null || nuclideData.isEmpty()) { throw new IllegalArgumentException("数据不能为空"); } - // 计算结束边界 - double maxConc = nuclideData.stream() - .mapToDouble(NuclideActConcIntvl::getConc) - .max() - .orElse(0.0); - double end = Math.ceil(maxConc / step) * step + step; + int numBins = calculateOptimalBins(nuclideData); + //int numBins = 50; + log.info("分箱个数",numBins); + //统计 min/max/count + DoubleSummaryStatistics statsData = nuclideData.stream() + .map(nuclideActConcIntvl -> nuclideActConcIntvl.getConc()) + .filter(Objects::nonNull) + .filter(d -> !d.isNaN() && !d.isInfinite()) + .mapToDouble(Double::doubleValue) + .summaryStatistics(); + if (statsData.getCount() == 0) { + return Collections.emptyList(); + } + double min = statsData.getMin(); + double max = statsData.getMax(); + log.info("数据min:",min); + log.info("数据max:",max); + //2. 处理所有值相同或极小范围(关键!避免 binWidth = 0) + if (max - min < 1e-8) { + // 取值向下取整为起点,向上扩展一个单位 + min = Math.floor(min); + max = min + Math.max(1.0, Math.abs(min) * 0.1); // 避免 min=-1000 时扩展太小 + } + + double binWidth = (max - min) / numBins; + if (binWidth <= 0) { + binWidth = 1.0; // 极端防御 + } + + List bins = new ArrayList<>(); // 初始化区间映射 Map> intervalMap = new TreeMap<>(); - for (double lower = start; lower < end; lower += step) { - double upper = lower + step; - String key = String.format("[%.1f, %.1f)", lower, upper); + for (int i = 0; i < numBins; i++) { + double start1 = min + i * binWidth; + double end1 = min + (i + 1) * binWidth; + + // 最后一个区间使用闭区间,语义更清晰 + String key = i == numBins - 1 + ? String.format("[%.4f ~ %.4f]", start1, max) + : String.format("[%.4f ~ %.4f)", start1, end1); + bins.add(key); intervalMap.put(key, new ArrayList<>()); } // 分配数据到区间 for (NuclideActConcIntvl nuclide : nuclideData) { - double value=nuclide.getConc(); - double lower = Math.floor(value / step) * step; - String key = String.format("[%.1f, %.1f)", lower, lower + step); - - intervalMap.get(key).add(nuclide); + double value = nuclide.getConc(); + // 计算当前对象属于哪个区间索引 + int index = (int) ((value - min) / binWidth); + if (index < 0) { + index = 0; + } + if (index >= numBins) { + index = numBins - 1; + } + intervalMap.get(bins.get(index)).add(nuclide); } - // 转换为统计结果对象 List stats = new ArrayList<>(); - for (Map.Entry> entry : intervalMap.entrySet()) { - stats.add(new IntervalStat(entry.getKey(), entry.getValue())); + for (Map.Entry> entry : intervalMap.entrySet()) { + stats.add(new IntervalStat(entry.getKey(), entry.getValue())); } return stats; } - - - /** * 计算95%累积线 * @@ -218,7 +259,6 @@ public class DistributionAnalysisToolkit { } - /** * 核函数接口 */ @@ -228,10 +268,12 @@ public class DistributionAnalysisToolkit { } // 常用核函数实现 - public static final KernelFunction GAUSSIAN_KERNEL = u -> Math.exp(-0.5 * u * u) / Math.sqrt(2 * Math.PI); - public static final KernelFunction EPANECHNIKOV_KERNEL = u -> (Math.abs(u) <= 1) ? 0.75 * (1 - u * u) : 0; - public static final KernelFunction TRIANGULAR_KERNEL = u -> (Math.abs(u) <= 1) ? 1 - Math.abs(u) : 0; - + public static final KernelFunction GAUSSIAN_KERNEL = + u -> Math.exp(-0.5 * u * u) / Math.sqrt(2 * Math.PI); + public static final KernelFunction EPANECHNIKOV_KERNEL = + u -> (Math.abs(u) <= 1) ? 0.75 * (1 - u * u) : 0; + public static final KernelFunction TRIANGULAR_KERNEL = + u -> (Math.abs(u) <= 1) ? 1 - Math.abs(u) : 0; /** @@ -239,7 +281,9 @@ public class DistributionAnalysisToolkit { */ public static double calculateBandwidthSilverman(List data) { int n = data.size(); - if (n <= 1) return 1.0; + if (n <= 1) { + return 1.0; + } // 计算标准差 double mean = data.stream().mapToDouble(Double::doubleValue).average().orElse(0.0); @@ -310,15 +354,15 @@ public class DistributionAnalysisToolkit { } - //获取浓度值集合 public static List convertConcToDoubleList(List nuclideList) { return nuclideList.stream() // 创建流 .map(NuclideActConcIntvl::getConc) // 提取conc值 .collect(Collectors.toList()); // 收集为List } + //累积和 - public static List cumulativeSum(List data) { + public static List cumulativeSum(List data) { List result = new ArrayList<>(); double sum = 0.0; @@ -331,15 +375,32 @@ public class DistributionAnalysisToolkit { } - - - - - - - - - + public static int calculateOptimalBins(List data) { + if (Objects.isNull(data)) { + return 5; + } + List values = data.stream() + .map(NuclideActConcIntvl::getConc) + .filter(Objects::nonNull) + .filter(d -> !d.isNaN() && !d.isInfinite()) + .sorted() + .collect(Collectors.toList()); + int n = values.size(); + if (n < 2) { + return 5; + } + double q1 = values.get(n / 4); + double q3 = values.get((3 * n) / 4); + double iqr = q3 - q1; + if (iqr <= 0) { + return (int) Math.max(3, Math.ceil(Math.sqrt(n))); + } + double binWidth = 2 * iqr * Math.pow(n, -1.0 / 3.0); + double dataRange = values.get(n - 1) - values.get(0); + int bins = (int) Math.ceil(dataRange / binWidth); + bins = Math.max(3, Math.min(40, bins)); + return bins; + } }