美文网首页python
pygal的简单使用

pygal的简单使用

作者: sunhaiyu | 来源:发表于2017-06-22 22:52 被阅读3706次

    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,该数据来源于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 Notebook\matplotlib_pygal_csv_json\population.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 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']
            
            # 有些值是小数,先转为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_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个字典,把人口按照数量分阶梯,人口一千万以下的存放在cc_populations_1中,一千万~十亿级别的存放在cc_populations_2中,十亿以上的存放在cc_populations_3中,这样做的目的是使得颜色分层更加明显,更方便找出各个阶梯里人口最多的国家。由于分了三个层次,在添加数据的的时候也add三次,把这三个字典分别传进去。
    • world = World(style=wm_style)这是一个地图类,导入方法from pygal_maps_world.maps import World
    • wm_style = RotateStyle('#336699', base_style=LightColorizedStyle)这里修改了pygal默认的主题颜色,第一个参数是16进制的RGB颜色,前两位代表R,中间两位代表G,最后两位代表B。数字越大颜色越深。第二个参数设置基础样式为亮色主题,pygal默认使用较暗的颜色主题,通过此方法可以修改默认样式。

    中国大佬,No. 1

    图中可以看出,分的三个颜色层次为。紫色系,十亿以上;蓝色系,一千万到十亿之间;绿色系,一千万一下。这三种颜色里面颜色最深的就对应了三个阶梯里人口最多的国家。

    仔细观察,图中有些是空白的。并不是这些地方全部没人,而是我们的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排序。

    访问这个网址就可查看。数据大概长这样

    {
      "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_style=LightColorizedStyle)
    # 配置
    my_config = pygal.Config()
    # x轴的文字旋转45度
    my_config.x_label_rotation = -45
    # 隐藏左上角的图例
    my_config.show_legend = False
    # 标题字体大小
    my_config.title_font_size = 30
    # 副标签,包括x轴和y轴大部分
    my_config.label_font_size = 20
    # 主标签是y轴某数倍数,相当于一个特殊的刻度,让关键数据点更醒目
    my_config.major_label_font_size = 24
    # 限制字符为15个,超出的以...显示
    my_config.truncate_label = 15
    # 不显示y参考虚线
    my_config.show_y_guides = False
    # 图表宽度
    my_config.width = 1000
    
    # 第一个参数可以传配置
    chart = pygal.Bar(my_config, style=my_style)
    chart.title = '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里面的三个数据都显示出来了,点击即可跳转。

    好了,就折腾这么多吧,这个库也不是特别大众的...


    by @sunhaiyu

    2017.6.22

    相关文章

      网友评论

      • fa4715aece23:不错的项目
      • SYF0505:最后config里边那个标题字号,主、副标签字号的设置似乎没有用吧?
      • Miozus:# -*- coding: utf-8 -*-
        """
        Created on Wed Feb 21 16:43:52 2018

        @Author: miao
        """

        from pygal_maps_world.i18n import COUNTRIES

        def get_country_code(country_name):
        """
        根据国家名返回2个字国别码
        """
        for code, name in COUNTRIES.items():
        if name == country_name:
        return code
        elif country_name == 'Yemen, Rep.':
        return 'ye'
        elif country_name == 'Macedonia, FYR':
        return 'mk'
        elif country_name == 'Macao SAR, China':
        return 'mo'
        elif country_name == 'Lao PDR':
        return 'la'
        elif country_name == 'Kyrgyz Republic':
        return 'kg'
        elif country_name == 'Korea, Rep.':
        return 'kr'
        elif country_name == 'Korea, Dem. Rep.':
        return 'kp'
        elif country_name == 'Iran, Islamic Rep.':
        return 'ir'
        elif country_name == 'Gambia, The':
        return 'gm'
        elif country_name == 'Egypt, Arab Rep.':
        return 'eg'
        elif country_name == 'Congo, Dem. Rep.':
        return 'cd'
        elif country_name == 'Congo, Rep.':
        return 'cg'
        elif country_name == 'Libya':
        return 'ly'
        elif country_name == 'Bolivia':
        return 'bo'
        elif country_name == 'Venezuela, RB':
        return 've'
        elif country_name == 'Tanzania':
        return 'tz'
        elif country_name == 'Vietnam':
        return 'vn'
        return None

        #参考世界地图补充了17个国别号code,并且忽略某些地图上看不出来的地区
        Miozus::sweat: 回复里的代码放出来,缩进都坏了
      • DiegoSlytherin:请问pip3 install pygal_maps_world报告权限不够怎么办啊?
      • da21ba0da774:想问一下,我的svg文件在chrome 打开后为什么不能数据交互啊?代码一样啊。
        sunhaiyu:@林_f1d2 换其他浏览器试试

      本文标题:pygal的简单使用

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