利用机器学习算法中sklean库中KNN、高效分析地图位置点的关系。其中KNN模块中主要使用ball-tree数据结构模型,提高运算效率完成各类距离关系分析。k-近邻算法的核心思想是未标记样本的类别,由距离其最近的k个邻居投票决定。

一、计算经纬度间的距离

经纬度计算有测地距离、大圆距离和欧几里得距离,其中测地距离使用地球椭圆模型,大圆距离使用地球未圆形模型、欧几里得直接计算两点间直线距离,精度依次降低但是(随着计算复杂度降低)运算速度依次增加。Geopy可以用 geodesic distance 或 great-circle distance ,默认测地线距离作为函数

geopy.distance.distance
geodesic distance 地表两点之间的最短路径的距离。 有许多类型的椭球体模型,哪一个模型最精确取决于你的点在地球上的位置。默认值是WGS-84椭球体,它是全局最精确的。geopy还包括 distance.ELLIPSOIDS 字典: model major (km) minor (km) flattening ELLIPSOIDS = {‘WGS-84’: (6378.137, 6356.7523142, 1 / 298.257223563), ‘GRS-80’: (6378.137, 6356.7523141, 1 / 298.257222101), ‘Airy (1830)’: (6377.563396, 6356.256909, 1 / 299.3249646), ‘Intl 1924’: (6378.388, 6356.911946, 1 / 297.0), ‘Clarke (1880)’: (6378.249145, 6356.51486955, 1 / 293.465), ‘GRS-67’: (6378.1600, 6356.774719, 1 / 298.25), }

大圆距离( great_circle )使用地球的球形模型,使用国际大地测量学和地球物理学联合会定义的平均地球半径,(2)a + b) /3=6371.0087714150598公里,约6371.009公里(WGS-84),误差高达0.5%。半径值存储在 distance.EARTH_RADIUS ,因此可以对其进行自定义(但是,它应该始终以公里为单位)。

欧几里得距离 使用scipy通过numpy可以高效计算欧几里得距离。

from scipy.spatial.distance import cdist
    lonlat1=data_lonlat_1[['lat_1','lon_1']]
    lonlat2=data_lonlat_2[['lat_2','lon_2']]
    distance=cdist(lonlat1,lonlat2,metric='euclidean')

1、计算任意两个点的距离

利用Geopy库计算测地距离,地球模型使用默认值WGS-84椭球体。语句d=distance.distance(old, new).m 的m(代表米)可以改为km。

def dis_N(xlon,xlat,ylon,ylat):
    old=(xlat,xlon)
    new=(ylat,ylon)
    d=distance.distance(old, new).m
    s=round(float(d),2)
    return s

2、计算numpy数组的距离。

注意事项:

1、Geopy库的经纬度输入先lat后lon,为了适配数据源中最常见的 经度—纬度 数据结构调整了输入元组中的顺序为 old=(o[1],o[0])。

2、为了适配机器学习中常用的numpy数据,通过以下数据转化数据格式

data_jwd=data[[“经度”,“纬度”]]
#转为numpy 数组
data_p = data_jwd.values
距离函数

def disN(o,n):
    # Geopy库先lat后lon
    old=(o[1],o[0])
    new=(n[1],n[0])
    d=distance.distance(old, new).m
    s=round(float(d),2)
    return s

二、计算距离m内个所有的点。

代码如下

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.neighbors import NearestNeighbors
from geopy import distance
pd.options.mode.chained_assignment = None
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

距离计算函数

def disN(o,n):
    # Geopy库先lat后lon
    old=(o[1],o[0])
    new=(n[1],n[0])
    d=distance.distance(old, new).m
    s=round(float(d),2)
    return s

逻辑函数

def distance_m(df_s,find_t,dis_m):

    #形成待查经纬度表,并清洗NA值
    find_n=find_t[["经度","纬度"]]
    find_n.dropna(inplace=True)
    find_n.reset_index(drop=True, inplace=True)
    #形成待查工参表,并清洗NA值
    data=df_s[["小区中文名","CGI","经度","纬度"]]
    data.dropna(inplace=True)
    data.reset_index(drop=True,inplace=True)
    #形成numpy数组
    data_cgi=data["CGI"].values
    data_jwd=data[["经度","纬度"]]
    data_p = data_jwd.values


    #knn 建立最近邻模型和训练,主要是对data_p中数据建立了'ball_tree'数据模型
    nn = NearestNeighbors(metric=disN, algorithm='ball_tree')
    nn.fit(data_p)
    distances, points = nn.radius_neighbors(find_n, dis_m, return_distance=True)

将索引转换为想要的字段

  k = list(points)
    r = len(k)
    for i in range(r):
        z_array = k[i]
        z_array = z_array.astype(np.str)
        m = len(z_array)
        for j in range(m):
            n = z_array[j]
            n = int(n)
            z_array[j] = data_cgi[n]
        k[i] = z_array

    find_cgi = pd.DataFrame(k)
    data_dis = pd.DataFrame(distances)
    data_dis = pd.DataFrame(data_dis[0].values.tolist())

    df=pd.concat([find_t,data_dis,find_cgi],axis=1)
    return df

df_s 为source,对find_t表中的每一个经纬度从df_s表查找距离最近的N个点,
#df_s需要包含[“小区中文名”, “CGI”, “经度”, “纬度”]

df_s=pd.read_csv(r"path\xx.csv",encoding='utf-8')

find_t为需要查找的表 target表,需要包含[“经度”, “纬度”]

find_t=pd.read_csv(r"path\xx.csv",encoding='utf-8')
df=distance_m(df_s,find_t,200)
df.to_csv(r"path\yy.csv",encoding='utf-8')

代码解释及注意事项:

