问题来由:《Python编程:从入门到实践》16.1.6的代码无法绘制出与图16-2一模一样的图表。
如果你没有这本书也不要紧,我会给出书中代码、图片和数据。
书中16.1.6的代码如下
绘制斯特卡地区2014年7月份最高气温折线图。
import csv
from datetime import datetime
from matplotlib import 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()
【代码中所需的sitka_weather_07-2014.csv可以在这里(书本配套资源)下载到,进入网站点击Download .zip即可。
所需csv文件在路径ehmatthes-pcc-f555082\chapter_16中。】
书中图16-2是这样的
而那段代码画出来的图是这样的
hmmm
于是找到matplotlib文档查找对坐标轴的操作:
1. 坐标轴上下限设置
可以用pyplot模块中的axis()方法方便地完成横纵坐标轴上下限的设置,使用这个方法时四个限值必须全部给出。
matplotlib.pyplot.axis([xmin, xmax, ymin, ymax])
也可以用xlim()、ylim()方法分别设置横轴与纵轴的上下限。
matplotlib.pyplot.xlim(left, right) # 或 xlim((left, right))
matplotlib.pyplot.ylim(bottom, top) # 或 ylim((bottom, top))
使用这个方法时,可以通过关键词传递实参,只设置单个限值。
xlim(right=3) # 只调整横轴上限
2. 坐标轴间隔设置
可以用pyplot模块中的xticks()、yticks()方法设置横纵坐标轴的上下限和间隔。
matplotlib.pyplot.xticks(ticks, [labels], **kwargs)
matplotlib.pyplot.yticks(ticks, [labels], **kwargs)
ticks接收一个序列,作为坐标轴的刻度标签,或者作为可选参数lable的位置标记,可选参数lable接收一个序列,放在ticks标记的位置上,**kwargs收集text属性,用于控制lable的外观。
例如:
plt.xticks(range(1, 13, 1), calendar.month_name[1:13], rotation=20)
3. 获取日期对象
为了将横坐标的单位变为日期,需要将日期对象作为实参传递给xlim()方法。通过之前已经导入的datetime类,我们可以获取datetime对象。
datetime(year, month, day, hour=0, minute=0, second=0, microsecond=0, tzinfo=None)
其中可选参数tzinfo(time zone information,时区信息)接收tzinfo对象。
也可以使用datetime模块中的date类,获取一个date对象。
from datetime import date
date(year, month, day)
4. 日期格式
书中表16-1提及了一部分日期格式代码,但是偏偏就没有所需的月份名缩写代码,这里列出完整的日期格式代码表,这些格式代码继承自C语言。
代码 | 含义 |
---|---|
%a | 星期的三位字母缩写 |
%A | 星期的全称 |
%w | 一位数字表示的星期,其中0为周日,6为周6 |
%d | 两位数字表示的月份中的日期 |
%b | 月份的三位字母缩写 |
%B | 月份的全称 |
%m | 两位数字表示的月份 |
%y | 两位数字表示的年份 |
%Y | 四位数字表示的年份 |
%H | 两位数字表示的24小时制的小时数 |
%I | 两位数字表示的12小时制的小时数 |
%p | 月份的全称 |
%p | AM或PM |
%M | 两位数字表示的分钟数 |
%S | 两位数字表示的秒钟数 |
%f | 六数字表示的毫秒数 |
%z | 以±HHMM表示的世界时偏移量 |
%Z | 时区名 |
%j | 三位数字表示的一年中的天数 |
%U | 两位数字表示的一年中的周数,周日作为一周首日,一年中第一个周日前的日子视作第0周 |
%W | 两位数字表示的一年中的周数,周一作为一周首日,一年中第一个周一前的日子视作第0周 |
%c | 当地时间日期格式 |
%x | 当地日期格式 |
%X | 当地的时间格式 |
%% | %字符 |
从上面代码绘制出的折线图可知,默认的日期格式为“%Y-%m-%d”而书中绘制出折线图使用的日期格式为“%b %d %Y”
5. 设置日期格式
有了日期格式代码,下一步就需要把它作为参数传递给相关方法,但上面提到的pyplot中设置坐标轴格式的几个方法并不接收日期格式实参。
matplotlib还有一个名为axis的模块,其中包含坐标轴和坐标轴标记的类,这些类中提供了对坐标轴进行更多的操作的方法,其中XAxis和YAxis类,就有设置坐标轴格式的set_major_formatter()方法。
XAxis.set_major_formatter(self, formatter)
YAxis.set_major_formatter(self, formatter)
那么怎么获得这两个类的对象呢?可以pyplot模块中的gca()方法,该方法返回与关键词实参相匹配图标的XAxis类的xaxis对象和YAxis类的yaxis对象。
matplotlib.pyplot.gca(**kwargs)
有了对象和方法,可以进行参数传递了,可是set_major_formatter()方法只接收formatter对象,而我们只有表示日期格式的字符串,这时需要从matplotlib中再导入一个新的模块:dates,其中的DateFormatter类,可以把字符串转换为用于日期的DateFomatter对象,这是一种专门用于日期的formatter对象。
matplotlib.dates.DateFormatter(fmt, tz=None)
其中fmt接收日期格式的字符串,tz接收tzinfo对象,设置时区。
6. 设置日期间隔
同样地,在XAxis和YAxis类中有设置坐标轴间隔的set_major_locator()方法。
XAxis.set_major_locator(self, locator)
YAxis.set_major_locator(self, locator)
这个方法接收locator对象,我们需要将日期间隔设置为3天,相应地,在dates模块中有DayLocator类。
matplotlib.dates.DayLocator(bymonthday=None, interval=1, tz=None)
其中bymonthday接收一个序列,默认情况下会标记出一月中的每一天,即bymonthday=range(1,32),interval设置日期间隔,tz设置时区。
7. 设置刻度线朝向
pyplot模块中的tick_params()方法可以修改坐标刻度,刻度标签和网格线的外观
matplotlib.pyplot.tick_params(axis='both', **kwargs)
可选形参axis可以传入三种实参{‘x’, ‘y’, ‘both’},指示对哪个坐标轴进行修改,默认值为’both’;看到**kwargs我们就知道这里可以传入一堆关键词实参来修改坐标刻度,刻度标签和网格线的外观,这里只介绍涉及的几个形参:witch可以传入三种实参{‘major’, ‘minor’, ‘both’},默认值为’major’,指示对主刻度还是辅刻度进行修改;direction可以传入三种实参{‘in’, ‘out’, ‘inout’},设置刻度线朝向;labelsize可以传入浮点数或字符串,通过数值或字符串(例如’large’)的方式修改刻度标签的字体大小。
我们可以看到书中代码已经使用了这个方法:
plt.tick_params(axis='both', which='major', labelsize=16)
所以只需要再传递direction实参就可以修改刻度线朝向了。
8. 设置双坐标轴
书中折线图上边和右边也带刻度,其实是建立了双坐标轴。matplotlib的axes模块提供了twinx()、twiny()方法建立双坐标轴,需要注意的是twinx()方法建立的是共享x轴的双轴,亦即建立的是双y轴,相应twiny()建立的是双x轴。
建立双坐标轴之后,我们只需将先前设置好的x、y坐标轴的上下限和刻度间隔复制到新建立的x、y坐标轴上,然后将这两个轴的刻度标签隐藏即可。
twinx()、twiny()方法返回的都是axes对象,我们可以使用axes模块中提供的方法设置坐标轴格式:get_xlim()、get_ylim()方法用以获取指定坐标轴的上下限,get_xticks()、get_yticks()方法用以获取指定坐标轴的刻度位置列表,set_xlim()、set_ylim()方法用以设置指定坐标轴的上下限,set_xticks()、set_yticks()方法用以设置指定坐标轴的刻度位置,set_xticklabels()、set_yticklabels()方法用以设置指定坐标轴的刻度标签格式,可以传入空列表来关闭坐标轴的刻度标签,需要注意的是x轴以日期为单位,处理起来总是特殊一些,需要使用xaxis_date()方法,将x轴刻度和刻度标签中的数据当做日期对待。
于是,要画出和书中一模一样的折线图,代码如下:
#coding=gbk
import csv
from datetime import datetime
from matplotlib import pyplot as plt
from matplotlib import dates as mdates
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)
highs.append(int(row[1]))
# 根据数据绘制图表
fig = plt.figure(figsize=(10, 6))
plt.plot(dates, highs, c='red')
# 设置图表的格式
plt.title("Daily high temperatures, July 2014", fontsize=24)
plt.xlim([datetime(2014, 7, 1),datetime(2014, 7, 31)]) # 日期上下限
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%b %d %Y')) # 日期格式,%B为月份名,%b为月份名缩写
plt.gca().xaxis.set_major_locator(mdates.DayLocator(interval=3)) # 日期间隔
plt.xlabel('', fontsize=16)
fig.autofmt_xdate() # 斜的日期标签
plt.yticks(range(54, 74, 2)) # 温度上下限和间隔
plt.ylabel("Temperature (F)", fontsize=16)
plt.tick_params(axis='both', which='major', direction='in', labelsize=12)
xlm = plt.gca().get_xlim()
xtk = plt.gca().get_xticks()
ylm = plt.gca().get_ylim()
ytk = plt.gca().get_yticks()
x2 = plt.gca().twiny()
x2.xaxis_date()
x2.set_xlim(xlm)
x2.set_xticks(xtk)
x2.set_xticklabels([])
plt.tick_params(axis='x', which='major', direction='in')
y2 = plt.gca().twinx()
y2.set_ylim(ylm)
y2.set_yticks(ytk)
y2.set_yticklabels([])
plt.tick_params(axis='y', which='major', direction='in')
plt.show()
注意,这样写会报错:
from matplotlib import dates
plt.gca().xaxis.set_major_formatter(dates.DateFormatter('%Y/%m/%d'))
因为dates在之前已用作变量