核密度估計(KDE)原理及實現

https://blog.csdn.net/qq_40996400/article/details/103772706

此處僅爲備檔收藏,建議查看原文,鏈接見上

參數估計指樣本數據來自一個具有明確概率密度函數的總體,而在非參數估計中,樣本數據的概率分佈未知,這時,爲了對樣本數據進行建模,需要估計樣本數據的概率密度函數,核密度估計即是其中一種方式。

引言
統計學中,核密度估計,即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
使用直方圖進行數據可視化如下:

 

該直方圖使用單位間隔的人數(頻次/組距)表示爲每個矩形的高度,因此每個矩形的面積表示該區間內的人數,矩形的總面積即爲 12.4 億。

而當直方圖使用(頻次/組距/總數)表示爲每個矩形的高度時,數據可視化如下:

 

此時,矩形的面積表示該區間所佔的頻率,矩形的總面積爲 1,該直方圖也即頻率直方圖。

頻率直方圖有以下特點:

矩形面積爲該區間的頻率;
矩形的高度爲該區間的平均頻率密度。
概率密度函數
極限思維:我們使用微分思想,將頻率直方圖的組距一步步減小,隨着組距的減小,矩形寬度越來越小,因此,在極限情況下頻率直方圖就會變成一條曲線,而這條曲線即爲概率密度曲線。

對於概率密度曲線,我們知道:隨機變量的取值落在某區域內的概率值爲概率密度函數在這個區域的積分(見概率密度函數),即:P ( a < x ≤ b ) = ∫ a b f ( x ) d x P(a< x \leq b) = \int\limits_a^b f(x)dxP(a<x≤b)= 
a

b

 f(x)dx。

設累積分佈函數爲 F ( x ) F(x)F(x),根據上述定義,則 F ( x ) = ∫ − ∞ x f ( x ) d x F(x) = \int\limits_{-\infty}^x f(x)dxF(x)= 
−∞

x

 f(x)dx。

根據微分思想,則有:

f ( x 0 ) = F ( x 0 ) ˙ = lim ⁡ h → 0 F ( x 0 + h ) − F ( x 0 − h ) 2 h
f(x0)=F(x0)˙=limh→0F(x0+h)−F(x0−h)2h
f(x0)=F(x0)˙=limh→0F(x0+h)−F(x0−h)2h
f(x 
0

 )

  

F(x 
0

 )
˙

 

h→0
lim

  
2h
F(x 
0

 +h)−F(x 
0

 −h)

 

 

核密度估計
根據上述分析,我們應該已經明白核密度估計的目的事實上就是估測所給樣本數據的概率密度函數。

公式推導
考慮一維數據,有如下 n nn 個樣本數據:x 1 , x 2 , x 3 , . . . , x n x_1,x_2,x_3,...,x_nx 
1

 ,x 
2

 ,x 
3

 ,...,x 
n

 。

假設該樣本數據的累積分佈函數爲 F ( x ) F(x)F(x),概率密度函數爲 f ( x ) f(x)f(x),則有:

F ( x i − 1 < x < x i ) = ∫ x i − 1 x i f ( x ) d x F(x_{i-1} < x < x_i) = \int\limits_{x_{i-1}}^{x_i} f(x)dx
F(x 
i−1

 <x<x 
i

 )= 

i−1

 


i

 

 f(x)dx

f ( x i ) = lim ⁡ h → 0 F ( x i + h ) − F ( x i − h ) 2 h f(x_i) = \lim^{}_{h \to 0}\frac{F(x_i+h)-F(x_i-h)}{2h}
f(x 
i

 )= 
h→0
lim

  
2h
F(x 
i

 +h)−F(x 
i

 −h)

 

引入累積分佈函數的經驗分佈函數:

F n ( t ) = 1 n ∑ i = 1 n 1 x i ≤ t F_n(t) = \frac{1}{n}\sum^{n}_{i=1}1_{x_i \leq t}

n

 (t)= 
n
1

  
i=1

n

 1 

i

 ≤t

 

該函數大意爲:使用 n nn 次觀測中 x i ≤ t x_i \leq tx 
i

 ≤t 出現的次數與 n nn 的比值來近似描述 P ( x ≤ t ) P(x \leq t)P(x≤t)。

將該函數代入 f ( x i ) f(x_i)f(x 
i

 ),有:

f ( x i ) = lim ⁡ h → 0 1 2 n h ∑ i = 1 n 1 x i − h ≤ x j ≤ x i + h f(x_i) = \lim^{}_{h \to 0}\frac{1}{2nh}\sum^{n}_{i=1}1_{x_i-h \leq x_j \leq x_i+h}
f(x 
i

 )= 
h→0
lim

  
2nh
1

  
i=1

n

 1 

i

 −h≤x 
j

 ≤x 
i

 +h

 

根據該公式,在實際計算中,必須給定 h hh 值,h hh 值不能太大也不能太小,太大不滿足 h → 0 h \to 0h→0 的條件,太小使用的樣本數據點太少,誤差會很大,因此,關於 h hh 值的選擇有很多研究,該值也被稱爲核密度估計中的帶寬。

