美文网首页数目字生存
用Pyecharts绘制世界地图的避坑经历——首要是先修正各国的

用Pyecharts绘制世界地图的避坑经历——首要是先修正各国的

作者: askka | 来源:发表于2020-03-10 14:33 被阅读0次

    Global Health Security Index

    www.ghsindex.org:2019 Global Health Security Index
    2019 Global Health Security Index:全球卫生安全指数是由《国际卫生条例》缔约国的195个国家(IHR [2005])对各国卫生安全相关能力的评估指数,由Nuclear Threat Initiative (NTI,核威胁倡议) 、Johns Hopkins Center for Health Security (JHU,美国约翰霍普金斯大学布隆伯格公共卫生学院)以及 The Economist Intelligence Unit (EIU,经济学人智库)三家机构发起并完成。
    该报告是在2019年10月推出,满分100分的分值设置下,报告评估范围中的195个国家(地区)的平均分仅为40.2分(我国的评分为45.7分)。题图便是官方网站上给出的各国卫生安全指数的动态查询结果。

    “ The average overall GHS Index score among all 195 countries assessed is 40.2 of a possible score of 100. ”

    评估结论中提到:
    世界各国卫生安全从根本上而言是薄弱的。没有任何一个国家为疫病流行做好了充分准备,每个国家存在重要的差距需要解决。

    “ The world's national health security is fundamentally weak. Any country is fully prepared for an epidemic or epidemic, and each country has important gaps to address. ”

    没想到一语成谶,报告发布后的2020年新型冠状病毒全球施虐,似乎更说明了卫生安全对于全人类的重要性。尽管张文宏医生说,与病毒病菌的历次斗争人类从未输过,只希望这次也能否极泰来吧。
    关于2019 Global Health Security Index的详细信息大家可移步前往进一步了解。
    接下来还是说下用Pyecharts在世界地图上绘制全球卫生安全指数热度图时,对于各国名称的填坑经历。

    pyecharts.charts Map默认的世界各国名称与ISO标准不尽一致

    一直觉得用Echart绘制动态地图比较美观,又想用上pandas处理表格数据,于是就装了Pyecharts。Pyecharts分为 V0.5.X 和V1 两个大版本,V0.5.X 和 V1 间不兼容。V0.5.X支持 Python2.7,3.4+,且官方技术文档已停止更新;V1版本仅支持 Python3.6+。本人装了Pyecharts V1版本的。

     pip install pyecharts -U
    

    安装完毕,对照文档提供的范例开始绘制。其中POPULATION是pyecharts自带的示例数据,给出了共计233个国家(和地区)2019年的人口数,表格中的数据如下(前5行):

    Country (or dependency) Population (2019)
    China 1,420,062,022
    India 1,368,737,513
    United States 329,093,110
    Indonesia 269,536,482
    Brazil 212,392,717

    绘制世界地图的代码如下:

    from pyecharts.charts import Map
    # pyecharts自带的世界各国人口数据
    from pyecharts.faker import POPULATION
    
    
    data = [x for _, x in POPULATION[1:]]
    low, high = min(data), max(data)
    (
        Map(init_opts=opts.InitOpts(theme=ThemeType.LIGHT))
        .add("World Pupulation", POPULATION[1:], "world", is_map_symbol_show=False)
        .set_series_opts(label_opts=opts.LabelOpts(is_show=False))
        .set_global_opts(visualmap_opts=opts.VisualMapOpts(max_=high, min_=low))
        .render_notebook()
    )
    

    从运行结果可以看到,图中有不少国家显示的是灰色,亦即pyecharts没有识别出这片地域所对应的数据。比较明显的有韩国、朝鲜、老挝、刚果(金),南苏丹……等国。而实际上在POPULATION列表中是有这些国家的人口数的,问题出在列表中给出的国家名称与pyecharts默认的国家名称不一致。
    上图中鼠标经过地图上的国家时的浮动数据标签中显示的才是pyecharts可以识别的国家名称,而这些默认的名称与标准的写法,比如ISO标准国家代码(《GB/T 2659-2000 世界各国和地区名称代码》等效采用),不尽相同。


    不完整显示的世界地图.gif
    pyecharts默认中的各国名称与POPULATION列表对比:
    Country ISO 3166 Pyecharts POPULATION List
    韩国 Korea, Republic of Korea South Korea
    朝鲜 Korea, Democratic People's Republic of Dem. Rep. Korea North Korea
    刚果(金) Democratic Republic of the Congo Dem. Rep. Congo DR Congo
    南苏丹 South Sudan S. Sudan South Sudan
    多米尼加 Dominican Republic Dominican Rep. Dominican Republic
    法罗群岛 Faroe Islands Faeroe Is. Faeroe Islands
    西撒哈拉 Western Sahara W. Sahara Western Sahara

    从表格中可以看出,pyecharts在生成世界地图时,默认的各国国家名称中,许多都用了英文的简写方式表示。而且有些岛国名称,简化得相当暴力,比如赫德岛和麦克唐纳群岛(Heard Island and McDonald Islands)在pyecharts中默认的表示是Heard I. and McDonald Is.,Island直接用I.给简化了。也因此当传入POPULATION列表后,由于无法被pyecharts可识别的国家名称的区域地图上显示灰色,没有数据。
    如果连Pyecharts自带的样例数据都无法保证其中的国家名称是正确的,那我想要绘制GHI指数的热度图,其中肯定也免不了会出现无法正确解析的情况。
    于是动了心思想要整理一个用来映射得到可以被pyecharts解析的字典,思路就是先将国家名称转换为两字母代码(ALPHA-2 Code),然后再通过ALPHA-2 Code来得到pyecharts世界地图中的国家(地区)名称。之所以要考虑用ALPHA-2 Code,是想着ALPHA-2 Code是唯一的,而且利用唯一对应的ALPHA-2 Code,进一步还可以得到每个国家所在洲、国土面积等其它数据。另外,用ALPHA-2 Code来中间转换,也是想到以后如果用其它的包,比如plotly来绘制世界地图也许也会存在类似的问题。
    也想到过利用字符串模糊匹配来找出pyecharts可识别的国家名称,但试了一下后放弃了。用的是FuzzyWuzzy程序包,代码及部分运行结果如下。

    from fuzzywuzzy import process  #模糊匹配备选项中最接近的字符串
    names = []
    for name in POPULATION_df[POPULATION_df.Alpha2.isna()].Country:
        # choices为整理后的各国名称备选列表
        # 'Token Sort Ratio'为忽略顺序匹配,详见FUZZYWUZZY文档
        print(name + ":" + str(process.extract(name, choices, limit=1, scorer=fuzz.token_sort_ratio)))
        names.append(process.extract(name, choices, limit=1, scorer=fuzz.token_sort_ratio)[0][0])
    #names
    POPULATION_df.loc[POPULATION_df.Alpha2.isna(),'Country'] = names
    
    fuzzywuzzy模糊匹配结果示例
    应该说绝大多数情况下,模糊匹配的结果都能够找到对应正确的国家名称,但也有例外。如上图中将'and'写作'&''Island'写作'Islands',或是'State of Palestine''Palestine, State of '。但fuzzywuzzy没法处理的特例,比如pyecharts的Map('world')默认'刚果(金)''Dem. Rep. Congo',而自带的POPULATION列表中的是'DR Congo',fuzzywuzzy用 Levenshtein Distance 判断的结果,会认为'DR Congo''Congo',而不是'Dem. Rep. Congo',更接近。但很明显,这里的'DR Congo'应该就是'Dem. Rep. Congo'的简写才对。所以用模糊查找的方法有点勉为其难,与其每次都来手工干预改写,不如用笨方法来直接映射了事。
    print('简单匹配(Simple Ratio)')
    print('\t"DR Congo" VS."Dem. Rep. Congo" :{}'.format(fuzz.ratio("DR Congo", "Dem. Rep. Congo")))
    print('\t"DR Congo" VS."Congo":' + str(fuzz.ratio("DR Congo", "Congo")))
    print('-'*50)
    print('非完全匹配(Partial Ratio)')
    print('\t"DR Congo" VS."Dem. Rep. Congo" :{}'.format(fuzz.partial_ratio("DR Congo", "Dem. Rep. Congo")))
    print('\t"DR Congo" VS."Congo":{}'.format(fuzz.partial_ratio("DR Congo", " Congo'")))
    print('-'*50)
    print('忽略顺序匹配(Token Sort Ratio)')
    print('\t"DR Congo" VS."Dem. Rep. Congo" :{}'.format(fuzz.token_sort_ratio("DR Congo", "Dem. Rep. Congo")))
    print('\t"DR Congo" VS."Congo":{}'.format(fuzz.token_sort_ratio("DR Congo", "Congo")))
    print('-'*50)
    print('去重子集匹配(Token Set Ratio)')
    print('\t"DR Congo" VS."Dem. Rep. Congo" :{}'.format(fuzz.token_set_ratio("DR Congo'", "Dem. Rep. Congo")))
    print('\t"DR Congo" VS."Congo":{}'.format(fuzz.token_set_ratio("DR Congo", "Congo")))
    print('-'*50)
    
    fuzzywuzzy运行结果

    当然,关键的一步是要得到pyecharts可解析的国家名称列表。说起来这一步也费了些周折,在pyecharts的安装目录下源文件查找没有可用的信息。比如recharts:Basic Plots 31 - Map是基于Echarts2(v2.2.7)开发的在R中调用Echarts的程序包,其中的国家名称与pyecharts也不一致。

    recharts中的国家名称
    网上搜索了一番后,发现可以安装echarts-countries-pypkg后,其中的world.js是echarts默认的世界地图对应代码。echarts-countries-pypkg(以及其它的js地图文件包)的安装代码如下。
    pip install echarts-countries-pypkg #全球国家地图
    #pip install echarts-china-provinces-pypkg
    #pip install echarts-china-cities-pypkg
    #pip install echarts-china-counties-pypkg
    #pip install echarts-china-misc-pypkg
    #pip install echarts-united-kingdom-pypkg
    
    echarts-countries-pypkg包world.js
    如图所示紧随“properties:{name:”之后的便是国家名称,用正则表达式全部提取出来,共213个国家(地区)名称。严格说来,world.js中共有215个国际(地区)名称,其中的两个:
    "Siachen Glacier":锡亚琴冰川(争议地区)和 "N. Cyprus":北塞浦路斯土耳其共和国(这个好像还没被各国普遍承认)
    没有对应的ISO 3166代码所以直接舍弃了。也许开发者自有其意图,也懒得去揣测了。
    找到了正确的pyecharts可识别的国家(地区)名称,接下来就是将名称与ALPHA 2代码对应起来。一番折腾下来,最后的结果整理成了两个csv文件:

    明显的,pyecharts也没有将全球所有的国家和地区都收入,比如梵蒂冈、摩纳哥、安圭拉……,以及港澳台等。原因猜测可能是因为,除了中国台湾省和港澳两个特区外,这些国家(地区)的国土面积实在是不大,绘制在世界地图上也看不大出来。不过也不一定,也许以后随着版本升级,会相应加入这些吧。
    另外一个有意思的是pyecharts对于克什米尔地区的处理。前述已提到,在POPULATION列表中有部分国家(地区)pyecharts并不识别,本人一开始转换名称时没有关注到,程序没有查到对应的Pyecharts国家(地区)名称,返回了None。如下图,当传入地图的数据中,如果国家名称一列(pyecharts默认为第一列)含有None、NA之类的缺失值时,pyecharts会自动将其对应到克什米尔地区,即左图所示模样。右图是去除列表中的缺失值后的显示效果,可以明显看到克什米尔地区呈现背景色,没有被填充上色。不过这倒是提醒了我,由于在默认设置的世界地图中这部分位置正好处于视线中心附近,很容易被看到。而中间缺了这一小块看着也挺不舒服的,以后可以在传入pyecharts的数据中加入一条NA值对应的数据条,将这一小部分给掩盖起来。顺便提一下,前面提到的Siachen Glacier(锡亚琴冰川)就是在克什米尔和中国交界处。
    另外,pyecharts还会自作主张,将列表中所有的国家(地区)名称一列中的缺失值所对应数据全部自动相加,求和的结果作为克什米尔的对应数据。如左图中克什米尔的人口数为35,121,548,实际上是把POPULATION中的台湾、香港、梵蒂冈、摩纳哥这些全部加起来求和的结果。这个也算是一个不大不小的坑吧,莫名其妙地被我发现了。
    左:列表中包含None数据 右:列表中不含None数据

    用Pyecharts绘制全球卫生安全指数热度图

    前面的准备工作做完,剩下的就好办了。从 2019 Global Health Security Index下载了原始数据的excel表格,读入pandas,将表格中的国家(地区)名称映射转换为了pyecharts可识别的名称传入数据即可。下图即为最终绘制完成的各国卫生安全指数热度图,也没有去优化主题、标题、浮动显示框这些了。这里单只说下在Pyecharts中绘制世界地图时,对于各国名称的清洗过程。

    绘制完成的全球卫生安全指数热度图
    上图中,把格陵兰岛(Greenland)用丹麦的得分补上了。还是觉得那么一大片空在那看着不舒服(强迫症?)非洲那的一小块没有数据的是西撒哈拉(Western Sahara),猜想可能不是IHR [2005])的缔约国所以报告中没有该国的得分吧?
    pyecharts的地图传入数据时用的列表。直接用DataFrame,或是{'name': 'Afghanistan', 'value': 32.3}列表都不能识别,只能是[ ['Afghanistan', 32.3], ……]列表。用[ ['Afghanistan', [32.3, 32209007,…]], ……]类似列表一次传入该国对应的多个数据不知道是否可以,Pyecharts的TreeMap矩形树图这些是可以的,Map地图这个行与不行后面再去慢慢试。
    另外,Pyecharts的文档中,图表类型中好像没看到MapGlobe()类型?绘制个旋转的三维地球,其实也挺有意思的。
    from pyecharts.charts import MapGlobe
    
    POPULATION_name_has_modified = POPULATION_df[['EchartsNameEN','Population']].values.tolist()
    
    #data = [x for _, x in POPULATION[1:]]
    data = [x for _, x in POPULATION_name_has_modified]
    low, high = min(data), max(data)
    
    mg = (
        MapGlobe(init_opts=opts.InitOpts(theme=ThemeType.DARK))
        .add_schema()
        .add(
            maptype="world",
            series_name="World Population",
            data_pair=POPULATION_name_has_modified,
            is_map_symbol_show=False,
            label_opts=opts.LabelOpts(is_show=False),
        )
        .set_global_opts(
            visualmap_opts=opts.VisualMapOpts(
                min_=low,
                max_=high,
                range_text=["max", "min"],
                is_calculable=True,
                range_color=["lightskyblue", "yellow", "red"],
            )
        )
    )
    mg.render_notebook()
    

    效果如下。为例看清分布在大洋之间的小岛,用了DARK主题。文档中还提到了Map3D三维地图图像类型等,也留待后续慢慢去试。


    MapGlobe()生成的世界地图

    结论

    • 《2019 Global Health Security Index:全球卫生安全指数》报告指出:没有任何一个国家为疫病流行做好了充足准备
    • Pyecharts绘制世界地图,需将各国名称替换为pyecharts可识别的名称,目前可识别的213个国家和地区。其实准确的说,不管用什么工具绘制何种类型的世界地图时,都涉及到国家(地区)名称的标准化表述这个问题。
    Country: Alpha2 Country: Alpha2 Country: Alpha2 Country: Alpha2 Country: Alpha2
    Andorra: AD United Arab Emirates: AE Afghanistan: AF Antigua and Barb.: AG Albania: AL
    Armenia: AM Angola: AO Argentina: AR American Samoa: AS Austria: AT
    Australia: AU Aland: AX Azerbaijan: AZ Bosnia and Herz.: BA Barbados: BB
    Bangladesh: BD Belgium: BE Burkina Faso: BF Bulgaria: BG Bahrain: BH
    Burundi: BI Benin: BJ Bermuda: BM Brunei: BN Bolivia: BO
    Brazil: BR Bahamas: BS Bhutan: BT Botswana: BW Belarus: BY
    Belize: BZ Canada: CA Dem. Rep. Congo: CD Central African Rep.: CF Congo: CG
    Switzerland: CH Côte d'Ivoire: CI Chile: CL Cameroon: CM China: CN
    Colombia: CO Costa Rica: CR Cuba: CU Cape Verde: CV Curaçao: CW
    Cyprus: CY Czech Rep.: CZ Germany: DE Djibouti: DJ Denmark: DK
    Dominica: DM Dominican Rep.: DO Algeria: DZ Ecuador: EC Estonia: EE
    Egypt: EG W. Sahara: EH Eritrea: ER Spain: ES Ethiopia: ET
    Finland: FI Fiji: FJ Falkland Is.: FK Micronesia: FM Faeroe Is.: FO
    France: FR Gabon: GA United Kingdom: GB Grenada: GD Georgia: GE
    Ghana: GH Greenland: GL Gambia: GM Guinea: GN Eq. Guinea: GQ
    Greece: GR S. Geo. and S. Sandw. Is.: GS Guatemala: GT Guam: GU Guinea-Bissau: GW
    Guyana: GY Heard I. and McDonald Is.: HM Honduras: HN Croatia: HR Haiti: HT
    Hungary: HU Indonesia: ID Ireland: IE Israel: IL Isle of Man: IM
    India: IN Br. Indian Ocean Ter.: IO Iraq: IQ Iran: IR Iceland: IS
    Italy: IT Jersey: JE Jamaica: JM Jordan: JO Japan: JP
    Kenya: KE Kyrgyzstan: KG Cambodia: KH Kiribati: KI Comoros: KM
    Dem. Rep. Korea: KP Korea: KR Kuwait: KW Cayman Is.: KY Kazakhstan: KZ
    Lao PDR: LA Lebanon: LB Saint Lucia: LC Liechtenstein: LI Sri Lanka: LK
    Liberia: LR Lesotho: LS Lithuania: LT Luxembourg: LU Latvia: LV
    Libya: LY Morocco: MA Moldova: MD Montenegro: ME Madagascar: MG
    Macedonia: MK Mali: ML Myanmar: MM Mongolia: MN N. Mariana Is.: MP
    Mauritania: MR Montserrat: MS Malta: MT Mauritius: MU Malawi: MW
    Mexico: MX Malaysia: MY Mozambique: MZ Namibia: NA New Caledonia: NC
    Niger: NE Nigeria: NG Nicaragua: NI Netherlands: NL Norway: NO
    Nepal: NP Niue: NU New Zealand: NZ Oman: OM Panama: PA
    Peru: PE Fr. Polynesia: PF Papua New Guinea: PG Philippines: PH Pakistan: PK
    Poland: PL St. Pierre and Miquelon: PM Puerto Rico: PR Palestine: PS Portugal: PT
    Palau: PW Paraguay: PY Qatar: QA Romania: RO Serbia: RS
    Russia: RU Rwanda: RW Saudi Arabia: SA Solomon Is.: SB Seychelles: SC
    Sudan: SD Sweden: SE Singapore: SG Saint Helena: SH Slovenia: SI
    Slovakia: SK Sierra Leone: SL Senegal: SN Somalia: SO Suriname: SR
    S. Sudan: SS São Tomé and Principe: ST El Salvador: SV Syria: SY Swaziland: SZ
    Turks and Caicos Is.: TC Chad: TD Fr. S. Antarctic Lands: TF Togo: TG Thailand: TH
    Tajikistan: TJ Timor-Leste: TL Turkmenistan: TM Tunisia: TN Tonga: TO
    Turkey: TR Trinidad and Tobago: TT Tanzania: TZ Ukraine: UA Uganda: UG
    United States: US Uruguay: UY Uzbekistan: UZ St. Vin. and Gren.: VC Venezuela: VE
    U.S. Virgin Is.: VI Vietnam: VN Vanuatu: VU Samoa: WS Yemen: YE
    South Africa: ZA Zambia: ZM Zimbabwe: ZW Siachen Glacier: - N. Cyprus: -
    • 面积较小的国家(地区)及中国台湾省、香港澳门特区等没有在pyecharts世界地图中绘制。这里的世界地图时指的** maptype="world"**的情况,用Map()直接绘制比如香港特区的地图是可以的。
    pyeharts世界地图未列入的国家和地区
    • 读取国家名称的Alpha二字符码时需小心Namibia: NA纳米比亚所对应的Alpha2码为NA,因此在pd.read_csv()中加入keep_default_na = False,以免pandas自动将NA处理为缺失值(这样说起来,用Alpha3三字符代码似乎也有优势)。指定encoding='utf-8'也是必须的,否则类似Curaçao、São Tomé and Principe可能无法正常识别。
    • Pyecharts的Map()命令中有'name_map'参数,可以用来将数据中的地图区域名称(国、省、市)映射为pyecharts可以识别的名称,参见Pyecharts绘制地图……中的代码。用'name_map'的好处在于地图中各项数据的区域名称与原始数据相一致,但要生成name_map字典,其实还是少不了前面提到的步骤。
    • 传入pyecharts Map()中的数据,如果包含有国家(地区)为缺失值(None、NA、NaN…)的情况,会被pyecharts自动将缺失值所在行的数据相加后,作为克什米尔地区的对应数据(至少目前发现是这样的)。所以为了避免不必要的误解,务必要去除国家(地区)名称列中的缺失值!
    • 中国之外各国地图的绘制参见在Pyecharts中绘制美国(以及其它各国)地图的方法
    参考资料
    1. 2019 Global Health Security Index
    2. 界面新闻:《全球卫生安全指数》与各国疫情应对能力点评
    3. Pyecharts V1 官方技术文档
    4. List of country codes by alpha-2, alpha-3 code (ISO 3166)
    5. FuzzyWuzzy
    6. echarts-countries-pypkg
    7. Country_Name_of_pyecharts.csv
    8. Pyecharts绘制地图,maptype="world"时传入数组的一个小问题
    9. 在Pyecharts中绘制美国(以及其它各国)地图的方法

    相关文章

      网友评论

        本文标题:用Pyecharts绘制世界地图的避坑经历——首要是先修正各国的

        本文链接:https://www.haomeiwen.com/subject/nzvpdhtx.html