Global Health Security Index
www.ghsindex.org:2019 Global Health Security Index2019 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()直接绘制比如香港特区的地图是可以的。
- 读取国家名称的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中绘制美国(以及其它各国)地图的方法。
网友评论