Python编程:从入门到实践 第16章

672 阅读6分钟

Python编程:从入门到实践 第16章 课后习题


plot貌似没法直接显示中文,百度了解决办法:

# 这三句用来在图表上显示中文
from pylab import mpl
mpl.rcParams['font.sans-serif'] = ['SimHei'] # 指定默认字体
mpl.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号'-'显示为方块的问题

16-1 旧金山:旧金山的气温更接近于锡特卡还是DV谷呢?请绘制一个显示旧金山最高气温和最低气温的图表,并进行比较。可从www.wunderground.com/history/ 下载几乎任何地方的天气数据。为此,请输入相应的地方和日期范围,滚动到页面底部,找到名为Comma-Delimited File的链接,再单击该链接,将数据存储为CSV文件。

略。在wunderground找不到下载数据的地方。。。囧。。。。

16-2 比较锡特卡和DV谷的气温:在有关锡特卡和DV谷的图表中,气温刻度反映了数据范围的不同。为准确地比较锡特卡和DV谷的气温范围,需要在y轴上使用相同的刻度。为此,请修改图16-5和图16-6所示图表的y轴设置,对锡特卡和DV谷的气温范围进行直接比较(你也可以对任何两个地方的气温范围进行比较)。你还可以尝试在一个图表中呈现这两个数据集。

Figure_1.png

import csv
import matplotlib.pyplot as plt
from datetime import datetime
'''
# 这三句用来在图表上显示中文
from pylab import mpl
mpl.rcParams['font.sans-serif'] = ['SimHei'] # 指定默认字体
mpl.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号'-'显示为方块的问题
'''

def get_data(filename):
    with open(filename) as f:
        reader = csv.reader(f)
        header = next(reader)
        for index, column_header in enumerate(header):
            print(index, column_header)

        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(f"Missing data for {current_date}.")
            else:
                dates.append(current_date)
                highs.append(high)
                lows.append(low)

        return {"dates": dates, "highs": highs, "lows": lows}


sitka_data = get_data("sitka_weather_2014.csv")
dea_valley_data = get_data(("dea_valley_2014.csv"))

fig, ax = plt.subplots(dpi=128, figsize=(10, 6))

ax.plot(sitka_data["dates"], sitka_data["highs"], color="red", alpha=0.5,
        label="sitka high")
ax.plot(sitka_data["dates"], sitka_data["lows"], color="blue", alpha=0.5,
        label="sitka low")
ax.fill_between(sitka_data["dates"], sitka_data["highs"], sitka_data["lows"],
                facecolor="blue", alpha=0.3)

ax.plot(dea_valley_data["dates"], dea_valley_data["highs"], color="black",
        alpha=0.5, label="dea valley high")
ax.plot(dea_valley_data["dates"], dea_valley_data["lows"], color="green",
        alpha=0.5, label="dea valley low")
ax.fill_between(dea_valley_data["dates"], dea_valley_data["highs"],
                dea_valley_data["lows"], facecolor="black", alpha=0.3)

ax.set_ylim([10, 120])

ax.set_xlabel("Date")
ax.set_ylabel("Temperature")

ax.legend()

ax.set_title("Sitka and dea Valley temperature")

#plt.gcf().autofmt_xdate()

# 这三句和autofmt_xdate()同样的效果,但是是手动。
# 看StackOverflow上,用autofmt在某些特殊情况下会导致无效果。
for label in ax.get_xticklabels():
    label.set_ha("right")
    label.set_rotation(30)


plt.show()

16-3 降雨量:选择你感兴趣的任何地方,通过可视化将其降雨量呈现出来。为此,可先只涵盖一个月的数据,确定代码正确无误后,再使用一整年的数据来运行它。

16-4 探索:生成一些图表,对你好奇的任何地方的其他天气数据进行研究。

16-5 涵盖所有国家 :本节制作人口地图时,对于大约12个国家,程序不能自动确定其两个字母的国别码。请找出这些国家,在字典COUNTRIES 中找到它们的国别 码;然后,对于每个这样的国家,都在get_country_code() 中添加一个if-elif 代码块,以返回其国别码:

if country_name == 'Yemen, Rep.' 
    return 'ye' elif 
    --snip--

16-7 选择你自己的数据 :世界银行(The World Bank)提供了很多数据集,其中包含有关全球各国的信息。请访问data.worldbank.org/indicator/ ,并找到一个你感兴趣的数据集。单击该数据集,再单击链接Download Data并选择CSV。你将收到三个CSV文件,其中两个包含字样Metadata,你应使用第三个CSV文件。编写一个程序,生成一个字典,它将两个字母的Pygal国别码作为键,并将你从这个文件中选择的数据作为值。使用Worldmap 制作一个地图,在其中呈现这些数据,并根据你的 喜好设置这个地图的样式。

16-5和16-7一起回答了。

import csv
from pygal.maps.world import World, COUNTRIES
from pygal.style import RotateStyle, LightColorizedStyle


def get_country_code(country):
    """根据指定的国家,返回pygal使用的两个字母的国别码"""
    for code, name in COUNTRIES.items():
        if name == country:
            return code

    if country == "Kyrgyz Republic":
        return "kg"
    elif country == "Korea, Rep.":
        return "kr"
    elif country == "Moldova":
        return "md"

    return None


filename = "debt-to-gdp.csv"
# https://data.worldbank.org/indicator/GC.DOD.TOTL.GD.ZS?view=chart

with open(filename, encoding='utf-8') as f:
    reader = csv.reader(f)
    # 去掉前几行的无用内容
    for i in range(5):
        header_row = next(reader)
    # 我觉得enumerate很好用,特别是对这种列数多的文件,所以注释了后留在这里了。
    # for index, column_header in enumerate(header_row):
    #    print(index,column_header)

    # debt_to_gdp_2014这个列表对用pygal生成图像无影响,但如果需要用pyplot生成折线
    # 图则需要。姑且保留在这。
    # debt_to_dgp_2014 = []
    country_debt_dict = {}

    for row in reader:
        try:
            country = row[0]
            debt = int(float(row[58]))
        except ValueError:
            pass
        else:
            # debt_to_dgp_2014.append(debt)

            code = get_country_code(country)

            # 打印pygal world的默认两字母代码,用以对照def get_country_code(country)
            # 中未能自动生成的代码,确定哪些是pygal world的确没有代码、哪些是名称不对导致
            # 无国家代码。然后手动到函数中添加三个国家代码。
            # for country in sorted(COUNTRIES.keys()):
            # print(country, COUNTRIES[country])
            
            # 经下面的打印及对比刚才的pygal world的代码,发现三个国家吉尔吉斯斯坦、韩国和
            # 摩尔多瓦是因为两者英文名不同导致无法自动更新成2个字符代码,因此在函数中手动添加。
            # if code:
            #    print(code + ":" + str(debt))
            # else:
            #    print(country + ":" + "error")

            # 将国家代码和对应的数据生成一个字典。
            if code:
                country_debt_dict[code] = debt

dict1, dict2, dict3 = {}, {}, {}
for c, d in country_debt_dict.items():
    # 将debt to gdp ratio按照50%以下,100%以下,超过100%分成三个dict
    if d < 50:
        dict1[c] = d
    elif d < 100:
        dict2[c] = d
    else:
        dict3[c] = d

wm_style = RotateStyle("#336699", base_style=LightColorizedStyle)
wm = World(style=wm_style)
wm.title = "World Governtment Debt to GDP ratio in 2014, by Country"
wm.add("Debt < 50% of GDP", dict1)
wm.add("Debt < 100% of GDP", dict2)
wm.add("Debt >100% of GDP", dict3)

wm.render_to_file("world.svg")

16-6 国内生产总值 :OpenKnowledge Foundation提供了一个数据集,其中包含全球各国的国内生产总值(GDP),可在data.okfn.org/data/core/g… 找到这个数据集。请下载这个数据集的JSON版本,并绘制一个图表,将全球各国最近一年的GDP呈现出来。

这个网站打不开了,在别的地方找了json的数据,尝试一下。

# data source: https://datahub.io/core/co2-fossil-by-nation

Figure_1.png

import json
import matplotlib.ticker as ticker
import matplotlib.pyplot as plt

filename = "co2-emissions-by-nation.json"


def get_data(country_name):
    years, totals = [], []
    for co2_dict in co2_data:
        if co2_dict["Country"] == country_name:
            year = co2_dict["Year"]
            years.append(year)

            total = co2_dict["Total"]
            totals.append(total)

    return years, totals


with open(filename) as f:
    co2_data = json.load(f)

# cn data
cn_years, cn_totals = get_data("CHINA (MAINLAND)")

# us data
us_years, us_totals = get_data("UNITED STATES OF AMERICA")

fig, ax = plt.subplots(dpi=128, figsize=(10, 6))

ax.plot(cn_years, cn_totals, label="China CO2 Emissions", color="tab:red")
ax.plot(us_years, us_totals, label="United States CO2 Emissions",
        color="tab:gray")

ax.set(title="US and China CO2 Emissions", xlabel="Year", 
       ylabel="CO2 Emissions, in Million")

ax.yaxis.set_major_formatter(
    ticker.FuncFormatter(lambda x, pos: '{:,.2f}'.format(x / 1000000) + 'M'))
ax.legend()
plt.show()

'''
from matplotlib.ticker import FuncFormatter

def millions_formatter(x, pos):
    return f'{x / 1000000}'
--snip--
ax.yaxis.set_major_formatter(FuncFormatter(millions_formatter))
'''

16-8 测试模块country_codes :我们编写模块country_codes 时,使用了print 语句来核实get_country_code() 能否按预期那样工作。请利用你在第11 章学到的知识,为这个函数编写合适的测试。

很奇怪,在pycharm中,如果输入unittest.main()则反而不运行了:

Ran 0 tests in 0.000s

OK

Process finished with exit code 0

Empty suite

StackOverflow上搜索一圈,都这么说:

python - "No tests were found" when running unittest.main() - Stack Overflow

As a beginner using Pycharm, Don Kirkby's answer helped me the most.

The solution, at least for me was to simply remove unittest.main() from the file altogether. It appears that Pycharm is using the command python -m unittest by default whenever the test is run and unittest.main() method is messing up the syntax.

I hope this helps anyone else who came here with the same problem.

翻看自己以前11章的练习,那看来两个问题都解决了:

  1. 为啥unittest.main()不运行: 改成当时文章中的就行了:
if __name__ == '__main__': 
    unittest
  1. 当时为啥在文末说不加unittest.main()就能运行: pycharm不需要加。。。囧
import unittest
from pygal.maps.world import COUNTRIES


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


class CountryCodesTestCase(unittest.TestCase):
    """Tests for country_codes.py."""

    def test_get_country_code(self):
        country_code = get_country_code("Andorra")
        self.assertEqual(country_code, "ad")

        country_code = get_country_code("United Arab Emirates")
        self.assertEqual(country_code, "ae")

        country_code = get_country_code("Afghanistan")
        self.assertEqual(country_code, "af")

    def test_get_country_code_fail(self):
        # 应该是“Korea, Republic of”
        country_code = get_country_code("Korea")
        self.assertEqual(country_code, "kr")

        country_code = get_country_code("Jordan")
        # 应该是“jo”
        self.assertEqual(country_code, "jor")

# unittest.main()

结果是第二个test fail,第一个通过。