参数估计指样本数据来自一个具有明确概率密度函数的总体,而在非参数估计中,样本数据的概率分布未知,这时,为了对样本数据进行建模,需要估计样本数据的概率密度函数,核密度估计即是其中一种方式。

引言

统计学中,核密度估计,即Kernel Density Estimation,用以基于有限的样本推断总体数据的分布,因此,核密度估计的结果即为样本的概率密度函数估计,根据该估计的概率密度函数,我们就可以得到数据分布的一些性质,如数据的聚集区域。

从直方图开始

直方图由 Karl Pearson 提出,用以表示样本数据的分布,帮助分析样本数据的众数、中位数等性质,横轴表示变量的取值区间,纵轴表示在该区间内数据出现的频次与区间的长度的比例。

美国人口普查局(The U.S. Census Bureau)调查了 12.4 亿人的上班通勤时间,数据如下:

起点

组距

频次

频次/组距

频次/组距/总数

0

5

4180

836

0.0067

5

5

13687

2737

0.0221

10

5

18618

3723

0.03

15

5

19634

3926

0.0316

20

5

17981

3596

0.029

25

5

7190

1438

0.0116

30

5

16369

3273

0.0264

35

5

3212

642

0.0052

40

5

4122

824

0.0066

45

15

9200

613

0.0049

60

30

6461

215

0.0017

90

60

3435

57

0.0005

使用直方图进行数据可视化如下

核密度回归_kde

该直方图使用单位间隔的人数(频次/组距)表示为每个矩形的高度,因此每个矩形的面积表示该区间内的人数,矩形的总面积即为 12.4 亿。

 

核密度回归_核密度分析_02

此时,矩形的面积表示该区间所占的频率,矩形的总面积为 1,该直方图也即频率直方图

频率直方图有以下特点:

  1. 矩形面积为该区间的频率;
  2. 矩形的高度为该区间的平均频率密度。

概率密度函数

极限思维:我们使用微分思想,将频率直方图的组距一步步减小,随着组距的减小,矩形宽度越来越小,因此,在极限情况下频率直方图就会变成一条曲线,而这条曲线即为概率密度曲线。

对于概率密度曲线,我们知道:随机变量的取值落在某区域内的概率值为概率密度函数在这个区域的积分(见概率密度函数),即:核密度回归_kde_03

设累积分布函数为 核密度回归_arcgis_04,根据上述定义,则 核密度回归_核密度分析_05

根据微分思想,则有:

核密度回归_核密度分析_06

核密度估计

根据上述分析,我们应该已经明白核密度估计的目的事实上就是估测所给样本数据的概率密度函数。

公式推导

考虑一维数据,有如下 核密度回归_kde_07 个样本数据:核密度回归_核密度分析_08

假设该样本数据的累积分布函数为 核密度回归_arcgis_04,概率密度函数为 核密度回归_核密度分析_10,则有:

核密度回归_kde_11

核密度回归_kde_12

引入累积分布函数的经验分布函数

核密度回归_kde_13

该函数大意为:使用 核密度回归_kde_07 次观测中 核密度回归_arcgis_15 出现的次数与 核密度回归_kde_07 的比值来近似描述 核密度回归_核密度分析_17

将该函数代入 核密度回归_kde_18,有:

核密度回归_kde_19

根据该公式,在实际计算中,必须给定 核密度回归_arcgis_20 值,核密度回归_arcgis_20 值不能太大也不能太小,太大不满足 核密度回归_数据分析_22 的条件,太小使用的样本数据点太少,误差会很大,因此,关于 核密度回归_arcgis_20 值的选择有很多研究,该值也被称为核密度估计中的带宽

确定带宽后,我们可以写出 核密度回归_核密度分析_10

核密度回归_核密度回归_25

核函数

核密度回归_核密度分析_10

核密度回归_核密度分析_27

其中,令 核密度回归_arcgis_28,则当 核密度回归_arcgis_29 时,核密度回归_核密度分析_30.

且:

核密度回归_kde_31

注意,此处的 核密度回归_数据分析_32 指的是 核密度回归_kde_07 次实验,而不是累计,因此计算值为 核密度回归_kde_07

核密度回归_数据分析_35,根据概率密度函数的定义,我们有:

核密度回归_kde_36

其中当 核密度回归_arcgis_29 时,核密度回归_数据分析_38.

此时 核密度回归_arcgis_39 就称为核函数,常用的核函数有:uniform,triangular, biweight, triweight, Epanechnikov, normal…

核密度回归_核密度分析_10

核密度回归_核密度回归_41

对于二维数据,核密度回归_核密度分析_10

核密度回归_核密度分析_43

实验:POI 点核密度分析

技术选型

  • 栅格数据可视化:canvas
  • KFC POI 爬取:POIKit

核函数、带宽选择

使用 ArcGIS 软件说明文档提供的带宽选择方案,核函数为:

核密度回归_数据分析_44

概率密度估测函数为:
核密度回归_kde_45

其中,核密度回归_kde_46 为给定的权重字段,若不含有该字段,则取值为 1,核密度回归_kde_07

带宽为:

核密度回归_arcgis_48

