百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

Python 数据分析——SciPy 空间算法库-spatial

bigegpt 2024-08-28 12:08 3 浏览

本节介绍scipy.spatial模块中提供的K-d树、凸包、沃罗诺伊图、德劳内三角化等空间算法类的使用方法。

一、计算最近旁点

众所周知,对于一维的已排序的数值,可以使用二分法快速找到与指定数值最近的数值。在下面的例子中,在一个升序排序的随机数数组x中,使用numpy.searchsorted()搜索离0.5最近的数。排序算法的时间复杂度为O(NlogN),而每次二分搜索的时间复杂度为O(logN)。

x = np.sort(np.random.rand(100))
idx = np.searchsorted(x, 0.5)
print x[idx], x[idx - 1] #距离0.5最近的数是这两个数中的一个
0.542258714465 0.492205345391

类似的算法可以推广到N维空间,spatial模块提供的cKDTree类使用K-d树快速搜索N维空间中的最近点。在下面的例子中,用平面上随机的100个点创建cKDTree对象,并对其搜索与targets中每个点距离最近的3个点。cKDTree.query()返回两个数组dist和idx,dist[i, :]是距离targets[i]最近的3个点的距离,而idx[i, :]是这些最近点的下标:

from scipy import spatial
np.random.seed(42)
N = 100
points = np.random.uniform(-1, 1, (N, 2))
kd = spatial.cKDTree(points)

targets = np.array([(0, 0), (0.5, 0.5), (-0.5, 0.5), (0.5, -0.5), (-0.5, -0.5)])
dist, idx = kd.query(targets, 3)
dist                         idx
-----------------------------------------  --------------
[[ 0.15188266,  0.21919416,  0.27647793],  [[48, 73, 81],
[ 0.09595807,  0.15745334,  0.22855398],   [37, 78, 43],
[ 0.05009422,  0.17583445,  0.1807312 ],   [79, 22, 92],
[ 0.11180181,  0.16618122,  0.18127473],   [35, 58,  6],
[ 0.19015485,  0.19060739,  0.19361173]]   [83,  7, 42]]

cKDTree.query_ball_point()搜索与指定点在一定距离之内的所有点,它只返回最近点的下标,由于每个目标点的近旁点数不一定相同,因此idx2数组中的每个元素都是一个列表:

r = 0.2
idx2 = kd.query_ball_point(targets, r)
idx2
array([[48], [37, 78], [79, 92, 22], [58, 35, 6], [7, 55, 83, 42]], dtype=object)

cKDTree.query_pairs()找到points中距离小于指定值的每一对点,它返回的是一个下标对的集合对象。下面的程序使用集合差集运算找出所有距离在0.08到0.1之间的点对:

idx3 = kd.query_pairs(0.1) - kd.query_pairs(0.08)
idx3
{(1, 46),     (3, 21),     (3, 82),     (3, 95),     (5, 16),     (9, 30),
(10, 87),    (11, 42),    (11, 97),    (18, 41),    (29, 74),    (32, 51),
(37, 78),    (39, 61),    (41, 61),    (50, 84),    (55, 83),    (73, 81)}

在图1中,与target中的每个点(用五角星表示)最近的点用与其相同的颜色标识:

图1 用cKDTree寻找近旁点

cKDTree的所有搜索方法都有一个参数p,用于定义计算两点之间距离的函数。读者可以尝试使用不同的p参数,观察图1的变化:

·p = 1:绝对值之和作为距离

·p = 2:欧式距离

·p = np.inf:最大坐标差值作为距离

此外,cKDTree.query_ball_tree()可以在两棵K-d树之间搜索距离小于给定值的所有点对。

distance子模块中的pdist()计算一组点中每对点的距离,而cdist()计算两组点中每对点的距离。由于pdist()返回的是一个压缩之后的一维数组,需要用squareform()将其转换成二维数组。dist1[i, j]是points中下标为i和j的两个点的距离,dist2[i, j]是points[i]和targets[j]之间的距离。

from scipy.spatial import distance
dist1 = distance.squareform(distance.pdist(points))
dist2 = distance.cdist(points, targets)
dist1.shape  dist2.shape
-----------  -----------
(100, 100)   (100, 5)

下面使用np.min()在dist2中搜索points中与targets距离最近的点,其结果与cKDTree.query()的结果相同:

print dist[:, 0] # cKDTree.query()返回的与targets最近的距离
print np.min(dist2, axis=0)
[ 0.15188266  0.09595807  0.05009422  0.11180181  0.19015485]
[ 0.15188266  0.09595807  0.05009422  0.11180181  0.19015485]

