Python数据分析 | (19) 数据加载与存储---文本格式数据

在本篇博客中,我们将讨论用pandas读取(或加载)和写入数据集的工具。之后,我们将更深入地研究使用pandas进行数据清洗、规整、分析和可视化 工具。访问数据是使用本书所介绍的这些工具的第一步。我会着重介绍pandas的数 据输入与输出,虽然别的库中也有不少以此为目的的工具。

输入输出通常可以划分为几个大类:读取文本文件和其他更高效的磁盘存储 格式,加载数据库中的数据,利用Web API操作网络资源。

目录

1. 读写文本格式数据综述

2. 逐块读取文本文件

3. 将数据写出到文本格式

4. 处理分隔符格式

5. json数据

6. XML和HTML:Web信息收集

7. 利用lxml.objectify解析XML


1. 读写文本格式数据综述

pandas提供了一些用于将表格型数据读取为DataFrame对象的函数。下表对它们进行了总结,其中read_csv和read_table可能会是你今后用得最多 的。

我将大致介绍一下这些函数在将文本数据转换为DataFrame时所用到的一些 技术。这些函数的选项可以划分为以下几个大类:

1)索引:将一个或多个列当做返回的DataFrame处理,以及是否从文件、 用户获取列名。

2)类型推断和数据转换:包括用户定义值的转换、和自定义的缺失值标记 列表等。

3)日期解析:包括组合功能,比如将分散在多个列中的日期时间信息组合 成结果中的单个列。

4)迭代:支持对大文件进行逐块迭代。

5)不规整数据问题:跳过一些行、页脚、注释或其他一些不重要的东西 (比如由成千上万个逗号隔开的数值数据)。

因为工作中实际碰到的数据可能十分混乱,一些数据加载函数(尤其是read_csv)的选项逐渐变得复杂起来。面对不同的参数,感到头痛很正常 (read_csv有超过50个参数)。pandas文档有这些参数的例子,如果你感到 阅读某个文件很难,可以通过相似的足够多的例子找到正确的参数。

其中一些函数,比如pandas.read_csv,有类型推断功能,因为列数据的类 型不属于数据类型。也就是说,你不需要指定列的类型到底是数值、整数、 布尔值,还是字符串。其它的数据格式,如HDF5、Feather和msgpack,会 在格式中存储数据类型。

日期和其他自定义类型的处理需要多花点工夫才行。首先我们来看一个以逗 号分隔的(CSV)文本文件:

! cat examples/ex1.csv

由于该文件以逗号分隔,所以我们可以使用read_csv将其读入一个DataFrame:

df = pd.read_csv('examples/ex1.csv')
df

我们还可以使用read_table,并指定分隔符:

pd.read_table('examples/ex1.csv', sep=',')

并不是所有文件都有标题行。看看下面这个文件:

!cat examples/ex2.csv

读入该文件的办法有两个。你可以让pandas为其分配默认的列名,也可以自 己定义列名:

pd.read_csv('examples/ex2.csv', header=None) #默认列名

pd.read_csv('examples/ex2.csv', names=['a', 'b', 'c', 'd', 'message'])#自定义列名

假设你希望将message列做成DataFrame的索引。你可以明确表示要将该列 放到索引4的位置上,也可以通过index_col参数指定"message":

names = ['a', 'b', 'c', 'd', 'message']
pd.read_csv('examples/ex2.csv', names=names, index_col='message')

如果希望将多个列做成一个层次化索引,只需传入由列编号或列名组成的列表即可:

!cat examples/csv_mindex.csv
parsed = pd.read_csv('examples/csv_mindex.csv',
                     index_col=['key1', 'key2'])
parsed

有些情况下,有些表格可能不是用固定的分隔符去分隔字段的(比如空白符或其它模式)。看看下面这个文本文件:

list(open('examples/ex3.txt'))

虽然可以手动对数据进行规整,这里的字段是被数量不同的空白字符间隔开 的。这种情况下,你可以传递一个正则表达式作为read_table的分隔符。可 以用正则表达式表达为\s+,于是有:

result = pd.read_table('examples/ex3.txt', sep='\s+') #\s+ 一个或多个空白字符
result

这里,由于列名比数据行的数量少,所以read_table推断第一列应该是DataFrame的索引。

这些解析器函数还有许多参数可以帮助你处理各种各样的异形文件格式(下表列出了一些)。比如说,你可以用skiprows跳过文件的第一行、第三行和 第四行:

!cat examples/ex4.csv
pd.read_csv('examples/ex4.csv', skiprows=[0, 2, 3])

缺失值处理是文件解析任务中的一个重要组成部分。缺失数据经常是要么没 有(空字符串),要么用某个标记值表示。默认情况下,pandas会用一组经 常出现的标记值进行识别,比如NA及NULL:

!cat examples/ex5.csv
result = pd.read_csv('examples/ex5.csv')
result

pd.isnull(result)

na_values可以用一个列表或集合的字符串表示缺失值:

result = pd.read_csv('examples/ex5.csv', na_values=['NULL'])
result

字典的各列可以使用不同的NA标记值:

sentinels = {'message': ['foo', 'NA'], 'something': ['two']}
pd.read_csv('examples/ex5.csv', na_values=sentinels)

下表列出了pandas.read_csv和pandas.read_table常用的选项。

 

2. 逐块读取文本文件

在处理很大的文件时,或找出大文件中的参数集以便于后续处理时,你可能只想读取文件的一小部分或逐块对文件进行迭代。

在看大文件之前,我们先设置pandas显示地更紧些:

pd.options.display.max_rows = 10
result = pd.read_csv('examples/ex6.csv')
result

如果只想读取几行(避免读取整个文件),通过nrows进行指定即可:

pd.read_csv('examples/ex6.csv', nrows=5)

要逐块读取文件,可以指定chunksize(行数):

chunker = pd.read_csv('examples/ex6.csv', chunksize=1000)
chunker

read_csv所返回的这个TextParser对象使你可以根据chunksize对文件进行逐 块迭代(逐块处理)。比如说,我们可以迭代处理ex6.csv,将值计数聚合到"key"列中, 如下所示:

chunker = pd.read_csv('examples/ex6.csv', chunksize=1000)

tot = pd.Series([])
for piece in chunker:
    print(piece)
    break
for piece in chunker: #对每一块 逐块处理
    tot = tot.add(piece['key'].value_counts(), fill_value=0)

tot = tot.sort_values(ascending=False)
tot[:10]

TextParser还有一个get_chunk方法,它使你可以读取任意大小的块。

 

3. 将数据写出到文本格式

数据也可以被输出为分隔符格式的文本。我们再来看看之前读过的一个CSV文件:

data = pd.read_csv('examples/ex5.csv')
data

利用DataFrame的to_csv方法,我们可以将数据写到一个以逗号分隔的文件 中:

data.to_csv('examples/out.csv')
!cat examples/out.csv

当然,还可以使用其他分隔符(由于这里直接写出到sys.stdout,所以仅仅是 打印出文本结果而已):

import sys
data.to_csv(sys.stdout, sep='|')

缺失值在输出结果中会被表示为空字符串。你可能希望将其表示为别的标记值:

data.to_csv(sys.stdout, na_rep='NULL')

如果没有设置其他选项,则会写出行和列的标签。当然,它们也都可以被禁用:

data.to_csv(sys.stdout, index=False, header=False)

此外,你还可以只写出一部分的列,并以你指定的顺序排列:

data.to_csv(sys.stdout, index=False, columns=['a', 'b', 'c'])

Series也有一个to_csv方法:

dates = pd.date_range('1/1/2000', periods=7)
ts = pd.Series(np.arange(7), index=dates)
print(ts)
print("-----------")
ts.to_csv('examples/tseries.csv')
!cat examples/tseries.csv

4. 处理分隔符格式