確定帶寬後,我們可以寫出 f ( x ) f(x)f(x) 的表達式:

f ( x ) = 1 2 n h ∑ i = 1 n 1 x − h ≤ x i ≤ x + h f(x) = \frac{1}{2nh}\sum^{n}_{i=1}1_{x-h \leq x_i \leq x+h}
f(x)= 
2nh
1

  
i=1

n

 1 
x−h≤x 
i

 ≤x+h

 

核函數
f ( x ) f(x)f(x) 表達式可變形爲:

f ( x ) = 1 2 n h ∑ i = 1 n 1 x − h ≤ x i ≤ x + h = 1 2 n h ∑ i = 1 n K ( ∣ x − x i ∣ h )
f(x)=12nh∑i=1n1x−h≤xi≤x+h=12nh∑i=1nK(|x−xi|h)
f(x)=12nh∑i=1n1x−h≤xi≤x+h=12nh∑i=1nK(|x−xi|h)
f(x)

  

2nh
1

  
i=1

n

 1 
x−h≤x 
i

 ≤x+h

 

2nh
1

  
i=1

n

 K( 
h
∣x−x 
i

 ∣

 )

 

其中,令 t = ∥ x − x i ∥ h t = \frac{\|x-x_i\|}{h}t= 
h
∥x−x 
i

 ∥

 ,則當 0 ≤ t ≤ 1 0 \leq t \leq 10≤t≤1 時,K ( t ) = 1 K(t) = 1K(t)=1.

且:

∫ f ( x ) d x = ∫ 1 2 n h ∑ i = 1 n K ( ∣ x − x i ∣ h ) d x = ∫ 1 2 n ∑ i = 1 n K ( t ) d t = ∫ 1 2 K ( t ) d t
∫f(x)dx=∫12nh∑i=1nK(|x−xi|h)dx=∫12n∑i=1nK(t)dt=∫12K(t)dt
∫f(x)dx=∫12nh∑i=1nK(|x−xi|h)dx=∫12n∑i=1nK(t)dt=∫12K(t)dt
∫f(x)dx

  
=∫ 
2nh
1

  
i=1

n

 K( 
h
∣x−x 
i

 ∣

 )dx
=∫ 
2n
1

  
i=1

n

 K(t)dt
=∫ 
2
1

 K(t)dt

 

注意,此處的 ∑ i = 1 n \sum^{n}_{i=1}∑ 
i=1
n

  指的是 n nn 次實驗,而不是累計,因此計算值爲 n nn。

令 K 0 ( t ) = 1 2 K ( t ) K_0(t) = \frac{1}{2} K(t)K 
0

 (t)= 
2
1

 K(t),根據概率密度函數的定義,我們有:

∫ K 0 ( t ) d t = 1 \int K_0(t)dt = 1
∫K 
0

 (t)dt=1

其中當 0 ≤ t ≤ 1 0 \leq t \leq 10≤t≤1 時,K 0 ( t ) = 1 2 K_0(t) = \frac{1}{2}K 
0

 (t)= 
2
1

 .

此時 K 0 ( t ) K_0(t)K 
0

 (t) 就稱爲核函數,常用的核函數有:uniform,triangular, biweight, triweight, Epanechnikov, normal…。

f ( x ) f(x)f(x) 的表達式變爲:

f ( x ) = 1 n h ∑ i = 1 n K 0 ( ∣ x − x i ∣ h ) f(x) = \frac{1}{nh}\sum^{n}_{i=1}K_0(\frac{|x-x_i|}{h})
f(x)= 
nh
1

  
i=1

n

 K 
0

 ( 
h
∣x−x 
i

 ∣

 )

對於二維數據,f ( x ) f(x)f(x) 爲:

f ( x , y ) = 1 n h 2 ∑ i = 1 n K 0 ( d i s t ( ( x , y ) , ( x i , y i ) ) h ) f(x, y) = \frac{1}{nh^2}\sum^{n}_{i=1}K_0(\frac{dist((x, y), (x_i, y_i))}{h})
f(x,y)= 
nh 
2
 
1

  
i=1

n

 K 
0

 ( 
h
dist((x,y),(x 
i

 ,y 
i

 ))

 )

實驗:POI 點核密度分析
技術選型
柵格數據可視化:canvas
KFC POI 爬取:POIKit
核函數、帶寬選擇
使用 ArcGIS 軟件說明文檔提供的帶寬選擇方案,核函數爲:

K 0 ( t ) = 3 π ( 1 − t 2 ) 2 K_0(t) = \frac{3}{\pi}(1-t^2)^2

0

 (t)= 
π
3

 (1−t 
2
 ) 
2
 

概率密度估測函數爲:
f ( x , y ) = 1 n ∗ h 2 ∑ i = 1 n p o p i K 0 ( d i s t i h ) f(x, y) = \frac{1}{n*h^2}\sum_{i=1}^n pop_i K_0(\frac{dist_i}{h})
f(x,y)= 
n∗h 
2
 
1

  
i=1

n

 pop 
i

 K 
0

 ( 
h
dist 
i

 

 )

