web-dev-qa-db-ja.com

地理的位置データのクラスタリングのためのDBSCAN

緯度と経度のペアを持つデータフレームがあります。

これは私のデータフレームのようなものです。

    order_lat  order_long
0   19.111841   72.910729
1   19.111342   72.908387
2   19.111342   72.908387
3   19.137815   72.914085
4   19.119677   72.905081
5   19.119677   72.905081
6   19.119677   72.905081
7   19.120217   72.907121
8   19.120217   72.907121
9   19.119677   72.905081
10  19.119677   72.905081
11  19.119677   72.905081
12  19.111860   72.911346
13  19.111860   72.911346
14  19.119677   72.905081
15  19.119677   72.905081
16  19.119677   72.905081
17  19.137815   72.914085
18  19.115380   72.909144
19  19.115380   72.909144
20  19.116168   72.909573
21  19.119677   72.905081
22  19.137815   72.914085
23  19.137815   72.914085
24  19.112955   72.910102
25  19.112955   72.910102
26  19.112955   72.910102
27  19.119677   72.905081
28  19.119677   72.905081
29  19.115380   72.909144
30  19.119677   72.905081
31  19.119677   72.905081
32  19.119677   72.905081
33  19.119677   72.905081
34  19.119677   72.905081
35  19.111860   72.911346
36  19.111841   72.910729
37  19.131674   72.918510
38  19.119677   72.905081
39  19.111860   72.911346
40  19.111860   72.911346
41  19.111841   72.910729
42  19.111841   72.910729
43  19.111841   72.910729
44  19.115380   72.909144
45  19.116625   72.909185
46  19.115671   72.908985
47  19.119677   72.905081
48  19.119677   72.905081
49  19.119677   72.905081
50  19.116183   72.909646
51  19.113827   72.893833
52  19.119677   72.905081
53  19.114100   72.894985
54  19.107491   72.901760
55  19.119677   72.905081

私の距離行列に従って、互いに最も近い(200メートルの距離)このポイントをクラスター化します。

from scipy.spatial.distance import pdist, squareform
distance_matrix = squareform(pdist(X, (lambda u,v: haversine(u,v))))

array([[ 0.        ,  0.2522482 ,  0.2522482 , ...,  1.67313071,
     1.05925366,  1.05420922],
   [ 0.2522482 ,  0.        ,  0.        , ...,  1.44111548,
     0.81742536,  0.98978355],
   [ 0.2522482 ,  0.        ,  0.        , ...,  1.44111548,
     0.81742536,  0.98978355],
   ..., 
   [ 1.67313071,  1.44111548,  1.44111548, ...,  0.        ,
     1.02310118,  1.22871515],
   [ 1.05925366,  0.81742536,  0.81742536, ...,  1.02310118,
     0.        ,  1.39923529],
   [ 1.05420922,  0.98978355,  0.98978355, ...,  1.22871515,
     1.39923529,  0.        ]])

次に、距離行列にDBSCANクラスタリングアルゴリズムを適用しています。

 from sklearn.cluster import DBSCAN

 db = DBSCAN(eps=2,min_samples=5)
 y_db = db.fit_predict(distance_matrix)

Epsとmin_samplesの値を選択する方法がわかりません。それは、1つのクラスター内で遠すぎる点をクラスター化します。(距離約2 km)クラスター化中にユークリッド距離を計算するためですか?助けてください。

19
Neil

