如何一劳永逸地解决Python的matplotlib模块绘图时无法显示中文的问题

如何一劳永逸地解决Python的matplotlib模块绘图时无法显示中文的问题

在这篇文章之前的一篇文章《如何解决Python的matplotlib模块绘图时无法显示中文的问题》中我介绍了通过自定义字体解决中文问题的方法。但是今天我遇到了一个通过这种办法没办法解决的问题。此外这篇文章还介绍了一个非常好用的Python包——monthly_returns_heatmap

今天下午我发现了一个非常有趣的包——monthly_returns_heatmap,只一个比较简单的包,主要功能是绘制收益率的热力图。首先先了解一下这个包吧。

monthly_returns_heatmap包

下面的代码是https://github.com/ranaroussi/monthly-returns-heatmap给出的,因为我刚刚已经充分的改造了这个包,所以我现在画不出来和他一模一样的图了(由于get_data_google()函数有问题,所以下面的代码是不能运行的,稍后会讲如何修正):

1
2
3
4
5
from pandas_datareader import data
prices = data.get_data_google("SPY")['Close']
returns = prices.pct_change()
import monthly_returns_heatmap as mrh
mrh.plot(returns)


这个plot()函数的详细参数有:

1
2
3
4
5
6
7
8
9
10
11
12
def plot(returns,
title="Monthly Returns (%)\n",
title_color="black",
title_size=14,
annot_size=10,
figsize=None,
cmap='RdYlGn',
cbar=True,
square=False,
is_prices=False,
compounded=True,
eoy=False):

此外还可以把这个图片上的数据提取出来:

1
2
3
4
5
6
7
8
9
10
11
12
13
heatmap = mrh.get(returns)
print(heatmap)
# prints:
Month Jan Feb Mar Apr ... Dec
Year
2010 0.000000 0.031195 0.056529 0.015470 ... 0.061271
2011 0.023300 0.034737 -0.004807 0.030413 ... 0.003117
2012 0.045498 0.043137 0.028129 -0.006751 ... 0.001759
2013 0.051190 0.012759 0.033375 0.019212 ... 0.020387
2014 -0.035248 0.045516 0.003865 0.006951 ... -0.008012
2015 -0.029629 0.056205 -0.020080 0.009834 ... -0.023096
2016 -0.049787 -0.001910 0.062943 0.003941 ... 0.014293
2017 0.017895 0.039292 -0.003087 0.009926 ... 0.000000

问题

我想给图片定义一个中文标题,结果是显而易见的,中文会被以框框替代,而且这个时候由于绘图代码被封装起来了,所以没有办法用上面那一篇文章中的方法解决问题。于是我就想到了“为什么不直接修改源代码呢?”最开始我的想法是直接修改monthly_returns_heatmap包的源代码,后来一转念想,既然这个包是依赖matplotlib包绘图的,那么为什么不直接修改matplot包呢?于是说干就干。

找到matplotlib包的位置

网上说有一种方法就是在终端运行pip show 包名就可以显示包的位置,但是我运行之后没有任何反应,所以我就用下面的笨方法找了。
首先用sys.path显示所有包的可能位置:

1
2
import sys
sys.path

结果:

1
2
3
4
5
6
7
8
9
10
['/Applications/PyCharm.app/Contents/helpers/pydev',
'/Applications/PyCharm.app/Contents/helpers/pydev',
'/Library/Frameworks/Python.framework/Versions/3.6/lib/python36.zip',
'/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6',
'/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/lib-dynload',
'/Users/mr.cheng/Library/Python/3.6/lib/python/site-packages',
'/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages',
'/Applications/PyCharm.app/Contents/helpers/pycharm_matplotlib_backend',
'/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/IPython/extensions',
'/Users/mr.cheng/Desktop/pandas']

经过查找,matplotlib包是在这个路径上:
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/
同时还可以发现,monthly_returns_heatmap包是在这个路径上:
/Users/mr.cheng/Library/Python/3.6/lib/python/site-packages

首先打开路径
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/,找到文件pyplot.pyfont_manager.py打开它们。

改源代码

既然解决中文问题的绘图代码是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
## 单利与复利
from matplotlib.font_manager import *
from pylab import *
# 定义自定义字体,文件名从查看系统中文字体中来
myfont = FontProperties(fname='/Library/Fonts/Songti.ttc')
# 解决负号'-'显示为方块的问题
matplotlib.rcParams['axes.unicode_minus'] = False
rcParams['savefig.dpi'] = 600 # 图片像素
rcParams['figure.dpi'] = 600 # 分辨率
pv, n, r = (1000, 10, 0.08)
t = np.linspace(0, n, n)
y1 = np.ones(len(t))*pv # 一条水平参考线
y2 = pv*(1+r*t)
y3 = pv*(1+r)**t
title(u"单利vs复利", fontproperties = myfont)
xlabel(u"期数", fontproperties = myfont)
ylabel(u"值", fontproperties = myfont)
xlim(0, 11)
ylim(800, 2200)
plot(t, y1, 'b-')
plot(t, y2, 'g--')
plot(t, y3, 'r-')
savefig('20180623a4.png')
show()

那么就直接根据上面的代码修改源代码即可。首先是pyplot.py,我在所有的gcf().开头的、可能用到中文的,例如gca().set_title(s, *args, **kwargs)都添加了一个选项,例如

1
gca().set_title(s, *args, **kwargs, fontproperties = FontProperties(fname='/Library/Fonts/Songti.ttc'))

此外我还在开头加了这么几句:

1
2
3
4
from matplotlib.font_manager import FontProperties, rcParams
rcParams['axes.unicode_minus'] = False
rcParams['savefig.dpi'] = 600 # 图片像素
rcParams['figure.dpi'] = 600 # 分辨率

这样就能提高图片分辨率和保存的像素了。
我修改好的文件如下:
pyplot.py
理论上这样应该就没问题了,但是后来我绘图的时候又出现了我无法理解的问题,所以保险起见我又在font_manager.py文件中做了一些修改就是,也就是把668行改成了fname = '/Library/Fonts/Songti.ttc'。下面再运行一下下面的代码检验一下修改效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pylab import *
pv, n, r = (1000, 10, 0.08)
t = np.linspace(0, n, n)
y1 = np.ones(len(t))*pv # 一条水平参考线
y2 = pv*(1+r*t)
y3 = pv*(1+r)**t
title("单利vs复利")
xlabel("期数")
ylabel("值")
xlim(0, 11)
ylim(800, 2200)
plot(t, y1, 'b-')
plot(t, y2, 'g--')
plot(t, y3, 'r-')
show()


完美!暂时没出现问题,日后遇到问题再解决。

改进monthly_returns_heatmap包

进入monthly_returns_heatmap包的路径打开__init__.py,在里面找到用于绘图的plot()函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def plot(returns,
title="Monthly Returns (%)\n",
title_color="black",
title_size=14,
annot_size=10,
figsize=None,
cmap='RdYlGn',
cbar=True,
square=False,
is_prices=False,
compounded=True,
eoy=False):

returns = get(returns, eoy=eoy, is_prices=is_prices, compounded=compounded)
returns *= 100

if figsize is None:
size = list(plt.gcf().get_size_inches())
figsize = (size[0], size[0] // 2)
plt.close()

fig, ax = plt.subplots(figsize=figsize)
ax = sns.heatmap(returns, ax=ax, annot=True, center=0,
annot_kws={"size": annot_size},
fmt="0.2f", linewidths=0.5,
square=square, cbar=cbar, cmap=cmap)
ax.set_title(title, fontsize=title_size,
color=title_color, fontweight="bold")

fig.subplots_adjust(hspace=0)
plt.yticks(rotation=0)
plt.show()
plt.close()

我把这个函数的功能拓展了一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
def plot(returns,
title="月度收益率(%)\n",
title_color="black",
title_size=14,
annot_size=10,
figsize=None,
cmap='RdYlGn',
cbar=True,
square=False,
is_prices=False,
compounded=True,
eoy=False,
xlabel=u'月份',
ylabel=u'年份',
figname=None):
returns = get(returns, eoy=eoy, is_prices=is_prices, compounded=compounded)
returns *= 100

if figsize is None:
size = list(plt.gcf().get_size_inches())
figsize = (size[0], size[0] // 2)
plt.close()

fig, ax = plt.subplots(figsize=figsize)
ax = sns.heatmap(returns, ax=ax, annot=True, center=0,
annot_kws={"size": annot_size},
fmt="0.2f", linewidths=0.5,
square=square, cbar=cbar, cmap=cmap)
ax.set_title(title, fontsize=title_size,
color=title_color, fontweight="bold")

fig.subplots_adjust(hspace=0)
plt.yticks(rotation=0)
plt.xlabel(xlabel)
plt.ylabel(ylabel)
plt.tight_layout()
if figname != None:
plt.savefig(figname)
plt.show()
plt.close()

上面有四点改进:

  1. title的默认值被我改成了中文;
  2. 添加了坐标轴标签的选项;
  3. 添加了保存文件的选项;
  4. 添加了一句这个代码:plt.tight_layout(),这个是因为我发现直接保存的图片会不完整。参考了这篇博客:python matplotlib 画图保存前后显示不完整的处理

测试改进后的monthly_returns_heatmap包

这里还有一个问题需要解决,就是get_data_google()函数有问题无法获取数据的问题。下午偶尔还发现了另外一个解决这个问题的包fix_yahoo_finance,配合上这个包就能用了,例如绘制上证指数收盘价的月收益率的热力图:

1
2
3
4
5
6
7
from pandas_datareader import data as pdr
import fix_yahoo_finance as yf
yf.pdr_override()
prices = pdr.get_data_yahoo("000001.ss", start="2010-01-01", end="2018-07-29")['Close']
returns = prices.pct_change()
import monthly_returns_heatmap as mrh
mrh.plot(returns, title = u'上证指数收益率', title_color = 'black', figname='上证指数收益率.png')


上面的代码获取数据的时候可能会出错,多试两次就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Month       Jan       Feb       Mar       Apr       May       Jun       Jul  \
Year
2010 -0.078448 0.020959 0.018730 -0.076708 -0.097006 -0.074755 0.099706
2011 -0.006190 0.040979 0.007937 -0.005670 -0.057715 0.006781 -0.021848
2012 0.042372 0.059267 -0.068231 0.059010 -0.010050 -0.061884 -0.054729
2013 0.051251 -0.008313 -0.054520 -0.026249 0.056330 -0.139698 0.007373
2014 -0.039176 0.011421 -0.011183 -0.003417 0.006343 0.004470 0.074810
2015 -0.007517 0.031130 0.132192 0.185105 0.038294 -0.072537 -0.143433
2016 -0.226488 -0.018126 0.117537 -0.021835 -0.007388 0.004454 0.016976
2017 0.017892 0.026136 -0.005929 -0.021057 -0.011881 0.024140 0.025248
2018 0.052510 -0.063613 -0.027769 -0.027349 0.004296 -0.080135 0.009193
Month Aug Sep Oct Nov Dec
Year
2010 0.000491 0.006389 0.121694 -0.053260 -0.004292
2011 -0.049742 -0.081065 0.046215 -0.054628 -0.057425
2012 -0.026674 0.018875 -0.008287 -0.042904 0.145957
2013 0.052454 0.036353 -0.015198 0.036837 -0.047073
2014 0.007103 0.066151 0.023820 0.108563 0.205656
2015 -0.124938 -0.047787 0.108026 0.018579 0.027218
2016 0.035629 -0.026183 0.031880 0.048232 -0.045045
2017 0.026820 -0.003531 0.013258 -0.022442 -0.003019
2018 0.000000 0.000000 0.000000 0.000000 0.000000
# Python

评论

程振兴

程振兴 @czxa.top
截止今天,我已经在本博客上写了659.4k个字了!

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×