其中,p o p i pop_ipop 
i

  爲給定的權重字段,若不含有該字段,則取值爲 1,n nn 爲 POI 點個數。

帶寬爲:

h = 0.9 ∗ m i n ( A , 1 ln ⁡ ( 2 ) ∗ D m ) ∗ n − 0.2 h = 0.9*min(A,\sqrt{\frac{1}{\ln(2)}}*D_m)*n^{-0.2}
h=0.9∗min(A, 
ln(2)
1

 

 ∗D 
m

 )∗n 
−0.2
 

參數解釋:

平均中心:指 n nn 個 POI 點的平均中心,即經度和緯度分別取平均;

加權平均中心:指 n nn 個 POI 點的加權平均中心,即經度和緯度分別乘以權重再取平均;

標準距離計算公式:

S D = ∑ i = 1 n ( x i − X ˉ ) 2 n + ∑ i = 1 n ( y i − Y ˉ ) 2 n + ∑ i = 1 n ( z i − Z ˉ ) 2 n SD = \sqrt{\frac{\sum_{i=1}^n(x_i-\bar X)^2}{n}+\frac{\sum_{i=1}^n(y_i-\bar Y)^2}{n}+\frac{\sum_{i=1}^n(z_i-\bar Z)^2}{n}}
SD= 
n
∑ 
i=1
n

 (x 
i

 − 
X
ˉ
 ) 
2
 

 + 
n
∑ 
i=1
n

 (y 
i

 − 
Y
ˉ
 ) 
2
 

 + 
n
∑ 
i=1
n

 (z 
i

 − 
Z
ˉ
 ) 
2
 

 

 

其中:

x i , y i , z i x_i,y_i,z_ix 
i

 ,y 
i

 ,z 
i

  是 POI 的座標
X ˉ , Y ˉ , Z ˉ {\bar X,\bar Y, \bar Z} 
X
ˉ
 , 
Y
ˉ
 , 
Z
ˉ
  表示平均中心
n nn 是 POI 總數
加權標準距離計算公式:

S D w = ∑ i = 1 n w i ( x i − X ˉ w ) 2 ∑ i = 1 n w i + ∑ i = 1 n w i ( y i − Y ˉ w ) 2 ∑ i = 1 n w i + ∑ i = 1 n w i ( z i − Z ˉ w ) 2 ∑ i = 1 n w i SD_w = \sqrt{\frac{\sum_{i=1}^n w_i(x_i-\bar X_w)^2}{\sum_{i=1}^{n}w_i}+\frac{\sum_{i=1}^nw_i(y_i-\bar Y_w)^2}{\sum_{i=1}^{n}w_i}+\frac{\sum_{i=1}^nw_i(z_i-\bar Z_w)^2}{\sum_{i=1}^{n}w_i}}
SD 
w

 = 
∑ 
i=1
n

 w 
i

 
∑ 
i=1
n

 w 
i

 (x 
i

 − 
X
ˉ
  
w

 ) 
2
 

 + 
∑ 
i=1
n

 w 
i

 
∑ 
i=1
n

 w 
i

 (y 
i

 − 
Y
ˉ
  
w

 ) 
2
 

 + 
∑ 
i=1
n

 w 
i

 
∑ 
i=1
n

 w 
i

 (z 
i

 − 
Z
ˉ
  
w

 ) 
2
 

 

 

其中:

w i w_iw 
i

  是要素 i ii 的權重
X ˉ w , Y ˉ w , Z ˉ w {\bar X_w,\bar Y_w, \bar Z_w} 
X
ˉ
  
w

 , 
Y
ˉ
  
w

 , 
Z
ˉ
  
w

  表示加權平均中心
n nn 是 POI 總數
若 POI 點不含有權重字段,則 D m D_mD 
m

  爲到平均中心距離的中值,n nn 是 POI 點數目,A AA 是標準距離 S D SDSD;

若 POI 點含有權重字段,則 D m D_mD 
m

  爲到加權平均中心距離的中值,n nn 是 POI 點權重字段值的總和,A AA 是加權標準距離S D w SD_wSD 
w

 。

數據爬取
使用 POIKit,爬取河南省 KFC POI 數據,參數設置如下:

 

共爬取得到 219 條數據,如下圖所示:

 

程序編寫
核函數:

/**
 * 核函數
 * @param {Number} t 變量
 * @returns
 */
function kernel(t) {
  return (3 / Math.PI) * Math.pow(1 - t * t, 2);
}
1
2
3
4
5
6
7
8
帶寬:

/**
 * 帶寬
 * @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);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
平均中心:

/**
 * 平均中心
 * @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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
標準距離 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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
核密度估計:

/**
 * 核密度估計
 * @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 };
}
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
然後使用 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);
      }
    }
  }
}
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
核密度估計結果
下圖是核密度估計結果:

 

通過該圖,可發現核密度估計的結果能很好的展示數據的熱點區域。

全部代碼
關注微信公衆號古月有三木,回覆核密度分析即可獲取全部代碼及數據。
————————————————
版權聲明:本文爲CSDN博主「Civitasv」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/qq_40996400/article/details/103772706

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章