pygal的簡單使用
例子來自此書: 《Python編程從入門到實戰(zhàn)》【美】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,統(tǒng)計每個數(shù)字出現(xiàn)的次數(shù)
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軸坐標(biāo)
hist.x_labels = [1, 2, 3, 4, 5, 6]
# x、y軸的描述
hist.x_title = 'Result'
hist.y_title = 'Frequency of Result'
# 添加數(shù)據(jù), 第一個參數(shù)是數(shù)據(jù)的標(biāo)題
hist.add('D6', frequencies)
# 保存到本地,格式必須是svg
hist.render_to_file('die_visual.svg')
使用瀏覽器打開這個文件,鼠標(biāo)指向數(shù)據(jù),可以看到顯示了標(biāo)題“D6”, x軸的坐標(biāo)以及y軸坐標(biāo)。
可以發(fā)現(xiàn),六個數(shù)字出現(xiàn)的頻次是差不多的(理論上概率是1/6, 隨著實驗次數(shù)的增加,趨勢越來越明顯)
同時擲兩個骰子
稍微改下代碼就行,再實例化一個骰子
die_1 = Die()
die_2 = Die()
result_list = []
for roll_num in range(5000):
# 兩個骰子的點數(shù)和
result = die_1.roll() + die_2.roll()
result_list.append(result)
frequencies = []
# 能擲出的最大數(shù)
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'
# 添加數(shù)據(jù)
hist.add('two D6', frequencies)
# 格式必須是svg
hist.render_to_file('2_die_visual.svg')
從圖中可以看出,兩個骰子之和為7的次數(shù)最多,和為2的次數(shù)最少。因為能擲出2的只有一種情況 -> (1, 1);而擲出7的情況有(1, 6) , (2, 5), (3, 4), (4, 3), (5, 2), (6, 1)共6種情況,其余數(shù)字的情況都沒有7的多,故擲得7得概率最大。
處理json數(shù)據(jù)--世界人口地圖
需要用到人口數(shù)據(jù)
點擊這里下載population.json,該數(shù)據(jù)來源于okfn.org這個網(wǎng)站
打開看下數(shù)據(jù),其實這是個很長的列表,包含了許多國家從1960~2015年的人口數(shù)據(jù)??吹谝粩?shù)據(jù),如下。后面的數(shù)據(jù)和第一個鍵都一樣。
[
{
"Country Name":"Arab World",
"Country Code":"ARB",
"Year":"1960",
"Value":"92496099"
},
...
只有四個鍵,其中Country Code指的是國別碼,這里是3位的。Value就是人口數(shù)了。
import json
filename = r'F:\Jupyter Notebook\matplotlib_pygal_csv_json\population.json'
with open(filename) as f:
# json.load()可以將json文件轉(zhuǎn)為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']
# 有些值是小數(shù),先轉(zhuǎn)為float再轉(zhuǎn)為int
population = int(float(pop_dict['Value']))
print(country_name + ': ' + population)
上面的程序打印了2015年各個國家的人口數(shù),當(dāng)然要分析2014年的,代碼中數(shù)字改改就行。
Arab World: 392168030
Caribbean small states: 7116360
Central Europe and the Baltics: 103256779
Early-demographic dividend: 3122757473.68203
East Asia & Pacific: 2279146555
...
需要注意的是,人口數(shù)據(jù)有些值是小數(shù)(不可思議)。人口數(shù)據(jù)類型是字符串str,如果直接轉(zhuǎn)int,像'35435.12432'這樣的字符串是不能強轉(zhuǎn)位int的,必須先轉(zhuǎn)為float,再丟失精度轉(zhuǎn)為int。
獲取兩個字母的國別碼
我們的數(shù)據(jù)中,國別碼是三位的,而pygal的地圖工具使用兩位國別碼。要使用pygal繪制世界地圖。需要安裝依賴包。
pip install pygal_maps_world就可以了
國別碼位于i18n模塊
from pygal_maps_world.i18n import COUNTRIES這樣就導(dǎo)入了, COUNTRIES是一個字典,鍵是兩位國別碼,值是具體國家名。
key -> value
af Afghanistan
af Afghanistan
al Albania
al Albania
dz Algeria
dz Algeria
ad Andorra
ad Andorra
ao Angola
寫一個函數(shù),根據(jù)具體國家名返回pygal提供的兩位國別碼
def get_country_code(country_name):
"""
根據(jù)國家名返回兩位國別碼
"""
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
# 顏色相關(guān)
from pygal.style import RotateStyle
from pygal.style import LightColorizedStyle
def get_country_code(country_name):
"""
根據(jù)國家名返回兩位國別碼
"""
for code, name in COUNTRIES.items():
if name == country_name:
return code
return None
filename = r'F:\Jupyter Notebook\matplotlib_pygal_csv_json\population.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']
# 有些值是小數(shù),先轉(zhuǎn)為float再轉(zhuǎn)為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_style=LightColorizedStyle)
world = World(style=wm_style)
world.title = '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個字典,把人口按照數(shù)量分階梯,人口一千萬以下的存放在cc_populations_1中,一千萬~十億級別的存放在cc_populations_2中,十億以上的存放在cc_populations_3中,這樣做的目的是使得顏色分層更加明顯,更方便找出各個階梯里人口最多的國家。由于分了三個層次,在添加數(shù)據(jù)的的時候也add三次,把這三個字典分別傳進(jìn)去。 -
world = World(style=wm_style)這是一個地圖類,導(dǎo)入方法from pygal_maps_world.maps import World -
wm_style = RotateStyle('#336699', base_style=LightColorizedStyle)這里修改了pygal默認(rèn)的主題顏色,第一個參數(shù)是16進(jìn)制的RGB顏色,前兩位代表R,中間兩位代表G,最后兩位代表B。數(shù)字越大顏色越深。第二個參數(shù)設(shè)置基礎(chǔ)樣式為亮色主題,pygal默認(rèn)使用較暗的顏色主題,通過此方法可以修改默認(rèn)樣式。
中國大佬,No. 1
圖中可以看出,分的三個顏色層次為。紫色系,十億以上;藍(lán)色系,一千萬到十億之間;綠色系,一千萬一下。這三種顏色里面顏色最深的就對應(yīng)了三個階梯里人口最多的國家。
<div class="image-package"></div>
仔細(xì)觀察,圖中有些是空白的。并不是這些地方全部沒人,而是我們的json數(shù)據(jù)中有些國家的名稱與pygal中COUNTIES模塊的國家名不對應(yīng)導(dǎo)致。這算是一個遺憾,不過可以修改get_country_code函數(shù),使得遇到不對應(yīng)的國家名時,不返回None。對于這些國家,查閱COUNTRIES的代碼,找出對應(yīng)的國別碼,返回之,應(yīng)該就Ok了。比如下面這樣
# 傳入的參數(shù)country_name是json數(shù)據(jù)中的,可能與COUNTRIES里面的國家名不一致,按照上面的代碼直接就返回None,導(dǎo)致繪圖時這個國家的人口數(shù)據(jù)空白
if country_name == 'Yemen, Rep':
return 'ye'
不過我懶得試了233
使用Web API分析數(shù)據(jù)
以GitHub為例,我想查看最受歡迎的Python庫。以stars排序。
訪問這個網(wǎng)址就可查看。數(shù)據(jù)大概長這樣
{
"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,
...
}, {...}
...]
}
第三個數(shù)據(jù),items。里面是得到stars最多的top 30。name即庫名稱,owner下的login是庫的擁有者,html_url是該庫的網(wǎng)址(注意owner下也有個html_url。不過那個是用戶的GitHub網(wǎng)址,我們要定位到該用戶的具體這個庫,所以不要用owner下的html_url),stargazers_count至關(guān)重要,所得的stars數(shù)目。
順便說下第一個鍵total_count,表示Python語言的倉庫的總數(shù);第二個鍵,incomplete_results,表示響應(yīng)的值不完全,一般來說是false,表示響應(yīng)的數(shù)據(jù)完整。
import requests
url = 'https://api.github.com/search/repositories?q=language:python&sort=stars'
response = requests.get(url)
# 200為響應(yīng)成功
print(response.status_code, '響應(yīng)成功!')
response_dict = response.json()
total_repo = response_dict['total_count']
repo_list = response_dict['items']
print('總倉庫數(shù): ', 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'])
其實像這樣已經(jīng)得到結(jié)果了
200 響應(yīng)成功!
總倉庫數(shù): 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.
...
可視化一下當(dāng)然會更加直觀。
pygal可視化數(shù)據(jù)
代碼不是很難,有一個plot_dict比較關(guān)鍵,這是鼠標(biāo)放在條形圖上時,會顯示出來的數(shù)據(jù),鍵基本上都是固定寫法,xlink里面時倉庫地址,只要點擊,瀏覽器就會新開一個標(biāo)簽跳轉(zhuǎn)到該頁面了!
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為響應(yīng)成功
print(response.status_code, '響應(yīng)成功!')
response_dict = response.json()
total_repo = response_dict['total_count']
repo_list = response_dict['items']
print('總倉庫數(shù): ', total_repo)
print('top', len(repo_list))
names, plot_dicts = [], []
for repo_dict in repo_list:
names.append(repo_dict['name'])
# 加上str強轉(zhuǎn),因為我遇到了'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)
# 改變默認(rèn)主題顏色,偏藍(lán)色
my_style = LightenStyle('#333366', base_style=LightColorizedStyle)
# 配置
my_config = pygal.Config()
# x軸的文字旋轉(zhuǎn)45度
my_config.x_label_rotation = -45
# 隱藏左上角的圖例
my_config.show_legend = False
# 標(biāo)題字體大小
my_config.title_font_size = 30
# 副標(biāo)簽,包括x軸和y軸大部分
my_config.label_font_size = 20
# 主標(biāo)簽是y軸某數(shù)倍數(shù),相當(dāng)于一個特殊的刻度,讓關(guān)鍵數(shù)據(jù)點更醒目
my_config.major_label_font_size = 24
# 限制字符為15個,超出的以...顯示
my_config.truncate_label = 15
# 不顯示y參考虛線
my_config.show_y_guides = False
# 圖表寬度
my_config.width = 1000
# 第一個參數(shù)可以傳配置
chart = pygal.Bar(my_config, style=my_style)
chart.title = 'Most-Starred Python Projects on GitHub'
# x軸的數(shù)據(jù)
chart.x_labels = names
# 加入y軸的數(shù)據(jù),無需title設(shè)置為空,注意這里傳入的字典,
# 其中的鍵--value也就是y軸的坐標(biāo)值了
chart.add('', plot_dicts)
chart.render_to_file('most_stars_python_repo.svg')
看下圖,chrome瀏覽器里顯示效果??偢杏Xconfig里面有些設(shè)置沒有起到作用, x、y軸的標(biāo)簽還是那么小orz...不過plot_dict里面的三個數(shù)據(jù)都顯示出來了,點擊即可跳轉(zhuǎn)。
好了,就折騰這么多吧,這個庫也不是特別大眾的...