为了找到points中最近的点对,需要将dist1对角线上的元素填充为无穷大:

dist1[np.diag_indices(len(points))] = np.inf
nearest_pair = np.unravel_index(np.argmin(dist1), dist1.shape)
print nearest_pair, dist1[nearest_pair]
(22, 92) 0.00534621024816

用cKDTree.query()可以快速找到这个距离最近的点对:在K-d树中搜索它自己包含的点,找到与每个点最近的两个点,其中距离最近的点就是它本身,距离为0,而距离第二近的点就是每个点的最近旁点,然后只需要找到这些距离中最小的那个即可:

dist, idx = kd.query(points, 2)
print idx[np.argmin(dist[:, 1])], np.min(dist[:, 1])
[22 92] 0.00534621024816

让我们看一个使用K-d树提高搜索速度的实例。下面的start和end数组保存用户登录和离开网站的时间,对于任意指定的时刻time,计算该时刻在线用户的数量。

N = 1000000
start = np.random.uniform(0, 100, N)
span = np.random.uniform(0.01, 1, N)
span = np.clip(span, 2, 100)
end = start + span

下面的naive_count_at()采用逐个比较的方法计算指定时间的在线用户数量:

def naive_count_at(start, end, time):
mask = (start<time)&(end>time)
return np.sum(mask)

图2显示了如何使用K-d树实现快速搜索。图中每点的横坐标为start,纵坐标为end。由于end大于start,因此所有的点都在y=x斜线的上方。图中阴影部分表示满足(start<time)&(end>time)条件的区域。该区域中的点数为时刻time时的在线人数。

图2 使用二维K-d树搜索指定区间的在线用户

tree.count_neighbors(other, r, p=2)可以统计tree中到other的距离小于r的点数,其中p为计算距离的范数。距离按照如下公式计算:

当p=2时,上面的公式就是欧几里得空间中的向量长度。当p=∞时,该距离变为各个轴上的最大绝对值:

N∞(x)=||x||∞=max(|x1|,|x2|…,|xn|)

当使用p=∞时可以计算某正方形区域之内的点数。我们将该正方形的中心设置为(time - max_time, time + max_time),正方形的边长为2 * max_time,即r = max_time。其中max_time为end中的最大值。下面的KdSearch类实现了该算法:

class KdSearch(object):
def __init__(self, start, end, leafsize=10):
self.tree = spatial.cKDTree(np.c_[start, end], leafsize=leafsize)
self.max_time = np.max(end)

def count_at(self, time):
max_time = self.max_time
to_search = spatial.cKDTree([[time - max_time, time + max_time]])
return self.tree.count_neighbors(to_search, max_time, p=np.inf)

naive_count_at(start, end, 40) == KdSearch(start, end).count_at(40)
True

下面比较运算时间,由结果可知创建K-d树需要约0.5秒时间,K-d树的搜索速度则为线性搜索的17倍左右。

请读者研究点数N和leafsize参数与创建K-d树和搜索时间之间的关系。

%time ks = KdSearch(start, end, leafsize=100)
%timeit naive_search(start, end, 40)
%timeit ks.count_at(40)
Wall time: 484 ms
100 loops, best of 3: 3.85 ms per loop
1000 loops, best of 3: 221 μs per loop

二、凸包

所谓凸包是指N维空间中的一个区域,该区域中任意两点之间的线段都完全被包含在该区域之中,二维平面上的凸多边形就是典型的凸包。ConvexHull可以快速计算包含N维空间中点的集合的最小凸包。下面先看一个二维的例子:points2d是一组二维平面上的随机点,ch2d是这些点的凸包对象。ConvexHull.simplices是凸包的每条边线的两个顶点在points2d中的下标,由于它的形状为(5, 2),因此凸包由5条线段构成。对于二维的情况,ConvexHull.vertices是凸多边形的每个顶点在points2d中的下标,按逆时针方向的顺序排列。

np.random.seed(42)
points2d = np.random.rand(10, 2)
ch2d = spatial.ConvexHull(points2d)
ch2d.simplices   ch2d.vertices
--------------  ---------------
[[2, 5],        [5, 2, 6, 1, 0]
[2, 6],
[0, 5],
[1, 6],
[1, 0]]

使用matplotlib中的Polygon对象可以绘制如图3所示的多边形。

图3 二维平面上的凸包

