利用Python进行数据分析--数据加载、存储与文件格式

转载自:http://blog.csdn.net/ssw_1990/article/details/23911901

1、手工处理分隔符格式

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

[python]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. In [1]: !cat ch06/ex7.csv  
  2. "a","b","c"  
  3. "1","2","3"  
  4. "1","2","3","4"  
对于任何单字符分隔符文件,可以直接使用Python内置的csv模块。将任意已打开的文件或文件型的对象传给csv.reader:
[python]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. import csv  
  2. f = open('ch06/ex7.csv')  
  3.   
  4. reader = csv.reader(f)  
对这个reader进行迭代将会为每行产生一个列表(并移除了所有的引号):
[python]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. In [1]: for line in reader:  
  2. .....: print line  
  3. ['a''b''c']  
  4. ['1''2''3']  
  5. ['1''2''3''4']  
现在,为了使数据格式合乎要求,你需要对其做一些整理工作:

[python]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. In [2]: lines = list(csv.reader(open('ch06/ex7.csv')))  
  2. In [3]: header, values = lines[0], lines[1:]  
  3. In [4]: data_dict = {h: v for h, v in zip(header, zip(*values))}  
  4. In [5]: data_dict  
  5. Out[5]: {'a': ('1''1'), 'b': ('2''2'), 'c': ('3''3')}  
CSV文件的形式有很多。只需定义csv.Dialect的一个子类即可定义出新格式(如专门的分隔符、字符串引用约定、行结束符等):

[python]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. class my_dialect(csv.Dialect):  
  2. lineterminator = '\n'  
  3. delimiter = ';'  
  4. quotechar = '"'  
  5.   
  6. reader = csv.reader(f, dialect=my_dialect)  
