最近、仕事でちょっとしたニーズがあり、おそらく地図上の多数の点のうち最も点の密度が高い場所を表示する必要がありました。最初は良い方法が思いつかなかったので、すべてのポイントの座標を平均するという非常に単純な戦略を使用しました。ほとんどの都市のすべてのポイントは基本的に特定の中心の周りにあるため、この方法はほとんどの場合に使いやすいです. 点は四方八方に広がっています。しかし、実際にオンラインで利用してみると、2つの特殊なケースに遭遇しました。
1 つ目は、点の分布が異常な形を示している場合 (ダンベル型のデータが両端に分布している場合など)、平均的な手法では、成都のデータで遭遇したのと同じように、中央の最もデータ密度が低い場所を見つけます。下図のように の赤い点が平均値から計算された中心点です。
もう 1 つの異常なケースは、北京のデータのように、データが円形の分布を示している場合です。北京の中心は紫禁城です。点を持つことは不可能です。平均を直接計算すると、計算された中心点は次のようになります。以下の図に示すように、ここではデータが最もまばらになります。
後で調べてみると、カーネル密度法で問題を解決できることが分かり、実験した結果、その効果は悪くないことが分かりましたので、ここで共有させていただきます。カーネル密度の考え方も非常にシンプルで、すべての点を横断し、他の点から現在の点までのカーネル密度の合計値を計算し、平均密度が最大の点を見つけるというものです。簡単な例として、ある点が与えられた場合、他の点がこの点に近い場合、密度値は高くなります。そうでない場合は、遠くなります。この点から他のすべての点までの密度の合計の平均が、最終的な密度になります。この点の値、ここでは距離の逆数をカーネル関数として直接使用できますが、このカーネル関数は線形であり、最終的な結果は私の平均値とあまり変わりません。
アイデアを最適化すると、ある点間の距離が遠ければ、それによってもたらされる密度値は小さくなるはずでしょうか? 先人もそう考えていたので、非線形カーネル関数が多くなり、最終的にガウシアンカーネルを使いましたが、カーネル関数の帯域幅を調整した後、他の点からもたらされる密度値も距離に応じて正規分布するようにする方法です。減衰量は下図のようになり、例えば縦軸の座標値が離れるほど座標値が低くなり、図中のシグマがカーネル関数の帯域幅となります。
次に, 計算プロセスと効果を見てみましょう. 私たちは Java システムなので, 私の最終的な実装は Java を使用して単純なパッケージを呼び出すことです. 全体的なコードは次のとおりです:
private double[] getHotpot(double[][] data) {
// 创建高斯核
MercerKernel<double[]> kernel = new GaussianKernel(0.02);
// 计算所有点的核密度估计
double[] densities = new double[data.length];
for (int i = 0; i < data.length; i++) {
for (int j = 0; j < data.length; j++) {
densities[i] += kernel.k(data[i], data[j]);
}
// 计算平均密度
densities[i] /= data.length;
}
// 找出密度最大的点
int maxDensityIndex = 0;
for (int i = 1; i < densities.length; i++) {
if (densities[i] > densities[maxDensityIndex]) {
maxDensityIndex = i;
}
}
return data[maxDensityIndex];
}
ここでは帯域幅 (ガウス カーネルのシグマ) に 0.02 を使用しました。これも複数のデバッグの結果です。大きすぎる場合、計算された密度値はグローバル平均に近づきます。小さすぎる場合、数ポイント上記の 2 つの異常なケースを取り上げて、カーネル密度法の効果を見てみましょう。まずは成都ダンベルタイプのデータです。
次は北京の年輪データです
。上の図では、Python の sklearn を使用してカーネル密度を実装し、地図を描画するために folium を使用しました。完全なコードも参考までに掲載します。
# -*- coding: utf-8 -*-
import folium
import pandas as pd
from sklearn.neighbors import KernelDensity
import numpy as np
def getCenterPoint(sites):
points = sites[['latitude', 'longitude']].values
weights = sites['score'].values
# 实例化KernelDensity对象
kde = KernelDensity(kernel='gaussian', bandwidth=0.02)
# 对数据进行拟合
kde.fit(points)
# 使用KDE模型评估每个点的密度
log_densities = kde.score_samples(points)
# 密度最高的点是评估密度最高(即,log_densities值最大)的点
highest_density_point = points[np.argmax(log_densities)]
print(highest_density_point.tolist())
return highest_density_point.tolist()
# 创建一个以给定经纬度为中心的地图,初始缩放级别设为14
m = folium.Map(zoom_start=14)
for i, s in data.iterrows():
# 在地图上添加一个点标记
folium.Marker(
location=[s['latitude'], s['longitude']], # 经纬度
popup=s['resblock'],
).add_to(m)
# 保存为html文件
centerPoint = getCenterPoint(cityDf)
folium.Marker(
location=centerPoint, # 经纬度
popup='中心点', # 弹出内容
radius=50,
icon=folium.Icon(color="red", icon="info-sign")
).add_to(m)
m.location = centerPoint
m.save('map.html')