天天看点

Python学习for数据可视化项目(二)Python学习for数据可视化项目(二)

Python学习for数据可视化项目(二)

图片后续添加

文章目录

  • Python学习for数据可视化项目(二)
    • 1 CSV文件格式
      • 1.1 分析CSV文件头
      • 1.2 打印文件头及其位置
      • 1.3 提取并读取数据
      • 1.4 绘制气温图表
      • 1.5 模块datetime
      • 1.6 在图表中添加日期
      • 1.7 涵盖更长的时间
      • 1.8 添加最低气温数据
      • 1.9 给图表区域着色
      • 1.10 错误检查
    • 2 制作世界人口地图:JSON格式
      • 2.1 下载世界人口数据
      • 2.2 提取相关数据
      • 2.3 将字符串转换为数字值
      • 2.4 获取两个字母的国别码
      • 2.5 制作世界地图
      • 2.6 在世界地图上呈现数字数据
      • 2.7 绘制完整的世界人口地图
      • 2.8 根据人口数量将国家分组
      • 2.9 使用Pygal设置世界地图的样式

1 CSV文件格式

在文本文件中存储数据最简单的方式时将数据作为一系列**以逗号分隔的值(CSV)**写入。这样的文件被成为CSV文件。本部分将使用《Python编程:从入门到实践》的配套资源,来对阿拉斯加西特卡地区的天气数据进行分析,文件名为sitka_weather_07-2014.csv。

1.1 分析CSV文件头

csv模块包含在Python标准库中,可用于分析CSV文件中的数据行。

import csv
filename='sitka_weather_07-2014.csv'
#打开文件,将结果文件对象存储在f
with open(filename) as f:
    #调用csv.reader()将文件对象传入,创建一个阅读器对象
    reader=csv.reader(f)
    #csv中的next()函数返回阅读器对象中的下一行
    header_row=next(reader)
    print(header_row)
           

所打开文件的第一行是与天气相关的文件头:

文件头AKDT 表示阿拉斯加时间(Alaska Daylight Time) , 其位置表明每行的第一个值都是日期或时间。 文件头Max TemperatureF 指出每行的第二个值都是当天的最高华氏温度。

注:文件头的格式并非总是一致,空格和单位可能出现在奇怪的地方,这在原始文件中很常见,不会带来问题。

1.2 打印文件头及其位置

使用**enumerate()**来获取每个元素的索引和值,以将列表中的每个文件头及其位置都打印出来:

import csv
filename='sitka_weather_07-2014.csv'

with open(filename) as f:
    reader=csv.reader(f)
    header_row=next(reader)
    #print(header_row)
    for index,columb_header in enumerate(header_row):
        print(index,columb_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
           

从中看出,时间在第0列,最高气温在第1列。故而为了分析时间和最高气温,应当处理文件中的每一行数据,提取出索引为0和1的值。

1.3 提取并读取数据

创建一个空列表,用一个循环来将每一行的最高气温存进列表。

import csv
filename='sitka_weather_07-2014.csv'

with open(filename) as f:
    reader=csv.reader(f)
    header_row=next(reader)
    #print(header_row)
    #for index,columb_header in enumerate(header_row):
        #print(index,columb_header)
    highs=[]
    for row in reader:
        highs.append(row[1])

    print(highs)
           

注意:阅读器对象从其停留的地方继续往下读取CSV文件, 每次都自动返回当前所处位置的下一行。

由于之前已经用next()读取了文件头行, 程序中的循环将从第二行开始,而文件中从这行开始包含的是实际数据。

由于是在列表中使用了字符串保存天气的数据,为了让matplotlib可以读取,应当将其转换为数字。

上文程序中的循环部分改成:

for row in reader:
    high=int(row[1])
    highs.append(high)
           

此时数据为:

1.4 绘制气温图表

使用matplotlib来创建一个显示每日最高气温的折线图。

import matplotlib.pyplot as plt
--snip--#接上节代码
#设置分辨率和图片大小
fig=plt.figure(dpi=128,figsize=(10,6))
plt.plot(highs,c='red')

plt.title('Daily high temperatures,July 2014',fontsize=24)
plt.xlabel('',fontsize=16)
plt.ylabel('Temperature(F)',fontsize=16)
plt.tick_params(axis='both',which='major',labelsize=16)

plt.show()
           
Python学习for数据可视化项目(二)Python学习for数据可视化项目(二)

1.5 模块datetime

下面在图表中添加日期。在天气数据文件中,第一个日期在第二行。

使用**datetime模块中的strptime()**方法来将一个如2014-7-1的字符串转换为一个表示相应日期的对象。

该方法的第一个实参为包含日期的字符串,第二个实参为解读字符串的式:’%Y-’ 将对应部分视为四位的年份; ‘%m-’ 将对应部分视为表示月份的数字; 而’%d’ 将对应部分视为月份中的一天(1~31)。其工作示例为:

>>> from datetime import datetime
>>> first_date=datetime.strptime('2014-7-1','%Y-%m-%d')
>>> print(first_date)
2014-07-01 00:00:00
>>> 
           

**datetime**模块中设置日期和时间格式的实参:

实参 含义
%A 星期名称,如Monday
%B 月份名,如January
%m 用数字表示的月份(01~12)
%d 用数字表示月份中的一天(01~31)
%Y 四位的年份,如2015
%y 两位的年份,如15
%H 24小时制的小时数(00~24)
%I 12小时制的小时数(01~12)
%p am或pm
%M 分钟数(00~59)
%S 秒数(00~61)???

1.6 在图表中添加日期

这一小节中提取日期和最高气温,传递给**plot()**。

fig.autofmt_xdate() 来绘制斜的日期标签,以避免彼此重叠。

import csv
#从模块datetime导入datetime
from datetime import datetime
import matplotlib.pyplot as plt

filename='sitka_weather_07-2014.csv'

with open(filename) as f:
    reader=csv.reader(f)
    header_row=next(reader)

    dates,highs=[],[]
    for row in reader:
        #设置读取时间格式
        current_date=datetime.strptime(row[0],'%Y-%m-%d')
        dates.append(current_date)
        high=int(row[1])
        highs.append(high)

fig=plt.figure(dpi=128,figsize=(10,6))
plt.plot(dates,highs,c='red')

plt.title('Daily high temperatures,July 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()
           
Python学习for数据可视化项目(二)Python学习for数据可视化项目(二)

1.7 涵盖更长的时间

将读取的数据文件替换成2014年一整年的文件,再修改上节代码中的标题即可。

此时的图为:

Python学习for数据可视化项目(二)Python学习for数据可视化项目(二)

1.8 添加最低气温数据

在上文的程序中添加提取最低气温的代码,然后将最低气温在图中绘制出,并将颜色设为蓝色。

#提取最低气温代码修改为:
    dates,highs,lows=[],[],[]
    for row in reader:
        current_date=datetime.strptime(row[0],'%Y-%m-%d')
        dates.append(current_date)
        
        high=int(row[1])
        highs.append(high)
        
        low=int(row[3])
        lows.append(low)
        --sinp--
     #绘制最低气温的代码
     plt.plot(dates,lows,c='blue')
           
Python学习for数据可视化项目(二)Python学习for数据可视化项目(二)

1.9 给图表区域着色

这里通过着色来呈现每天的气温范围。

使用**fill_between()**方法:接受一个x值的数据系列,接受两个y值的数据系列,填充两个y值数据系列之间的空间。

实参**alpha**:指定颜色的透明度,设为0表示完全透明,1表示完全不透明。

将上文绘制图表部分的代码修改如下

fig=plt.figure(dpi=128,figsize=(10,6))
plt.plot(dates,highs,c='red',alpha=0.5)
plt.plot(dates,lows,c='blue',alpha=0.5)
plt.fill_between(dates,highs,lows,facecolor='blue',alpha=0.5)
           

此时图表为:

Python学习for数据可视化项目(二)Python学习for数据可视化项目(二)

1.10 错误检查

当文件中的部分数据产生错误,如缺失、格式错误,可能会引发异常,需要妥善处理。

用上节代码来处理加利福尼亚死亡谷的气温数据,文件名death_valley_2014.csv。将会出现一个错误。

Traceback (most recent call last):
  File "/home/choupin/PycharmProjects/DataVisualization/DownloadData/highs_lows.py", line 19, in <module>
  #row[1]出错说明时某一天的最高气温出错
    high=int(row[1])
    #空字符无法转成整型数据
ValueError: invalid literal for int() with base 10: ''

           

错误说明,程序无法处理某一天的最高气温,因为其无法将空字符串转换成证书。

为此在原程序中添加处理异常的try-expect代码块。

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 date')
    else:
        dates.append(current_date)
        lows.append(low)
        highs.append(high)
           

这样在读取数据时如果发现数据缺失,那么将抛出异常,并指出引发异常的日期,然后开始下一个循环,图表依然可以绘制出,同时也能知道哪一天的数据出了问题。

Python学习for数据可视化项目(二)Python学习for数据可视化项目(二)

注:在处理其他数据错误的情况,还可以使用continue来跳过一些数据,或者使用remove()、del将已提取的数据删除。可根据情况而定

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

本节需要使用JSON格式的人口数据,并用json模块来进行处理。

2.1 下载世界人口数据

将文件population_data.json复制到本章程序所在文件夹,这个文件包含全球大部分国家1960~2010年的人口数据。

注:github上有这个文件,点此到达

注:Open Knowledge Foundation(http://data.okfn.org/ ) 提供了大量可以免费使用的数据集, 这些数据就来自其中一个数据集。

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"
  },
  {
    "Country Name": "Arab World",
    "Country Code": "ARB",
    "Year": "1962",
    "Value": "101474075.8"
  },
  --snip--
           

可见其是一个以字典为元素的列表。

接下来提起2010年各个国家的人口数据。

#导入json模块
import json
filename='population_data.json'

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

for pop_dict in pop_data:
    #选定年份
    if pop_dict['Year']=='2010':
        country_name=pop_dict['Country Name']
        poplation=pop_dict['Value']
        print(country_name+':'+poplation)
           

输出结果为:

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
--snip--
           

2.3 将字符串转换为数字值

由于文件中的键值对都是字符串,因此需要转成数字值再用pygal来处理,在此转成int型。

而由于有些数据带有小数点,而不能直接将带小数点的字符串转成整型,所以先转成浮点型,然后再转成整型。

修改上节倒数两行代码为:

population=int(float(pop_dict['Value']))
print(country_name+':'+str(poplation))
           

2.4 获取两个字母的国别码

Pygal中的地图制作工具要求数据为特定的格式:用国别码表示国家,以及用数字表示人口数量。处理地理政治数据时,经常需要用到几个标准化国别码集。

而文件中的国别码时三个字母的,因此需要转换。

Pygal使用的国别码原本存储在模块**il8n**中,然而该模块已经被pygal-2.0.0移除,需要在模块***pygal.map.world***中查找。字典COUNTRIES 包含的键和值分别为两个字母的国别码和国家名。要查看这些国别码,可从模块i18n 中导入这个字典, 并打印其键和值:

from pygal.maps.world import COUNTRIES
for country_code in sorted(COUNTRIES.keys()):
    print(country_code,COUNTRIES[country_code])
           

输出为:

ad Andorra
ae United Arab Emirates
af Afghanistan
al Albania
am Armenia
ao Angola
--snip--
           

编写一个函数用于接受国家名称,然后返回国别码,文件名命名为country_codes.py。

from pygal.maps.world import COUNTRIES

def get_country_code(country_name):
    for code,name in COUNTRIES:
        if name==country_name:
            return code
    return None
           

改写上文的程序,用上这个返回国别码的函数,将国别码和对应国家人口打印出来。

import json
#导入返回国别码的函数
from country_codes import get_country_code

filename='population_data.json'

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

for pop_dict in pop_data:
    if pop_dict['Year']=='2010':
        country_name=pop_dict['Country Name']
        poplation=int(float(pop_dict['Value']))
        #获取国别码
        code=get_country_code(country_name)
        if code:
            print(code+':'+str(poplation))
        else:
            #若未找到国别码就返回错误信息,并指出国家名
            print('Error-'+country_name)
           

2.5 制作世界地图

《Python编程:从入门到实践》中写到“pygal提供了图表类型Worldmap”,实际上同上节一样,这个模块也已经从新版的pygal中移除了。需要到pygal.maps.world中找到,且模块名从Worldmap变为World。因此代码如下:

#导入需要的新模块
import pygal.maps.world as m

wm=m.World()
wm.title='Noth,Central,and South America'

#使用该模块的add方法来添加一个标签和一个国家码列表
#默认每次使用不同颜色
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('america.svg')
           
Python学习for数据可视化项目(二)Python学习for数据可视化项目(二)

2.6 在世界地图上呈现数字数据

修改上小节程序,将传入add方法的列表替换成字典,键值对是国别码和对面国家的人口数量,即可在地图上显示国家的人口数量。

import pygal.maps.world as m

wm=m.World()
wm.title='Noth,Central,and South America'
#将列表改为字典
wm.add('North America',{'ca':34126000,'mx':309349000,'us':113423000})
#为方便先将这两行注释
#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('na_population.svg')
           
Python学习for数据可视化项目(二)Python学习for数据可视化项目(二)

2.7 绘制完整的世界人口地图

先处理数据,将国别码和人口数量转换成字典,以方便pygal来读取。

修改代码如下:

import json
import pygal.maps.world as m
from country_codes import get_country_code

filename='population_data.json'

with open(filename) as f:
    pop_data=json.load(f)
#创建一个字典
cc_populations={}
for pop_dict in pop_data:
    if pop_dict['Year']=='2010':
        country_name=pop_dict['Country Name']
        poplation=int(float(pop_dict['Value']))
        code=get_country_code(country_name)
        if code:
            #以国别码为键,人口为值存入
            cc_populations[code]=poplation

wm=m.World()
wm.title='World Population in 2010,by Country'
wm.add('2010',cc_populations)

wm.render_to_file('world_population.svg')
           

此时的地图图表为:

Python学习for数据可视化项目(二)Python学习for数据可视化项目(二)

注:根据本文获得的两张svg图片,并未获得“交互性功能”,即用浏览器打开,鼠标停留在国家上,并未出现人口数据,待后续解决。貌似是打开的浏览器联网问题,电脑联网后竟然就有了。

2.8 根据人口数量将国家分组

根据人口数量将其分成三组

import json
import pygal.maps.world as m
from country_codes import get_country_code

filename='population_data.json'

with open(filename) as f:
    pop_data=json.load(f)
cc_populations={}
for pop_dict in pop_data:
    if pop_dict['Year']=='2010':
        country_name=pop_dict['Country Name']
        poplation=int(float(pop_dict['Value']))
        code=get_country_code(country_name)
        if code:
            cc_populations[code]=poplation

cc_pops1,cc_pops2,cc_pops3={},{},{}
for code,pop in cc_populations.items():
    if pop <10000000:
        cc_pops1[code]=pop
    elif pop<100000000:
        cc_pops2[code]=pop
    else:
        cc_pops3[code]=pop

wm=m.World()
wm.title='World Population in 2010,by Country'
wm.add('0-10m',cc_pops1)
wm.add('10m-1nb',cc_pops2)
wm.add('>1bn',cc_pops3)

wm.render_to_file('world_population1.svg')
           
Python学习for数据可视化项目(二)Python学习for数据可视化项目(二)

2.9 使用Pygal设置世界地图的样式

Pygal样式存储在模块***style***中。这里可以直接声明该模块,也可只声明模块中将要用到的函数。

在此将使用***style***模块中的***RotateStyle***指定让pygal的图表使用一种基色,让三个分组的颜色差别更大。

然后使用***LightColorizedStyle***来加亮地图的颜色。

import json
import pygal.maps.world as m
#声明pygal.style模块
import pygal.style as s
from country_codes import get_country_code

filename='population_data.json'

with open(filename) as f:
    pop_data=json.load(f)
cc_populations={}
for pop_dict in pop_data:
    if pop_dict['Year']=='2010':
        country_name=pop_dict['Country Name']
        poplation=int(float(pop_dict['Value']))
        code=get_country_code(country_name)
        if code:
            cc_populations[code]=poplation

cc_pops1,cc_pops2,cc_pops3={},{},{}
for code,pop in cc_populations.items():
    if pop <10000000:
        cc_pops1[code]=pop
    elif pop<100000000:
        cc_pops2[code]=pop
    else:
        cc_pops3[code]=pop

wm=m.World()
#第一个参数为16进制的RGB颜色,前两位代表R,以此类推,数字越大颜色越深。
#第二个参数为设置基础样式为亮色主题,默认使用较暗的主题。
wm_style=s.RotateStyle('#336699',base_style=s.LightColorizedStyle)
#注意这里用实参style传递
wm=m.World(style=wm_style)
wm.title='World Population in 2010,by Country'
wm.add('0-10m',cc_pops1)
wm.add('10m-1nb',cc_pops2)
wm.add('>1bn',cc_pops3)

wm.render_to_file('world_population2.svg')

           
Python学习for数据可视化项目(二)Python学习for数据可视化项目(二)