曾经沧海难为水,除却巫山不是云|

Joey-Wang

园龄:4年3个月粉丝:17关注:0

📂Python
🔖Python
2021-03-20 03:56阅读: 763评论: 0推荐: 0

Python项目实践2——数据可视化

第十五章 生成数据

总教程可查https://github.com/matplotlib/AnatomyOfMatplotlib

学习如何使用matplotlib和Pygal生成数据,以及根据这些数据创建实用的图表。

  1. 数据可视化:指通过可视化表示来探索数据。

  2. 数据挖掘/数据分析:指使用代码来探索数据集的规律和关联。

  3. matplotlib:是一个数学绘图库,可用于制作简单的图表,Eg:折线图和散点图。

    (查看使用matplotlib可制作的图表:访问 matplotlib的官方样图

  4. Plotly包:专注于生成适合在数字设备上显示的图表。

    • Plotly 生成的图标可根据显示设备的尺寸自动调整大小,还具备众多交互特性,如在用户将鼠标指向图表的不同部分时突出数据集的特定方面。

15.1 绘制折线图、散点图

  1. 导入matplotlib的模块pyplot(可给它执行别名plt)

    image-20200427173946816
  2. fig, ax = pyplot.subplots(dpi=, figsize=)——在一张图中绘制一个子图表。

    • 返回值1:变量 fig 表示整张图

    • 返回值2:变量 ax 表示接下来要绘制的子图表

    • 参数 dpi:指定分辨率

    • 参数 figsize:元组,指定绘图窗口的尺寸,单位为英寸

    🌰 fig, ax = pyplot.subplots(figsize=(15, 9), dpi=128)

  3. pyplot.plot(x, y, 'xxx', label=, linewidth=, alpha=)——绘制图形

    • 参数1:位置参数,点的 x 坐标,可迭代对象(不写则默认从0开始,步长为1

    • 参数2:位置参数,点的 y 坐标,可迭代对象【必填】

    • 参数3:位置参数,点和线的样式,字符串

      此参数分为三部分:点线的颜色、点的形状、线的形状(也可分开设置)

      • 点线的颜色 color 可简写为c:g | 绿色;b | 蓝色;r | 红色;c | 蓝绿色;m | 品红色

        (也可使用RGB表示,Eg:color=' #054E9F')

      • 点的形状marker:. | 点;^ | 三角形;v | 实心倒三角;o | 实心圆;* | 实心五角星;+ | 加号

      • 线的形状 linestyle:- | 实线;-- | 虚线;-. | 点划线;: | 冒号

      matplotlib颜色及线条控制

    • 参数4:label关键字参数,设置图例,还需调用pyplot.legend()函数

    • 参数5:linewidth 关键字参数,设置线的粗细

    • 参数6:alpha 关键字参数,设施线的透明度(0为完全透明,默认为1不透明)

    返回值是一个被添加的线条 list。

    import matplotlib.pyplot as plt
    fig, ax = plt.subplots()
    ax.plot(参数)
    # 等价于 plt.plot(参数)
    
  4. pyplot.scatter(x, y, edgecolor=,c=, s=)——绘制散点图

    • 参数1:点的 x 坐标(可为一个点的x值,也可是一系列点的x值列表)【必填】

    • 参数2:点的 y 坐标(可为一个点的y值,也可是一系列点的y值列表)【必填】

    • 参数3:实参 edgecolor 设置散点的轮廓颜色(默认黑色轮廓);若要删除点的轮廓,设置edgecolor='none'

    • 参数4:实参 c 设置散点的颜色(默认蓝色点)

      • 🌰 c = 'red',

      • 也可使用 RGB 颜色模式自定义颜色——使用元组,其中包含三个0-1的小数数值表示R、G、B;值越接近 0 指定的颜色越深。

        🌰 c = (0, 0, 0.8)

    • 参数5:实参 s 设置绘制散点的尺寸大小

    • 设置参数 cmap 可使用颜色映射

      • 颜色映射:一系列颜色,从起始颜色渐变到结束颜色,用于突出数值的规律
  5. pyplot.title('titleName', fontsize=)——设置图标标题

    • 参数1:图标标题的内容
    • 参数2:fontsize关键字参数,字体大小
  6. pyplot.xlabel('xLabelName', fontsize=)——设置x轴标签

  7. pyplot.ylabel('yLabelName', fontsize=)——设置y轴标签

  8. pyplot.tick_params(axis=, labelsize=)——设置坐标轴刻度标记

    • 参数1:axis 关键词参数

      • axis='x' ——设置x轴刻度
      • axis='y' ——设置y轴刻度
      • axis='both' ——同时设置x、y轴刻度
    • 参数2:labelsize 关键字参数,刻度标记的字号大小

    • 参数3:which 关键字参数

      • which='major' —— 设置主刻度线
      • which='minor' —— 设置副刻度线
      • which='both' —— 同时设置主副刻度线

    tick_params参数刻度线样式设置

  9. pyplot.figure.autofmt_xdate()——绘制倾斜的x轴标签,以避免它们彼此重叠

  10. pyplot.legend() ——显示图例

    • 在画图时就设置标签,然后legend放置图例

      ax = pyplot.subplots()
      line1, = ax.plot([1, 2, 3], label='label1')
      ax.legend()
      
    • 通过set_label重载标签,然后legend放置图例

      ax = pyplot.subplots()
      line1, = ax.plot([1, 2, 3])  # 注意逗号
      line1.set_label('label1')
      ax.legend()
      
    • 通过下面方式给出标签

      fig, ax = plt.subplots()
      line1, = ax.plot([1, 2, 3])
      line2, = ax.plot([3, 5, 6])
      line3, = ax.plot([5, 7, 8])
      plt.legend((line1, line2, line3), ('label1', 'label2', 'label3')) ## !!!
      plt.show()
      

    图例legend语法及设置

    官方文档legend函数

  11. pyplot.axis([x1, x2, y1, y2])——设置坐标轴的取值范围

    • x1, x2, y1, y2分别是x和y轴的最大值和最小值
  12. pyplot.axes().get_xaxis().set_visible(False)——隐藏x坐标轴

    pyplot.axes().get_yaxis().set_visible(False)——隐藏y坐标轴

    import matplotlib.pyplot as plt
    fig, ax = plt.subplots()
    ax.get_xaxis().set_visible(False)
    # 等价于 plt.axes().get_yaxis().set_visible(False)
    
  13. pyplot.figure(dpi=,figsize=)——调整图表尺寸,指定图表的宽度、高度、分辨率和背景色(python假定屏幕分辨率为80像素/英寸)

    • 参数1:dip指定分辨率

    • 参数2:figsize需指定一个元组,指定绘图窗口的尺寸,单位为英寸

      🌰 pyplot.figure(dpi=128, figsize=(10, 6))

    • 返回值为pyplot.figure的变量,表示整张图

  14. pyplot.show()——打开matplotlib查看器,并显示绘制的图形

  15. pyplot.savefig('xxx.png', bbox_inches='tight')——自动保存图表到文件中

    • 参数1:指定保存文件名
    • 参数2:指定将图表多余的空白区域裁减掉;若要保留空白区域,则可省略此参数
  16. help(pyplot.plot)——查看官方关于pyplot模块的plot函数帮助

# 绘制折线图

import matplotlib.pyplot as plt

input_value = [1, 2, 3, 4, 5]
squares = [1, 4, 9, 16, 25]
plt.plot(input_value, squares, linewidth=5, label='xxx') ## !!!
# 设置图表标题,并给坐标轴加上标签
plt.title("Square Numbers", fontsize=24)
plt.xlabel("Value", fontsize=14)
plt.ylabel("Square of Value", fontsize=14)
plt.legend()
# 设置刻度标记的大小
plt.tick_params(axis='both', labelsize=14)
plt.show()
image-20200426235409327
# 绘制散点图
# 此处让python循环帮我们计算数据,绘制1000个点

import matplotlib.pyplot as plt

x_value = list(range(1, 1001))
y_value = [x**2 for x in x_value]
plt.scatter(x_value, y_value, edgecolor='none', c=(0, 0, 0.8), s=40, label='square') ## !!!
# 设置图表标题,并给坐标轴加上标签
plt.title("Square Numbers", fontsize=24)
plt.xlabel("Value", fontsize=14)
plt.ylabel("Square of Value", fontsize=14)
plt.legend()
# 设置每个坐标轴的取值范围
plt.axis([0, 1100, 0, 1100000])
# 设置刻度标记的大小
plt.tick_params(axis='both', labelsize=14)
plt.show()
image-20200427000657201
# 使用pyplot内置的颜色映射
# 将scatter函数中参数c设置成y值列表,使用参数cmap告诉pyplot使用哪个颜色映射。则会将y值小的点显示为浅蓝色,y值大的点显示为深蓝色

import matplotlib.pyplot as plt

x_value = list(range(1, 1001))
y_value = [x**2 for x in x_value]
plt.scatter(x_value, y_value, c=y_value, s=40, label='square', cmap=plt.cm.Blues) ## !!!
# 设置图表标题,并给坐标轴加上标签
plt.title("Square Numbers", fontsize=24)
plt.xlabel("Value", fontsize=14)
plt.ylabel("Square of Value", fontsize=14)
plt.legend()
# 设置每个坐标轴的取值范围
plt.axis([0, 1100, 0, 1100000])
# 设置刻度标记的大小
plt.tick_params(axis='both', labelsize=14)
plt.show()
image-20200427001312319

Matplotlib 提供了很多内置样式(设置好背景色、网格线、线条粗细、字体、字号等)

import matplotlib.pyplot as plt
print(plt.style.available)  # 获悉你的系统中可使用哪些样式

若要使用某个内置样式,可在生成图表的代码前添加代码行:

import matplotlib.pyplot as plt
input_value = [1, 2, 3, 4, 5]
squares = [1, 4, 9, 16, 25]
plt.style.use('seaborn')  # !!!
fig, ax = plt.subplots()
ax.plot(input_value, squares, linewidth=3)

# 设置图标标题并给坐标轴加上标签
# ax.set_title("平方数", fontsize=24)
# ax.set_xlabel("值", fontsize=14)
# ax.set_ylabel("值的平方", fontsize=14)

# 设置刻度标记的大小
# ax.tick_params(axis='both', labelsize=14)
plot.show()
image-20210319003648318

15.2 随机漫步

  1. 随机漫步:这样得到路径——每次行走都完全随机,没有明确的方向,结果是由一系列随机决策决定的。
# random_walk.py
# 为做出随机决策,我们将所有可能的选择都存储在一个列表中,并在每次做出决策时都使用choice()来决定使用哪种选择。最后可根据所有随机点生成散点图

from random import choice

class RandomWalk():
    """一个生成随机漫步数据的类"""

    def __init__(self, num_points=5000):  # 漫步的默认点数
        """初始化随机漫步的属性"""

        self.num_points = num_points
        # 所有随机漫步都始于(0, 0)
        self.x_values = [0]
        self.y_values = [0]

    def fill_walk(self):
        """计算随机漫步包含的所有点"""

        # 不断漫步,直到列表达到指定的长度
        while len(self.x_values) < self.num_points:
            # 决定前进方向以及沿此方向前进的距离
            # 将移动方向乘以移动距离,确定沿x和y轴移动的距离。
            # 若x_step>0 向右移动;<0 向左移动;=0 垂直移动。若y_step>0 向上移动;<0 向下移动;=0 水平移动
            x_direction = choice([1, -1])  # 往右 or 往左
            x_distance = choice([0, 1, 2, 3, 4])  # 步幅
            x_step = x_direction * x_distance

            y_direction = choice([1, -1])
            y_distance = choice([0, 1, 2, 3, 4])
            y_step = y_direction * y_distance

            # 不能原地踏步
            if x_step == 0 and y_step == 0:
                continue

            # 计算下一个点的x和y值
            next_x = self.x_values[-1] + x_step
            next_y = self.y_values[-1] + y_step

            self.x_values.append(next_x)
            self.y_values.append(next_y)
  1. 绘制随机漫步图
import matplotlib.pyplot as plt
from random_walk import RandomWalk

# 创建一个RandomWalk实例,并将其包含的点都绘制出来
rw = RandomWalk()
rw.fill_walk()
plt.scatter(rw.x_values, rw.y_values, s=5)
plt.show()
image-20200427170538317
  1. 模拟多次随机漫步
import matplotlib.pyplot as plt
from random_walk import RandomWalk

# 只要程序处于活动状态,就不断模拟随机漫步
while True:
    # 创建一个RandomWalk实例,并将其包含的点都绘制出来(一次随机漫步)
    rw = RandomWalk()
    rw.fill_walk()

    point_numbers = list(range(rw.num_points)) # 用于给点着色(颜色映射)
    # scatter中参数c设置成漫步点数列表,参数cmap设定颜色映射。则会将点数值小的点显示为浅蓝色,y值大的点显示为深蓝色
    plt.scatter(rw.x_values, rw.y_values, s=5, c=point_numbers, cmap=plt.cm.Blues, edgecolors='none')

    # 突出起点和终点
    plt.scatter(rw.x_values[0], rw.y_values[0], c='green', edgecolors='none', s=50)
    plt.scatter(rw.x_values[-1], rw.y_values[-1], c='red', edgecolors='none', s=50)

    # 隐藏坐标轴
    plt.axes().get_xaxis().set_visible(False)
    plt.axes().get_yaxis().set_visible(False)

    # 设置绘图窗口的尺寸
    plt.figure(figsize=(10, 6))

    plt.show()

    keep_running = input("是否做另一次随机漫步?(y/n):")
    if keep_running == 'n':
        break
image-20200427175832736

15.4 使用 Pygal 模拟掷骰子

《Python编程从入门到实践》第二版采用 Plotly 模拟掷骰子。(pygal 为第一版)

个人认为 pygal 更容易,采用 Plotly 的版本详见书 p292 页。

  1. 使用 Python 可视化包 Pygal 来生成交互式图表——可缩放的矢量图形文件

    • 很适合需要在尺寸不同的屏幕上显示的图表,因为它可自动缩放,以适合观看者的屏幕
    • 最后将图表渲染为 SVG 文件(文件扩展名必须为.svg),用 Web 浏览器打开。Pygal 让图表具有交互性——若将鼠标指向此图表中的任何条形,将看到与之相关联的数据。
  2. 直方图:一种条形图,指出了各种结果出现的频率。

  3. Pygal三种条形图绘制

    • 创建普通条形图 Basic:pygal.Bar()
    • 创建堆积条形图 Stacked:pygal.StackedBar()
    • 创建水平条形图 Horizontal:pygal.HorizontalBar()

    👆方法创建图表实例时,可传入参数定制图表外观。

  4. 更改图表基色:

    导入模块pygal.style的样式类RotateStyleLightenStyle,在创建此类的实例时提供实参——十六进制的RGB颜色,将返回一个样式对象。在创建图表实例时传入此样式对象作为参数style

    import pygal 
    from pygal.style import LightenStyle as LS
    my_style = LS('#333366')
    chart = pygal.Bar(style=my_style)
    
  5. 在基色的基础上提亮颜色:

    导入模块模块pygal.style的样式类LightColorizedStyle,在创建基色样式类实例时将LightColorizedStyle作为基本样式传入。

    import pygal 
    from pygal.style import LightColorizedStyle as LCS, LightenStyle as LS
    my_style = LS('#333366', base_style=LCS)
    chart = pygal.Bar(style=my_style)
    
  6. 定制图表其他外观:

    • 若只定制一小部分,则可在创建图表实例时,直接传入相应参数

      import pygal 
      chart = pygal.Bar(x_label_rotation=45, show_legend=False)  # 让x轴标签绕x轴旋转45度,隐藏图例
      
    • 若需定制大量参数,则可创建一个Pygal类Config的实例my_config,在创建图表实例时将my_config作为 第一个实参 传递所有配置设置。

      import pygal 
      from pygal.style import LightColorizedStyle as LCS, LightenStyle as LS
      my_style = LS('#333366', base_style=LCS)
      my_config = pygal.Config()
      my_config.x_label_rotation = 45       # 让x轴标签绕x轴旋转45度
      my_config.show_legend = False         # 隐藏图例
      my_config.title_font_size = 24        # 图表标题字体大小
      my_config.label_font_size = 14        # 标签字体大小
      my_config.major_label_font_size = 18  # 主标签字体大小
      my_config.truncate_label = 15  # 设置标签的最长显示字符(若将鼠标放到被截短的标签上,将显示完整的标签)
      my_config.show_y_guides = False       # 隐藏图表中的水平线
      my_config.width = 1000                # 自定义图表宽度
      my_config.style = my_style            # 设置图表样式
      chart = pygal.Bar(my_config)
      
  7. 对图表实例设置属性 title = string —— 设置图表标题

  8. 对图表实例设置属性 x_labels = list —— 设置图表 x 轴标签

  9. 对图表实例设置属性 x_title = stirng —— 设置图表 x 轴标题

  10. 对图表实例设置属性 y_title = stirng —— 设置图表 y 轴标题

  11. 对图表实例调用方法add(string,list)——为图表添加数据。并在图表左侧显示标签图例

    • 参数1:标签名(若无需在图表左侧显示标签,则填空字符''
    • 参数2:添加的数据列表

    若要在图表中添加自定义的工具提示——鼠标指向条形将显示它表示的信息,见章节17.2.2

    若要将条形图中的每个条形都转变为可点击的连接,见章节17.2.3

  12. 对图表实例调用方法 render_to_file(文件名) 保存此图表为 .svg 文件,参数指定文件名。

绘制掷骰子直方图:

# 掷骰子类
from random import randint

class Die():
    """表示一个骰子的类"""

    def __init__(self, num_sides=6):
        """骰子默认为6面"""
        self.num_sides = num_sides

    def roll(self):
        """返回一个位于1和骰子面数之间的随机值"""
        return randint(1, self.num_sides)
import pygal
from die import Die

# 创建一个6面的骰子————D6
die =Die()

# 掷几次骰子,并将结果存储在一个列表中
results = []
for roll_num in range(1000):
    result = die.roll()
    results.append(result)

# 分析结果,将每个面的次数存储在列表frequencies中
frequencies = []
for value in range(2, die.num_sides+1):
    frequency = results.count(value)
    frequencies.append(frequency)

# 对结果进行可视化————绘制直方图
hist = pygal.Bar()

hist.title = "掷1000次D6骰子的结果"                  ## 设置直方图标题
hist.x_labels = ['1', '2', '3', '4', '5', '6']      ## 设置直方图x轴标签
hist.x_title = "结果"                               ## 设置直方图x轴标题
hist.y_title = "频率次数"                            ## 设置直方图y轴标题

hist.add('D6', frequencies)           ## 使用add函数将值添加到图表中(参数1:给添加值指定的标签,参数2:一个列表,是要出现在图表中的值)
hist.render_to_file('die_visual.svg') ## 存储为svg文件

用浏览器打开 die_visual.svg 文件,Pygal让此图表具有交互性,鼠标指向此图表中的任何图形,将看到与之相关联的数据。

image-20200428175526437
import pygal
from die import Die

# 创建一个D6、一个D10
die_1 = Die()
die_2 = Die(10)

# 掷几次骰子,并将结果存储在一个列表中
results = []
for roll_num in range(50000):
    result = die_1.roll()+die_2.roll()
    results.append(result)

# 分析结果,将每个面的次数存储在列表frequencies中
frequencies = []
for value in range(2, die_1.num_sides+die_2.num_sides+1):
    frequency = results.count(value)
    frequencies.append(frequency)

# 对结果进行可视化————绘制直方图
hist = pygal.Bar()

hist.title = "掷50000次D6和D10骰子的结果"
hist.x_labels = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16']
hist.x_title = "结果"
hist.y_title = "频率次数"

hist.add('D6 + D10', frequencies)
hist.render_to_file('die_visual.svg')
image-20200428180328681

更多 Pygal 绘制实例:

使用 Pygal 绘制世界人口地图:16.2节

使用 Pygal 可视化 GitHub上最受欢迎的 Python项目:17.2节

第十六章 下载数据

两种常见格式存储的数据:CSV、JSON

本章内容:

  1. 使用 Python 模块csv处理以 CSV 格式存储的天气数据,找出两个地区在一段时间内的最高温度和最低温度。
  2. 使用 Matplotlib 根据下载的数据创建一个图表,展示两个不同地区(sitka、deathValley)的温度变化。
  3. 使用模块 json 访问以 JSON 格式存储的人口数据,并使用Pygal绘制一幅按国别划分的人口地图。

16.1 CSV文件格式

  1. CSV文件:文件中的数据是一系列以 逗号分隔 的值。
  2. 使用Python标准库中的csv模块,分析CSV文件的数据行。import csv
  3. csv.reader(文件对象)
    • 参数:存储的文件对象
    • 返回值:一个与文件相关联的阅读器(reader)对象
  4. next(阅读器对象)
    • 参数:阅读器(reader)对象
    • 返回值:列表,表示文件中的下一行(将以逗号分隔的每项数据作为一个元素存储在列表中)
  5. enumerate()
    • 参数:列表
    • 可获得每个元素的索引及其值

16.1.1 实例:分析文件头

import csv

filename = 'sitka_weather_07-2014.csv'
with open(filename) as f:
    reader = csv.reader(f)
    head_row = next(reader)  # 获取文件头
    for index, column_header in enumerate(head_row):
        print(index, column_header)

​ 输出:

0 AKDT
1 Max TemperatureF
2 Mean TemperatureF
3 Min TemperatureF
4 Max Dew PointF
5 MeanDew PointF
6 Min DewpointF
7 Max Humidity
8  Mean Humidity
9  Min Humidity
10  Max Sea Level PressureIn
11  Mean Sea Level PressureIn
12  Min Sea Level PressureIn
13  Max VisibilityMiles
14  Mean VisibilityMiles
15  Min VisibilityMiles
16  Max Wind SpeedMPH
17  Mean Wind SpeedMPH
18  Max Gust SpeedMPH
19  PrecipitationIn
20  CloudCover
21  Events
22  WindDirDegrees

16.1.2 分析文件数据

  1. 模块datetime中的datetime类中的函数strptime()——将日期字符串转为表示相应日期的对象

    • 参数1:日期字符串
    • 参数2:要设置的日期格式
    实参 含义
    %Y 四位的年份,Eg:2015
    %y 两位的年份,Eg:15
    %m 用数字表示的月份(01~12)
    %d 用数字表示月份中的一天(01~31)
    %A 星期的名字,Eg:Monday
    %B 月份名,Eg:January
    %H 24小时制的小时数(00~23)
    %I(是大写的i) 12小时制的小时数(01~12)
    %M 分钟数(00~59)
    %S 秒数(00~59)
    %p am或pm
    from datetime import datetime
    first_date = datetime.strptime('2014-7-1', '%Y-%m-%d')
    print(first_date)  # 2014-07-01 00:00:00
    
  2. pyplot.fill_between(x, y1, y2, facecolor=, alpha=)——填充两个y值系列之间的空间

    • 前三个参数为x值系列及两个y值系列,将填充两个y值系列之间的空间
    • 参数facecolor:指定填充区域的颜色
    • 参数alpha:指定颜色的透明度(0为完全透明,默认值为1完全不透明)

16.1.3 实例:分析文件数据

绘制sitka的每日最高温度图:

import csv
from datetime import datetime
import matplotlib.pyplot as plt

filename = 'sitka_weather_2014.csv'
with open(filename) as f:
    reader = csv.reader(f)
    head_row = next(reader)  # 获取文件头

    # 从文件中获取日期、最高气温
    dates, highs = [], []
    for row in reader:   # 遍历文件中的余下各行
        dates.append(datetime.strptime(row[0], "%Y-%m-%d"))
        highs.append(int(row[1]))  # 注意转换为int类型

    # 绘制最高气温图表
    fig = plt.figure(dpi=128, figsize=(10, 6))
    plt.plot(dates, highs, c='red')
    # 设置图表格式
    plt.title("Daily high temperatures - 2014", fontsize=24)
    plt.xlabel('', fontsize=16)
    fig.autofmt_xdate()  ## 绘制斜的日期标签,以避免它们彼此重叠 !!!!!
    plt.ylabel("Temperature (F)", fontsize=16)
    plt.tick_params(axis='both', which='major', labelsize=16)

    plt.show()

绘制sitka的每日最高温和最低温图:

import csv
from datetime import datetime
import matplotlib.pyplot as plt

filename = 'sitka_weather_2014.csv'
with open(filename) as f:
    reader = csv.reader(f)
    head_row = next(reader)  # 获取文件头

    # 从文件中获取日期、最高气温、最低气温
    dates, highs, lows = [], [], []
    for row in reader:
        dates.append(datetime.strptime(row[0], "%Y-%m-%d"))
        highs.append(int(row[1]))  # 注意转换为int类型
        lows.append(int(row[3]))

    # 绘制气温图表
    fig = plt.figure(dpi=128, figsize=(10, 6))
    plt.plot(dates, highs, c='red')
    plt.plot(dates, lows, c='blue')
    plt.fill_between(dates, highs, lows, facecolor='blue', alpha=0.1)  # 填充颜色

    # 设置图表格式
    plt.title("Daily high and low temperatures - 2014", fontsize=24)
    plt.xlabel('', fontsize=16)
    fig.autofmt_xdate()  # 绘制斜的日期标签,以避免它们彼此重叠 !!!!!
    plt.ylabel("Temperature (F)", fontsize=16)
    plt.tick_params(axis='both', which='major', labelsize=16)

    plt.show()
image-20200430213820308

16.1.4 错误检查

我们使用的数据集可能缺失数据、格式不正确或数据本身不正确。缺失数据时,可使用try-except-else代码块来处理数据。

在有些情况下,需使用continue跳过一些数据,或使用remove()del将已提取的数据删除。

🌰 以上述天气数据为例,可能有些气象站会偶尔出现故障,未能收集部分或全部应收集的数据。缺失的数据可能会引发异常,若不妥善处理可能导致程序崩溃。如:death_valley_2014.csv中有行数据如下,可见2014-2-16日,表示最高气温和最低气温的字符串为空,则上述程序会报错。

2014-2-16,,,,,,,,,,,,,,,,,,,0.00,,,-1

此时我们使用try-except-else代码块,绘制死亡谷 deathValley 的每日最高温和最低温图:

import csv
from datetime import datetime
import matplotlib.pyplot as plt

filename = 'death_valley_2014.csv'
with open(filename) as f:
    reader = csv.reader(f)
    head_row = next(reader)  # 获取文件头

    # 从文件中获取日期、最高气温、最低气温
    dates, highs, lows = [], [], []
    for row in reader:
        try:
            current_date = datetime.strptime(row[0], "%Y-%m-%d")
            high = int(row[1])
            low = int(row[3])
        except ValueError:
            print(current_date, 'missing data')
        else:
            dates.append(current_date)
            highs.append(high)
            lows.append(low)


    # 绘制气温图表
    fig = plt.figure(dpi=128, figsize=(10, 6))
    plt.plot(dates, highs, c='red')
    plt.plot(dates, lows, c='blue')
    plt.fill_between(dates, highs, lows, facecolor='blue', alpha=0.1)

    # 设置图表格式
    plt.title("Daily high and low temperatures - 2014\nDeath Valley, CA", fontsize=24)
    plt.xlabel('', fontsize=16)
    fig.autofmt_xdate()  # 绘制斜的日期标签,以避免它们彼此重叠 !!!!!
    plt.ylabel("Temperature (F)", fontsize=16)
    plt.tick_params(axis='both', which='major', labelsize=16)

    plt.show()
image-20200430215423235

同时,控制台输出:

2014-02-16 00:00:00 missing data

16.2 制作世界人口地图:JSON格式

《Python编程从入门到实践》第二版本节为——制作全球地震散点图:JSON格式。

采用 json 模块处理数据,Plotly 绘制地震散点图。详见书 p314 页。

文件 population_data.json 中包含全球大部分国家 1960~2010 年的人口数据。

目标:使用 json 模块处理此文件数据,并使用 Pygal 对2010年全国人口数据进行可视化。

16.2.1 涉及到的函数

  1. json.load(file_object)——读取数据,将数据转换为 Python 能处理的格式。

  2. Python不能直接将包含小数点的字符串转为整数,可先转为浮点数,再转为整数。int(float(str))

  3. Pygal中的地图制作工具要求数据为特定格式:用 两位字母的国别码 表示国家,用数字表示人口数量。

    • Pygal使用的国别码存储在Pygal_maps_world.i18n模块中,模块中的字典COUNTRIES包含的键和值分别为两个字母的国别码和国家名。
    • 因此我们可以 import 此模块,通过国家名获得 Pygal 制作国家人口地图所需的两位字母国别码。
    from pygal_maps_world.i18n import COUNTRIES
    
    # 将键按字母顺序排序,打印每个国别码及其对应的国家
    for country_code in sorted(COUNTRIES.keys()):
        print(country_code, COUNTRIES[country_code])
    

    输出:

    ad Andorra 
    ae United Arab Emirates 
    af Afghanistan 
    ---略--- 
    zw Zimbabwe
    
  4. Pygal制作地图示例:

    from pygal_maps_world.maps import World
    wm = World()
    wm.title = "北美、中美和南美"
    wm.add('North America', ['ca', 'mx', 'us'])  # 北美包括加拿大,墨西哥,美国
    wm.add('Central America', ['bz', 'cr', 'gt', 'hn', 'ni', 'pa', 'sv'])
    wm.add('South America', ['ar', 'bo', 'br', 'cl', 'co', 'ec', 'gf', 'gy', 'pe', 'py', 'sr', 'uy', 've'])
    wm.render_to_file("美国.svg")
    
    image-20210319054428975
    from pygal_maps_world.maps import World
    wm = World()
    wm.title = "北美国家的人口数量"
    wm.add('North America', {'ca': 34126000, 'us': 309349000, 'mx': 113423000})
    wm.render_to_file("美国.svg")
    
    image-20210319055133569
    • pygal_maps_world.maps模块提供的图表类型World用于制作呈现各国数据的世界地图。

    • 对图表实例调用add(string,列表或字典)——为国别码列表指定的地图部分选择一种颜色突出,并在地图左边显示该颜色和指定的标签

      • 参数1:标签名
      • 参数2:地图中,我们要突出的国家的国别码列表 or 我们要突出的国家的 {国别码 : 数值} 字典使用字典时 Pygal 根据这些数字自动给不同国家着以深浅不一的某种同色系颜色
    • 设置 Pygal 使用的基色:导入模块pygal.style的样式RotateStyle,在创建此类的实例时提供实参——十六进制的RGB颜色,将返回一个样式对象。在创建World实例时传入此样式对象。

      from pygal_maps_world.maps import World
      from pygal.style import RotateStyle
      # 混合了少量红色(33)、多些绿色(66)和更多一些蓝色(99)
      wm_style = RotateStyle('#336699')  
      wm = World(style=wm_style)
      
    • 在基色的基础上加亮 Pygal 地图的颜色:导入模块模块pygal.style的样式LightColorizedStyle,在创建RotateStyle实例时将LightColorizedStyle作为基本样式传入。

      from pygal_maps_world.maps import World
      from pygal.style import RotateStyle, LightColorizedStyle
      # 混合了少量红色(33)、多些绿色(66)和更多一些蓝色(99)
      wm_style = RotateStyle('#336699', base_style=LightColorizedStyle)  
      wm = World(style=wm_style)
      

16.2.2 处理数据

population_data.json 文件内容:

[
  {
    "Country Name": "Arab World",
    "Country Code": "ARB",
    "Year": "1960",
    "Value": "96388069"
  },
  {
    "Country Name": "Arab World",
    "Country Code": "ARB",
    "Year": "1961",
    "Value": "98882541.4"
  },
  ---略---
]

可见此文件实质是一个很长的 Python 列表,其中每个元素都是一个包含四个键值对的字典:国家名、国别码、年份、该年人口数量。

  1. 因为我们只关心2010年数据,故将该年份的数据提取出来;
  2. 因为Pygal中的地图制作工具要求人口数量用数字表示,因此我们将 string 转为 int。
import json

# 将数据加载到一个列表中
filename = 'data/population_data.json'
with open(filename) as f:
    pop_data = json.load(f)

# 打印每个国家2010年的人口数量
for pop_dict in pop_data:
    if pop_dict['Year'] == '2010':
        country_name = pop_dict['Country Name']
        population = int(float(pop_dict['Value']))
        print(f"{country_name} : {str(population)}")

输出:

Arab World : 357868000
Caribbean small states : 6880000
East Asia & Pacific (all income levels) : 2201536674
East Asia & Pacific (developing only) : 1961558757
Euro area : 331766000
Europe & Central Asia (all income levels) : 890424544
Europe & Central Asia (developing only) : 405204000
European Union : 502125000
---略---
Zimbabwe : 12571000
  1. 因为Pygal中的地图制作工具要求用两个字母的国别码表示国家,此数据集中的国别码用三位字母表示,因此用不上,我们只能通过国家名,从Pygal_maps_world.i18n模块中获取二位字母国别码。
import json
from pygal_maps_world.i18n import COUNTRIES

def get_country_code(country_name):
    """根据指定的国家,返回Pygal使用的两个字母的国别码"""
    for code, name in COUNTRIES.items():
        if name == country_name:
            return code
    # 如果没有找到指定的国家,就返回 None
    return None

# 将数据加载到一个列表中
filename = 'data/population_data.json'
with open(filename) as f:
    pop_data = json.load(f)

# 打印每个国家2010年的人口数量
for pop_dict in pop_data:
    if pop_dict['Year'] == '2010':
        country_name = pop_dict['Country Name']
        population = int(float(pop_dict['Value']))
        country_code = get_country_code(country_name)
        if country_code:
            print(f"{country_code} : {str(population)}")
        else:
            print('Error - '+country_name)

输出:

ERROR - Arab World 
ERROR - Caribbean small states 
ERROR - East Asia & Pacific (all income levels) 
---略--- 
af: 34385000 
al: 3205000 
dz: 35468000 
---略--- 
ERROR - Yemen, Rep. 
zm: 12927000 
zw: 12571000

导致显示错误消息的原因有两个。

  1. 并非所有人口数量对应的都是国家,有些人口数量对应的是地区(阿拉伯世界)和经济类群(所有收入水平)。
  2. 有些统计数据使用了不同的完整国家名(如Yemen, Rep.,而不是Yemen)

我们将忽略导致错误的数据。

16.2.2 制作世界地图

import json
from pygal_maps_world.i18n import COUNTRIES
from pygal_maps_world.maps import World

def get_country_code(country_name):
    """根据指定的国家,返回Pygal使用的两个字母的国别码"""
    for code, name in COUNTRIES.items():
        if name == country_name:
            return code
    # 如果没有找到指定的国家,就返回 None
    return None

# 将数据加载到一个列表中
filename = 'data/population_data.json'
with open(filename) as f:
    pop_data = json.load(f)

# 创建包含每个国家2010年的人口数量的字典
cc_populations = {}
for pop_dict in pop_data:
    if pop_dict['Year'] == '2010':
        country_name = pop_dict['Country Name']
        population = int(float(pop_dict['Value']))
        country_code = get_country_code(country_name)
        if country_code:
            cc_populations[country_code] = population

# 根据人口数量将所有的国家分成三组
cc_pops_1, cc_pops_2, cc_pops_3 = {}, {}, {}
for cc, pop in cc_populations.items():
    if pop < 10_000_000:
        cc_pops_1[cc] = pop
    elif pop < 1_000_000_000:
        cc_pops_2[cc] = pop
    else:
        cc_pops_3[cc] = pop

# 查看每组分别包含多少国家
print(len(cc_pops_1), len(cc_pops_2), len(cc_pops_3))  # 控制台输出:85 69 2

wm = World()
wm.title = '2010年世界人口数量'
wm.add('0 ~ 1000万', cc_pops_1)
wm.add('1000万 ~ 10亿', cc_pops_2)
wm.add('10亿以上', cc_pops_3)
wm.render_to_file('2010年世界人口数量.svg')
image-20210319060859384

图中使用三种不同的颜色,让我们能够看出人口数量上的差别。在每组中,各个国家都按人口从少到多着以从浅到深的颜色。(其中白色是缺乏数据的国家)

我们可使用Pygal样式美化地图。

import json
from pygal_maps_world.i18n import COUNTRIES
from pygal_maps_world.maps import World
from pygal.style import RotateStyle,LightColorizedStyle  # !!!!!!!!!!!!!!!1


def get_country_code(country_name):
    """根据指定的国家,返回Pygal使用的两个字母的国别码"""
    for code, name in COUNTRIES.items():
        if name == country_name:
            return code
    # 如果没有找到指定的国家,就返回 None
    return None


# 将数据加载到一个列表中
filename = 'data/population_data.json'
with open(filename) as f:
    pop_data = json.load(f)

# 创建包含每个国家2010年的人口数量的字典
cc_populations = {}
for pop_dict in pop_data:
    if pop_dict['Year'] == '2010':
        country_name = pop_dict['Country Name']
        population = int(float(pop_dict['Value']))
        country_code = get_country_code(country_name)
        if country_code:
            cc_populations[country_code] = population

# 根据人口数量将所有的国家分成三组
cc_pops_1, cc_pops_2, cc_pops_3 = {}, {}, {}
for cc, pop in cc_populations.items():
    if pop < 10_000_000:
        cc_pops_1[cc] = pop
    elif pop < 1_000_000_000:
        cc_pops_2[cc] = pop
    else:
        cc_pops_3[cc] = pop

# 查看每组分别包含多少国家
print(len(cc_pops_1), len(cc_pops_2), len(cc_pops_3))

wm_style = RotateStyle('#336699', base_style=LightColorizedStyle)  # !!!!!!!!!!!!!!!1
wm = World(style=wm_style)  # !!!!!!!!!!!!!!!1
wm.title = '2010年世界人口数量'
wm.add('0 ~ 1000万', cc_pops_1)
wm.add('1000万 ~ 10亿', cc_pops_2)
wm.add('10亿以上', cc_pops_3)
wm.render_to_file('2010年世界人口数量_3.svg')
image-20210319062824836

第十七章 使用API

介绍如何编写一个独立的程序,并对其获取的数据进行可视化。

此程序将使用 Web应用编程接口(API)自动请求网站的特定信息而不是整个网页,再对这些信息进行可视化。

由于这样编写的程序始终使用最新的数据来生成可视化,因此即便数据瞬息万变,它呈现的信息也都是最新的。

17.1 使用 Web API

Web API:网站的一部分,用于与 使用非常具体的URL请求特定信息的程序 交互。这种请求称为API调用

请求的数据将以易于处理的格式(如JSON或CSV)返回。依赖于外部数据源的大多数应用程序都依赖于API调用。

本章我们将使用 GitHub 的 API 来请求有关该网站中 Python 项目的信息,然后使用 Pygal 生成交互式可视化,以呈现这些项目的受欢迎程度。

17.1.1 使用 API 调用请求数据

🌰 在浏览器中输入以下地址并回车:

https://api.github.com/search/repositories?q=language:python&sort=stars

此 API 调用将返回 GitHub 当前托管的项目中,以python为语言,按星级(star)排序的项目。

  1. https://api.github.com/ —— 将请求发送到 GitHub 网站中响应 API 调用的部分
  2. search/repositories —— 让 API 搜索 GitHub 上的所有仓库
    • repositories 后面的问号(?) —— 指出要传递一个实参。
    • q —— 表示查询,等号(=) —— 指定查询,& —— 逻辑与

响应数据:

{
  "total_count": 6742683,
  "incomplete_results": true,
  "items": [
    {
      "id": 83222441,
      "node_id": "MDEwOlJlcG9zaXRvcnk4MzIyMjQ0MQ==",
      "name": "system-design-primer",
      "full_name": "donnemartin/system-design-primer",
      --- 略 ---
  1. total_count——总共的项目数

  2. "incomplete_results": false—— 请求时成功的(并非不完整的)

    • 若 GitHub 无法处理该 API,则此处返回 True

    本人做的时候,这里返回true。但个人认为这只能表示此处返回的信息是不完整的,不能代表 GitHub 无法处理此 API

  3. items——各个项目详细信息的列表

17.1.2 编写程序处理 API 响应

我们编写程序来自动执行 17.1.1 中的 API 调用并处理结果。

import requests

# 执行API调用并存储响应
url = 'https://api.github.com/search/repositories?q=language:python&sort=stars'
headers = {'Accept': 'application/vnd.github.v3+json'}
r = requests.get(url, headers=headers)
print(f"Status code:n{r.status_code}")

# 将API响应赋给一个变量
response_dict = r.json()

# 处理结果
print(response_dict.keys())
  1. 需导入模块 requests
  2. 最新的 GitHub API 版本为第三版,因此通过指定 headers 显示地要求使用此版本的 API
  3. requests.get(url)—— 调用 API。参数为 URL 地址,返回一个响应对象
    • 响应对象包含属性 status_code—— 状态码,指出请求是否成功(200表示请求成功)
    • 对响应对象调用json()方法—— 将 API 返回 JSON 格式的信息转换为一个 Python 数据格式(此处为字典)

输出:

Status code: 200
dict_keys(['total_count', 'incomplete_results', 'items'])

17.1.3 处理响应字典

将API调用返回的信息存储到字典中后,就可以处理这个字典中的数据了。下面来生成一些概述这些信息的输出。

这是一种不错的方式,可确认收到了期望的信息,进而可以开始研究感兴趣的信息:

import requests

# 执行API调用并存储响应
url = 'https://api.github.com/search/repositories?q=language:python&sort=stars'
headers = {'Accept': 'application/vnd.github.v3+json'}
r = requests.get(url, headers=headers)
print(f"Status code: {r.status_code}")

# 将API响应赋给一个变量
response_dict = r.json()

print(f"总仓库数:{response_dict['total_count']}")

# 研究有关仓库的信息
repo_dicts = response_dict['items']
print(f"返回仓库数:{len(repo_dicts)}")

# 研究第一个仓库(即最受欢迎的仓库)
repo_dict = repo_dicts[0]
print(f"含有键值对:{len(repo_dict)}")
for key in sorted(repo_dict.keys()):
    print(key)

输出:

Status code: 200
总仓库数:6886103
返回仓库数:30
含有键值对:74
archive_url
archived
assignees_url
blobs_url
branches_url
--- 略 ---
url
watchers
watchers_count

由此,我们得知总仓库数、本次调用返回仓库数、每个仓库中含有的键值对数目,也打印出了其中所有的键值,方便我们提取需要的信息。

下面我们提取 repo_dict 中一些键相关联的值:

--- 略 ---

# 研究第一个仓库(即最受欢迎的仓库)
repo_dict = repo_dicts[0]
print("\n提取其中最受欢迎的仓库的相关信息:")
print('Name:', repo_dict['name'])
print('Owner:', repo_dict['owner']['login'])
print('Stars:', repo_dict['stargazers_count'])
print('Repository:', repo_dict['html_url'])
print('Created:', repo_dict['created_at'])
print('Updated:', repo_dict['updated_at'])
print('Description:', repo_dict['description'])

输出:

Status code: 200
总仓库数:6886311
返回仓库数:30

提取其中最受欢迎的仓库的相关信息:
Name: system-design-primer
Owner: donnemartin
Stars: 124174
Repository: https://github.com/donnemartin/system-design-primer
Created: 2017-02-26T16:15:28Z
Updated: 2021-03-19T16:35:50Z
Description: Learn how to design large-scale systems. Prep for the system design interview.  Includes Anki flashcards.

从上述输出可知,目前 GitHub 上星级最高的Python 项目名为 system-design-primer,所有者为用户 donnemartin,已有 124174 位用户给此项目加星。还可得到此项目仓库的 URL,以及创建和更新日期,对项目的描述。

我们最终希望使用 Pygal 生成交互式可视化,以呈现这些返回数据中项目的受欢迎程度。故需在👆代码的基础上写个循环,获取所有需要信息:

--- 略 ---
print("\n提取每个仓库的相关信息:")
for repo_dict in repo_dicts:
    print('\nName:', repo_dict['name'])
    print('Owner:', repo_dict['owner']['login'])
    print('Stars:', repo_dict['stargazers_count'])
    print('Repository:', repo_dict['html_url'])
    print('Created:', repo_dict['created_at'])
    print('Updated:', repo_dict['updated_at'])
    print('Description:', repo_dict['description'])

17.1.4 监视 API 的速率限制

大多数 API 存在速率限制 —— 在特定时间内可执行的请求数存在限制。

在浏览器中输入👇网址,可得知 GitHub的限制:

https://api.github.com/rate_limit

响应数据:

{ 
  "resources": { 
    "core": { 
    "limit": 60, 
    "remaining": 58, 
    "reset": 1550385312
  },
  "search": {
    "limit": 10,
    "remaining": 8,
    "reset": 1550381772
  },
--- 略 ---

我们关心的是搜索API的速率限制。在search中可见极限为每分钟10个请求,在当前分钟内还可执行8个请求。reset 值指的是配额将重置的Unix时间新纪元时间(1970年1月1日午夜后多少秒)。用完配额后,你将收到一条简单的相应,由此知道已到达 API 极限。到达极限后,必须等待配额重置。

17.2 使用 Pygal 可视化仓库

《Python编程从入门到实践》第二版本节采用 Plotly 可视化仓库,详见书 p332 页。

我们对获取的数据有了进一步了解后,将这些数据通过 Pygal 可视化,呈现GitHub上Python项目的受欢迎程度。

我们的目标——创建一个交互式条形图:条形的高度表示项目获得了多少颗星。单击条形将带你进入项目在GitHub上的主页。

初步代码:

import requests
import pygal
from pygal.style import LightColorizedStyle as LCS, LightenStyle as LS

# 执行API调用并存储响应
url = 'https://api.github.com/search/repositories?q=language:python&sort=stars'
headers = {'Accept': 'application/vnd.github.v3+json'}
r = requests.get(url, headers=headers)
print(f"Status code: {r.status_code}")

# 将API响应赋给一个变量
response_dict = r.json()

# 搜索有关仓库的信息
repo_dicts = response_dict['items']
names, stars = [], []
for repo_dict in repo_dicts:
    names.append(repo_dict['name'])
    stars.append(repo_dict['stargazers_count'])

# 可视化
my_style = LS('#333366', base_style=LCS)
chart = pygal.Bar(style=my_style, x_label_rotation=45, show_legend=False) #让x轴标签绕x轴旋转45度,隐藏图例
chart.title = 'GitHub上最受欢迎的 Python 项目'
chart.x_labels = names

chart.add('', stars)
chart.render_to_file('python_repos.svg')
image-20210320025746644

17.2.1 改进 Pygal 图表外观

若需定制大量参数,则可创建一个Pygal类Config的实例my_config,在创建图表实例时将my_config作为 第一个实参 传递所有配置设置。

--- 略 ---

# 可视化
my_style = LS('#333366', base_style=LCS)

my_config = pygal.Config()
my_config.x_label_rotation = 45       # 让x轴标签绕x轴旋转45度
my_config.show_legend = False         # 隐藏图例
my_config.title_font_size = 24        # 图表标题字体大小
my_config.label_font_size = 14        # 标签字体大小
my_config.major_label_font_size = 18  # 主标签字体大小
my_config.truncate_label = 15  # 设置标签的最长显示字符(若将鼠标放到被截短的标签上,将显示完整的标签)
my_config.show_y_guides = False       # 隐藏图表中的水平线
my_config.width = 1000                # 自定义图表宽度
chart = pygal.Bar(my_config, style=my_style)
chart.title = 'GitHub上最受欢迎的 Python 项目'
chart.x_labels = names

chart.add('', stars)
chart.render_to_file('python_repos2.svg')
image-20210320030110203

17.2.2 添加自定义工具提示

工具提示:Pygal中,将鼠标指向条形时将显示它表达的信息。

🌰 在👆示例中,当前显示的是项目获得了多少个星。下面来创建一个自定义工具提示,以同时显示项目的描述。

import requests
import pygal
from pygal.style import LightColorizedStyle as LCS, LightenStyle as LS

# 执行API调用并存储响应
url = 'https://api.github.com/search/repositories?q=language:python&sort=stars'
headers = {'Accept': 'application/vnd.github.v3+json'}
r = requests.get(url, headers=headers)
print(f"Status code: {r.status_code}")

# 将API响应赋给一个变量
response_dict = r.json()

# 搜索有关仓库的信息
repo_dicts = response_dict['items']
names, plot_dicts = [], []
for repo_dict in repo_dicts:
    names.append(repo_dict['name'])
    # plot_dicts.append(repo_dict['stargazers_count'])
    plot_dict = {
        'value': repo_dict['stargazers_count'],
        'label': str(repo_dict['description']),  # 注意逗号
    }
    plot_dicts.append(plot_dict)

# 可视化
my_style = LS('#333366', base_style=LCS)

my_config = pygal.Config()
my_config.x_label_rotation = 45       # 让x轴标签绕x轴旋转45度
my_config.show_legend = False         # 隐藏图例
my_config.title_font_size = 24        # 图表标题字体大小
my_config.label_font_size = 14        # 标签字体大小
my_config.major_label_font_size = 18  # 主标签字体大小
my_config.truncate_label = 15  # 设置标签的最长显示字符(若将鼠标放到被截短的标签上,将显示完整的标签)
my_config.show_y_guides = False       # 隐藏图表中的水平线
my_config.width = 1000                # 自定义图表宽度
chart = pygal.Bar(my_config, style=my_style)
chart.title = 'GitHub上最受欢迎的 Python 项目'
chart.x_labels = names

chart.add('', plot_dicts)
chart.render_to_file('python_repos3.svg')

在循环内部,对于每个项目,我们都创建了字典 plot_dict(见20行)。在这个字典中,我们使用键value存储了星数,并使用键label存储了项目描述。接下来,我们将字典 plot_dict 附加到 plot_dicts 末尾(见24行)。在42行处,我们将列表 plot_dicts 传递给了add()

image-20210320032114682

⚠️ 这里之所以要用'label': str(repo_dict['description']) 而不是 'label': repo_dict['description'],是因为运行时发现报错:

AttributeError: 'NoneType' object has no attribute 'decode'

这个报错信息提示有一个变量的值是None,None 的类型是NoneType , 它没有decode 方法。

故我们将这个None转为string形式,就解决了。

17.2.3 添加可单击的链接

Pygal还允许你 将图表中的每个条形用作网站的链接。为此,只需添加一行代码,在为每个项目创建的字典中,添加一个键为xlink的键—值对:

--- 略 ---
names, plot_dicts = [], []
for repo_dict in repo_dicts:
    names.append(repo_dict['name'])
    # plot_dicts.append(repo_dict['stargazers_count'])
    plot_dict = {
        'value': repo_dict['stargazers_count'],
        'label': str(repo_dict['description']),
        'xlink': repo_dict['html_url'],
    }
    plot_dicts.append(plot_dict)
--- 略 ---

Pygal根据与键xlink相关联的URL将每个条形都转换为链接。单击图表中的任何条形时,都将在浏览器中打开一个新的标签页。

参考文献

总教程可查https://github.com/matplotlib/AnatomyOfMatplotlib

matplotlib的官方样图

matplotlib颜色及线条控制

tick_params参数刻度线样式设置

图例legend语法及设置

官方文档legend函数

matplotlib中axes与axis的区别

Python之Matplotlib库常用函数

pygal文档

本文作者:Joey-Wang

本文链接:https://www.cnblogs.com/joey-wang/p/14559855.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Joey-Wang  阅读(763)  评论(0编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
展开