数据可视化字体与折线图绘制
### matplotlib 中的字体管理
使用 matplotlib 可视化数据时,经常要面对字体设置的问题。在要用到中文时,如果设置不当,会出现乱码。网上已经有很多文章提供了一些解决方法,但多数是片段式的“授人以鱼”,本文将系统地讨论 matplotlib 中的字体管理,希望能“授人以渔”。
环境:Windows 10 + anaconda (python 3.8) + matplotlib 3.5
如果你的环境不同,相关程序安装位置不同,一些细节,如路径、数量等可能会有一些差异,但“对应”到你的环境即可。
matplotlib 中使用的字体来源于三个途径:
1. matplotlib 自带的字体;
2. windows 系统字体;
3. 用户提供的第三方字体。
**注意**
1. 实际中不建议调用用户字体目录中的第三方字体文件;
2. 有很多特殊的字体不兼容,会报错
#### windows 10 系统字体
Windows 系统字体文件保存在 `C:\Windows\Fonts` 文件夹中,在资源管理器中打开这个文件夹,一般是以大图标的形式显示,看起来是这样的:
字体系列,相当于一个子文件夹,可以进一步打开,如下图
在 windows 中,字体是以“字体文件”的形式保存的,但在应用软件中为了调用的直观性和方便性,则是通过字体的“名称”属性来调用的。
#### 字体文件的类型和字体类型
字体文件格式和字体类型繁多,常见的有:
字体文件:`.TTF, .FON, .TTC, .AFM`等后缀(格式)。
字体类型:`OpenType, TrueType, 光栅` 等类型。
**光栅字体(.FON)**:是针对特定的显示分辨率以不同大小存储的位图,用于Windows系统中屏幕上的菜单、按钮等处文字的显示。它并不是以矢量描述的,放大以后会出现锯齿,只适合屏幕描述。不过它的显示速度非常快,所以作为系统字体而在Windows中使用。
**矢量字体(.FON)**
虽然扩展名和光栅字体一样,但是这种字体却是由基于矢量的数学模型定义的,是Windows系统字体的一类,一些windows应用程序会在较大尺寸的屏幕显示中自动使用矢量字体来代替光栅字体的显示。
.fon 字体文件其实是标准的windows可执行文件(.exe)格式,分NE(New Executable)和PE(Portable Executable)两种类型,字体作为资源存在其中。
**TrueType字体(.TTF)**
TrueType是由美国苹果公司和微软公司共同开发的一种电脑轮廓字体(曲线描边字)类型标准。这是我们日常操作中接触得最多的一种类型的字体,其最大的特点就是它是由一种数学模式来进行定义的基于轮廓技术的字体,这使得它们比基于矢量的字体更容易处理,保证了屏幕与打印输出的一致性。同时,这类字体和矢量字体一样可以随意缩放、旋转而不必担心会出现锯齿。
OpenType标准还定义了 OpenType 文件名称的后缀名。包含 TrueType 字体的OpenType文件后缀名为.ttf,包含PostScript字体的文件后缀名为.OTF。如果是包含一系列TrueType字体的字体包文件,那么后缀名为.TTC。
**.AFM** Adobe Font Metrics
这由Adobe公司开发,并包含了有关Type 1 PostScript 字体的度量特性的信息。AFM结构需要一个定义了每一个字体符号的样式的控制模版。它主要被用于UNIX。
#### matplotlib 字体管理
matplotlib 的matplotlib.font_manager 模块,用于跨平台查找、管理和使用字体。
这个模块提供了一个可以跨后端(backend)和平台共享的 FontManager 实例。其中的 findfont 函数在返回本地或系统字体路径中与指定的 FontPropeties 实例匹配的最佳 TrueType (TTF) 字体文件。FontManager还处理 PostScript 后端使用的 Adobe Font Metrics (AFM) 字体文件。
也就是说 matplotlib 主要处理和使用 TTF, AFM 两种字体。
字体文件可以保存在 matplotlib 的 fonts 文件夹下,也可以保存在系统字体路径中。
> matplotlib 的字体文件夹路径如下:
> C:\Users\用户名\anaconda3\Lib\site-packages\matplotlib\mpl-data\fonts
该文件夹下有三个子文件夹,保存的都是 TTF, AFM 格式的字体文件。其中的 ttf 文件夹中约有 40 个 TTF 字体文件。
打开 matplotlib 的 .fonts/ttf 文件夹,显示如下
#### matplotlib的 font_manager 模块
font_manager 模块主要设计了3个类:
- .FontEntry:FontEntry 主要供 matplotlib 平台管理、存储可用字体属性使用。我们可以用它来统一 matplotlib 绘图的字体,减少多图中字体设置的重复劳动。
- .FontManager:FontManager 类提供了字体管理功能。
- .FontProperties, 这个是一般用户最常用的类,用法与`FontEntry`相似。定义一个 `FontProperties`实例,可以设置 text 对象的全部属性,并可以重复使用。无需在创建 text 时一个一个的设置。
```python
%matplotlib inline
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.font_manager as fm
x = np.arange(0, 10, 0.2)
y = np.sin(x)
# strech,拉伸,相当于word中的字体加宽
font_S = fm.FontProperties(family='Stencil',size=24, stretch=0)
font_M = fm.FontProperties(family='Mistral',size='xx-large',stretch=1000, weight='bold')
fig = plt.figure(constrained_layout=True)
ax = fig.add_subplot(111)
ax.set_title('Axes\'s Title', fontproperties = font_S)
ax.set_xlabel('xaxis label', fontproperties=font_M)
ax.plot(x,y)
plt.show()
```
#### matplotlib 常用中文字体名称对照表
Windows的字体对应名称,根据需要自行更换!
> 黑体 SimHei
> 微软雅黑 Microsoft YaHei
> 微软正黑体 Microsoft JhengHei
> 新宋体 NSimSun
> 新细明体 PMingLiU
> 细明体 MingLiU
> 标楷体 DFKai-SB
> 仿宋 FangSong
> 楷体 KaiTi
> 仿宋_GB2312 FangSong_GB2312
> 楷体_GB2312 KaiTi_GB2312
#### matplotlib 中文乱码问题的解决
在matplotlib中,在使用中文的时候经常会出现一些乱码的问题,如中文会变成小方格子。有以下几种解决方案:
##### 方法一:修改配置文件matplotlibrc(不推荐这样做)
```python
#使用下面的命令查看配置文件位置
import matplotlib
matplotlib.matplotlib_fname()
```
结果:
```
C:\\Users\\你的用户名\\anaconda3\\lib\\site-packages\\matplotlib\\mpl-data\\matplotlibrc
```
比如要添加黑体,步骤:
> 1. 一般在C:\Windows\Fonts 里可以找到`黑体常规`字体文件
>
> 2. 将字体文件拷贝至:C:\\Users\\你的用户名\anaconda3\\lib\\site-packages\\matplotlib\\mpl-data\fonts\ttf中
>
> 3. 用写字板或记事本打开matplotlibrc文件:
>
> - 删除 font.family 前面的 # 并将冒号后面改为“SimHei”;
>
> - 删除 font.sans-serif 前面的 # 并在冒号后面添加“SimHei”
>
>
>
> 4. 修改后保存,重新运行程序。
##### 方法二:动态设置参数(可用)
在python脚本中动态设置matplotlibrc,这样就避免了更改配置文件的麻烦,方便灵活,例如:
```在python脚本中动态设置matplotlibrc,这样就避免了更改配置文件的麻烦,方便灵活,例如:import matplotlib as mplmpl.rcParams[‘font.sans-serif] = [‘SimHei’]
import matplotlib as mpl
mpl.rcParams[‘font.sans-serif] = [‘SimHei’]
```
由于更改了字体导致显示不出负号,将配署文件中axes.unicode minus : True修改为False 就可以了,当然这而可以在代码中完成。
```python
import matplotlib as mpl
mpl.rcParams['font.sans-serif'] = ['SimHei'] # 指定默认字体
mpl.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号’-'显示为方块的问题
```
##### 方法三:使用字体管理器(推荐的方法)
优点:直接在代码中指定中文字体文件,在每个出现中文的地方指定 fontproperties为刚才设置的字体;
缺点:每个出现中文的地方如title都要指定字体,并不是每个地方如legend都提供指定字体的参数。
```python
myfont = matplotlib.font_manager.FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14)
```
直接设置为中文时
```python
ax.set_title(u'matplotlib中文显示测试', fontproperties = myfont)
```
还可以直接:
```python
ax.set_title(u'matplotlib中文显示测试', fontproperties='SimHei')
```
### 折线图
折线图用于分析自变量和因变量之间的趋势关系,最适合用于显示随着时间而变化的连续数据,同时还可以看出数量的差异,增长情况。
Matplotlib 中绘制散点图的函数为 plot() ,使用语法如下:
> `matplotlib.pyplot.plot`(**args*, *scalex=True*, *scaley=True*, *data=None*, ***kwargs*)
(1)简单的折线图
在matplotlib面向对象的绘图库中,pyplot是一个方便的接口。plot()函数:支持创建单条折线的折线图,也支持创建包含多条折线的复式折线图----只要在调用plot()时传入多个分别代表X轴和Y轴数据的list列表即可。
```python
import matplotlib.pyplot as plt
x_data = ['2011','2012','2013','2014','2015','2016','2017']
y_data = [58000,60200,63000,71000,84000,90500,107000]
plt.plot(x_data,y_data)
plt.show()
```
(2)复式折线图:
```python
import matplotlib.pyplot as plt
x_data = ['2011','2012','2013','2014','2015','2016','2017']
y_data = [58000,60200,63000,71000,84000,90500,107000]
y_data2 = [52000,54200,51500,58300,56800,59500,62700]
plt.plot(x_data,y_data,color='red',linewidth=2.0,linestyle='--')
plt.plot(x_data,y_data2,color='blue',linewidth=3.0,linestyle='-.')
plt.show()
```
注:
说明:
参数color=’ red ‘,可以换成color=’ #054E9F’,每两个十六进制数分别代表R、G、B分量,除了使用red、blue、green等还可以参照下图
参数linestyle可以选择使用下面的样式:
> '-' solid line style 表示实线
>
> '--' dashed line style 表示虚线
>
> '-.' dash-dot line style 表示短线、点相间的虚线
>
> ':' dotted line style 表示点线
参数 linewidth 可以指定折线的宽度
参数 marker 是指标记点,有如下的:
(3)管理图例
对于复式折线图,应该为每条折线添加图例,可以通过legend()函数来实现。该函数可传入两个list参数,其中第一个list参数(handles参数)用于引用折线图上的每条折线;第二个list参数(labels)代表为每条折线所添加的图例
```python
import pandas as pd
import matplotlib.pyplot as plt
#读取数据
data = pd.read_excel('matplotlib.xlsx')
plt.figure(figsize=(10,5))#设置画布的尺寸
plt.title('Examples of line chart',fontsize=20)#标题,并设定字号大小
plt.xlabel(u'x-year',fontsize=14)#设置x轴,并设定字号大小
plt.ylabel(u'y-income',fontsize=14)#设置y轴,并设定字号大小
#color:颜色,linewidth:线宽,linestyle:线条类型,label:图例,marker:数据点的类型
in1, = plt.plot(data['时间'],data['收入_Jay'],color="deeppink",linewidth=2,linestyle=':', marker='o')
in2, = plt.plot(data['时间'],data['收入_JJ'],color="darkblue",linewidth=1,linestyle='--', marker='+')
in3, = plt.plot(data['时间'],data['收入_Jolin'],color="goldenrod",linewidth=1.5,linestyle='-', marker='*')
plt.legend(handles = [in1,in2,in3],labels=['Jay income','JJ income','Jolon income'],loc=2)#图例展示位置,数字代表第几象限
plt.show()#显示图像
```
(4)管理多个子图
```python
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.gridspec as gridspec
import matplotlib.font_manager as fm #字体管理器
my_font = fm.FontProperties(fname="/System/Library/Fonts/PingFang.ttc")
plt.figure()
x_data = np.linspace(-np.pi,np.pi,64,endpoint=True)
gs = gridspec.GridSpec(2,3) #将绘图区分成两行三列
ax1 = plt.subplot(gs[0,:])#指定ax1占用第一行(0)整行
ax2 = plt.subplot(gs[1,0])#指定ax2占用第二行(1)的第一格(第二个参数为0)
ax3 = plt.subplot(gs[1,1:3])#指定ax3占用第二行(1)的第二、三格(第二个参数为1:3)
#绘制正弦曲线
ax1.plot(x_data,np.sin(x_data))
ax1.spines['right'].set_color('none')
ax1.spines['top'].set_color('none')
ax1.spines['bottom'].set_position(('data',0))
ax1.spines['left'].set_position(('data',0))
ax1.set_title('正弦曲线',fontproperties=my_font)
#绘制余弦曲线
ax2.plot(x_data,np.cos(x_data))
ax2.spines['right'].set_color('none')
ax2.spines['top'].set_color('none')
ax2.spines['bottom'].set_position(('data',0))
ax2.spines['left'].set_position(('data',0))
ax2.set_title('余弦曲线',fontproperties=my_font)
#绘制正切曲线
ax3.plot(x_data,np.tan(x_data))
ax3.spines['right'].set_color('none')
ax3.spines['top'].set_color('none')
ax3.spines['bottom'].set_position(('data',0))
ax3.spines['left'].set_position(('data',0))
ax3.set_title('正切曲线',fontproperties=my_font)
plt.show()
```
结果: