Getting the center point of a cluster for latitude and longitude in Python

Question:

I have a list of of coordinates that have areas mapped out as follows

df=pd.DataFrame({'user_id':[55,55,356,356,356,356,632,752,938,963,963,1226,2663,2663,2663,2663,2663,3183,3197,3344,3387,3387,3387,3387,3396,3515,3536,3570,3819,3883,3883,3883,3883,3883,3883,3883,3883,3883,3883,3883,3883,4584,4594,4713,4931,4931,5026,5487,5487,5575,5575,5575,5602,5639,5639,5639,5639,5783,5783,5783,5783,5783,5801,6373,6718,6886,6886,7055,7055,7608,7608,7777,8186,8186,8307,8712,9271,9896,9991,9991,9991,],
    'latitude':[13.2633943,13.2633964,12.809677124,12.8099212646,12.8100585938,12.810065981,12.9440132,12.2958104,12.5265661,13.0767648,13.0853577,12.6301221,12.8558120728,12.8558349609,12.8558654785,12.8558807373,12.8558959961,12.9141417,13.0696411133,13.0708333,10.7904833,10.7904833,10.7904833,12.884091,13.0694428,13.204637,12.6922086,13.0767648,13.3489958,12.8653798,12.8654014,12.8654124,12.8654448,12.8654521,12.8654658,12.8654733,12.8654815,12.8654844,12.8655367,12.8655376,12.865576,12.4025539,13.1986348,12.9548317,11.664325,11.6690603,13.0656551,13.1137554,13.1137978,12.770418,12.9141417,12.9141417,15.3530727,12.8285405054,12.8285925,12.8288406,12.829668,12.2958104,12.5583190918,12.7367172241,12.7399597168,12.7422103882,12.8631981,13.3378762,12.5638375681,13.1961683,13.1993678,12.1210997,12.5265661,13.1332778931,13.13331604,12.1210997,13.0649372,13.0658797,12.6955714,12.8213806152,13.0641708374,13.2013835,13.1154662,13.1957473755,13.2329025269,],
                      'longitude':[75.4341412,75.4341377,77.6955155017,77.6952344177,77.6952628334,77.6952629697,75.7926285,76.6393805,78.2149575,77.6397007,77.6445166,77.1145378,77.7985897361,77.7985953164,77.798622112,77.7985610742,77.7986275271,74.8559568,77.6520116309,77.6519444,78.7046725,78.7046725,78.7046725,74.8372421,77.6523596,77.6506622,78.6181131,77.6397007,74.7855559,77.7972191,77.7971733,77.7971429,77.7971621,77.7970823,77.7970327,77.7970371,77.7972272,77.7970335,77.7969649,77.796956,77.7971244,75.9811564,77.7065928,77.4739615,78.1460142,78.139311,77.4380296,77.5732437,77.573201,74.8609332,74.8559568,74.8559568,75.1386825,77.6891233027,77.6899376,77.6892531,77.6902955,76.6393805,77.7842363745,77.7841222429,77.7837989946,77.7830295359,77.4336428,77.117325,75.5833357573,77.7053231,77.7095658,78.1582143,78.2149575,77.5728687166,77.5729374436,78.1582143,77.7435873,77.7444939,78.0620963,77.6606095672,77.746332751,77.7082838,77.6069977,77.7055573788,77.6956690934,],
                      })

For the following latitude longitude pairs I am using DBSCAN to cluster them

X=np.array(df[['latitude', 'longitude']])


kms_per_radian = 6371.0088
epsilon = 1 / kms_per_radian
db = DBSCAN(eps=epsilon, min_samples=5) 

model=db.fit(np.radians(X))
cluster_labels = db.labels_
num_clusters = len(set(cluster_labels))

cluster_labels = cluster_labels.astype(float)
cluster_labels[cluster_labels == -1] = np.nan

clusters = pd.Series( [X[cluster_labels==n] for n in range(num_clusters)] )

labels = pd.DataFrame(db.labels_,columns=['CLUSTER_LABEL'])

dfnew=pd.concat([df,labels],axis=1,sort=False)

How do I get the get the center point of these clusters and map it back to the dataset so that when I display the same in folium with a marker and the summary starts there?

So far I have tried

def get_centermost_point(cluster):
    centroid = (MultiPoint(cluster).centroid.x, MultiPoint(cluster).centroid.y)
    centermost_point = min(cluster, key=lambda point: great_circle(point, centroid).m)
    return tuple(centermost_point)

centermost_points = clusters.map(get_centermost_point)

which gives me a IndexError: list index out of range error

Asked By: Ani

||

Answers:

To get the coordinates of each cluster’s centroid:

for ea in clusters:
    print(MultiPoint(ea).centroid)

Outcome:

POINT (12.85585784912 77.79859915316)
POINT (12.86547048333333 77.79709629166666)
POINT (13.1982603551 77.70706457576)
POINT EMPTY

To create a geodataframe from the centroids and plot it.
(assuming the coordinates are long/lat)

# To create a geodataframe of the centroids
clusters_centroids = [MultiPoint(ea).centroid for ea in clusters]
crs = {'init': 'epsg:4326'}
cgdf = gpd.GeoDataFrame(clusters, crs=crs, geometry=clusters_centroids)
# Eliminate some empty row(s)
good_cdgf = cgdf[ ~cgdf['geometry'].is_empty ]

# plot to see the centroids
good_cdgf.plot()

The output plot:

centroids

Answered By: swatchai

To add the center points back into the original dataframe df.

Here I start with checking dfnew which is simply df with added column CLUSTER_LABEL.

print(dfnew)

    user_id   latitude  longitude  CLUSTER_LABEL
0        55  13.263394  75.434141             -1
1        55  13.263396  75.434138             -1
2       356  12.809677  77.695516             -1
3       356  12.809921  77.695234             -1
4       356  12.810059  77.695263             -1
..      ...        ...        ...            ...
76     9271  13.064171  77.746333             -1
77     9896  13.201384  77.708284              2
78     9991  13.115466  77.606998             -1
79     9991  13.195747  77.705557              2
80     9991  13.232903  77.695669             -1

[81 rows x 4 columns]

The column CLUSTER_LABEL will be used to join and get values from cgdf dataframe.

Add a new CLUSTER_LABEL column with proper cluster’s label values to cgdf

cgdf["CLUSTER_LABEL"] = [0,1,2, -1]

Drop column 0 of cgdf

cgdf.drop(columns=[0], axis=1, inplace=True)

Check current cgdf

print(cgdf)

                geometry  CLUSTER_LABEL
0  POINT (12.856 77.799)              0
1  POINT (12.865 77.797)              1
2  POINT (13.198 77.707)              2
3            POINT EMPTY             -1

Merge two dataframes into new dataframe dfnew2.

dfnew2 = dfnew.merge(cgdf, on='CLUSTER_LABEL')

Check current status of dfnew2, it should look like this:

    user_id   latitude  longitude  CLUSTER_LABEL               geometry
0        55  13.263394  75.434141             -1            POINT EMPTY
1        55  13.263396  75.434138             -1            POINT EMPTY
2       356  12.809677  77.695516             -1            POINT EMPTY
3       356  12.809921  77.695234             -1            POINT EMPTY
4       356  12.810059  77.695263             -1            POINT EMPTY
..      ...        ...        ...            ...                    ...
76     4594  13.198635  77.706593              2  POINT (13.198 77.707)
77     6886  13.196168  77.705323              2  POINT (13.198 77.707)
78     6886  13.199368  77.709566              2  POINT (13.198 77.707)
79     9896  13.201384  77.708284              2  POINT (13.198 77.707)
80     9991  13.195747  77.705557              2  POINT (13.198 77.707)

[81 rows x 5 columns]

‘dfnew2’ should be equivalent with the original dataframe with 2 additional special columns, ‘CLUSTER_LABEL’ and ‘geometry’ (of cluster’s center point).

Answered By: swatchai
try:
    from sklearn.tree import DecisionTreeClassifier
except:
    pass
from sklearn.cluster import KMeans

def kmeans_centers(list_of_lats_lngs): #type of input list of lists
    try:
        data = pd.DataFrame([list_of_lats_lngs],columns=['lat','lng'])
        data['eventType']= "test"
        data.dropna(axis=0,how='any',subset=['lat','lng'],inplace=True)
        
        X=data.loc[:,['eventType','lat','lng']]

        K_clusters = range(1,10)
        kmeans = [KMeans(n_clusters=i) for i in K_clusters]
        Y_axis = data[['lat']]
        X_axis = data[['lng']]
        
        kmeans = KMeans(n_clusters = 3, init ='k-means++')
        kmeans.fit(X[X.columns[1:3]])
        X['cluster_label'] = kmeans.fit_predict(X[X.columns[1:3]])
        centers = kmeans.cluster_centers_ # Coordinates of cluster centers.
        # labels = kmeans.predict(X[X.columns[1:3]]) # Labels of each point
        
        return centers
    except Exception as e:
        print("kmeans - CLustering exception",e)
        return None
  • Ready to use

Input

[[12.02,12.34],[12.12,12.04],[12.092,12.74],[22.02,13.34]]
Answered By: gamingflexer