本节书摘来自异步社区《Python数据科学指南》一书中的第1章,第1.20节,作者[印度] Gopi Subramanian ,方延风 刘丹 译,更多章节内容可以访问云栖社区“异步社区”公众号查看。
1.20 从表格数据使用数组
数据科学的应用程序要成功解决一个问题,必须先找到适当的处理数据的方法。例如在机器学习中对数据进行预测或分类,要么采用有监督的方法,要么采用无监督的方法。而在此之前,传输数据,把数据清洗到匹配算法,可能已经花费了很长的时间。
通常,有很多种方法对数据进行整理使之适合数据科学程序进行处理,数据科学程序开发者首先面对的挑战是如何访问数据,并用Python的数据结构让这些数据持续可用。掌握使用Python访问数据的诀窍是非常有用的,能让你避过纷扰,直接面对问题的核心内容。
一般数据是以文本的形式存放的,用逗号或者tab作为分隔符。我们可以采用Python的内置文件对象工具来进行处理。如前所述,文件对象实现了_iter_()和next()方法,这让我们可以处理非常大的文件,这些文件无法一次全部装载到内存里,只能每次读取其中的一小部分。
Python的机器学习库(如scikit-learn)就是基于NumPy库,在这节中,我们将研究如何高效地读取外部数据,并将之转为NumPy的数组以便后续的数据处理。
1.20.1 准备工作
NumPy提供了一个genfromtxt函数可以从表格数据中创建数组,数据存放到NumPy数组中以后,系统处理数据就轻松得多。我们通过一个NumPy 1.8.0编写的代码来看看如何使用genfrom text。
1.20.2 操作方法
我们先从导入必需的库开始,先定义输入的例子,然后演示如何处理表格数据。
# 1.我们先用StringIO来模拟一个小型的表格数据
import numpy as np
from StringIO import StringIO
in_data = StringIO("10,20,30\n56,89,90\n33,46,89")
# 2.使用NumPy的genfromtxt来读取数据,并创建一个NumPy数组
data = np.genfromtxt(in_data,dtype=int,delimiter=",")
# 3.清除掉一些我们不需要的列
in_data = StringIO("10,20,30\n56,89,90\n33,46,89")
data = np.genfromtxt(in_data,dtype=int,delimiter=",",usecols=(0,1))
# 4.设定列名
in_data = StringIO("10,20,30\n56,89,90\n33,46,89")
data = np.genfromtxt(in_data,dtype=int,delimiter=",",names="a,b,c")
# 5.使用列名来处理数据
in_data = StringIO("a,b,c\n10,20,30\n56,89,90\n33,46,89")
data = np.genfromtxt(in_data,dtype=int,delimiter=",",names=True)
1.20.3 工作原理
第1步中,我们用StringIO来模拟表格数据,有3个行和3个列,行通过换行表示,列则通过逗号分隔。
第2步中,我们用NumPy的genfromtxt导入数据到NumPy数组。genfromtxt的第一个参数是文件源和文件名,本例中是StringIO对象。输入由逗号分隔,分隔符参数允许我们自己定义分隔符。运行上面的代码后,数据格式如下。
>>> data
array([[10, 20, 30],
[56, 89, 90],
[33, 46, 89]])
如你所见,我们成功地将字符串数据加载到了NumPy数组中。
1.20.4 更多内容
下面列出了genfromtxt函数的各个参数以及默认值。
genfromtxt(fname, dtype=<type 'float'>, comments='#', delimiter=None,
skiprows=0, skip_header=0, skip_footer=0, converters=None,
missing='', missing_values=None, filling_values=None, usecols=None,
names=None, excludelist=None, deletechars=None, replace_space='_',
autostrip=False, case_sensitive=True, defaultfmt='f%i', unpack=None,
usemask=False, loose=True, invalid_raise=True)
唯一必备的参数是数据源的名字,本例中是一个StringIO对象,它可以是一个文件名或者带有read方法的类似于文件的对象,也可以是一个远程文件的URL。
首先必须将给定的行分成列,当文件被打开进行读取时,genfromtxt将非空行切分成一个字符串序列。空行和注释行会被忽略,注释选项帮助gentext判断哪些行是注释行。我们指定的分隔符将字符串切分为列。我们的示例使用“,”作为分隔符。制表符“/t”也是一种常用的分隔符。gentext的默认分隔符是None,这意味着行被空格分成多个列。
一般而言,行被转换成字符串序列之后,列被萃取出来,每个独立的列并没有被清除前导或者后导的空格。在上面示例代码的后面部分,这种情况需要进行处理,特别是有些变量要被作为字典的键。例如,若是前导或后导的空格没有被处理完全,代码可能会出现bug或错误。设置参数autostrip=True有助于避免这类问题。
很多情况下,我们在读取文件的时候要跳过一些数据,比如跳过最前n行或者最后n行,这就需要使用headers和footers参数。设置skip_header=n会在读文件时跳过最开始的n行。类似地,设置skip_footer=n则跳过最后的n行。
和不需要的行类似,有时我们需要跳过一些列,usecols参数可以指定一个包含所需要的列的列表。
in_data = StringIO("10,20,30\n56,89,90\n33,46,89")
data = np.genfromtxt(in_data,dtype=int,delimiter=",",usecols=(0,1))
在上面的示例中,我们只选择了两个列,第0和第1列。数据对象形式如下。
>>> data
array([[10, 20],
[56, 89],
[33, 46]])
使用names参数,我们可以自定义列名,由逗号分隔的列名字符串参数形式如下。
in_data = StringIO("10,20,30\n56,89,90\n33,46,89")
data = np.genfromtxt(in_data,dtype=int,delimiter=",",names="a,b,c")
>>> data
array([(10, 20, 30), (56, 89, 90), (33, 46, 89)],
dtype=[('a', '<i4'), ('b', '<i4'), ('c', '<i4')])
设定names参数为真,输入文件的第1行会被当成列名。
in_data = StringIO("a,b,c\n10,20,30\n56,89,90\n33,46,89")
data = np.genfromtxt(in_data,dtype=int,delimiter=",",names=True)
>>> data
array([(10, 20, 30), (56, 89, 90), (33, 46, 89)],
dtype=[('a', '<i4'), ('b', '<i4'), ('c', '<i4')])
NumPy里还有个叫作loadtxt的方法可以方便地从文本文件中创建NumPy数组,请参阅:http://docs.scipy.org/doc/numpy/reference/generated/numpy.loadtxt.html。
这个函数比genfromtxt要简单一些,如果你不需要复杂的数据处理架构,比如处理丢失的数据等情况,你可以选用loadtxt。
此外,如果你不需要装载数据到NumPy数组,只想把数据加载到列表中,Python默认提供了csv库,可以参考下面的URL。
https://docs.python.org/2/library/csv.html.
上面这个csv库里有一个有趣的方法叫作csv.Sniffer.sniff()。要处理一个很大的csv文件时,我们要理解它的结构,就可以使用sniff()函数,它返回一个具有csv文件大部分属性的子类。