Python3.绘图基础matplotlib.pyplot
能够方便地绘图是MATLAB一大优势,但这方面现在并不领先Python。
以环境物理学原理课一次作业为例,除去数据处理部分,使用matplotlib.pyplot结合一点pandas,绘制出不输MATLAB的常用曲线图。
千言万语都在源代码里(图在最后,刚学Markdown,图片排版有待研究):
# -*- coding: utf-8 -*-
"""
根据2012年太湖某站半小时气象数据(气压p、气温Ta和相对湿度RH)
计算实际水汽压ea、空气比湿q、水汽密度rhov和水汽摩尔分数Cva
(半小时值、日小时值、日均值、月均值和年均值)。
Created on Mon Oct 2 12:02:29 2017
@author: 墨大宝
"""
# 导入将要用到的模块,matplotlib.pyplot负责绘图
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import meteorolopy as mtr # 自建模块,用于计算各湿度指标
from datetime import date, timedelta
from matplotlib.dates import DateFormatter
#%% 读取数据,并简单整理
fn = 'E:\硕士课件\环境物理学原理\hw1_大气湿度的计算_20170930\hw1_data_20170930.xlsx'
data = pd.read_excel(fn, skiprows=[0,2], skip_footer=1) # 读取数据
data.rename(columns={'日序': '半小时序',
'气温': '气温(℃)',
'相对湿度': '相对湿度(%)',
'气压': '气压(kPa)'}, inplace=True) # 重命名表头
#%% 计算各气象指标
es = mtr.tc2es(data['气温(℃)']) # kPa
es.name = '饱和水汽压(kPa)'
ea = mtr.esRH2ea(es, data['相对湿度(%)']) # kPa
ea.name = '实际水汽压(kPa)'
Cva = mtr.eap2Cva(ea, data['气压(kPa)']) # ppm
Cva.name = '水汽摩尔分数(ppm)'
q = mtr.Cva2q(Cva) * 1e3 # g/kg
q.name = '比湿(g/kg)'
Cva *= 1e6 # ppm
rhov = mtr.eatc2rhov(ea, data['气温(℃)']) * 1e3 # g/m3
rhov.name = '水汽密度(g/m$^3$)'
#%% 整理计算结果
timenames = ['半小时序', '日序', '月序']
rawnames = ['气温(℃)', '相对湿度(%)', '气压(kPa)']
moistnames = ['实际水汽压(kPa)', '比湿(g/kg)',
'水汽密度(g/m$^3$)', '水汽摩尔分数(ppm)']
data['日序'] = np.int64(data['半小时序'])
data['月序'] = [(timedelta(int(x)) + date(2011, 12, 31)).month
for x in data['日序']]
data = pd.concat([data[timenames + rawnames], ea, q, rhov, Cva], axis=1)
del ea, q, rhov, Cva, es, fn # 删除已不再需要的中间变量
daymean = pd.concat(
[data.groupby('日序')[x].mean() for x in rawnames + moistnames],
axis=1)
daymean.index = pd.date_range('20120101', '20121231') # 生成时间序列作为索引
monthmean = pd.concat(
[data.groupby('月序')[x].mean() for x in rawnames + moistnames],
axis=1)
monthmean.index = pd.date_range('20120101', periods=12, freq='MS')
yearmean = pd.concat(
[data.groupby(np.tile(range(48), 366))[x].mean() for x in rawnames + moistnames],
axis=1)
yearmean.index = pd.date_range('201201010000', periods=48, freq='30min')
#%% 绘图,日均值季节变化
fontdict = {'family' : 'SimHei', 'fontsize': 8} # 图中字体设置
for factors in [rawnames, moistnames]:
fig, axes = plt.subplots(len(factors), 1, sharex=True, dpi=600) # 生成图形和子图句柄,并设置子图共享横坐标
fig.set_size_inches(5, 3.8) # 设置出图尺寸,单位inch
fig.subplots_adjust(hspace=0, left=0.15, right=0.98, bottom=0.1, top=0.99) # 设置子图间无空隙以及子图空间布置
for xid, x in enumerate(factors):
axes[xid].plot(daymean[x], label=x) # 绘制曲线,单条曲线label图例可省略
axes[xid].set_xlim([daymean[x].index[0], daymean[x].index[-1]]) # 设置x轴显示范围
axes[xid].xaxis.set_major_formatter(DateFormatter('%m-%d')) # x轴刻度标签格式化为指定日期格式
axes[xid].xaxis.set_tick_params(labelsize=7) # 设置x轴刻度标签字号
axes[xid].yaxis.set_tick_params(labelsize=7) # 设置y轴刻度标签字号
axes[xid].set_ylabel(x, **fontdict) # 设置y轴标题和字体字号
axes[xid].set_xlabel('日期', **fontdict) # 设置x轴标题和字体字号
axes[xid].grid(True) # 显示网格
fig.savefig(factors[0]+'日期.png', dpi=600) # 保存指定dpi图片
fig.show() # 显示图片
#%% 绘图,月均值季节变化
for factors in [rawnames, moistnames]:
fig, axes = plt.subplots(len(factors), 1, sharex=True, dpi=600)
fig.set_size_inches(2.8, 3.8)
fig.subplots_adjust(hspace=0, left=0.2, right=0.98, bottom=0.1, top=0.99)
for xid, x in enumerate(factors):
axes[xid].plot(monthmean[x], label=x)
axes[xid].set_xlim([monthmean[x].index[0], monthmean[x].index[-1]])
axes[xid].xaxis.set_major_formatter(DateFormatter('%m'))
axes[xid].xaxis.set_tick_params(labelsize=7)
axes[xid].yaxis.set_tick_params(labelsize=7)
axes[xid].set_ylabel(x, **fontdict)
axes[xid].set_xlabel('月份', **fontdict)
axes[xid].grid(True)
fig.savefig(factors[0]+'月份.png', dpi=600)
# fig.show()
#%% 绘图,年均值日变化
for factors in [rawnames, moistnames]:
fig, axes = plt.subplots(len(factors), 1, sharex=True, dpi=600)
fig.set_size_inches(12, 4)
fig.subplots_adjust(hspace=0, left=0.15, right=0.98, bottom=0.1, top=0.99)
for xid, x in enumerate(factors):
axes[xid].plot(yearmean[x], label=x)
axes[xid].set_xlim([yearmean[x].index[0], yearmean[x].index[-1]])
axes[xid].xaxis.set_major_formatter(DateFormatter('%H:%M'))
axes[xid].xaxis.set_tick_params(labelsize=7)
axes[xid].yaxis.set_tick_params(labelsize=7)
axes[xid].set_ylabel(x, **fontdict)
axes[xid].set_xlabel('时间', **fontdict)
axes[xid].grid(True)
fig.savefig(factors[0]+'时间.png', dpi=600)
fig.show()
我认为需要注意的地方有:
- “#%%”是Spyder中的分节标记,类似于MATLAB的“%%”,不确定在其他Python的IDE中适不适用,但用了肯定没有语法问题。
- plt.plot(x, y)函数和MATLAB中plot(x, y)函数类似,传入参数也几乎相同。
- 本文中使用的是axes子图对象的.plot()方法,效果等价于plt.plot()函数,只不过是更方便的表达形式。
- 当传入单个向量如plt.plot(y)时,其实和MATLAB中plot(y)一样,横坐标默认是y向量的整数索引,但是本文中传入的是一个<pandas.Series>对象,其索引已经是对应的时间,所以超级方便!Python+1
- matplotlib本身并不直接支持中文输出,必须指定中文字体才能在图中正确显示中文,但其实一点都不麻烦,反正字号通常都是要调整的,一并在fontdict中列出来就好了。
- fig.savefig()最好先于fig.show(),因为保存机制貌似所见即所得,如果设置的图片尺寸大于电脑屏幕,会自动更改显示比例,然后再保存的话便不是自己fig.set_size_inches()中所设置的尺寸了。fig.show()并不是必须的,据说大量出图时只使用fig.set_size_inches()效率极高。
- Spyder中默认在控制台中出图,并且是立即出图,不管有没有fig.show(),这应该是属于IPython的特征,在控制台中输入“%matplotlib qt5”可以切换为像MATLAB弹窗出图,这个魔法指令根据自己电脑的环境自行变化。
MATLAB可能也有很方便的画图方法我没有发现,尤其是一些随近几年版本新出现的方法,这也是我改投Python怀抱的原因之一。
显然绘图不仅仅是画几条折线图而已,还包括双Y坐标轴图、直方图、极坐标图、三维图等等,Python都能画,慢慢学,以后再说。
附图如下: