• 核密度分析


    一.算法介绍

    核密度估计(Kernel Density Estimation)是一种用于估计数据分布的非参数统计方法。它可以用于多种目的和应用,包括:

    • 数据可视化:核密度估计可以用来绘制平滑的密度曲线或热力图,从而直观地表示数据的分布情况。它可以帮助我们观察数据集中的高密度区域、低密度区域以及变化趋势。
    • 异常检测:通过核密度估计,我们可以识别数据中的异常点或离群值。异常点通常表现为低密度区域或与其他数据点明显不同的区域。
    • 概率密度计算:核密度估计可以用于计算给定数值的概率密度。通过将新数据点带入核密度估计函数,可以估计出该点在数据分布中的密度。
    • 模式识别:核密度估计可以用于识别数据中的模式或聚类。通过观察密度最高的区域,可以推断数据的聚类情况或潜在的模式。
    • 预测建模:核密度估计可以用于构建概率模型,进而进行预测。例如,在分类问题中,可以使用核密度估计来估计每个类别的概率密度,然后根据新的数据点所属的密度来进行分类预测。

    根据具体的应用需求,我们可以灵活地使用核密度估计来分析和理解数据集的特征和结构,可能的用途包括针对社区规划分析房屋密度或犯罪行为,或探索道路或公共设施管线如何影响野生动物栖息地。
    每个点位可以设置 weight 字段赋予某些要素比其他要素更大的权重,该字段还允许使用一个点表示多个观察对象。例如,一个地址可以表示一栋六单元的公寓,或者在确定总体犯罪率时可赋予某些罪行比其他罪行更大的权重。

    二.算法计算原理

    本算法以四次核函数为基础,四次核函数的特点是具有平滑的曲线形状,具有较宽的窗口,对数据点的贡献在距离较远时会迅速减小。由于其平滑性和较大的支持范围,四次核函数在核密度估计中被广泛使用。

    在这里插入图片描述

    在核密度估计中,通过将核函数应用于每个数据点,并对所有数据点的贡献进行求和,可以计算出在每个位置上的密度估计值。四次核函数的结果可视为在核密度估计中每个位置的密度贡献权重。较大的结果表示该位置的密度较高,而较小或接近零的结果表示该位置的密度较低。
    本算法中主要利用核密度公式计算空间范围内的核密度值,根据核密度值生成 png 或 jpg 格式的热力图,或者将整个空间切割成网格,用网格中心点参与核密度计算生成 geojson 文件,以供进一步空间探索分析。

        /**
         *  计算单个核密度
         * @param radius 半径
         * @param dist 两点的距离
         * @param weight 权重
         * @return
         */
        public static double computeKernel(double radius, double dist, double weight){
            return  (3 / Math.PI) * weight * Math.pow((1 - Math.pow(dist / radius,2)), 2);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    创新性说明:

    • 1.算法会自适应数据中的空间点位范围,此范围可根据参数bufferSize 设置缓冲区扩展,以获取数据范围外的点参与计算。
    • 2.根据空间范围每隔特定步长创建虚拟点位或划分网格,灵活性较高,步长越小则结果在地图分布上的精度越高,步长参数step(米) 可选,如果没有设置, 则默认在空间范围内自适应创建一百万左右虚拟点或网格。
    • 3.采用多线程的方式进行核密度计算,速度更快。
    • 4.可将结果值进行归一化处理,核密度计算出来的结果值主要用于观察数据分布,但是各个结果值之间相差范围较大,不易观察数据分布,归一化后能更清晰观察不同区域间的分布情况。
    • 5.可根据核密度值的大小根据不同需求生成热力图或 geojson 文件。可在geojson文件上做进一步探索。

    三.算法程序

    1. 核心流程代码

    从csv中获取源数据点信息, 获取坐标范围,如果需要缓冲区, 则设置缓冲区, 获取步长长度(默认一百万个像素点或网格),然后根据核密度信息创建图片或geojson

            // 输入文件路径
            String inputPath ="D:\\测试数据.csv";
            // 输出文件路径
            String outPath ="D:\\测试数据.geojson";
            // String outPath ="D:\\测试数据.jpg";
            // 经度字段
            String lonKey = "lon";
            // 纬度字段
            String latKey = "lat";
            // 权重字段
            String weightKey = "";
            // 影响半径
            double radius = 300.0;
            // 缓冲区
            double bufferSize = 0.1;
            // 生成的网格长度(单位: 米)
            int step = 0;
            
            int type;
            if (outPath.endsWith("png") || outPath.endsWith("jpg")){
                type = 0;
            }else if (outPath.endsWith("geojson")){
                type = 1;
            }else {
                throw new RuntimeException("输出文件格式只能是 png、jpg 或者 geojson");
            }
    
            // 从csv中获取源数据点信息
            List<EntryPoint> entryPoints = EntryPoint.formatToEntryPoints(inputPath, lonKey, latKey, weightKey, radius);
            
            // 获取坐标范围
            double[] coordsScope = KernelUtils.getCoordsScope(entryPoints);
            
            // 如果需要缓冲区, 则设置缓冲区
            if (bufferSize != 0){
                coordsScope = KernelUtils.getBufferScope(coordsScope[0], coordsScope[1], coordsScope[2], coordsScope[3], bufferSize);
            }
    
            // 获取默认的步长长度, 默认一百万个像素点或网格
            if (step ==0){
                step = KernelUtils.getDefaultSize(coordsScope);
            }
            
            // 根据核密度信息创建图片或geojson
            kernel(coordsScope, entryPoints, step, radius, type, outPath);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
        /**
         * 核密度方法
         * @param coordsScope 坐标范围
         * @param entryPoints  从csv中获取源数据点信息
         * @param step 步长长度
         * @param radius 影响半径
         * @param type 输出文件类型
         */
        public static void kernel(double[] coordsScope, List<EntryPoint> entryPoints, int step, double radius, int type, String path){
            // 获取网格坐标系的lon, lat的列表
            List<Double[]> coords = KernelUtils.getKennelPointCoords(coordsScope[0], coordsScope[1],coordsScope[2],coordsScope[3], step);
            Progress.progress( progress++);
    
            int width =  coords.get(0).length;
            int high = coords.get(1).length;
            if (type == 1){
                // 生产 geojson 网格结果
                generatorGridGeojson(coords, entryPoints, width-1, high-1, radius, path, step);
            }else {
                // 生产热力图图片
                generatorThermalMap(coords, entryPoints, width, high, radius, path, step);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    2.创建面的 geojson 文件

        /**
         *  根据核密度信息创建面的 geojson 文件
         * @param coords 虚拟数据点经纬度列表
         * @param entryPoints 数据点
         * @param width 横向点位数量
         * @param high 纵向点位数量
         * @param radius 影响半径
         */
        public static void generatorGridGeojson(List<Double[]> coords, List<EntryPoint> entryPoints,
                                                int width, int high, double radius, String path, int step){
            // 获取所有中心点位的数据
            List<PixelPoint> pixelPoints = KernelUtils.getGridCenters(coords);
    
            // 进行核密度计算, 并记录受到影响的网格信息
            KernelResult kernelResult = kernelCompute(entryPoints, pixelPoints, width, high, radius);
            Double[][] matrix = kernelResult.getMatrix();
            Double max = kernelResult.getMax();
            Double min = kernelResult.getMin();
    
            // 生产面的 geojson 文件
            writeToFile(KernelUtils.jointGridGeojson(matrix, max, min, coords), path);
            System.out.println(String.format("计算完成, 生成 geojson 文件, 参与计算网格  %d 个, 受影响网格 %d 个, 相邻网格间距 %s 米",
                    pixelPoints.size(), KernelUtils.effectiveGrid, step));
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    3.热力图图片

        /**
         * 根据核密度信息创建热力图图片
         * @param coords 虚拟数据点经纬度列表
         * @param entryPoints 数据点
         * @param width 横向点位数量
         * @param high 纵向点位数量
         * @param radius 影响半径
         */
        public static void generatorThermalMap(List<Double[]> coords, List<EntryPoint> entryPoints,
                                               int width, int high, double radius, String path, int step){
            // 获得所有点位
            List<PixelPoint> pixelPoints = KernelUtils.spliceKennelPoints(coords);
    
            // 进行核密度计算, 并记录受到影响的网格信息
            KernelResult kernelResult = kernelCompute(entryPoints, pixelPoints, width, high, radius);
            Double[][] matrix = kernelResult.getMatrix();
            Double max = kernelResult.getMax();
            Double min = kernelResult.getMin();
    
            // 生产热力图
            ImageGenerator.generatorImage(matrix, max, min, path);
            System.out.println(String.format("计算完成, 生成图片 像素: %d x  %d, 相邻像素点实际代表距离 %s 米", width, high, step));
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    4.计算所有点位的核密度

        /**
         * 计算所有点位的核密度
         * @param entryPoints 数据点信息
         * @param pixelPoints 创建的虚拟像素点
         * @param radius 影响半径
         * @return
         */
        public static KernelResult kernelCompute(List<EntryPoint> entryPoints, List<PixelPoint> pixelPoints, int width, int high, double radius){
    
            List<Double> values = new ArrayList<>();
            double affectLat = KernelUtils.getLatDist(radius);
    
            // 记录受到影响的网格
            Double[][] matrix = new Double[high][width];
            // 建立线程池
            ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                    30, 30, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(Integer.MAX_VALUE));
            // 线程等待计数器
            CountDownLatch countDownLatch = new CountDownLatch(pixelPoints.size());
            // 创建锁, 使计算数据具有线程间可见性
            Lock lock = new ReentrantLock();
    
            int stepPosition = pixelPoints.size() / 75;
            for (int i = 0; i < pixelPoints.size(); i++){
                PixelPoint pixelPoint = pixelPoints.get(i);
                Double kennelLon = pixelPoint.getLon();
                Double kennelLat = pixelPoint.getLat();
                threadPool.execute(() -> {
                            // 开始计算每个网格受到其他所有点所影响的核密度
                            double kernel = 0.0;
                            for (int j = 0; j < entryPoints.size(); j++){
                                EntryPoint entryPoint = entryPoints.get(j);
                                double lon = entryPoint.getLon();
                                double lat = entryPoint.getLat();
    
                                if (Math.abs(lon - kennelLon) > entryPoint.getAffectLon() || Math.abs(lat - kennelLat) > affectLat){
                                    continue;
                                }
    
                                // 获取权重, 默认为 1.0
                                double weight = 1.0;
                                if (entryPoint.getWeight() != null){
                                    weight = entryPoint.getWeight();
                                }
                                // 计算网格中心点与源数据点的距离
                                double distance = KernelUtils.getDistance(lon, lat, kennelLon, kennelLat);
    
                                // 影响半径大于距离的点直接去掉
                                if (distance <= radius){
                                    // 计算每个网格所受影响的核密度
                                    kernel += computeKernel(radius, distance, weight);
                                }
                            }
    
                            lock.lock();
                            // 为中心点实体类赋予核密度的值
                            Double value = 1 / Math.pow(radius, 2) * kernel;
                            matrix[pixelPoint.getI()][pixelPoint.getJ()] = value;
                            values.add(value);
                            lock.unlock();
                            countDownLatch.countDown();
    
                            if (countDownLatch.getCount() % stepPosition == 0 && progress < 80){
                                Progress.progress(progress++);
                            }
                        }
                );
            }
    
            // 等待所有任务执行完毕
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            // 关闭线程池
            threadPool.shutdown();
            return  new KernelResult(matrix, Collections.max(values), Collections.min(values));
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79

    5.可执行 jar 包

    该程序可打为可执行jar包, 文件夹中的: kernel.jar
    运行环境: jdk 1.8

    执行示例:

    java -jar kernel.jar 杭州市超市营业额.csv 杭州市超市营业额热力.jpg 经度 纬度 利润 2000.0 0.1 0
    java -jar kernel.jar 杭州市超市营业额.csv 杭州市超市营业额分布.geojson 经度 纬度 利润 2000.0 0.1 0
    java -jar kernel.jar 测试数据.csv 测试数据.jpg lon lat "" 300.0 0.1 0
    java -jar kernel.jar 测试数据.csv 测试数据.geojson lon lat "" 300.0 0.1 0
    
    • 1
    • 2
    • 3
    • 4
    参数参数位置参数说明
    inputPath1输入的csv文件路径
    outPath2输出的文件路径,程序根据文件后缀选择生产的文件类型,只允许 jpg、png、geojson 三种文件。
    lonKey3输入文件中的经度字段名
    latKey4输入文件中的纬度字段名
    weightKey5输入文件中的权重字段名,没有则输入””
    radius6影响半径,单位米,影响半径越长,周围空间受该数据的影响越广,需根据不同的输入数据情况调整
    bufferSize7空间缓冲区,可扩大数据空间范围,一般0.1即可,即扩大 10% 的区域
    step8空间划分步长,步长越小则参与计算的空间点数据越多,计算量越大,结果数据越精确, 需根据不同的输入数据情况调整,当值为0时,程序则适配生成一百万个点或网格参与计算,注:尽量不要在城市级别范围设置过低步长

    四.执行结果展示

    热力图示例:
    在这里插入图片描述

    平台分析示例:

    在这里插入图片描述

    杭州市超市营业额区域性分析-热力图:

    在这里插入图片描述

    杭州市超市营业额区域性分析-平台分析:
    在这里插入图片描述

    五、应用场景

    1. 金融风险评估:核密度算法可以用于评估某种投资方式的风险程度。将历史数据输入核密度估计器中,可以得出该投资方式在不同风险水平下的收益概率密度分布。这有助于金融机构更好地了解风险和收益之间的平衡。

    2. 生态学:核密度算法可用于研究动植物的栖息地和迁徙模式。将动植物的观察数据输入核密度估计器中,可以得出它们在不同地点出现的概率密度分布,帮助科学家更好地了解动植物的栖息地范围和活动规律。

    3. 交通流量预测:核密度算法可以用于预测道路上的交通流量。将历史交通流量数据输入核密度估计器中,可以得出在不同时间段内和不同位置上的交通流量概率密度分布。这有助于交通管理人员更好地规划道路、优化路线和管理交通拥堵。

    4. 模式识别:核密度算法可以使用于人脸识别、图像处理等领域。将输入数据的特征值输入核密度估计器中,可以得出不同特征值下相应数据的概率密度分布。这可用于识别图像中不同物体的特征值,例如人脸的轮廓和眼睛的位置,从而实现自动化识别。

  • 相关阅读:
    Spring Security和Shiro的异同?OAuth 2是什么?
    Synopsys新思科技2023“向新力”秋季校园招聘内推
    Node.js基础总结
    (干货) 差分对信号的长度和间距基于什么而界定的,一文了解。
    Python 关于整除以及负数取余遇到的问题
    k8s集群搭建
    【GEE笔记5】数据筛选Filter
    重装系统后电脑图片显示不出来怎么办
    Arduino追光小车
    c语言提高学习笔记——02-c提高03day
  • 原文地址:https://blog.csdn.net/xyy1028/article/details/136238660