Neo4j & Python 构建中国行政区划图

哇标题看起来挺厉害的,其实没有很难啦,而且非常有趣。

我为什么要做这个

我本人就很讨厌看到这种标题——我为什么xxx?喵喵喵?你为什么xxx关我什么事?唉,人总是会变成自己讨厌的人嘛。

换个说法,说说做这个的初衷,其实没有什么初衷,被逼的。

为了尽早完成毕业设计可以出去打工,我选择提前联系导师。于是就被提供了两个题目,我决定做水利相关的知识图谱。你看,多么河海特色!水水水,离开水就不能活了,没毛病。

于是先从图数据库入手,neo4j是个好东西,就决定是你了。在构建复杂的水水水相关实体前,我先拿中国行政区划数据试个水,逻辑相对简单,就一种隶属关系。

献上美图:

Screen Shot 2017-11-10 at 16.08.23.png

起步

首先,准备一下原料,中国行政区划元数据,不求完全结构化,起码半结构化,不然会很痛苦,毕竟有70W+数据。

Neo4j一套,python或者什么脚本语言一款,脑子一坨,足够了。

首先我拿到70W+的XML文件,哎哟我去,ls一下都卡半天,我都害怕python会不会应付不来,跑一遍几小时可能就太惨了。

首先用python列出几个文件:

files = os.listdir(datas_path)
print(files[0])

哈哈哈,我先搞个文件名,然后直接打开这个文件,避免了「我不能ls就不知道文件名,不知道文件名就不能看内容」的问题。可以看到里面的大致结构,这里取一部分:

<mdExtInfo>     
    <obj_att>      
        <AD_GRAD>        
            <key>行政区划级别</key>        
            <value>村</value>      
        </AD_GRAD>      
        <AD_CODE>        
            <key>行政区划代码</key>        
            <value>441323121207</value>      
        </AD_CODE>      
        <UP_AD_NAME>        
            <key>上级行政区划名称</key>        
            <value>白盆珠镇</value>      
        </UP_AD_NAME>      
        <AD_FULL_NAME>        
            <key>行政区划全称</key>        
            <value>广东省-惠州市-惠东县-白盆珠镇-布心村民委员会</value>      
        </AD_FULL_NAME>      
        <AD_NAME>        
            <key>行政区划名称</key>        
            <value>布心村民委员会</value>      
        </AD_NAME>    
    </obj_att>   
</mdExtInfo> 

枚举行政区划级别

唉,几十万文件,我都不知道有几种行政区,先跑一遍看看有哪些区划级别。

核心代码:

cates = set()
for file in files:
    doc = parse(datas_path + file)
    for item in doc.iterfind('mdExtInfo/obj_att/AD_GRAD'):
        title = item.findtext('value')
        cates.add(title)
        print(title, file, cates)

大概不到十分钟就能跑完,最后得到一共有6种级别:县级、村、国家级、乡级、地市级、省级。

给这些文件分类

每次都跑十分钟太过分了,而且我发现,不同的级别,XML里的结构不同,比如省级有十几个字段,乡级只有五个。所以分类是必须的。

按照不同级别,将同级别的文件名放在一个文件里,方便以后遍历。

到这里,我突然明白了一些道理,来来来,给自己加点戏!

本来以为都是非结构化数据,刚好有在看机器学习,哇可以学以致用了,把数据扔进去,自动聚类,开心的当上调参男孩…… 然而我发现数据格式化的挺不错,就开始自己去找他们的特征,这特喵不就是——人肉学习嘛!然而再反过来想,机器学习到底是什么呢?

构造 Neo4j 需要的 CSV

抽取属性

这么一大坨数据要导入的,用CQL实在太慢,生成指定格式的CSV文件就OK啦~真简单~

AD_CODE,AD_NAME,AD_GRAD,AD_AREA,AD_FULL_NAME,AD_ABBR_NAME,LOW_LEFT_LONG,UP_RIGHT_LONG,UP_RIGHT_LAT,AD_STAT_LAT,AD_STAT,LOW_LEFT_LAT,AD_STAT_LONG
610000000000,陕西省,省级,197025.84,陕西省,陕西,105.4872,111.2422,39.58533,34.26645358,西安市新城区新城大院,31.70674,108.94952476
650000000000,新疆维吾尔自治区,省级,1660000,新疆维吾尔自治区,新疆,73.49989,96.38728,49.18006,43.79179105,乌鲁木齐市中山路479号,34.33374,87.62484437
130000000000,河北省,省级,187700,河北省,河北,113.4551,119.8485,42.61558,38.03705206,石家庄市长安区裕华东路113号,36.04881,114.52429283

...

哈哈哈哈,先把它们有用的属性都拿出来,按级别分类放到6个CSV文件中,结构如上,从此就可以舒服很多了,毕竟不用遍历几十万个文件了。

构建行政单位实体

等…… 等一下……

其实在这之前,我发现了很多坑爹的地方,比如地市级单位有14个属性,但有个别数据只有几个属性,所以遍历的时候一定要判断,不存在的话要用空字符串替代,不然CSV对不齐后面就会更坑。

首先做个属性映射,我不想存到数据库里字段都是瞎眼的大写:

prop_maps = {
    'AD_CODE': 'code:ID(AD)',
    'AD_NAME': 'name',
    # 'AD_GRAD': 'level',
    'AD_AREA': 'area:double',
    'AD_FULL_NAME': 'full_name',
    'AD_ABBR_NAME': 'abbr_name',
    'LOW_LEFT_LONG': 'low_left_longtitude:double',
    'UP_RIGHT_LONG': 'up_right_longtitude:double',
    'UP_RIGHT_LAT': 'up_right_latitude:double',
    'AD_STAT_LAT': 'station_latitude:double',
    'AD_STAT': 'station',
    'LOW_LEFT_LAT': 'low_left_latitude:double',
    'AD_STAT_LONG': 'station_longtitude:double'
    # 'UP_AD_NAME': 'father_name'
}

根据 Neo4j Import Tool 上的格式,我们要注意ID字段,以及LABEL字段,我决定给它们两个标签,一个是级别,比如Province,一个是AD,表示Administrative Division 行政区划,毕竟这个数据库还会有很多水水水的数据进来。

code:ID(AD),name,area:double,full_name,abbr_name,low_left_longtitude:double,up_right_longtitude:double,up_right_latitude:double,station_latitude:double,station,low_left_latitude:double,station_longtitude:double,:LABEL
610000000000,陕西省,197025.84,陕西省,陕西,105.4872,111.2422,39.58533,34.26645358,西安市新城区新城大院,31.70674,108.94952476,AD;Province
650000000000,新疆维吾尔自治区,1660000,新疆维吾尔自治区,新疆,73.49989,96.38728,49.18006,43.79179105,乌鲁木齐市中山路479号,34.33374,87.62484437,AD;Province
130000000000,河北省,187700,河北省,河北,113.4551,119.8485,42.61558,38.03705206,石家庄市长安区裕华东路113号,36.04881,114.52429283,AD;Province

...

最后的数据如上所示(省级部分)。直接看一坨CSV体验很差对吧,其实不用看全,看到主要的几个字段就是可以了,比如code:ID(AD),表示code字段,行政代码,ID表示我要用于之后导入关系的主键,(AD)表示这个ID不是全局的,是一个叫AD的group,可以理解命名空间,详情请看官方文档。还有LABEL字段,表示标签……废话…… area:double表示用双精度,否则默认为字符串,你不希望数字都变成字符串吧。

构建行政区上下级关系

在这之前,必须要验证很多细节,比如真的所有数据都有上级这个属性嘛?所有数据都是完美的符合规则的嘛?果然不是。。。

甚至,数据还有一些错误的,比如有一条就是陕西省-嘉峪关市-市辖区,为什么我发现他是错误的呢?我是陕西人嘛?我地理及格了嘛?NONONO!因为我想验证一下所有地名的全称,去掉最后一段,即上级行政区全称,是否存在。哈哈哈不存在的,结果就是没找到甘肃省-嘉峪关市-市辖区这条数据,仿佛链条都断掉了。

于是我跑了一下元数据,看看嘉峪关市到底是哪里的,发现陕西和甘肃都有,见鬼了,去网上搜了一下,不存在的。要把这条数据的「陕西」替换为「甘肃」,不想动元数据,所以写在了清理数据的脚本中。

用 UP_AD_NAME 字段找上级

用几个字典,存下地名和代号,然后拿 UP_AD_NAME 去匹配上一级的字典。

我太天真了哈哈哈,测试一下有多少重名的,果然到了县级就没法看了,什么「西城区」,哪个城市都有。

所以干脆拿全名前缀来匹配

直接把所有行政区存到一个字典,全名为键,代号为值。找一个行政区的上级时,其实就是去掉最后一段。比如 江苏省-南京市-江宁区 的上级就是 江苏省-南京市,我验证了一下是不是字典里都存在。果然又㕛叒叕有几个不听话的数据。

比如 新疆生产建设兵团-农四师-兵团七十六团--三连 最后居然两个-,中间是空的,我发现有这样的 新疆生产建设兵团-农四师-兵团七十六团-兵团七十六团 数据,就尝试给他们都加上,但结果是有些又找不到了。所以我干脆删了,后果是某些村级直接属于县级,算了算了没毛病,毕竟数据没给全,至少这样没什么大问题。

最后!居然发现有相同全名的,无话可说,仔细观察可以发现,都是村级的,名字一样,代号却不同,只能认为是干扰数据,随缘选一个吧。

完成CSV

:START_ID(AD),:END_ID(AD),:TYPE
610000000000,000000000000,BELONGS_TO
650000000000,000000000000,BELONGS_TO
130000000000,000000000000,BELONGS_TO
660000000000,000000000000,BELONGS_TO
140000000000,000000000000,BELONGS_TO
520000000000,000000000000,BELONGS_TO
360000000000,000000000000,BELONGS_TO
630000000000,000000000000,BELONGS_TO
230000000000,000000000000,BELONGS_TO

...

最后就是生成这样的数据,再写个导入 neo4j 的脚本即可。

怎么玩 Neo4j

算了下一篇文章再写吧,累死了……

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容