DBSCANはmeantであり、生データで使用され、加速用の空間インデックスが使用されます。ジオ距離の加速で私が知っている唯一のツールは [〜#〜] elki [〜#〜] (Java)-scikit-learnは残念ながらユークリッド距離のようないくつかの距離でのみこれをサポートします(sklearn.neighbors.NearestNeighbors)。しかし、明らかに、ペアワイズ距離を事前計算する努力をすることができるので、これは(まだ)問題ではありません。

ただし、ドキュメントを十分に注意深く読んでいなかったため、DBSCANが距離行列を使用するという仮定は間違っています:

from sklearn.cluster import DBSCAN
db = DBSCAN(eps=2,min_samples=5)
db.fit_predict(distance_matrix)

距離行列行のユークリッド距離を使用しますが、これは明らかに意味をなしません。

DBSCANのドキュメントを参照してください(強調を追加):

クラスsklearn.cluster.DBSCAN(eps = 0.5、min_samples = 5、metric = 'euclidean'、algorithm = 'auto'、leaf_size = 30、p =なし、random_state =なし)

metric:文字列、または呼び出し可能

フィーチャ配列内のインスタンス間の距離を計算するときに使用するメトリック。 metricが文字列または呼び出し可能の場合、metricパラメーターのmetrics.pairwise.calculate_distanceで許可されるオプションの1つでなければなりません。 メトリックが「事前計算」される場合、Xは距離行列であると想定され、正方でなければなりません。Xはスパース行列である場合があり、その場合は非ゼロ」要素は、DBSCANの近傍と見なされる場合があります。

fit_predictでも同様:

[〜#〜] x [〜#〜]:形状の配列またはスパース(CSR)行列(n_samples、n_features)、または形状の配列( n_samples、n_samples)

特徴配列、またはサンプル間の距離の配列if metric = 'precomputed'。

言い換えれば、あなたはする必要があります

db = DBSCAN(eps=2, min_samples=5, metric="precomputed")
12
Anony-Mousse

距離行列を事前計算することなく、scikit-learnのDBSCANを使用して空間緯度経度データをクラスタリングできます。

_db = DBSCAN(eps=2/6371., min_samples=5, algorithm='ball_tree', metric='haversine').fit(np.radians(coordinates))
_

これは、scikit-learn DBSCANを使用した空間データのクラスター化に関するこのチュートリアルからのものです。特に、eps値はまだ2kmですが、ラジアンに変換するために6371で除算されています。また、.fit()は、haversineメトリックの座標をラジアン単位で取得することに注意してください。

33
eos

使用しているhaversineの実装がわかりませんが、kmで結果を返すように見えるので、epsは200 mの場合2ではなく0.2になります。

min_samplesパラメーターの場合、それは予想される出力によって異なります。以下に例をいくつか示します。私の出力は、 この答え に基づくhaversineの実装を使用しています。これにより、距離行列は似ていますが、同じではありません。

これはdb = DBSCAN(eps=0.2, min_samples=5)

[0 -1 -1 -1 1 1 1 1 -1 -1 1 1 1 2 2 1 1 1 1 -1 -1 -1 -1 1 -1 -1 -1 -1 -1 1 1 -1 1 1 1 1 1 1 2 0 -1 1 2 2 0 0 0 -1 -1 -1 1 1 1 -1 -1 1 -1 -1 1]

これにより、0, 12の3つのクラスターが作成され、多くのサンプルは少なくとも5つのメンバーを持つクラスターに分類されないため、クラスターに割り当てられません(-1として表示)。

より小さなmin_samples値で再試行します。

db = DBSCAN(eps=0.2, min_samples=2)

[0 1 1 2 3 3 3 4 4 3 3 3 5 5 3 3 3 2 6 6 7 3 2 2 8 8 8 3 3 6 3 3 3 3 3 5 0 -1 3 5 5 0 0 0 6 -1- 1 3 3 3 7 -1 3 -1 -1 3]

ここで、ほとんどのサンプルは少なくとも1つの他のサンプルから200m以内にあるため、0から7の8つのクラスターのいずれかに分類されます。

編集して追加

@ Anony-Mousseは正しいように見えますが、結果には何も問題はありませんでした。何かを提供するために、クラスターを表示するために使用していたコードを以下に示します。

from math import radians, cos, sin, asin, sqrt

from scipy.spatial.distance import pdist, squareform
from sklearn.cluster import DBSCAN

import matplotlib.pyplot as plt
import pandas as pd


def haversine(lonlat1, lonlat2):
    """
    Calculate the great circle distance between two points 
    on the earth (specified in decimal degrees)
    """
    # convert decimal degrees to radians 
    lat1, lon1 = lonlat1
    lat2, lon2 = lonlat2
    lon1, lat1, lon2, lat2 = map(radians, [lon1, lat1, lon2, lat2])

    # haversine formula 
    dlon = lon2 - lon1 
    dlat = lat2 - lat1 
    a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2
    c = 2 * asin(sqrt(a)) 
    r = 6371 # Radius of earth in kilometers. Use 3956 for miles
    return c * r


X = pd.read_csv('dbscan_test.csv')
distance_matrix = squareform(pdist(X, (lambda u,v: haversine(u,v))))

db = DBSCAN(eps=0.2, min_samples=2, metric='precomputed')  # using "precomputed" as recommended by @Anony-Mousse
y_db = db.fit_predict(distance_matrix)

X['cluster'] = y_db

plt.scatter(X['lat'], X['lng'], c=X['cluster'])
plt.show()
5
Jamie Bull