两个函数做类似的事情,调用时的参数也基本一致,并且文档中也声称loadtxt是genfromtxt的简化版本,但实际测试发现两者还是有一些不一致,比较讨厌。

平台:

  • Python 2.7.13 64bit
    • numpy 1.12.1
    • matplotlib 2.0.0
  • Windows7 64bit

初级的数据解析加载方式

用matplotlib实现自动化的图标生成,常常会遇到横轴为日期/时间的情况,最初的做法是自己写一个函数,通常叫做loadXXXData之类,逐行读取并解析,把时间字符串转换为自当日零时起开始的毫秒数,大致这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def loadXXXData(filepath):
f = open(filepath, 'r')
f.readline()
t, v = [], []
for line in f.readlines():
fds = line.split()
h, m, s = [float(x) for x in fds[0].split(':')]
tick = (h * 3600 + m * 60 + s) * 1000
t.appen(tick)
v.append(float(fds[1]))
return t, v

对于不同格式(大同小异)的数据文件,不光写了大量类似这样重复的代码,另一个问题是,为了使得到的图标好看点,还得在绘图时重新生成X轴的刻度标签(set_xticks(), set_xticklabels),非常之繁琐。后来尝试做了一个比较通用的小软件,用PyQt4实现前端,支持平时常用格式的时间字符串,可以选择载入哪些列的数据,等等,受到使用环境的一些限制(可能没有PyQt,没有网络不方便版本控制),后来也懒得维护更新了。

使用numpy::loadtxt

使用numpy::loadtxt,实际上实现了之前通用版本曲线生成功能,代码类似这样:

1
2
3
4
5
6
7
8
9
import numpy as np
def hms2tick(hms):
h, m, s = [float(x) for x in hms.split(':')]
return (h * 3600 + m * 60 + s) * 1000
def loadXXXData(filepath):
t, v = np.loadtxt(filepath, converters={0: hms2tick}, unpack=True})
return t, v

但还是不能解决自动在X轴显示时间的问题:loadtxt要求每一列数据都能够转换为float格式。

使用numpy::genfromtxt

使用numpy::genfromtxt,可以支持把时间字符串转换为datetime.datetime,并且直接在mpl中绘图,但发现与loadtxt的效果存在一些不一致,虽然numpy文档中声称loadtxt是genfromtxt的简化(特化)版本:

>> help(np.loadtxt)
Help on function loadtxt in module numpy.lib.npyio:

See Also

numpy.loadtxt : equivalent function when no data is missing.

以下是我用来测试两者行为不一致性的代码:

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
import numpy as np
import matplotlib.dates as mdates
import matplotlib.pyplot as plt
from datetime import datetime
def myTest(filepath):
# TypeError: float() argument must be a string or a number
# t, v = np.loadtxt(filepath, \
# converters={0: lambda x: datetime.strptime(x, '%H:%M:%S.%f').time()}, \
# unpack=True)
# TypeError: float() argument must be a string or a number
# datetime object has toordinal() method, while datetime.time object have not
# data = np.genfromtxt(filepath, \
# converters={0: lambda x: datetime.strptime(x, '%H:%M:%S.%f').time()})
# unpack behaves differently with np.loadtxt / np.genfromtxt
# t, v = np.genfromtxt(filepath, \
# converters={0: lambda x: datetime.strptime(x, '%H:%M:%S.%f')}, \
# unpack=True)
data = np.genfromtxt(filepath, \
converters={0: lambda x: datetime.strptime(x, '%H:%M:%S.%f')})
t = data['f0']
v = data['f1']
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(t, v)
ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S'))
#ax.xaxis.set_major_locator(mdates.AutoDateLocator(minticks=10))
plt.show()
if __name__ == '__main__':
import sys
myTest(sys.argv[1])

两者的差异

  • genfromtxt可以加载时间字符串为datetime.datetime对象,但loadtxt要求列数据必须能够转换为float
  • loadtxt函数传入unpack=True时,返回各个列数据的数组;而genfromtxt返回的是行数据的数组

好像主要就是这样。另外,用mpl绘图时,数据可以是datetime.datetime类型,但不能使用datetime.datetime.time类型,因为前者提供了toordinal()函数。

订正

当genfromtxt载入各列的数据类型不同时,返回的是一个1-d array,所以不能通过传入unpack=True取得列数据。另外,据说numpy的这些数据加载函数比起 pandas来说弱爆了。