大部分存储在磁盘上的表格型数据都能用pandas.read_table进行加载。然 而,有时还是需要做一些手工处理。由于接收到含有畸形行的文件而使read_table出毛病的情况并不少见。为了说明这些基本工具,看看下面这个 简单的CSV文件:

!cat examples/ex7.csv

 对于任何单字符分隔符文件,可以直接使用Python内置的csv模块。将任意 已打开的文件或文件型的对象传给csv.reader:

import csv
f = open('examples/ex7.csv')

reader = csv.reader(f)

对这个reader进行迭代将会为每行产生一个元组(并移除了所有的引号):

for line in reader:
    print(line)

 

现在,为了使数据格式合乎要求,你需要对其做一些整理工作。我们一步一步来做。首先,读取文件到一个多行的列表中:

with open('examples/ex7.csv') as f:
    lines = list(csv.reader(f))

然后,我们将这些行分为标题行和数据行:

header, values = lines[0], lines[1:]

然后,我们可以用字典构造式和zip(*values),后者将行转置为列,创建数据 列的字典:

data_dict = {h: v for h, v in zip(header, zip(*values))}
data_dict

 CSV文件的形式有很多。只需定义csv.Dialect的一个子类即可定义出新格式 (如专门的分隔符、字符串引用约定、行结束符等):

class my_dialect(csv.Dialect):
    lineterminator = '\n'
    delimiter = ';'
    quotechar = '"'
    quoting = csv.QUOTE_MINIMAL

reader = csv.reader(f,dialect=my_dialect)

各个CSV语支的参数也可以用关键字的形式提供给csv.reader,而无需定义 子类:

reader = csv.reader(f,delimiter='|')

 可用的选项(csv.Dialect的属性)及其功能如下表所示。

对于那些使用复杂分隔符或多字符分隔符的文件,csv模块就无 能为力了。这种情况下,你就只能使用字符串的split方法或正则表达式 方法re.split进行行拆分和其他整理工作了。

要手工输出分隔符文件,你可以使用csv.writer。它接受一个已打开且可写的 文件对象以及跟csv.reader相同的那些语支和格式化选项:

with open('mydata.csv','w') as f:
    writer = csv.writer(f,dialect=my_dialect)#可以传入定义的子类 也可以传入相应的关键字参数 也可以使用默认
    writer.writerow(('one','two','three')) #header
    writer.writerow(('1','2','3'))  #可以是元组 也可以是列表
    writer.writerow(('1','2','3'))  
    writer.writerow(('1','2','3')) 

 

5. json数据

JSON(JavaScript Object Notation的简称)已经成为通过HTTP请求在Web浏览器和其他应用程序之间发送数据的标准格式之一。它是一种比表格型文 本格式(如CSV)灵活得多的数据格式。下面是一个例子:

obj = """
{"name": "Wes",
 "places_lived": ["United States", "Spain", "Germany"],
 "pet": null,
 "siblings": [{"name": "Scott", "age": 30, "pets": ["Zeus", "Zuko"]},
              {"name": "Katie", "age": 38,
               "pets": ["Sixes", "Stache", "Cisco"]}]
}
"""

除其空值null和一些其他的细微差别(如列表末尾不允许存在多余的逗号)之 外,JSON非常接近于有效的Python代码。基本类型有对象(字典)、数组 (列表)、字符串、数值、布尔值以及null。对象中所有的键都必须是字符 串。许多Python库都可以读写JSON数据。我将使用json,因为它是构建于Python标准库中的。通过json.loads即可将JSON字符串转换成Python形式:

import json
result = json.loads(obj)
result

 

json.dumps则将Python对象转换成JSON格式:

asjson = json.dumps(result)
asjson

 

如何将(一个或一组)JSON对象转换为DataFrame或其他便于分析的数据 结构就由你决定了。最简单方便的方式是:向DataFrame构造器传入一个字 典的列表(就是原先的JSON对象),并选取数据字段的子集:

siblings = pd.DataFrame(result['siblings'], columns=['name', 'age'])
siblings

 pandas.read_json可以自动将特别格式的JSON数据集转换为Series或DataFrame。例如:

!cat examples/example.json #字典列表的形式

pandas.read_json的默认选项假设JSON数组中的每个对象是表格中的一 行:

data = pd.read_json('examples/example.json')
data

 之后会有一个关于USDA Food Database的那个例子将进一步讲解了JSON数据的读 取和处理(包括嵌套记录)。

如果你需要将数据从pandas输出到JSON,可以使用to_json方法:

print(data.to_json())
print(data.to_json(orient='records'))

 

6. XML和HTML:Web信息收集

Python有许多可以读写常见的HTML和XML格式数据的库,包括lxml、Beautiful Soup和html5lib。lxml的速度比较快,但其它的库处理有误的HTML或XML文件更好。

pandas有一个内置的功能,read_html,它可以使用lxml和Beautiful Soup自 动将HTML文件中的表格解析为DataFrame对象。为了进行展示,我从美国 联邦存款保险公司下载了一个HTML文件(pandas文档中也使用过),它记 录了银行倒闭的情况。首先,你需要安装read_html用到的库:

!pip install lxml
!pip install beautifulsoup4 html5lib

pandas.read_html有一些选项,默认条件下,它会搜索、尝试解析标签内的的表格数据。结果是一个列表的DataFrame对象:

tables = pd.read_html('examples/fdic_failed_bank_list.html')
print(len(tables)) #html中的表格数量
failures = tables[0]
failures.head()

 

这里,我们可以做一些数据清洗和分析(后面会进一步讲解),比如按年份计算倒闭的银行数:

close_timestamps = pd.to_datetime(failures['Closing Date'])
close_timestamps.dt.year.value_counts()

 

7. 利用lxml.objectify解析XML

XML(Extensible Markup Language)是另一种常见的支持分层、嵌套数据 以及元数据的结构化数据格式。本博客所使用的这些文件实际上来自于一个很 大的XML文档。

前面,我介绍了pandas.read_html函数,它可以使用lxml或Beautiful Soup从HTML解析数据。XML和HTML的结构很相似,但XML更为通用。这里,我会 用一个例子演示如何利用lxml从XML格式解析数据。

纽约大都会运输署发布了一些有关其公交和列车服务的数据资料(http:// www.mta.info/developers/download.html)。这里,我们将看看包含在一组XML文件中的运行情况数据。每项列车或公交服务都有各自的文件(如Metro-North Railroad的文件是Performance_MNR.xml),其中每条XML记 录就是一条月度数据,如下所示:

我们先用lxml.objectify解析该文件,然后通过getroot得到该XML文件的根节 点的引用:

from lxml import objectify

path = 'datasets/mta_perf/Performance_MNR.xml'
parsed = objectify.parse(open(path))
root = parsed.getroot()

root.INDICATOR返回一个用于产生各个XML元素的生成器。对于每条记录, 我们可以用标记名(如YTD_ACTUAL)和数据值填充一个字典(排除几个标 记):

data = []

skip_fields = ['PARENT_SEQ', 'INDICATOR_SEQ',
               'DESIRED_CHANGE', 'DECIMAL_PLACES']

for elt in root.INDICATOR:  #遍历所有数据
    el_data = {}
    for child in elt.getchildren(): #遍历子标签
        if child.tag in skip_fields: #跳过这几个标签
            continue
        el_data[child.tag] = child.pyval #构建字典
    data.append(el_data) #字典列表

最后,将这组字典转换为一个DataFrame:

perf = pd.DataFrame(data)
perf.head()

XML数据可以比本例复杂得多。每个标记都可以有元数据。看看下面这个HTML的链接标签(它也算是一段有效的XML):

from io import StringIO
tag = '<a href="http://www.google.com">Google</a>'
root = objectify.parse(StringIO(tag)).getroot()

现在就可以访问标签或链接文本中的任何字段了(如href):

print(root)
print(root.get('href')) #获取属性
print(root.text) #获取文本

 

 

 

 

 

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值