1、wps 的csv格式经常中文乱码,除了需要加encoding='utf-8’外,还需要将csv文件用txt打开然后另存为’utf-8’格式的文件。

2、 knn 建立最近邻模型和训练,主要是对data_p中的经纬度数据建立了’ball_tree’数据模型。然后利用该数据模型高效的计算距离m内个所有的点。其中metric是用来计算距离的函数,既可以是标准的欧氏距离、马氏距离也可以是自定义的距离函数。algorithm=‘ball_tree’ 表示使用的数据结构模型。dis_m是搜索距离m。 返回值distances是一维, numpy数组,points 是元素为不同长度数组的一维数组。

nn = NearestNeighbors(metric=disN, algorithm='ball_tree')     
nn.fit(data_p)     
distances, points = nn.radius_neighbors(find_n, dis_m, return_distance=True)

3、points 是元素为数组的一维数组。他的元素是形成’ball_tree’数据模型的原始数据的索引,即是被查找的数据的索引。以下为查找该索引对应的字段的方法。由于直接遍历和修改元素是不同长度的数组的方法不熟练,使用转化为list的方法进行查找。

该步常见告警为:

#setting an array element with a sequence:元素长度不一致会报错,因为points是一个一维数组,它的元素是不同长度的数组

数组的修改前后,元素的type必须一致。否则报错invalid literal for int() with base 10。因此再需要查找的字段为str类型时需要先通过z_array = z_array.astype(np.str)将元素(下面例子中是data_cgi[n]的索引)转化为字符串才能进行修改,在需要通过索引(下面例子中即z_array 素组的元素)查找字段时需要n = int(n)转化回来。

#将索引转换为想要的字段

k = list(points)
r = len(k)
for i in range(r):
    z_array = k[i]
    z_array = z_array.astype(np.str)
    m = len(z_array)
    for j in range(m):
        n = z_array[j]
        n = int(n)
        z_array[j] = data_cgi[n]
     k[i] = z_array

4、数据源与清洗

以基站数据为例子

df_s 为source,对find_t表中的每一个经纬度从df_s表查找距离最近的N个点,df_s需要包含[“小区中文名”, “CGI”, “经度”, “纬度”] ,其中"经度", "纬度"是必选项。

find_t为需要查找的表 target表,需要包含[“经度”, “纬度”]。

由于KNN模型训练不能有空值需要删除NaN,同时index是第三步查找字段索引,必须要重设置,保障索引一致。

find_n=find_t[["经度","纬度"]]  
find_n.dropna(inplace=True)     
find_n.reset_index(drop=True, inplace=True)

三、查找n个最近的点

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.neighbors import NearestNeighbors
from geopy import distance
pd.options.mode.chained_assignment = None
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

距离计算函数

def disN(o, n):
    # Geopy库先lat后long
    old = (o[1], o[0])
    new = (n[1], n[0])
    d = distance.distance(old, new).m
    s = round(float(d), 2)
    return s

计算最近n个点,df_s 为source,对find_t表中的每一个经纬度从df_s表查找距离最近的N个点,find_t为需要查找的表 target表

def nearest_n(df_s,find_t,n):

由于输出结果包含要查找的经纬度(原始点),因此必须查找n+1个点其中才包含n个距离最近的点
m=n+1
# 形成待查经纬度表,并清洗NA值

find_n = find_t[["经度", "纬度"]]
find_n.dropna(inplace=True)
find_n.reset_index(drop=True, inplace=True)

形成待查工参表,并清洗NA值

data = df_s[["小区中文名", "CGI", "经度", "纬度"]]
data.dropna(inplace=True)
data.reset_index(drop=True, inplace=True)

形成numpy数组

data_cgi = data["CGI"].values
data_jwd=data[["经度","纬度"]]
data_p = data_jwd.values

knn 建立最近邻模型和训练,主要是对data_p中数据建立了’ball_tree’数据模型

nn = NearestNeighbors(metric=disN, algorithm='ball_tree')
nn.fit(data_p)
distances, points = nn.kneighbors(find_n.values, m, return_distance=True)

将索引转换为cgi

b_n = points.copy()
b_n = b_n.astype(np.str)
for i in np.nditer(b_n, op_flags=['readwrite']):
    i[...] = data_cgi[i.astype('int64')]

c = np.concatenate((distances, b_n), axis=1)
data_df = pd.DataFrame(c)
df_r = pd.concat([find_t, data_df], axis=1)
return df_r

剔除结果中的查找点。定义函数后使用

df=df.apply(lambda x:tichubenzhan(x),axis=1)

由于输出结果包含要查找的经纬度(原始点),因此索引和值之间的距离是n + 1

def tichubenzhan1(sr,n):
m=n+1
k=sr[sr.values==sr["CGI"]].index
index_cgi=k[1]
print(index_cgi)
index_dis=index_cgi-m
sr.drop(index_cgi,inplace=True)
sr.drop(index_dis,inplace=True)
return sr

df_s 为source,对find_t表中的每一个经纬度从df_s表查找距离最近的N个点,
df_s需要包含[“小区中文名”, “CGI”, “经度”, “纬度”]
df_s=pd.read_csv(rr"path\xx.csv",encoding=‘utf-8’)find_t为需要查找的表 target表,需要包含[“经度”, “纬度”]

find_t=pd.read_csv(r"D:\aTools\1Jupyter_workspace\find_t.csv",encoding='utf-8')
df=nearest_n(df_s,find_t,3)
df.to_csv(r"D:\aTools\1Jupyter_workspace\find_t2.csv", encoding='utf-8')
print(df)

注意:由于输出结果包含要查找的经纬度(原始点),因此必须查找n+1个点其中才包含n个距离最近的点。

更多推荐