python图表之pygal入门篇
文章目录
[隐藏]
- pygal的简单使用
- 模拟掷骰子
- 同时掷两个骰子
- 处理json数据–世界人口地图
- 获取两个字母的国别码
- 世界人口地图绘制
- 使用Web API分析数据
- pygal可视化数据
pygal的简单使用
例子来自此书: 《Python编程从入门到实战》【美】Eric Matthes
pygal是一个SVG图表库。SVG是一种矢量图格式。全称Scalable Vector Graphics — 可缩放矢量图形。
用浏览器打开svg,可以方便的与之交互。
以下代码均在Jupyter Notebook中运行
模拟掷骰子
来看一个简单的例子。它模拟了掷骰子。
import random class Die: """ 一个骰子类 """ def __init__(self, num_sides=6): self.num_sides = num_sides def roll(self): return random.randint(1, self.num_sides)
模拟掷骰子并可视化
import pygal die = Die() result_list = [] # 掷1000次 for roll_num in range(1000): result = die.roll() result_list.append(result) frequencies = [] # 范围1~6,统计每个数字出现的次数 for value in range(1, die.num_sides + 1): frequency = result_list.count(value) frequencies.append(frequency) # 条形图 hist = pygal.Bar() hist.title = 'Results of rolling one D6 1000 times' # x轴坐标 hist.x_labels = [1, 2, 3, 4, 5, 6] # x、y轴的描述 hist.x_title = 'Result' hist.y_title = 'Frequency of Result' # 添加数据, 第一个参数是数据的标题 hist.add('D6', frequencies) # 保存到本地,格式必须是svg hist.render_to_file('die_visual.svg')
使用浏览器打开这个文件,鼠标指向数据,可以看到显示了标题“D6”, x轴的坐标以及y轴坐标。
可以发现,六个数字出现的频次是差不多的(理论上概率是1/6, 随着实验次数的增加,趋势越来越明显)
同时掷两个骰子
稍微改下代码就行,再实例化一个骰子
die_1 = Die() die_2 = Die() result_list = [] for roll_num in range(5000): # 两个骰子的点数和 result = die_1.roll() + die_2.roll() result_list.append(result) frequencies = [] # 能掷出的最大数 max_result = die_1.num_sides + die_2.num_sides for value in range(2, max_result + 1): frequency = result_list.count(value) frequencies.append(frequency) # 可视化 hist = pygal.Bar() hist.title = 'Results of rolling two D6 dice 5000 times' hist.x_labels = [x for x in range(2, max_result + 1)] hist.x_title = 'Result' hist.y_title = 'Frequency of Result' # 添加数据 hist.add('two D6', frequencies) # 格式必须是svg hist.render_to_file('2_die_visual.svg')
从图中可以看出,两个骰子之和为7的次数最多,和为2的次数最少。因为能掷出2的只有一种情况 -> (1, 1);而掷出7的情况有(1, 6) , (2, 5), (3, 4), (4, 3), (5, 2), (6, 1)共6种情况,其余数字的情况都没有7的多,故掷得7得概率最大。
处理json数据–世界人口地图
需要用到人口数据
下载population.json: http://pan.baidu.com/s/1pKLB9N1 ,该数据来源于okfn.org这个网站
打开看下数据,其实这是个很长的列表,包含了许多国家从1960~2015年的人口数据。看第一数据,如下。后面的数据和第一个键都一样。
[ { "Country Name":"Arab World", "Country Code":"ARB", "Year":"1960", "Value":"92496099" }, ...
只有四个键,其中Country Code指的是国别码,这里是3位的。Value就是人口数了。
import json filename = r'F:Jupyter Notebookmatplotlib_pygal_csv_jsonpopulation.json' with open(filename) as f: # json.load()可以将json文件转为Python能处理的形式,这里位列表,列表里是字典 pop_data = json.load(f) cc_populations = {} for pop_dict in pop_data: if pop_dict['Year'] == '2015': country_name = pop_dict['Country Name'] # 有些值是小数,先转为float再转为int population = int(float(pop_dict['Value'])) print(country_name + ': ' + population)
上面的程序打印了2015年各个国家的人口数,当然要分析2014年的,代码中数字改改就行。
Arab World: 392168030 Caribbean small states: 7116360 Central Europe and the Baltics: 103256779 Early-demographic dividend: 3122757473.68203 East Asia & Pacific: 2279146555 ...
需要注意的是,人口数据有些值是小数(不可思议)。人口数据类型是字符串str,如果直接转int,像’35435.12432’这样的字符串是不能强转位int的,必须先转为float,再丢失精度转为int。
获取两个字母的国别码
我们的数据中,国别码是三位的,而pygal的地图工具使用两位国别码。要使用pygal绘制世界地图。需要安装依赖包。
pip install pygal_maps_world就可以了
国别码位于i18n模块
from pygal_maps_world.i18n import COUNTRIES这样就导入了, COUNTRIES是一个字典,键是两位国别码,值是具体国家名。
key -> value af Afghanistan af Afghanistan al Albania al Albania dz Algeria dz Algeria ad Andorra ad Andorra ao Angola
写一个函数,根据具体国家名返回pygal提供的两位国别码
def get_country_code(country_name): """ 根据国家名返回两位国别码 """ for code, name in COUNTRIES.items(): if name == country_name: return code return None
世界人口地图绘制
先给出全部代码,需要用到World类
import json from pygal_maps_world.i18n import COUNTRIES from pygal_maps_world.maps import World # 颜色相关 from pygal.style import RotateStyle from pygal.style import LightColorizedStyle def get_country_code(country_name): """ 根据国家名返回两位国别码 """ for code, name in COUNTRIES.items(): if name == country_name: return code return None filename = r'F:Jupyter Notebookmatplotlib_pygal_csv_jsonpopulation.json' with open(filename) as f: pop_data = json.load(f) cc_populations = {} for pop_dict in pop_data: if pop_dict['Year'] == '2015': country_name = pop_dict['Country Name'] # 有些值是小数,先转为float再转为int population = int(float(pop_dict['Value'])) code = get_country_code(country_name) if code: cc_populations[code] = population # 为了使颜色分层更加明显 cc_populations_1,cc_populations_2, cc_populations_3 = {}, {}, {} for cc, population in cc_populations.items(): if population < 10000000: cc_populations_1[cc] = population elif population < 1000000000: cc_populations_2[cc] = population else: cc_populations_3[cc] = population wm_style = RotateStyle('#336699', base_World Populations in 2015, By Country' world.add('0-10m', cc_populations_1) world.add('10m-1bn', cc_populations_2) world.add('>1bn', cc_populations_3) world.render_to_file('world_population_2015.svg')
有几个变量比较重要
- cc_populations是一个dict,里面存放了两位国别码与人口的键值对。
-
cc_populations_1,cc_populations_2, cc_populations_3这是3个字典,把人口按照数量分阶梯,人口一千万以下的存放在cc_populations_1中,一千万~十亿级别的存放在cc_populations_2中,十亿以上的存放在cc_populations_3中,这样做的目的是使得颜色分层更加明显,更方便找出各个阶梯里人口最多的国家。由于分了三个层次,在添加数据的的时候也add三次,把这三个字典分别传进去。
-
world = World(https://www.centos.bz/wp-content/uploads/2017/10/3-25.png” alt=”” srcset=”https://www.centos.bz/wp-content/uploads/2017/10/3-25.png 953w, https://www.centos.bz/wp-content/uploads/2017/10/3-25-768×509.png 768w” sizes=”(max-width: 953px) 100vw, 953px” />
仔细观察,图中有些是空白的。并不是这些地方全部没人,而是我们的json数据中有些国家的名称与pygal中COUNTIES模块的国家名不对应导致。这算是一个遗憾,不过可以修改get_country_code函数,使得遇到不对应的国家名时,不返回None。对于这些国家,查阅COUNTRIES的代码,找出对应的国别码,返回之,应该就Ok了。比如下面这样
# 传入的参数country_name是json数据中的,可能与COUNTRIES里面的国家名不一致,按照上面的代码直接就返回None,导致绘图时这个国家的人口数据空白 if country_name == 'Yemen, Rep': return 'ye'
不过我懒得试了233
使用Web API分析数据
以GitHub为例,我想查看最受欢迎的Python库。以stars排序。
访问: https://api.github.com/search/repositories?q=language:python&sort=stars 就可查看。数据大概长这样
{ "total_count": 1767997, "incomplete_results": false, "items": [{ { "id": 21289110, "name": "awesome-python", "full_name": "vinta/awesome-python", "owner": { "login": "vinta", ... }, ... "html_url": "https://github.com/vinta/awesome-python", ... "stargazers_count": 35234, ... }, {...} ...] }
第三个数据,items。里面是得到stars最多的top 30。name即库名称,owner下的login是库的拥有者,html_url是该库的网址(注意owner下也有个html_url。不过那个是用户的GitHub网址,我们要定位到该用户的具体这个库,所以不要用owner下的html_url),stargazers_count至关重要,所得的stars数目。
顺便说下第一个键total_count,表示Python语言的仓库的总数;第二个键,incomplete_results,表示响应的值不完全,一般来说是false,表示响应的数据完整。
import requests url = 'https://api.github.com/search/repositories?q=language:python&sort=stars' response = requests.get(url) # 200为响应成功 print(response.status_code, '响应成功!') response_dict = response.json() total_repo = response_dict['total_count'] repo_list = response_dict['items'] print('总仓库数: ', total_repo) print('top', len(repo_list)) for repo_dict in repo_list: print('nName: ', repo_dict['name']) print('Owner: ', repo_dict['owner']['login']) print('Stars: ', repo_dict['stargazers_count']) print('Repo: ', repo_dict['html_url']) print('Description: ', repo_dict['description'])
其实像这样已经得到结果了
200 响应成功! 总仓库数: 1768021 top 30 Name: awesome-python Owner: vinta Stars: 35236 Repo: https://github.com/vinta/awesome-python Description: A curated list of awesome Python frameworks, libraries, software and resources Name: httpie Owner: jakubroztocil Stars: 30149 Repo: https://github.com/jakubroztocil/httpie Description: Modern command line HTTP client – user-friendly curl alternative with intuitive UI, JSON support, syntax highlighting, wget-like downloads, extensions, etc. https://httpie.org Name: thefuck Owner: nvbn Stars: 28535 Repo: https://github.com/nvbn/thefuck Description: Magnificent app which corrects your previous console command. ...
可视化一下当然会更加直观。
pygal可视化数据
代码不是很难,有一个plot_dict比较关键,这是鼠标放在条形图上时,会显示出来的数据,键基本上都是固定写法,xlink里面时仓库地址,只要点击,浏览器就会新开一个标签跳转到该页面了!
import requests import pygal from pygal.style import LightColorizedStyle, LightenStyle url = 'https://api.github.com/search/repositories?q=language:python&sort=stars' response = requests.get(url) # 200为响应成功 print(response.status_code, '响应成功!') response_dict = response.json() total_repo = response_dict['total_count'] repo_list = response_dict['items'] print('总仓库数: ', total_repo) print('top', len(repo_list)) names, plot_dicts = [], [] for repo_dict in repo_list: names.append(repo_dict['name']) # 加上str强转,因为我遇到了'NoneType' object is not subscriptable (: 说明里面有个没有此项, 是NoneType plot_dict = { 'value' : repo_dict['stargazers_count'], # 有些描述很长很长,选最前一部分 'label' : str(repo_dict['description'])[:200]+'...', 'xlink' : repo_dict['html_url'] } plot_dicts.append(plot_dict) # 改变默认主题颜色,偏蓝色 my_style = LightenStyle('#333366', base_Most-Starred Python Projects on GitHub' # x轴的数据 chart.x_labels = names # 加入y轴的数据,无需title设置为空,注意这里传入的字典, # 其中的键--value也就是y轴的坐标值了 chart.add('', plot_dicts) chart.render_to_file('most_stars_python_repo.svg')
看下图,chrome浏览器里显示效果。总感觉config里面有些设置没有起到作用, x、y轴的标签还是那么小orz…不过plot_dict里面的三个数据都显示出来了,点击即可跳转。
好了,就折腾这么多吧,这个库也不是特别大众的…
原文出处:简书 -> http://www.jianshu.com/p/6fcd47b3528b