poly = pl.Polygon(points2d[ch2d.vertices], fill=None, lw=2, color="r", alpha=0.5)
ax = pl.subplot(aspect="equal")
pl.plot(points2d[:, 0], points2d[:, 1], "go")
for i, pos in enumerate(points2d):
pl.text(pos[0], pos[1], str(i), color="blue")
ax.add_artist(poly)

三维空间中的凸包是一个凸多面体,每个面都是一个三角形。在下面的例子中,由simplices的形状可知,所得到的凸包由38个三角形面构成:

np.random.seed(42)
points3d = np.random.rand(40, 3)
ch3d = spatial.ConvexHull(points3d)
ch3d.simplices.shape
(38, 3)

下面的程序用TVTK直观地显示凸包,图4中所有的绿色圆球表示points3d中的点,由红色线段构成的三角形面表示凸多面体上的面。没有和红色线段相连的点就是凸包之内的点:

图4 三维空间中的凸包

from scpy2 import vtk_convexhull, vtk_scene, vtk_scene_to_array
actors = vtk_convexhull(ch3d)
scene = vtk_scene(actors, viewangle=22)
%array_image vtk_scene_to_array(scene)
scene.close()

如果读者希望采用三维交互界面,可以在Notebook中执行如下代码:

%gui qt
from scpy2 import ivtk_scene
ivtk_scene(actors)

三、沃罗诺伊图

沃罗诺伊图(Voronoi Diagram)是一种空间分割算法,它根据指定的N个胞点将空间分割为N个区域,每个区域中的所有坐标点都与该区域对应的胞点最近。

points2d = np.array([[0.2, 0.1], [0.5, 0.5], [0.8, 0.1],
[0.5, 0.8], [0.3, 0.6], [0.7, 0.6], [0.5, 0.35]])
vo = spatial.Voronoi(points2d)
vo.vertices           vo.regions     vo.ridge_vertices
--------------------  -----------------  -----------------
[[ 0.5   ,  0.045 ],   [[-1, 0, 1],       [[-1, 0],
[ 0.245 ,  0.351 ],   [-1, 0, 2],        [0, 1],
[ 0.755 ,  0.351 ],   [],                [-1, 1],
[ 0.3375,  0.425 ],   [6, 4, 3, 5],      [0, 2],
[ 0.6625,  0.425 ],   [5, -1, 1, 3],     [-1, 2],
[ 0.45  ,  0.65  ],   [4, 2, 0, 1, 3],   [3, 5],
[ 0.55  ,  0.65  ]]   [6, -1, 2, 4],     [3, 4],
[6, -1, 5]]        [4, 6],
[5, 6],
[1, 3],
[-1, 5],
[2, 4],
[-1, 6]]

使用voronoi_plot_2d()可以将沃罗诺伊图显示为图表,效果如图5(左)所示。图中蓝色小圆点为points2d指定的胞点,红色大圆点表示Voronoi.vertices中的点,图中为每个vertices点标注了下标。由虚线和实线将空间分为7个区域,以虚线为边的区域为无限大的区域,一直向外延伸,全部由实线构成的区域为有限区域。每个区域都以vertices中的点为顶点。

图5 沃罗诺伊图将空间分割为多个区域

Voronoi.regions是区域列表,其中每个区域由一个列表(忽略空列表)表示,列表中的整数为vertices中的序号,包含-1的区域为无限区域。例如[6, 4, 3, 5]为图中正中心的那块区域。

Voronoi.ridge_vertices是区域分割线列表,每条分割线由vertices中的两个序号构成,包含-1的分割线为图中的虚线,其长度为无限长。

如果希望将每块区域以不同颜色填充,但由于外围的区域是无限大的,因此无法使用matplotlib绘图,可以在外面添加4个点将整个区域围起来,这样每个points2d中的胞点都对应一个有限区域。在图5(右)中,黑圈点为points2d指定的胞点,将空间中与其最近的区域填充成胞点的颜色。

bound = np.array([[-100, -100], [-100,  100],
[ 100,  100], [ 100, -100]])
vo2 = spatial.Voronoi(np.vstack((points2d, bound)))

使用沃罗诺伊图可以解决最大空圆问题:找到一个半径最大的圆,使得其圆心在一组点的凸包区域之内,并且圆内没有任何点。根据图5可知最大空圆必定是以vertices为圆心,以与最近胞点的距离为半径的圆中的某一个。为了找到胞点与vertices之间的联系,可以使用Voronoi.point_region属性。point_region[i]是与第i个胞点(points2d[i])最近的区域的编号。例如由下面的输出可知,下标为5的蓝色点与下标为6的区域对应,而由Voronoi.regions[6]可知,该区域由编号为6、2、4的三个vertices点(图中红色的点)构成。因此vertices中与胞点points2d[5]最近的点的序号为[2, 4, 6]。

