基于坐标和非空间特征的聚类地理空间数据

Clustering geospatial data on coordinates AND non spatial feature

假设我将以下数据框存储为一个名为坐标的变量,其中前几行如下所示:

   business_lat  business_lng  business_rating
0   19.111841     72.910729           5.
1   19.111342     72.908387           5.
2   19.111342     72.908387           4.
3   19.137815     72.914085           5.
4   19.119677     72.905081           2.
5   19.119677     72.905081           2.
        .             .               .
        .             .               .
        .             .               .

如您所见,此数据是地理空间数据(具有纬度和经度)并且每一行都有一个附加值 business_rating,对应于该行中 latlng 的企业评级。我想对数据进行聚类,将附近且具有相似评级的企业分配到同一个聚类中。本质上我需要一个地理空间集群,附加要求是集群必须考虑评级列。

我在网上看过,但真的找不到太多解决方法:只有严格的地理空间聚类(只有要聚类的特征是 latlng)或非空间聚类。

我在下面有一个简单的 DBSCAN 运行,但是当我绘制聚类结果时,它似乎没有按照我的要求正确执行。

from sklearn.cluster import DBSCAN
import numpy as np
db = DBSCAN(eps=2/6371., min_samples=5, algorithm='ball_tree', metric='haversine').fit(np.radians(coordinates))

尝试调整 DBSCAN 的参数、对数据进行一些额外处理或一起使用不同的方法是否会更好?

使用 DBSCAN 方法,我们可以计算点之间的距离(欧几里得距离或其他距离)并寻找远离其他点的点。您可能需要考虑使用 MinMaxScaler 来规范化值,这样一个功能就不会压倒其他功能。

你的代码在哪里,你的最终结果是什么?没有实际的代码示例,我只能猜测你在做什么。

我为您编写了一些示例代码。您可以在下面看到结果。

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
import seaborn as sns; sns.set()
import csv

df = pd.read_csv('C:\your_path_here\business.csv')

X=df.loc[:,['review_count','latitude','longitude']]

K_clusters = range(1,10)
kmeans = [KMeans(n_clusters=i) for i in K_clusters]
Y_axis = df[['latitude']]
X_axis = df[['longitude']]
score = [kmeans[i].fit(Y_axis).score(Y_axis) for i in range(len(kmeans))]# Visualize

plt.plot(K_clusters, score)
plt.xlabel('Number of Clusters')
plt.ylabel('Score')
plt.title('Elbow Curve')
plt.show()

kmeans = KMeans(n_clusters = 3, init ='k-means++')
kmeans.fit(X[X.columns[0:2]]) # Compute k-means clustering.

X['cluster_label'] = kmeans.fit_predict(X[X.columns[0:2]])
centers = kmeans.cluster_centers_ # Coordinates of cluster centers.

labels = kmeans.predict(X[X.columns[0:2]]) # Labels of each point
X.head(10)

X.plot.scatter(x = 'latitude', y = 'longitude', c=labels, s=50, cmap='viridis')
plt.scatter(centers[:, 0], centers[:, 1], c='black', s=200, alpha=0.5)

from scipy.stats import zscore
df["zscore"] = zscore(df["review_count"])
df["outlier"] = df["zscore"].apply(lambda x: x <= -2.5 or x >= 2.5)
df[df["outlier"]]

df_cord = df[["latitude", "longitude"]]
df_cord.plot.scatter(x = "latitude", y = "latitude")

from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
df_cord = scaler.fit_transform(df_cord)
df_cord = pd.DataFrame(df_cord, columns = ["latitude", "longitude"])
df_cord.plot.scatter(x = "latitude", y = "longitude")

from sklearn.cluster import DBSCAN
outlier_detection = DBSCAN(
  eps = 0.5,
  metric="euclidean",
  min_samples = 3,
  n_jobs = -1)
clusters = outlier_detection.fit_predict(df_cord)

clusters

from matplotlib import cm
cmap = cm.get_cmap('Accent')
df_cord.plot.scatter(
  x = "latitude",
  y = "longitude",
  c = clusters,
  cmap = cmap,
  colorbar = False
)

说实话,最后的结果看起来有点奇怪。请记住,并非所有内容都是可聚类的。

关于将两种不同类型的信息(位置和评级)聚类的棘手部分是确定它们应该如何相互关联。当它只是一个域并且您正在比较相同的单位时,询问很简单。我的方法是查看如何关联域中的行,然后确定域之间的一些交互。这可以使用像 MinMaxScaler 提到的缩放选项来完成,但是,我认为这有点笨拙,我们可以利用我们对域的知识来更好地聚类。

处理地点

最好直接处理位置距离,因为这具有现实意义,我们可以预先计算距离。米的意思直接跟我们说的一样

您可以使用上一个答案中提到的缩放选项,但这有扭曲位置数据的风险。例如,如果您有一组细长的位置,则 MinMaxScaling 会比长轴更重视细轴上的变化。如果您要使用缩放,请在计算的距离矩阵上进行缩放,而不是在经纬度本身上进行缩放。

import numpy as np
from sklearn.metrics.pairwise import haversine_distances


points_in_radians = df[['business_lat','business_lng']].apply(np.radians).values
distances_in_km = haversine_distances(points_in_radians) * 6371

添加评级

我们可以通过提出几个与距离有关的评分问题来思考这个问题。我们可能会问,评级必须有多大差异才能在同一个地方分开观察?什么是仪表差异与评级差异比率?有了比率的想法,我们可以为所有观察的评分差异计算另一个距离矩阵,并使用它来缩放或添加到原始位置距离矩阵,或者我们可以增加评分中每个差距的距离。然后可以对这个位置加评级差异矩阵进行聚类。

from sklearn.metrics.pairwise import euclidean_distances

added_km_per_rating_gap = 1
rating_distances = euclidean_distances(df[['business_rating']].values) * added_km_per_rating_gap 

然后我们可以简单地将它们加在一起并在生成的矩阵上聚类。

from sklearn.cluster import DBSCAN

distance_matrix = rating_distances + distances_in_km

clustering = DBSCAN(metric='precomputed', eps=1, min_samples=2)
clustering.fit(distance_matrix)

我们所做的是按位置聚类,对收视率差异增加惩罚。使惩罚直接且可控允许优化以找到最佳聚类。

测试

我发现的问题是(至少在我的测试数据中)DBSCAN 倾向于 'walk' 从观察到观察形成集群,这些集群要么将评级混合在一起,因为惩罚不够高,或者分为单个评级组。可能是 DBSCAN 不适合这种类型的聚类。如果我有更多时间,我会寻找一些开放数据来测试它并尝试其他聚类方法。

这是我用来测试的代码。我使用评分距离的平方来强调较大的差距。

import random
from sklearn.datasets import make_blobs


X, y = make_blobs(n_samples=300, centers=6, cluster_std=0.60, random_state=0)
ratings = np.array([random.randint(1,4) for _ in range(len(X)//2)] \
          +[random.randint(2,5) for _ in range(len(X)//2)]).reshape(-1, 1)

distances_in_km = euclidean_distances(X)
rating_distances = euclidean_distances(ratings)


def build_clusters(multiplier, eps):
    rating_addition = (rating_distances ** 2) * multiplier
    distance_matrix = rating_addition + distances_in_km
    clustering = DBSCAN(metric='precomputed', eps=eps, min_samples=10)
    clustering.fit(distance_matrix)
    return clustering.labels_