一、需求
手头有500多个地址信息,另外有十万个poi点的详情信息(包括poi点的详细地址)。
现在需要判断这10万个poi点是否覆盖了这500多个地址。
二、分析
可以很轻松地得出选点思路:
1.看地址信息中是否包含poi点信息;
2.看地址信息是否与poi详细地址相同。
为什么不直接比对两边的地址呢?
原因在于,同一个地址,可能有多种表示方式,有的用“xx省xx市xx区xx路2296号”,有的直接用“xx省xx市xx区xx小区”表示。
三、 手工匹配【不推荐】
手工匹配是最直接的方法,而且手工匹配最可靠。
但是,手工比对最慢。
这种大批量的比对,只有脑袋被门夹了,才会想都不想就用手工开干。
所以,手工比对,只有在万不得已的情况下采用。
四、利用地图慧匹配【不推荐】
由于地图慧可以把地址信息在地图上显示出来,所以,理论上,我们只要分别把poi的详细地址和手头的地址导入地图慧,那么就可以通过技术手段筛选出在地图上重合的点。
但是,地图慧免费版不能超过1000个点,每增加1000个点需要缴200元费用(使用一年),而poi点位是百万级,这将是一笔巨大开支。并且判断重合的点的技术手段需要研发(或寻找)。
所以,这种方式也不推荐。
五、利用powerbi来选点【推荐】
先来看看基本思路。
(一)基本思路
利用powerbi来选点的基本思路是:
首先判断客户地址中是否包含poi点名称(同一个poi点可能出现在不同的省市,所以还需对匹配结果进行筛选,剔除省市不一致的结果)。如果包含,那么选点成功,同时把匹配失败的客户地址单独剔除出来。
把匹配失败的客户地址信息,与poi点的详细地址进行比对。如果两者比对成功,那么,标记为选点成功;如果比对失败,再次剔除出来。
对两次比对都失败的点位进行人工处理:要么手工匹配,要么忽略掉这一部分地址。
(二)具体操作
由于匹配要求PowerBI在每一条具体地址信息中,去10万个poi点中寻找匹配项。
所以,我们可以有两种思路:
一是把地址信息进行分词,然后把分词结果和poi点进行比对。但是PowerBI并不能进行有效的自动分词,而python我又不会,所以,放弃。
二是把每一个poi点名称拿来和地址信息比对,看地址中是否包含poi点名称。这个方法比较简单,直接用PowerBI就可以实现。所以采用此方法。
由于要在10万个poi点中去寻找匹配信息,所以必须要用到函数。该函数的功能是检查每一个poi点是否出现在地址中,如果包含,则返回地址信息,如果不包含,则返回空。
说人话就是:在地址信息中筛选poi点名称,据此建立函数。只不过理论上,我要拿十万个词去地址中筛选十万次,然后返回每一次筛选的结果。
这里就有两种方法,一种是雷公子提供的比较快速的方法,一种是我自己想到的笨办法。
一一列举如下。
1.雷公子的快速方法
这个方法是我参考雷公子的一篇文章《PowerBI中如何实现高性能的模糊词根匹配》
,照猫画虎改写的(连有些名字都没改):
= [数据 = List.Buffer(Table.ToRecords(源)),
地域词根 = List.Buffer(Table.ToRecords(#"全国点位表 (2)")),
result = List.Transform( 数据,(x)=>[ 地址=x[地址], 地域词根 = List.Select(地域词根, each Text.Contains(x[地址],_[poi名称])){0}?] ),
table = Table.FromRecords(result) ][table]
这个公式非常吓人。虽然我能勉强解释清楚,但是自己绝对没本事独立写出这个公式来。
首先【[]】在powerquery中,可以理解为record这种数据格式,所以:
[数据 = List.Buffer(Table.ToRecords(源)),
地域词根 = List.Buffer(Table.ToRecords(#"全国点位表 (2)")),
result = List.Transform( 数据,(x)=>[ 地址=x[地址], 地域词根 = List.Select(地域词根, each Text.Contains(x[地址],_[poi名称])){0}?] ),
table = Table.FromRecords(result) ]
表示一个巨大无比的record。这个record有五个字段,分别是:数据、地域词根、result、地域词根和table。
其次,【[]】在powerquery中,可以理解为一种record操作符——它能获取到record中的字段名称。所以[record][table]表示获取[record]这个record里“table”这个字段的值,它是一个包含匹配结果(为record数据格式)的table。
而在result这一步里,又把一个包含函数的record作为list.transform()函数的第二参数嵌入进去了。
?表示容错,具体怎么个容错法,我自己还没搞清楚。
用这种方式可以在5分钟内获取结果,而且地址和poi匹配成功的结果,是一个record,匹配失败的显示为null。
2.我自己找到的不优雅方法
这种方法比较直观,很好理解。
首先基于地址信息制作一个函数,这个函数接受poi点作为参数,结果是把包含这个poi点的地址筛选出来。
let
源 = (word as text) => let
源 = Excel.Workbook(File.Contents(path), null, true),
Sheet1_Sheet = 源{[Item="Sheet1",Kind="Sheet"]}[Data],
更改的类型 = Table.TransformColumnTypes(Sheet1_Sheet,{{"Column1", type text}}),
提升的标题 = Table.PromoteHeaders(更改的类型, [PromoteAllScalars=true]),
更改的类型1 = Table.TransformColumnTypes(提升的标题,{{"地址", type text}}),
删除的副本 = Table.Distinct(更改的类型1),
筛选的行 = Table.SelectRows(删除的副本, each Text.Contains([地址], word)),//这是函数的主体部分,这一行之后的几个自定义不是必须的
已添加自定义 = Table.AddColumn(筛选的行, "省", each Text.Start([地址],Text.PositionOfAny([地址],{"省","市","区"},Occurrence.First)+1)),
已添加自定义1 = Table.AddColumn(已添加自定义, "市", each Text.Start([地址],Text.PositionOfAny([地址],{"市"},Occurrence.Last)+1)),
自定义1 = Table.Buffer(已添加自定义1)
in
自定义1
in
源
接下来,在引入poi点源的查询里添加一个新的列,引用该函数,参数是每一行的poi名称。
= Table.AddColumn(自定义1, "自定义", each fnadd([poi名称]))
由于数据量巨大,所以运行一次要耗时十分钟左右。
接下来处理没有匹配上的结果,也就是在地址信息中过滤掉匹配出来的结果,然后再对这些没匹配出来的结果添加一个新的列,函数还是fnadd(),只不过参数变成了poi信息中的“详细地址”列。
再接下来是合并两次匹配到的结果,把第二次仍未匹配到的结果筛选出来进行手工匹配。具体过程略。
六、总结
用powerbi选点的好处是:
- 没有数据量的限制,powerbi可以轻松处理百万级数据;
- 处理速度能接受,在10万个poi中匹配500多个地址,耗费十分钟左右;
- 只要客户地址所在的Excel表格格式不变,模型可以反复使用,跟具体的地址信息无关;
- 免费。
目前powerbi选点的不足在于:
- 做匹配工作的人必须熟悉powerbi;
- 处理速度跟计算机硬件配置关系很大(8核、32G内存,处理500多个客户地址占用内存超过3G,耗时十分钟左右)。
此外,PowerBI提供了模糊合并的功能,我直觉它应该可以处理这个问题,但是我试验了一下,结果不太理想,也许是我的阈值没设置对或者别的什么原因。
不知有没有大神研究过这类批量匹配的问题。