各个CSV语支的参数也可以关键字的形式提供给csv.reader,而无需定义子类:
[python]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. reader = csv.reader(f, delimiter='|'  
可用的选项(csv.Dialect的属性)及其功能如下所示:


注意:

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

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

[python]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. with open('mydata.csv''w') as f:  
  2. writer = csv.writer(f, dialect=my_dialect)  
  3. writer.writerow(('one''two''three'))  
  4. writer.writerow(('1''2''3'))  
  5. writer.writerow(('4''5''6'))  
  6. writer.writerow(('7''8''9'))  


2、JSON数据

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

[python]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. obj = """ 
  2. {"name": "Wes", 
  3. "places_lived": ["United States", "Spain", "Germany"], 
  4. "pet": null, 
  5. "siblings": [{"name": "Scott", "age": 25, "pet": "Zuko"}, 
  6. {"name": "Katie", "age": 33, "pet": "Cisco"}] 
  7. } 
  8. """  
除其空值null和一些其他的细微差别(如列表末尾不允许存在多余的逗号)之外,JSON非常接近于有效的Python代码。基本类型有对象(字典)、数组(列表)、字符串、数值、布尔值以及null。对象中所有的键都必须是字符串。许多Python库都可以读写JSON数据。我将使用json,因为它是构建于Python标准库中的。通过json.loads即可将JSON字符串转换成Python形式:

[python]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. In [6]: import json  
  2. In [7]: result = json.loads(obj)  
  3. In [8]: result  
  4. Out[8]:   
  5. {u'name': u'Wes',  
  6. u'pet'None,  
  7. u'places_lived': [u'United States', u'Spain', u'Germany'],  
  8. u'siblings': [{u'age'25, u'name': u'Scott', u'pet': u'Zuko'},  
  9. {u'age'33, u'name': u'Katie', u'pet': u'Cisco'}]}  
相反,json.dumps则将Python对象转换成JSON格式:

[python]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. In [9]: asjson = json.dumps(result)  

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

[python]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. In [10]: siblings = DataFrame(result['siblings'], columns=['name''age'])  
  2. In [11]: siblings  
  3. Out[11]:   
  4.     name age  
  5. 0  Scott  25  
  6. 1  Katie  33  

3、XML和HTML:Web信息收集

Python有许多可以读写HTML和XML格式数据的库。lxml(http://lxml.de)就是其中之一,它能够高效且可靠地解析大文件。lxml有多个编程接口。首先我要用lxml.html处理HTML,然后再用lxml.objectify做一些XML处理。

许多网站都将数据放到HTML表格中以便在浏览器中查看,但不能以一种更易于机器阅读的格式(如JSON、HTML或XML)进行下载。我发现Yahoo! Finance的股票期权数据就是这样。可能你对这种数据不熟悉:期权是指使你有权从现在开始到未来某个时间(到期日)内以某个特定价格(执行价)买进(看涨期权)或卖出(看跌期权)某公司股票的衍生合约。人们的看涨和看跌期权交易有多种执行价和到期日,这些数据都可以在Yahoo! Finance的各种表格中找到。

首先,找到你希望获取数据的URL,利用urllib2将其打开,然后用lxml解析得到的数据流,如下所示:

[python]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. from lxml.html import parse  
  2. from urllib2 import urlopen  
  3.   
  4. parsed = parse(urlopen('http://finance.yahoo.com/q/op?s=AAPL+Options'))  
  5.   
  6. doc = parsed.getroot()  
通过这个对象,你可以获取特定类型的所有HTML标签(tag),比如含有所需数据的table标签。给这个简单的例子加点启发性,假设你想得到该文档中所有的URL链接。HTML中的链接是a标签。使用文档根节点的findall方法以及一个XPath(对文档的“查询”的一种表示手段):

[python]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. In [1]: links = doc.findall('.//a')  
  2. In [2]: links[15:20]  
  3. Out[2]:   
  4. [<Element a at 0x6c488f0>,  
  5. <Element a at 0x6c48950>,  
  6. <Element a at 0x6c489b0>,  
  7. <Element a at 0x6c48a10>,  
  8. <Element a at 0x6c48a70>]  

但这些是表示HTML元素的对象。要得到URL和链接文本,你必须使用各对象的get方法(针对URL)和text_content方法(针对显示文本):

[python]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. In [3]: lnk = links[28]  
  2. In [4]: lnk  
  3. Out[4]: <Element a at 0x6c48dd0>  
  4. In [5]: lnk.get('href')  
  5. Out[5]: 'http://biz.yahoo.com/special.html'  
  6. In [6]: lnk.text_content()  
  7. Out[6]: 'Special Editions'  
因此,编写下面这条列表推导式(list comprehension)即可获取文档中的全部URL:

[python]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. In [7]: urls = [lnk.get('href'for lnk in doc.findall('.//a')]  
  2. In [8]: urls[-10:]  
  3. Out[8]:   
  4. ['http://info.yahoo.com/privacy/us/yahoo/finance/details.html',  
  5. 'http://info.yahoo.com/relevantads/',  
  6. 'http://docs.yahoo.com/info/terms/',  
  7. 'http://docs.yahoo.com/info/copyright/copyright.html',  
  8. 'http://help.yahoo.com/l/us/yahoo/finance/forms_index.html',  
  9. 'http://help.yahoo.com/l/us/yahoo/finance/quotes/fitadelay.html',  
  10. 'http://help.yahoo.com/l/us/yahoo/finance/quotes/fitadelay.html',  
  11. 'http://www.capitaliq.com',  
  12. 'http://www.csidata.com',  
  13. 'http://www.morningstar.com/']  

现在,从文档中找出正确表格的办法就是反复试验了。有些网站会给目标表格加上一个id属性。我确定有两个分别放置看涨数据和看跌数据的表格:

[python]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. tables = doc.findall('.//table')  
  2. calls = tables[9]  
  3. puts = tables[13]  
每个表格都有一个标题行,然后才是数据行:

[python]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. In [7]: rows = calls.findall('.//tr')  

对于标题行和数据行,我们希望获取每个单元格内的文本。对于标题行,就是th单元格,而对于数据行,则是td单元格:

[python]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. def _unpack(row, kind='td'):  
  2. elts = row.findall('.//%s' % kind)  
  3. return [val.text_content() for val in elts]  
这样,我们就得到了:

[python]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. In [8]: _unpack(rows[0], kind='th')  
  2. Out[8]: ['Strike''Symbol''Last''Chg''Bid''Ask''Vol''Open Int']  
  3. In [9]: _unpack(rows[1], kind='td')  
  4. Out[9]:   
  5. ['295.00',  
  6. 'AAPL120818C00295000',  
  7. '310.40',  
  8. ' 0.00',  
  9. '289.80',  
  10. '290.80',  
  11. '1',  
  12. '169']  
现在,把所有步骤结合起来,将数据转换为一个DataFrame。由于数值型数据仍然是字符串格式,所以我们希望将部分列(可能不是全部)转换为浮点数格式。虽然你可以手工实现该功能,但是pandas恰好就有一个TextParser类可用于自动类型转换(read_csv和其他解析函数其实在内部都用到了它):

[python]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. from pandas.io.parsers import TextParser  
  2.   
  3. def parse_options_data(table):  
  4.     rows = table.findall('.//tr')  
  5.     header = _unpack(rows[0], kind='th')  
  6.     data = [_unpack(r) for r in rows[1:]]  
  7.     return TextParser(data, names=header).get_chunk()  
最后,我对那两个lxml表格对象调用该解析函数并得到最终的DataFrame:

[python]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. In [10]: call_data = parse_options_data(calls)  
  2. In [11]: put_data = parse_options_data(puts)  
  3. In [12]: call_data[:10]  
  4. Out[12]:   
  5. Strike             Symbol   Last Chg    Bid    Ask Vol Open Int  
  6. 0 295 AAPL120818C00295000 310.40 0.0 289.80 290.80   1      169  
  7. 1 300 AAPL120818C00300000 277.10 1.7 284.80 285.60   2      478  
  8. 2 305 AAPL120818C00305000 300.97 0.0 279.80 280.80  10      316  
  9. 3 310 AAPL120818C00310000 267.05 0.0 274.80 275.65   6      239  
  10. 4 315 AAPL120818C00315000 296.54 0.0 269.80 270.80  22       88  
  11. 5 320 AAPL120818C00320000 291.63 0.0 264.80 265.80  96      173  
  12. 6 325 AAPL120818C00325000 261.34 0.0 259.80 260.80 N/A      108  
  13. 7 330 AAPL120818C00330000 230.25 0.0 254.80 255.80 N/A       21  
  14. 8 335 AAPL120818C00335000 266.03 0.0 249.80 250.65   4       46  
  15. 9 340 AAPL120818C00340000 272.58 0.0 244.80 245.80   4       30  


4、利用lxml.objectify解析XML

XML(Extensible Markup Language)是另一种常见的支持分层、嵌套数据以及元数据的结构化数据格式。之前,我介绍了lxml库及其lxml.html接口。这里我将介绍另一个用于操作XML数据的接口,即lxml.objectify。

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

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. <INDICATOR>  
  2. <INDICATOR_SEQ>373889</INDICATOR_SEQ>  
  3. <PARENT_SEQ></PARENT_SEQ>  
  4. <AGENCY_NAME>Metro-North Railroad</AGENCY_NAME>  
  5. <INDICATOR_NAME>Escalator Availability</INDICATOR_NAME>  
  6. <DESCRIPTION>Percent of the time that escalators are operational  
  7. systemwide. The availability rate is based on physical observations performed  
  8. the morning of regular business days only. This is a new indicator the agency  
  9. began reporting in 2009.</DESCRIPTION>  
  10. <PERIOD_YEAR>2011</PERIOD_YEAR>  
  11. <PERIOD_MONTH>12</PERIOD_MONTH>  
  12. <CATEGORY>Service Indicators</CATEGORY>  
  13. <FREQUENCY>M</FREQUENCY>  
  14. <DESIRED_CHANGE>U</DESIRED_CHANGE>  
  15. <INDICATOR_UNIT>%</INDICATOR_UNIT>  
  16. <DECIMAL_PLACES>1</DECIMAL_PLACES>  
  17. <YTD_TARGET>97.00</YTD_TARGET>  
  18. <YTD_ACTUAL></YTD_ACTUAL>  
  19. <MONTHLY_TARGET>97.00</MONTHLY_TARGET>  
  20. <MONTHLY_ACTUAL></MONTHLY_ACTUAL>  
  21. </INDICATOR>  
我们先用lxml.objectify解析该文件,然后通过getroot得到该XML文件的根节点的引用:
[python]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. from lxml import objectify  
  2.   
  3. path = 'Performance_MNR.xml'  
  4. parsed = objectify.parse(open(path))  
  5. root = parsed.getroot()  
root.INDICATOR返回一个用于产生各个<INDICATOR>XML元素的生成器。对于每条记录,我们可以用标记名(如YTD_ACTUAL)和数据值填充一个字典(排除几个标记):

[python]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. data = []  
  2.   
  3. skip_fields = ['PARENT_SEQ''INDICATOR_SEQ''DESIRED_CHANGE''DECIMAL_PLACES']  
  4.   
  5. for elt in root.INDICATOR:  
  6.     el_data = {}  
  7.     for child in elt.getchildren():  
  8.         if child.tag in skip_fields:  
  9.             continue  
  10.         el_data[child.tag] = child.pyval  
  11.     data.append(el_data)  

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

[python]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. In [13]: perf = DataFrame(data)  
  2. In [14]: perf  
  3. Out[14]:   
  4. Empty DataFrame  
  5. Columns: array([], dtype=int64)  
  6. Index: array([], dtype=int64)  
XML数据可以比本例复杂得多。每个标记都可以有元数据。看看下面这个HTML的链接标记(它也算是一段有效的XML):

[python]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. from StringIO import StringIO  
  2. tag = '<a href="http://www.google.com">Google</a>'  
  3.   
  4. root = objectify.parse(StringIO(tag)).getroot()  
现在就可以访问链接文本或标记中的任何字段了(如href):

[python]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. In [15]: root  
  2. Out[15]: <Element a at 0x88bd4b0>  
  3.   
  4. In [16]: root.get('href')  
  5. Out[16]: 'http://www.google.com'  
  6.   
  7. In [17]: root.text  
  8. Out[17]: 'Google' 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值