核密度分析

一.算法介绍

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

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

根据具体的应用需求,我们可以灵活地使用核密度估计来分析和理解数据集的特征和结构,可能的用途包括针对社区规划分析房屋密度或犯罪行为,或探索道路或公共设施管线如何影响野生动物栖息地。

每个点位可以设置 weight 字段赋予某些要素比其他要素更大的权重,该字段还允许使用一个点表示多个观察对象。例如,一个地址可以表示一栋六单元的公寓,或者在确定总体犯罪率时可赋予某些罪行比其他罪行更大的权重。

二.算法计算原理

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

在核密度估计中,通过将核函数应用于每个数据点,并对所有数据点的贡献进行求和,可以计算出在每个位置上的密度估计值。四次核函数的结果可视为在核密度估计中每个位置的密度贡献权重。较大的结果表示该位置的密度较高,而较小或接近零的结果表示该位置的密度较低。

本算法中主要利用核密度公式计算空间范围内的核密度值,根据核密度值生成 png 或 jpg 格式的热力图,或者将整个空间切割成网格,用网格中心点参与核密度计算生成 geojson 文件,以供进一步空间探索分析。

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

三.算法程序

1. 核心流程代码

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

java 复制代码
        // 输入文件路径
        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);
java 复制代码
    /**
     * 核密度方法
     * @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);
        }
    }

2.创建面的 geojson 文件

java 复制代码
    /**
     *  根据核密度信息创建面的 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));
    }

3.热力图图片

java 复制代码
    /**
     * 根据核密度信息创建热力图图片
     * @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));
    }

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

java 复制代码
    /**
     * 计算所有点位的核密度
     * @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));
    }

5.可执行 jar 包

该程序可打为可执行jar包, 文件夹中的: kernel.jar

运行环境: jdk 1.8

执行示例:

bash 复制代码
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
参数 参数位置 参数说明
inputPath 1 输入的csv文件路径
outPath 2 输出的文件路径,程序根据文件后缀选择生产的文件类型,只允许 jpg、png、geojson 三种文件。
lonKey 3 输入文件中的经度字段名
latKey 4 输入文件中的纬度字段名
weightKey 5 输入文件中的权重字段名,没有则输入""
radius 6 影响半径,单位米,影响半径越长,周围空间受该数据的影响越广,需根据不同的输入数据情况调整
bufferSize 7 空间缓冲区,可扩大数据空间范围,一般0.1即可,即扩大 10% 的区域
step 8 空间划分步长,步长越小则参与计算的空间点数据越多,计算量越大,结果数据越精确, 需根据不同的输入数据情况调整,当值为0时,程序则适配生成一百万个点或网格参与计算,注:尽量不要在城市级别范围设置过低步长

四.执行结果展示

热力图示例:

平台分析示例:

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

杭州市超市营业额区域性分析-平台分析:

五、应用场景

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

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

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

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

相关推荐
云和数据.ChenGuang5 分钟前
《XML》教案 第1章 学习XML基础
xml·java·学习
王·小白攻城狮·不是那么帅的哥·天文13 分钟前
Java操作Xml
xml·java
发飙的蜗牛'23 分钟前
23种设计模式
android·java·设计模式
music0ant31 分钟前
Idean 处理一个项目引用另外一个项目jar 但jar版本低的问题
java·pycharm·jar
陈大爷(有低保)1 小时前
logback日志控制台打印与写入文件
java
繁川1 小时前
深入理解Spring AOP
java·后端·spring
Am心若依旧4091 小时前
[c++进阶(三)]单例模式及特殊类的设计
java·c++·单例模式
ZHOUPUYU4 小时前
最新 neo4j 5.26版本下载安装配置步骤【附安装包】
java·后端·jdk·nosql·数据库开发·neo4j·图形数据库
Q_19284999065 小时前
基于Spring Boot的找律师系统
java·spring boot·后端
谢家小布柔6 小时前
Git图形界面以及idea中集合Git使用
java·git