参数解释:

  • 平均中心:指 核密度回归_arcgis_49
  • 加权平均中心:指 核密度回归_arcgis_49
  • 标准距离计算公式:
    核密度回归_核密度分析_51
    其中:
  • 核密度回归_核密度分析_52
  • 核密度回归_核密度分析_53
  • 核密度回归_数据分析_54
  • 加权标准距离计算公式:
    核密度回归_kde_55
    其中:
  • 核密度回归_kde_56 是要素 核密度回归_arcgis_57
  • 核密度回归_核密度回归_58
  • 核密度回归_数据分析_54
  • 若 POI 点不含有权重字段,则 核密度回归_kde_60 为到平均中心距离的中值,核密度回归_arcgis_49 是 POI 点数目,核密度回归_kde_62 是标准距离 核密度回归_核密度回归_63
  • 若 POI 点含有权重字段,则 核密度回归_kde_60 为到加权平均中心距离的中值,核密度回归_arcgis_49 是 POI 点权重字段值的总和,核密度回归_kde_62 是加权标准距离核密度回归_kde_67

数据爬取

使用 POIKit,爬取河南省 KFC POI 数据,参数设置如下:

核密度回归_arcgis_68

共爬取得到 219 条数据,如下图所示:

核密度回归_arcgis_69

程序编写

核函数:

/**
 * 核函数
 * @param {Number} t 变量
 * @returns
 */
function kernel(t) {
  return (3 / Math.PI) * Math.pow(1 - t * t, 2);
}

带宽:

/**
 * 带宽
 * @param {Point[]} pts 所有POI点
 * @param {Point} avePt 平均中心
 * @returns
 */
function h(pts, avePt) {
  const SD_ = SD(pts, avePt);
  const Dm_ = Dm(pts, avePt);
  if (SD_ > Math.sqrt(1 / Math.log(2)) * Dm_) {
    return 0.9 * Math.sqrt(1 / Math.log(2)) * Dm_ * Math.pow(pts.length, -0.2);
  } else {
    return 0.9 * SD_ * Math.pow(pts.length, -0.2);
  }
}

平均中心:

/**
 * 平均中心
 * @param {Point} pts 所有POI点
 * @returns
 */
function ave(pts) {
  let lon = 0,
    lat = 0;
  pts.forEach((pt) => {
    lon += pt.lon;
    lat += pt.lat;
  });
  return new Point(lon / pts.length, lat / pts.length);
}

标准距离 SD:

/**
 * 标准距离
 * @param {Point[]} pts 所有POI点
 * @param {Point} avePt 平均中心
 * @returns
 */
function SD(pts, avePt) {
  let SDx = 0,
    SDy = 0;
  pts.forEach((pt) => {
    SDx += Math.pow(pt.lon - avePt.lon, 2);
    SDy += Math.pow(pt.lat - avePt.lat, 2);
  });
  return Math.sqrt(SDx / pts.length + SDy / pts.length);
}

Dm(到平均中心距离的中值):

/**
 * Dm
 * @param {Point[]}} pts 所有POI点
 * @param {Point} avePt 平均中心
 * @returns
 */
function Dm(pts, avePt) {
  let distance = [];
  pts.forEach((pt) => {
    distance.push(Dist(pt, avePt));
  });
  distance.sort();
  return distance[distance.length / 2];
}

核密度估计:

/**
 * 核密度估计
 * @param {Point[]} pts 所有POI点
 * @param {Rect} rect POI点边界
 * @param {Number} width 栅格图像宽度
 * @param {Number} height 栅格图像高度
 */
function kde(pts, rect, width, height) {
  const estimate = new Array(height)
    .fill(0)
    .map(() => new Array(width).fill(0));
  let min = Infinity,
    max = -Infinity;
  const avePt = ave(pts);
  const bandWidth = h(pts, avePt);
  rect.top += bandWidth;
  rect.bottom -= bandWidth;
  rect.left -= bandWidth;
  rect.right += bandWidth;
  const itemW = (rect.right - rect.left) / width;
  const itemH = (rect.top - rect.bottom) / height;
  for (let x = 0; x < width; x++) {
    const itemX = rect.left + itemW * x;
    for (let y = 0; y < height; y++) {
      const itemY = rect.bottom + itemH * y;
      let fEstimate = 0;
      for (let m = 0; m < pts.length; m++) {
        const distance = Dist(pts[m], new Point(itemX, itemY));
        if (distance < Math.pow(bandWidth, 2)) {
          fEstimate += kernel(distance / bandWidth);
        }
      }
      fEstimate = fEstimate / (pts.length * Math.pow(bandWidth, 2));
      min = Math.min(min, fEstimate);
      max = Math.max(max, fEstimate);
      estimate[height - y - 1][x] = fEstimate;
    }
  }
  return { estimate, min, max };
}

然后使用 html5 canvas 进行可视化:

/**
 * 绘制核密度估计结果栅格图
 * @param {CanvasRenderingContext2D} ctx canvas上下文
 * @param {*} param0 核密度估测值,最大值,最小值
 */
function draw(ctx, { estimate, min, max }) {
  const height = estimate.length,
    width = estimate[0].length;
  for (let x = 0; x < width; x++) {
    for (let y = 0; y < height; y++) {
      const val = (estimate[y][x] - min) / (max - min);
      if (val > 0 && val < 0.4) {
        ctx.fillStyle = "rgb(228, 249, 245)";
        ctx.fillRect(x, y, 1, 1);
      } else if (val >= 0.4 && val < 0.7) {
        ctx.fillStyle = "rgb(48, 227, 202)";
        ctx.fillRect(x, y, 1, 1);
      } else if (val >= 0.7 && val < 0.9) {
        ctx.fillStyle = "rgb(17, 153, 158)";
        ctx.fillRect(x, y, 1, 1);
      } else if (val >= 0.9 && val <= 1) {
        ctx.fillStyle = "rgb(64, 81, 78)";
        ctx.fillRect(x, y, 1, 1);
      }
    }
  }
}

核密度估计结果

下图是核密度估计结果: