做毕业论文的时候需要绘制相关的图像,发现遇到一个问题:就是我所得到的一维数组的数值太小了,直接用pyplot.plot()输出的话,似乎默认的纵坐标的刻度没那么小,所以得不到图像。(后来发现原来是我自己传递给plot是空数组不赖它的,其实它还是会很人性化地帮我把图画出来,就是刻度没那么明显,读书不是很方便而已)索性就自己把相关的东西都看一下然后整理,否则每次都是百度太纵容自己的惰性了。
一般的用法是
matplotlib.pyplot.plot(*args,**kwargs)
*args表示无名参数,获取之后形成一个元组tuble;**kwargs表示关键字参数,获取之后形成一个字典dic。 签名式为
plot([x], y, [fmt], data=None, **kwargs)
plot用于绘制折线图,通过我们给出的参数来绘制相应的图像。
plot(x, y) #用默认的直线类型和颜色绘制x和y的折线图
plot(x, y, 'bo') #用蓝色圆点来标记x和y坐标指示的点
plot(y) # 用自然数集[0,1,2....n -1]来默认作为x分量
plot(y, 'r+') # 同上,但是坐标点是用红色的×表示的
plot(x, y, 'go--', linewidth=2, markersize=12)
plot(x, y, color='green', marker='o', linestyle='dashed', linewidth=2, markersize=12
这里出现的 'bo','r+' 就是optional parameter [fmt] 可选择参数的内容。通过这种形式可以很方便的确定修改我们所绘制的折线图的有关属性。而 **kwargs 也是可以获取相关的参数,以关键字参数的形式,通过形成字典的途径,来确定有关属性。当两种方式所选定的属性发生冲突时,以关键字参数的内容为准。
Plotting labelled data
plot('xlabel', 'ylabel', data = obj)
这个用法不是很能理解。文档中写着,"plotting objects with labelled data (i.e. data that can be accessed by index obj['y']
)",绘制一些对象的折线图,这些对象是标签化的,所谓的标签化就是可以用 obj [ ' y ' ] 这个形式表示的,同时也是指标化就是可以用index表示其中某个元素的,最典型就是数组了。而这个 obj 可以是字典,可以是 pandas 库中的DataFeame 或者是 numpy 库中的 array。对于DataFrame可以理解,可以理解成一个二维表格,纵坐标是从0开始的自然数,作为标签index;然后横坐标有很多种,例如数学成绩 math + 物理成绩physics +语文成绩Chinese,我们通过选择标签index作为 xlabel,然后数学成绩math 作为 ylabel ,就可以看出学生在单科上考试成绩的变化。对于numpy中的array我猜测应该也是相同的,如果是二维数组,就是行数作为其中的一个个元素,通过寻找两个数字来代表我们所要找的哪两行来绘图,不过写了代码却画了一个空白图,就不知道咋回事了。
Plotting multiple sets of data
plot(x1, y1, 'go')
plot(x2, y2, 'bo')
最简单就是多次调用plot。另外如果你的数据是一个二维的数组,你可以直接把它传递到 x 和 y 上面,例如下面这个例子,我们把二维数组 a 的第一行作为 x 的值然后剩下的都作为 y 的值然后在同一个图中画若干个折线图。
plot(a[0], a[1:])
第三种方法如下。这种情况下的额外的参数就是作用在所有的数据上。
plot(x1, y1, 'g^', x2, y2, 'g-')
alpha | 浮点型,0.0 - 1.0,调节线条的透明度,0就是完全透明,1就是完全不透明。 |
antialiased/aa | 布尔型,默认为True。抗锯齿化,否则图像呈像素点明显的锯齿感。 |
color/c | 颜色 |
linestyle | ' solid ' 以及 ' - ' 表示实线;' dashed ' 以及' -- ' 表示的是虚线;' dashdot ' 以及 ' -. ' 表示的是虚线和点相间;' dotted ' 以及 '.' 表示散点线。 |
linewidth | 合适的浮点值。 |
marker | 标记点的类型。 |
fmt = ['colot']['marker'][line']
line的类型有四种:' - ',' -- ',' -. ',' : ' 。color话就是颜色英文单词的首字母,' b ' 就是blue蓝色, ' r ' 就是red红色。而marker种类比较多,复制文档内容如下:
character | description |
---|---|
'.' |
point marker |
',' |
pixel marker |
'o' |
circle marker |
'v' |
triangle_down marker |
'^' |
triangle_up marker |
'<' |
triangle_left marker |
'>' |
triangle_right marker |
'1' |
tri_down marker |
'2' |
tri_up marker |
'3' |
tri_left marker |
'4' |
tri_right marker |
's' |
square marker |
'p' |
pentagon marker |
'*' |
star marker |
'h' |
hexagon1 marker |
'H' |
hexagon2 marker |
'+' |
plus marker |
'x' |
x marker |
'D' |
diamond marker |
'd' |
thin_diamond marker |
'|' |
vline marker |
'_' |
hline marker |
ax.xaxis.set_major_locator(xmajor_locator)
ax.xaxis.set_minor_locator(xminor_locator)
ax.yaxis.set_major_locator(ymajor_locator)
ax.yaxis.set_minor_locator(yminor_locator)
我们所用的类是MultipleLocator,文档中的备注是"
Ticks and range are a multiple of base; either integer or float. ",意思就是刻度和范围都是默认base = 1 这个基数的倍数,可以是整数也可以是浮点数。以下面的代码为例,我们简单地打印一个图像,图像的内容是一段直线。更改之后我们看到的坐标轴,从原本的默认的2000,2002,2004,2006,2008,2010会变成,例如我们设定了 5,就变成了2000,2005,2010。另外如果我们不输入设定的间隔长度,默认值即是基数,为1,图像上显示的是2000,2001,2002这样子以1为间隔。
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.ticker import MultipleLocator
figure = plt.figure()
ax = figure.add_subplot(1,1,1)
xmajorLocator = MultipleLocator()
ax.plot(np.arange(2000,2011),range(11))
ax.xaxis.set_major_locator(xmajorLocator)
plt.show()
类 MultipleLocator 在 matplotlib.ticker 中的代码如下,它是继承自Locator类,具备了父类的一些特性不过不太关键,主要还是掌握具体的 MultipleLocator 的一些使用方法。
class MultipleLocator(Locator):
"""
Set a tick on every integer that is multiple of base in the
view interval
"""
def __init__(self, base=1.0):
self._base = Base(base)
def set_params(self, base):
"""Set parameters within this locator."""
if base is not None:
self._base = base
def __call__(self):
'Return the locations of the ticks'
vmin, vmax = self.axis.get_view_interval()
return self.tick_values(vmin, vmax)
def tick_values(self, vmin, vmax):
if vmax < vmin:
vmin, vmax = vmax, vmin
vmin = self._base.ge(vmin)
base = self._base.get_base()
n = (vmax - vmin + 0.001 * base) // base
locs = vmin - base + np.arange(n + 3) * base
return self.raise_if_exceeds(locs)
def view_limits(self, dmin, dmax):
"""
Set the view limits to the nearest multiples of base that
contain the data
"""
if rcParams['axes.autolimit_mode'] == 'round_numbers':
vmin = self._base.le(dmin)
vmax = self._base.ge(dmax)
if vmin == vmax:
vmin -= 1
vmax += 1
else:
vmin = dmin
vmax = dmax
return mtransforms.nonsingular(vmin, vmax)
在简单的文档中写了MultipleLocator实例有一个方法是set_params(),可以在创建实例之后修改 base 基数的数值,但我自己直接在创建后加上如下代码却报错:
xmajorLocator = MultipleLocator()
xmajorLocator.set_params(2)
查看上面的代码后发现,在定义之中并不是直接吸收我们输入的参数,而是利用它创建一个Base类的实例,Base类的目的就是针对MultipleLocator中引入基数时可能发生的问题做一些限制。作出如下一些修改,在import里额外引入Base类,然后创建一个Base类的实例,传递给set_params,之后就没问题了。
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.ticker import MultipleLocator,Base
figure = plt.figure()
ax = figure.add_subplot(1,1,1)
xmajorLocator = MultipleLocator()
base = Base(2)
xmajorLocator.set_params(base)
ax.plot(np.arange(2000,2011),range(11))
ax.xaxis.set_major_locator(xmajorLocator)
plt.show()
接下来是一个特殊方法__call__,添加之后我们创建的实例就可以像函数一样应用。以前所接触的方法,都是在实例后面加一个点然后加上方法的名称,而这个__call__是一种新的方法哈哈对我而言。直接像函数一样应用这个实例是不需要给入参数,它会自动调用方法self.axis.get_view_interval(),获取当前坐标轴的最左和最右的坐标,注意!这里的坐标不一定是显示出来的,而是确确实实那条轴的两端的位置所代表的坐标;获取之后再传入方法tick_values(vmin,vmax)中。方法tick_values就是根据传入的两个参数vmin和vmax,返回在这个范围内的坐标轴上面显示的值。具体来看tick_values方法中用到了Base类的几个类方法,所以也顺便把Base类的内容学习一下。
def closeto(x, y):
if abs(x - y) < 1e-10:
return True
else:
return False
class Base(object):
'this solution has some hacks to deal with floating point inaccuracies'
def __init__(self, base):
if base <= 0:
raise ValueError("'base' must be positive")
self._base = base
def lt(self, x):
'return the largest multiple of base < x'
d, m = _divmod(x, self._base)
if closeto(m, 0) and not closeto(m / self._base, 1):
return (d - 1) * self._base
return d * self._base
def le(self, x):
'return the largest multiple of base <= x'
d, m = _divmod(x, self._base)
if closeto(m / self._base, 1): # was closeto(m, self._base)
#looks like floating point error
return (d + 1) * self._base
return d * self._base
def gt(self, x):
'return the smallest multiple of base > x'
d, m = _divmod(x, self._base)
if closeto(m / self._base, 1):
#looks like floating point error
return (d + 2) * self._base
return (d + 1) * self._base
def ge(self, x):
'return the smallest multiple of base >= x'
d, m = _divmod(x, self._base)
if closeto(m, 0) and not closeto(m / self._base, 1):
return d * self._base
return (d + 1) * self._base
def get_base(self):
return self._base
在 isinstance 这个内置函数时,其实并没啥问题,很明显它的意义就是判断一个对象是否是已知的类型,这个关系可以是属于某个类,是某个类的子类等,通过返回值 True or False 给出结果。但是,item() 这个方法是不适用于一般的数据的,例如一个整型数或者浮点数是没有这个方法的,通常字典就可以用 item() 来成对地返回键和值。那么我们只能这么理解,isinstance这个比较,是会暂时改变我们输入的第一个对象的!我们在此处是跟 numpy.generic 类作比较,如果没猜错,我们传入的对象是整型就变成 numpy.generic.int 类的一个实例,然后这个实例有方法 item() 可以返回它的值。仔细一想这个 isinstance 函数还有不少学问,有空可以研究一下啊,挖坑。
百度了一下,six 这个模块是协调 python2 和 python3 之间一些不兼容的问题,具体就不研究了。Base里面若干的方法,总结起来就是 lt,le,gt,ge 和返回基数 base 的值,都是为了后面计算坐标轴显示情况做的铺垫。
了解完这些,我们就可以进一步看tick_values的原理啦~
def tick_values(self, vmin, vmax):
if vmax < vmin:
vmin, vmax = vmax, vmin
vmin = self._base.ge(vmin)
base = self._base.get_base()
n = (vmax - vmin + 0.001 * base) // base
locs = vmin - base + np.arange(n + 3) * base
return self.raise_if_exceeds(locs)
例如我们修改默认的基数,改为 base = 2 ,我们希望间隔不要那么密集。然后我们想要输入的参数是 vmin = 3,vmax = 15之间的坐标轴,于是计算就开始了。如果我们输入的时候没有先小后大,会交换一下,保证 vmax >= vmin。调用类方法ge,得到一个大于或等于 vmin 的 base 的最小整数倍,将其赋值给 vmin 覆盖原本的值,此时 vmin = 4;而 base 就简单地获取。接下来计算 n 的值,n = 6,这里之所以加上 0.001 * base 是避免 vmax 和 vmin 的差与 base 的比值很接近但小于整数时错失了很大一段坐标轴上的刻度,所以还是补上更好,表示起来更加人性化。算出来的locs = [2, 4, 6, 8, 10, 12, 14, 16],传入继承自父类 Locator 的一个方法 raise_if_exceeds(),判断这个 locs 数组的元素数量会不会超过 Locator 类的一个属性MAXTICKS = 1000,也就是说不能太多,坐标轴会表示不过来,不超过的话就返回 locs, 超过的话就会报错。根据 locs 的内容我们可以看出tick_values方法的意义是,返回一个数组,数组的元素表示将会在坐标轴上显示的刻度值,而这些刻度值包含的整段区间必须包含我们传入的两个参数,在确保了这个条件之后使得显示的坐标轴最短。
还剩最后一个部分,就是另一个方法view_limits。
def view_limits(self, dmin, dmax):
"""
Set the view limits to the nearest multiples of base that
contain the data
"""
if rcParams['axes.autolimit_mode'] == 'round_numbers':
vmin = self._base.le(dmin)
vmax = self._base.ge(dmax)
if vmin == vmax:
vmin -= 1
vmax += 1
else:
vmin = dmin
vmax = dmax
return mtransforms.nonsingular(vmin, vmax)
没看两行立马发现一个陌生的东西,查了一下,在开头声明各种库的调用时,通过 from matplotlib import rcParams 引入的。rcParams 是在 matplotlib 库的 __init__.py 文件中创建的一个实例,调用了几个函数和类嵌套着,哎懒得看......大概知道一般情况下是不会有问题的......哎要不还是研究一下,里面似乎还是有点东西的,耐心也是学习之路上需要培养的品质啊!
在 __init__.py 这个管理初始设定的文件中,我们在1060行处找到:
# this is the instance used by the matplotlib classes
rcParams = rc_params()
表明这个 rcParams 是 rc_Params 函数创建的一个实例,注释中也是这个意思。再往前看:
def rc_params(fail_on_error=False):
"""Return a :class:`matplotlib.RcParams` instance from the
default matplotlib rc file.
"""
fname = matplotlib_fname()
if not os.path.exists(fname):
# this should never happen, default in mpl-data should always be found
message = 'could not find rc file; returning defaults'
ret = RcParams([(key, default) for key, (default, _) in
six.iteritems(defaultParams)
if key not in _all_deprecated])
warnings.warn(message)
return ret
return rc_params_from_file(fname, fail_on_error)
rc_Params 这个函数有一个参数 fail_on_error ,默认值为 False。注释中讲的是“通过 matplotlib 中 rc file 这个文件的内容返回一个 ' matplotlib.RcParams ' 类的实例。
在694行处先找到了 matplotlib_fname() 这个函数的相关定义:
def matplotlib_fname():
"""
Get the location of the config file.
The file location is determined in the following order
- `$PWD/matplotlibrc`
- `$MATPLOTLIBRC` if it is a file
- `$MATPLOTLIBRC/matplotlibrc`
- `$MPLCONFIGDIR/matplotlibrc`
- On Linux,
- `$XDG_CONFIG_HOME/matplotlib/matplotlibrc` (if $XDG_CONFIG_HOME is defined)
- or `$HOME/.config/matplotlib/matplotlibrc` (if $XDG_CONFIG_HOME is not defined)
- On other platforms,
- `$HOME/.matplotlib/matplotlibrc` (if `$HOME` is defined.)
- Lastly, it looks in `$MATPLOTLIBDATA/matplotlibrc` for a
system-defined copy.
"""
def gen_candidates():
yield os.path.join(six.moves.getcwd(), 'matplotlibrc')
try:
matplotlibrc = os.environ['MATPLOTLIBRC']
except KeyError:
pass
else:
yield matplotlibrc
yield os.path.join(matplotlibrc, 'matplotlibrc')
yield os.path.join(_get_configdir(), 'matplotlibrc')
yield os.path.join(get_data_path(), 'matplotlibrc')
for fname in gen_candidates():
if os.path.isfile(fname):
break
# Return first candidate that is a file, or last candidate if none is
# valid (in that case, a warning is raised at startup by `rc_params`).
return fname
里面主要还是用到了 os 库的内容。os 库是一个帮助我们处理文件和目录的库,在上面这个函数中就用到 os.path.join 这个函数,six.move.getcwd() 获取当前打开的文件所在的文件夹,以路径的内容和字符串的形式返回,然后与后面的另一段字符串 matplotlibrc 相接在一起形成一个新的路径。
try...except...else 是一个错误处理的代码块,就是看一下在 os.environ 这个存储环境变量(我猜的)的字典里寻找键为 " MATPLOTLIBRC " 对应的值,并付给 matplotlibrc,如果找不到这个键就会相应 KeyError ,如果没有这块代码程序就会中断。但是加上了 try...except...else 之后就可以应对这种情况了。似乎有点跑太远了,这个没意义,我现在研究了没意义,还是先回归到应用中,学习 view_limits 这个方法的是使用方法。view_limits 用的是 Base 类里面的 le 和 ge,和 lt 与 gt 的区别在于可以相等,但是我输入了多少就返回多少,似乎是因为 rcParams['axes.autolimit_mode'] == 'round_numbers' 不成立。这句判定的意思是说,坐标轴自动设置 limit 应该是左右的端点的模式如果是 ' round_numbers ',也就是 base 整数倍,自然才会用到 le 和 ge 方法。
总而言之,view_limits 方法就是我们要判断想要给出两个值作为坐标轴的两端端点值,根据默认或者是人为的提前设置好的参数们,我们返回合理的极限值。如果要求端点也要是 base 的整数倍,就给出整数倍,如果不用,那么输入什么就输出什么。而 tick_values 方法,就是我们给出的一个区间的两个端点值,在坐标轴上所显示的数中,可以形成的包含这个区间的最小的区间的端点值,就返回了这个啦。