print vo.point_region
print vo.regions[6]
[0 3 1 7 4 6 5]
[6, -1, 2, 4]

下面是计算最大空圆的程序,效果如图6所示。程序中:?使用pylab.Polygon.contains_point()判断用ConvexHull计算的凸包多边形是否包含vertices中的点,用以在?处剔除圆心在凸包之外的圆。

图6 使用沃罗诺伊图计算最大空圆

?vertice_point_map是一个字典,它的键为vertices点的下标,值为与其最近的几个points2d点的序号。整个字典使用point_region和regions构建,注意这里剔除了所有在凸包之外的vertices点。

?对于vertice_point_map中的每一对点,找到距离最大的那对点,即可得出圆心坐标和圆的半径。

from collections import defaultdict

n = 50
np.random.seed(42)
points2d = np.random.rand(n, 2)
vo = spatial.Voronoi(points2d)
ch = spatial.ConvexHull(points2d)
poly = pl.Polygon(points2d[ch.vertices]) ?
vs = vo.vertices
convexhull_mask = [poly.contains_point(p, radius=0) for p in vs] ?

vertice_point_map = defaultdict(list) ?
for index_point, index_region in enumerate(vo.point_region):
region = vo.regions[index_region]
if -1 in region: continue
for index_vertice in region:
if convexhull_mask[index_vertice]:
vertice_point_map[index_vertice].append(index_point)

def dist(p1, p2):
return ((p1-p2)**2).sum()**0.5

max_cicle = max((dist(points2d[pidxs[0]], vs[vidx]), vs[vidx]) ?
for vidx, pidxs in vertice_point_map.iteritems())
r, center = max_cicle
print "r = ", r, ", center = ", center
r =  0.174278456762 , center =  [ 0.46973363  0.59356531]

四、德劳内三角化

德劳内三角化算法对给定的点集合的凸包进行三角形分割,使得每个三角形的外接圆都不含任何点。下面的程序演示了德劳内三角化的效果:

Delaunay对象dy的simplices属性是每个三角形的顶点在points2d中的下标。可以通过三角形的顶点坐标计算其外接圆的圆心,不过这里使用同一点集合的沃罗诺伊图的vertices属性,vertices就是每个三角形的外接圆的圆心。

x = np.array([46.445, 263.251, 174.176, 280.899, 280.899,
189.358, 135.521, 29.638, 101.907, 226.665])
y = np.array([287.865, 250.891, 287.865, 160.975, 54.252,
160.975, 232.404, 179.187, 35.765, 71.361])
points2d = np.c_[x, y]
dy = spatial.Delaunay(points2d)
vo = spatial.Voronoi(points2d)
dy.simplices            vo.vertices
------------  --------------------------------
[[8, 5, 7],   [[ 104.58977484,  127.03566055],
[1, 5, 3],    [ 235.1285    ,  198.68143374],
[5, 6, 7],    [ 107.83960707,  155.53682482],
[6, 0, 7],    [  71.22104881,  228.39479887],
[0, 6, 2],    [ 110.3105    ,  291.17642838],
[6, 1, 2],    [ 201.40695449,  227.68436282],
[1, 6, 5],    [ 201.61895891,  226.21958623],
[9, 5, 8],    [ 152.96231864,   93.25060083],
[4, 9, 8],    [ 205.40381294,  -90.5480267 ],
[5, 9, 3],    [ 235.1285    ,  127.45701644],
[9, 4, 3]]    [ 267.91709907,  107.6135    ]]

下面是用delaunay_plot_2d()绘制德劳内三角化的结果,此外还绘制每个外接圆及其圆心。可以看到三角形的所有顶点都不在任何外接圆之内,效果如图7所示:

图7 德劳内三角形的外接圆与圆心?

cx, cy = vo.vertices.T

ax = pl.subplot(aspect="equal")
spatial.delaunay_plot_2d(dy, ax=ax)
ax.plot(cx, cy, "r.")
for i, (cx, cy) in enumerate(vo.vertices):
px, py = points2d[dy.simplices[i, 0]]
radius = np.hypot(cx - px, cy - py)
circle = pl.Circle((cx, cy), radius, fill=False, ls="dotted")
ax.add_artist(circle)
ax.set_xlim(0, 300)
ax.set_ylim(0, 300)

相关推荐

得物可观测平台架构升级:基于GreptimeDB的全新监控体系实践

一、摘要在前端可观测分析场景中,需要实时观测并处理多地、多环境的运行情况,以保障Web应用和移动端的可用性与性能。传统方案往往依赖代理Agent→消息队列→流计算引擎→OLAP存储...

warm-flow新春版:网关直连和流程图重构

本期主要解决了网关直连和流程图重构,可以自此之后可支持各种复杂的网关混合、多网关直连使用。-新增Ruoyi-Vue-Plus优秀开源集成案例更新日志[feat]导入、导出和保存等新增json格式支持...

扣子空间体验报告

在数字化时代,智能工具的应用正不断拓展到我们工作和生活的各个角落。从任务规划到项目执行,再到任务管理,作者深入探讨了这款工具在不同场景下的表现和潜力。通过具体的应用实例,文章展示了扣子空间如何帮助用户...

spider-flow:开源的可视化方式定义爬虫方案

spider-flow简介spider-flow是一个爬虫平台,以可视化推拽方式定义爬取流程,无需代码即可实现一个爬虫服务。spider-flow特性支持css选择器、正则提取支持JSON/XML格式...

solon-flow 你好世界!

solon-flow是一个基础级的流处理引擎(可用于业务规则、决策处理、计算编排、流程审批等......)。提供有“开放式”驱动定制支持,像jdbc有mysql或pgsql等驱动,可...

新一代开源爬虫平台:SpiderFlow

SpiderFlow:新一代爬虫平台,以图形化方式定义爬虫流程,不写代码即可完成爬虫。-精选真开源,释放新价值。概览Spider-Flow是一个开源的、面向所有用户的Web端爬虫构建平台,它使用Ja...

通过 SQL 训练机器学习模型的引擎

关注薪资待遇的同学应该知道,机器学习相关的岗位工资普遍偏高啊。同时随着各种通用机器学习框架的出现,机器学习的门槛也在逐渐降低,训练一个简单的机器学习模型变得不那么难。但是不得不承认对于一些数据相关的工...

鼠须管输入法rime for Mac

鼠须管输入法forMac是一款十分新颖的跨平台输入法软件,全名是中州韵输入法引擎,鼠须管输入法mac版不仅仅是一个输入法,而是一个输入法算法框架。Rime的基础架构十分精良,一套算法支持了拼音、...

Go语言 1.20 版本正式发布:新版详细介绍

Go1.20简介最新的Go版本1.20在Go1.19发布六个月后发布。它的大部分更改都在工具链、运行时和库的实现中。一如既往,该版本保持了Go1的兼容性承诺。我们期望几乎所...

iOS 10平台SpriteKit新特性之Tile Maps(上)

简介苹果公司在WWDC2016大会上向人们展示了一大批新的好东西。其中之一就是SpriteKitTileEditor。这款工具易于上手,而且看起来速度特别快。在本教程中,你将了解关于TileE...

程序员简历例句—范例Java、Python、C++模板

个人简介通用简介:有良好的代码风格,通过添加注释提高代码可读性,注重代码质量,研读过XXX,XXX等多个开源项目源码从而学习增强代码的健壮性与扩展性。具备良好的代码编程习惯及文档编写能力,参与多个高...

Telerik UI for iOS Q3 2015正式发布

近日,TelerikUIforiOS正式发布了Q32015。新版本新增对XCode7、Swift2.0和iOS9的支持,同时还新增了对数轴、不连续的日期时间轴等;改进TKDataPoin...

ios使用ijkplayer+nginx进行视频直播

上两节,我们讲到使用nginx和ngixn的rtmp模块搭建直播的服务器,接着我们讲解了在Android使用ijkplayer来作为我们的视频直播播放器,整个过程中,需要注意的就是ijlplayer编...

IOS技术分享|iOS快速生成开发文档(一)

前言对于开发人员而言,文档的作用不言而喻。文档不仅可以提高软件开发效率,还能便于以后的软件开发、使用和维护。本文主要讲述Objective-C快速生成开发文档工具appledoc。简介apple...

macOS下配置VS Code C++开发环境

本文介绍在苹果macOS操作系统下,配置VisualStudioCode的C/C++开发环境的过程,本环境使用Clang/LLVM编译器和调试器。一、前置条件本文默认前置条件是,您的开发设备已...