美文网首页
12-16、17第06章 数据加载、存储与文件格式

12-16、17第06章 数据加载、存储与文件格式

作者: 渔家傲_俞 | 来源:发表于2018-12-18 16:19 被阅读0次

    访问数据是使用本书所介绍的这些工具的第一步。我会着重介绍pandas的数据输入与输出,虽然别的库中也有不少以此为目的的工具。

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

    6.1 读写文本格式的数据

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

    读取的函数
    这里我们给出这些函数的大致功能,就是把test data变为dataframe。这些函数的一些可选参数有以下几类:
    • Indexing(索引)
      能把返回的一列或多列作为一个dataframe。另外也可以选择从文件中获取列名或完全不获取列名

    • Type inference and data conversion(类型推测和数据转换)
      这个包括用户自己定义的转换类型和缺失值转换

    • Datetime parsing(日期解析)
      包含整合能力,可以把多列中的时间信息整合为一列

    • Iterating(迭代)
      支持对比较大的文件进行迭代

    • Unclean data issues(未清洗的数据问题)
      跳过行或柱脚,评论,或其他一些小东西,比如csv中的逗号

    因为现实中的数据非常messy(杂乱),所以有一些数据加载函数(特别是read_csv)的选项也变得越来越多。对于众多参数感觉不知所措是正常的(read_csv有超过50个参数)。具体的可以去看pandas官网给出的例子。

    一些函数,比如pandas.read_csv实现type inference,因为column data type不是数据类型的一种。这意味着我们没有必要指定哪些columns是数值,哪些是整数,哪些是字符串。其他一些数据格式,比如HDF5,数据类型是在格式里的。
    先来一个CSV文件热热身(CSV文件指的是用逗号隔开数据的文件):

    In [8]: !cat examples/ex1.csv
    a,b,c,d,message
    1,2,3,4,hello
    5,6,7,8,world
    9,10,11,12,foo
    

    笔记:这里,我用的是Unix的cat shell命令将文件的原始内容打印到屏幕上。如果你用的是Windows,你可以使用type达到同样的效果。

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

    In [9]: df = pd.read_csv('examples/ex1.csv')
    
    In [10]: df
    Out[10]: 
       a   b   c   d message
    0  1   2   3   4   hello
    1  5   6   7   8   world
    2  9  10  11  12     foo
    

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

    In [11]: pd.read_table('examples/ex1.csv', sep=',')
    Out[11]: 
       a   b   c   d message
    0  1   2   3   4   hello
    1  5   6   7   8   world
    2  9  10  11  12     foo
    

    一个文件不会总是有header row(页首行),考虑下面的文件:

    In [12]: !cat examples/ex2.csv
    1,2,3,4,hello
    5,6,7,8,world
    9,10,11,12,foo
    

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

    In [13]: pd.read_csv('examples/ex2.csv', header=None)
    Out[13]: 
       0   1   2   3      4
    0  1   2   3   4  hello
    1  5   6   7   8  world
    2  9  10  11  12    foo
    
    In [14]: pd.read_csv('examples/ex2.csv', names=['a', 'b', 'c', 'd', 'message'])
    Out[14]: 
       a   b   c   d message
    0  1   2   3   4   hello
    1  5   6   7   8   world
    2  9  10  11  12     foo
    

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

    In [15]: names = ['a', 'b', 'c', 'd', 'message']
    
    In [16]: pd.read_csv('examples/ex2.csv', names=names, index_col='message')
    Out[16]: 
             a   b   c   d
    message               
    hello    1   2   3   4
    world    5   6   7   8
    foo      9  10  11  12
    

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

    In [17]: !cat examples/csv_mindex.csv
    key1,key2,value1,value2
    one,a,1,2
    one,b,3,4
    one,c,5,6
    one,d,7,8
    two,a,9,10
    two,b,11,12
    two,c,13,14
    two,d,15,16
    
    In [18]: parsed = pd.read_csv('examples/csv_mindex.csv',
       ....:                      index_col=['key1', 'key2'])
    
    In [19]: parsed
    Out[19]: 
               value1  value2
    key1 key2                
    one  a          1       2
         b          3       4
         c          5       6
         d          7       8
    two  a          9      10
         b         11      12
         c         13      14
         d         15      16
    

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

    In [20]: list(open('examples/ex3.txt'))
    Out[20]: 
    ['            A         B         C\n',
     'aaa -0.264438 -1.026059 -0.619500\n',
     'bbb  0.927272  0.302904 -0.032399\n',
     'ccc -0.264273 -0.386314 -0.217601\n',
     'ddd -0.871858 -0.348382  1.100491\n']
    

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

    In [21]: result = pd.read_table('examples/ex3.txt', sep='\s+')
    
    In [22]: result
    Out[22]: 
                A         B         C
    aaa -0.264438 -1.026059 -0.619500
    bbb  0.927272  0.302904 -0.032399
    ccc -0.264273 -0.386314 -0.217601
    ddd -0.871858 -0.348382  1.100491
    

    因为列名比行的数量少,所以read_table推测第一列应该是dataframe的index。

    这个解析器功能有很多其他参数能帮你解决遇到文件格式异常的问题(可以见之后的表格)。比如,我们要跳过第一、三、四行,使用skiprows:

    In [23]: !cat examples/ex4.csv
    # hey!
    a,b,c,d,message
    # just wanted to make things more difficult for you
    # who reads CSV files with computers, anyway?
    1,2,3,4,hello
    5,6,7,8,world
    9,10,11,12,foo
    In [24]: pd.read_csv('examples/ex4.csv', skiprows=[0, 2, 3])
    Out[24]: 
       a   b   c   d message
    0  1   2   3   4   hello
    1  5   6   7   8   world
    2  9  10  11  12     foo
    

    对于缺失值,pandas使用一些sentinel value(标记值)来代表,比如NA和NULL:

    In [25]: !cat examples/ex5.csv
    something,a,b,c,d,message
    one,1,2,3,4,NA
    two,5,6,,8,world
    three,9,10,11,12,foo
    In [26]: result = pd.read_csv('examples/ex5.csv')
    
    In [27]: result
    Out[27]: 
      something  a   b     c   d message
    0       one  1   2   3.0   4     NaN
    1       two  5   6   NaN   8   world
    2     three  9  10  11.0  12     foo
    
    In [28]: pd.isnull(result)
    Out[28]: 
       something      a      b      c      d  message
    0      False  False  False  False  False     True
    1      False  False  False   True  False    False
    2      False  False  False  False  False    False
    

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

    In [29]: result = pd.read_csv('examples/ex5.csv', na_values=['NULL'])
    
    In [30]: result
    Out[30]: 
      something  a   b     c   d message
    0       one  1   2   3.0   4     NaN
    1       two  5   6   NaN   8   world
    2     three  9  10  11.0  12     foo
    

    我们还可以给不同的column设定不同的缺失值标记符,这样的话需要用到dict:

    In [31]: sentinels = {'message': ['foo', 'NA'], 'something': ['two']}
    
    In [32]: pd.read_csv('examples/ex5.csv', na_values=sentinels)
    Out[32]:
    something  a   b     c   d message
    0       one  1   2   3.0   4     NaN
    1       NaN  5   6   NaN   8   world
    2     three  9  10  11.0  12     NaN
    

    表6-2列出了pandas.read_csv和pandas.read_table常用的选项。


    pandas.read_csv和pandas.read_table中常用的选项
    pandas.read_csv和pandas.read_table中常用的选项

    1.逐块读取文本文件

    在处理很大的文件时,或找出大文件中的参数集以便于后续处理时,你可能只想读取文件的一小部分或逐块对文件进行迭代。
    对于一些比较大的文件,我们想要一次读取一小部分,或者每次迭代一小部分。在我们看一个比较大的文件前,先设置一下pandas中显示的数量:

    In [33]: pd.options.display.max_rows = 10
    #然后有
    In [34]: result = pd.read_csv('examples/ex6.csv')
    
    In [35]: result
    Out[35]: 
               one       two     three      four key
    0     0.467976 -0.038649 -0.295344 -1.824726   L
    1    -0.358893  1.404453  0.704965 -0.200638   B
    2    -0.501840  0.659254 -0.421691 -0.057688   G
    3     0.204886  1.074134  1.388361 -0.982404   R
    4     0.354628 -0.133116  0.283763 -0.837063   Q
    ...        ...       ...       ...       ...  ..
    9995  2.311896 -0.417070 -1.409599 -0.515821   L
    9996 -0.479893 -0.650419  0.745152 -0.646038   E
    9997  0.523331  0.787112  0.486066  1.093156   K
    9998 -0.362559  0.598894 -1.843201  0.887292   G
    9999 -0.096376 -1.012999 -0.657431 -0.573315   0
    [10000 rows x 5 columns]
    

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

    In [36]: pd.read_csv('examples/ex6.csv', nrows=5)
    Out[36]: 
            one       two     three      four key
    0  0.467976 -0.038649 -0.295344 -1.824726   L
    1 -0.358893  1.404453  0.704965 -0.200638   B
    2 -0.501840  0.659254 -0.421691 -0.057688   G
    3  0.204886  1.074134  1.388361 -0.982404   R
    4  0.354628 -0.133116  0.283763 -0.837063   Q
    

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

    In [874]: chunker = pd.read_csv('ch06/ex6.csv', chunksize=1000)
    
    In [875]: chunker
    Out[875]: <pandas.io.parsers.TextParser at 0x8398150>
    

    pandas返回的TextParser object能让我们根据chunksize每次迭代文件的一部分。比如,我们想要迭代ex6.csv, 计算key列的值的综合:

    chunker = pd.read_csv('examples/ex6.csv', chunksize=1000)
    
    tot = pd.Series([])
    for piece in chunker:
        tot = tot.add(piece['key'].value_counts(), fill_value=0)
    
    tot = tot.sort_values(ascending=False)
    

    然后有:

    In [40]: tot[:10]
    Out[40]: 
    E    368.0
    X    364.0
    L    346.0
    O    343.0
    Q    340.0
    M    338.0
    J    337.0
    F    335.0
    K    334.0
    H    330.0
    dtype: float64
    

    TextParser有一个get_chunk方法,能返回任意大小的数据片段:

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

    2 Writing Data to Text Format (写入数据到文本格式)

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

    In [41]: data = pd.read_csv('examples/ex5.csv')
    
    In [42]: data
    Out[42]: 
      something  a   b     c   d message
    0       one  1   2   3.0   4     NaN
    1       two  5   6   NaN   8   world
    2     three  9  10  11.0  12     foo
    

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

    In [43]: data.to_csv('examples/out.csv')
    
    In [44]: !cat examples/out.csv
    ,something,a,b,c,d,message
    0,one,1,2,3.0,4,
    1,two,5,6,,8,world
    2,three,9,10,11.0,12,foo
    

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

    In [45]: import sys
    
    In [46]: data.to_csv(sys.stdout, sep='|')
    |something|a|b|c|d|message
    0|one|1|2|3.0|4|
    1|two|5|6||8|world
    2|three|9|10|11.0|12|foo
    

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

    In [47]: data.to_csv(sys.stdout, na_rep='NULL')
    ,something,a,b,c,d,message
    0,one,1,2,3.0,4,NULL
    1,two,5,6,NULL,8,world
    2,three,9,10,11.0,12,foo
    

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

    In [48]: data.to_csv(sys.stdout, index=False, header=False)
    one,1,2,3.0,4,
    two,5,6,,8,world
    three,9,10,11.0,12,foo
    

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

    In [49]: data.to_csv(sys.stdout, index=False, columns=['a', 'b', 'c'])
    a,b,c
    1,2,3.0
    5,6,
    9,10,11.0
    

    Series也有一个to_csv方法:

    In [50]: dates = pd.date_range('1/1/2000', periods=7)
    
    In [51]: ts = pd.Series(np.arange(7), index=dates)
    
    In [52]: ts.to_csv('examples/tseries.csv')
    
    In [53]: !cat examples/tseries.csv
    2000-01-01,0
    2000-01-02,1
    2000-01-03,2
    2000-01-04,3
    2000-01-05,4
    2000-01-06,5
    2000-01-07,6
    

    3. 处理分隔符格式

    对于大部分磁盘中的表格型数据,用pandas.read_table就能解决。不过,有时候一些人工的处理也是需要的。

    当然,有时候,一些格式不正确的行能会把read_table绊倒。为了展示一些基本用法,这里先考虑一个小的CSV文件:

    In [54]: !cat examples/ex7.csv
    "a","b","c"
    "1","2","3"
    "1","2","3"
    

    对于单个字符的分隔符,可以使用python内建的csv方法。只要给csv.reader一个打开的文件即可:

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

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

    In [56]: for line in reader:
       ....:     print(line)
    ['a', 'b', 'c']
    ['1', '2', '3']
    ['1', '2', '3']
    

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

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

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

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

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

    In [59]: data_dict = {h: v for h, v in zip(header, zip(*values))}
    
    In [60]: data_dict
    Out[60]: {'a': ('1', '1'), 'b': ('2', '2'), 'c': ('3', '3')}
    

    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的属性)及其功能如表6-3所示。


    CSV可用的选项

    4.JSON数据

    JSON (short for JavaScript Object Notation)已经是发送HTTP请求的标准数据格式了。
    JSON(JavaScript Object Notation, JS 对象简谱) 是一种轻量级的数据交换格式。它基于 ECMAScript (欧洲计算机协会制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率

    5 XML and HTML: Web Scraping (网络爬取)

    python有很多包用来读取和写入HTML和XML格式。比如:lxml, Beautiful Soup, html5lib。其中lxml比较快,其他一些包则能更好的处理一些复杂的HTML和XML文件。

    pandas有一个内建的函数,叫read_html, 这个函数利用lxml和Beautiful Soup这样的包来自动解析HTML,变为DataFrame。这里我们必须要先下载这些包才能使用read_html:

    conda install lxml
    pip install beautifulsoup4 html5lib
    

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

    In [73]: tables = pd.read_html('examples/fdic_failed_bank_list.html')
    
    In [74]: len(tables)
    Out[74]: 1
    
    In [75]: failures = tables[0]
    
    In [76]: failures.head()
    Out[76]: 
                          Bank Name             City  ST   CERT  \
    0                   Allied Bank         Mulberry  AR     91   
    1  The Woodbury Banking Company         Woodbury  GA  11297   
    2        First CornerStone Bank  King of Prussia  PA  35312   
    3            Trust Company Bank          Memphis  TN   9956   
    4    North Milwaukee State Bank        Milwaukee  WI  20364   
                     Acquiring Institution        Closing Date       Updated Date  
    0                         Today's Bank  September 23, 2016  November 17, 2016  
    1                          United Bank     August 19, 2016  November 17, 2016  
    2  First-Citizens Bank & Trust Company         May 6, 2016  September 6, 2016  
    3           The Bank of Fayette County      April 29, 2016  September 6, 2016  
    4  First-Citizens Bank & Trust Company      March 11, 2016      June 16, 2016
    

    因为failures有许多列,pandas插入了一个换行符\。

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

    In [77]: close_timestamps = pd.to_datetime(failures['Closing Date'])
    
    In [78]: close_timestamps.dt.year.value_counts()
    Out[78]: 
    2010    157
    2009    140
    2011     92
    2012     51
    2008     25
           ... 
    2004      4
    2001      4
    2007      3
    2003      3
    2000      2
    Name: Closing Date, Length: 15, dtype: int64
    

    利用lxml.objectify解析XML

    6.2 二进制数据格式

    实现数据的高效二进制格式存储最简单的办法之一是使用Python内置的pickle序列化。pandas对象都有一个用于将数据以pickle格式保存到磁盘上的to_pickle方法:

    In [87]: frame = pd.read_csv('examples/ex1.csv')
    
    In [88]: frame
    Out[88]: 
       a   b   c   d message
    0  1   2   3   4   hello
    1  5   6   7   8   world
    2  9  10  11  12     foo
    
    In [89]: frame.to_pickle('examples/frame_pickle')
    

    你可以通过pickle直接读取被pickle化的数据,或是使用更为方便的pandas.read_pickle:

    In [90]: pd.read_pickle('examples/frame_pickle')
    Out[90]: 
       a   b   c   d message
    0  1   2   3   4   hello
    1  5   6   7   8   world
    2  9  10  11  12     foo
    

    注意:pickle只推荐用于短期存储。因为这种格式无法保证长期稳定;比如今天pickled的一个文件,可能在库文件更新后无法读取。

    python还支持另外两种二进制数据格式:HDF5和MessagePack。下面会介绍一个HDF5,但是我们鼓励你多尝试一个不同的文件格式,看看他们能有多快,是否符合你数据分析的要求。另外一些可用的存储格式有:bcolz 和 Feather。

    1.使用HDF5格式

    HDF5格式是用来存储大量的科学数组数据的。这种格式还能用于其他一些语言。其中HDF表示hierarchical data format。每一个HDF5格式能春初多个数据集,并支持metadata。

    元数据(meta data)——“data about data” 关于数据的数据,一般是结构化数据(如存储在数据库里的数据,规定了字段的长度、类型等)。元数据是指从信息资源中抽取出来的用于说明其特征、内容的结构化的数据(如题名,版本、出版数据、相关说明,包括检索点等),用于组织、描述、检索、保存、管理信息和知识资源。

    HDF5 支持多种压缩模式的on-the-fly compression(即时压缩),能让数据中一些重复的部分存储地更有效。HDF5对于处理大数据集是一个很好的选择,因为他不会把所有数据一次性读取到内存里,我们可以从很大的数组中有效率地读取一小部分。
    能用PyTables或h5py来访问HDF5数据,pandas也有提供一个high-level的交互界面。HDFStore类像dict一样能用来处理low-level细节:

    In [92]: frame = pd.DataFrame({'a': np.random.randn(100)})
    
    In [93]: store = pd.HDFStore('mydata.h5')
    
    In [94]: store['obj1'] = frame
    
    In [95]: store['obj1_col'] = frame['a']
    
    In [96]: store
    Out[96]: 
    <class 'pandas.io.pytables.HDFStore'>
    File path: mydata.h5
    /obj1                frame        (shape->[100,1])                               
            
    /obj1_col            series       (shape->[100])                                 
            
    /obj2                frame_table  (typ->appendable,nrows->100,ncols->1,indexers->
    [index])
    /obj3                frame_table  (typ->appendable,nrows->100,ncols->1,indexers->
    [index])
    

    HDF5文件中的对象可以通过与字典一样的API进行获取:

    In [97]: store['obj1']
    Out[97]: 
               a
    0  -0.204708
    1   0.478943
    2  -0.519439
    3  -0.555730
    4   1.965781
    ..       ...
    95  0.795253
    96  0.118110
    97 -0.748532
    98  0.584970
    99  0.152677
    [100 rows x 1 columns]
    

    HDFStore支持两种存储模式,'fixed'和'table'。后者通常会更慢,但是支持使用特殊语法进行查询操作:

    In [98]: store.put('obj2', frame, format='table')
    
    In [99]: store.select('obj2', where=['index >= 10 and index <= 15'])
    Out[99]: 
               a
    10  1.007189
    11 -1.296221
    12  0.274992
    13  0.228913
    14  1.352917
    15  0.886429
    
    In [100]: store.close()
    

    pandas.read_hdf函数可以快捷使用这些工具:

    In [101]: frame.to_hdf('mydata.h5', 'obj3', format='table')
    
    In [102]: pd.read_hdf('mydata.h5', 'obj3', where=['index < 5'])
    Out[102]: 
              a
    0 -0.204708
    1  0.478943
    2 -0.519439
    3 -0.555730
    4  1.965781
    

    2 Reading Microsoft Excel Files(读取微软的excel文件)

    pandas支持读取表格型数据(excel 2003或更高)文件,使用ExcelFile class或pandas.read_excel函数。这些工具需要一些附加的包xlrd和openpyxl来分别读取XLS和XLSX文件。你可以通过pip或conda来安装。

    使用ExcelFile,创建一个instance,通过给xls或xlsx一个路径:

    In [7]:  xlsx = pd.ExcelFile(r'C:\Users\yujiawen\Desktop\新建 Microsoft Excel 工作表.xlsx')
    
    In [8]: pd.read_excel(xlsx, 'Sheet1')
    Out[8]:
             J       JL         JK  Unnamed: 3     固定     斜率      转移电子
    0  4.41204  4.78128  57.131401         NaN  28.15  11.55  2.437229
    

    如果要读取一个文件中的多个表单,创建ExcelFile会更快,但你也可以将文件名传递到pandas.read_excel:

    In [9]: frame = pd.read_excel(r'C:\Users\yujiawen\Desktop\新建 Microsoft Excel 工作表.xlsx', 'Sheet1')
    
    In [10]: frame
    Out[10]:
             J       JL         JK  Unnamed: 3     固定     斜率      转移电子
    0  4.41204  4.78128  57.131401         NaN  28.15  11.55  2.437229
    

    如果要将pandas数据写入为Excel格式,你必须首先创建一个ExcelWriter,然后使用pandas对象的to_excel方法将数据写入到其中:

    In [108]: writer = pd.ExcelWriter('examples/ex2.xlsx')
    
    In [109]: frame.to_excel(writer, 'Sheet1')
    
    In [110]: writer.save()
    

    你还可以不使用ExcelWriter,而是传递文件的路径到to_excel:

    In [111]: frame.to_excel('examples/ex2.xlsx')
    

    6.3 Interacting with Web APIs (网络相关的API交互)

    API(Application Programming Interface,应用程序编程接口)是一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而又无需访问源码,或理解内部工作机制的细节。
    许多网站都有一些通过JSON或其他格式提供数据的公共API。通过Python访问这些API的办法有不少。一个简单易用的办法(推荐)是requests包(http://docs.python-requests.org)。
    找到github里pandas最新的30个issues,制作一个GET HTTP request, 通过使用requests包:

    In [11]: import pandas as pd
        ...: import numpy as np
        ...:
    In [12]: import requests
    
    In [13]:
    
    In [13]:  url = 'https://api.github.com/repos/pandas-dev/pandas/issues'
    
    In [14]: resp = requests.get(url)
    
    In [15]: resp
    Out[15]: <Response [200]>
    

    response的json方法能返回一个dict,包含可以解析为python object的JSON:

    In [16]: data = resp.json()
    
    In [17]:  data[0]['title']
    Out[17]: 'API: Remove CalendarDay'
    In [18]: data[0]
    Out[18]:
    {'url': 'https://api.github.com/repos/pandas-dev/pandas/issues/24330',
     'repository_url': 'https://api.github.com/repos/pandas-dev/pandas',
     'labels_url': 'https://api.github.com/repos/pandas-dev/pandas/issues/24330/labels{/name}',
     'comments_url': 'https://api.github.com/repos/pandas-dev/pandas/issues/24330/comments',
     'events_url': 'https://api.github.com/repos/pandas-dev/pandas/issues/24330/events',
     'html_url': 'https://github.com/pandas-dev/pandas/pull/24330',
     'id': 392011696,
     'node_id': 'MDExOlB1bGxSZXF1ZXN0MjM5Mzc2MDk4',
     'number': 24330,
     'title': 'API: Remove CalendarDay',
     'user': {'login': 'mroeschke',
      'id': 10647082,
      'node_id': 'MDQ6VXNlcjEwNjQ3MDgy',
      'avatar_url': 'https://avatars0.githubusercontent.com/u/10647082?v=4',
      'gravatar_id': '',
      'url': 'https://api.github.com/users/mroeschke',
      'html_url': 'https://github.com/mroeschke',
      'followers_url': 'https://api.github.com/users/mroeschke/followers',
      'following_url': 'https://api.github.com/users/mroeschke/following{/other_user}',
      'gists_url': 'https://api.github.com/users/mroeschke/gists{/gist_id}',
      'starred_url': 'https://api.github.com/users/mroeschke/starred{/owner}{/repo}',
      'subscriptions_url': 'https://api.github.com/users/mroeschke/subscriptions',
      'organizations_url': 'https://api.github.com/users/mroeschke/orgs',
      'repos_url': 'https://api.github.com/users/mroeschke/repos',
      'events_url': 'https://api.github.com/users/mroeschke/events{/privacy}',
      'received_events_url': 'https://api.github.com/users/mroeschke/received_events',
      'type': 'User',
      'site_admin': False},
     'labels': [],
     'state': 'open',
     'locked': False,
     'assignee': None,
     'assignees': [],
     'milestone': {'url': 'https://api.github.com/repos/pandas-dev/pandas/milestones/55',
      'html_url': 'https://github.com/pandas-dev/pandas/milestone/55',
      'labels_url': 'https://api.github.com/repos/pandas-dev/pandas/milestones/55/labels',
      'id': 3228419,
      'node_id': 'MDk6TWlsZXN0b25lMzIyODQxOQ==',
      'number': 55,
      'title': '0.24.0',
      'description': '',
      'creator': {'login': 'jorisvandenbossche',
       'id': 1020496,
       'node_id': 'MDQ6VXNlcjEwMjA0OTY=',
       'avatar_url': 'https://avatars2.githubusercontent.com/u/1020496?v=4',
       'gravatar_id': '',
       'url': 'https://api.github.com/users/jorisvandenbossche',
       'html_url': 'https://github.com/jorisvandenbossche',
       'followers_url': 'https://api.github.com/users/jorisvandenbossche/followers',
       'following_url': 'https://api.github.com/users/jorisvandenbossche/following{/other_user}',
       'gists_url': 'https://api.github.com/users/jorisvandenbossche/gists{/gist_id}',
       'starred_url': 'https://api.github.com/users/jorisvandenbossche/starred{/owner}{/repo}',
       'subscriptions_url': 'https://api.github.com/users/jorisvandenbossche/subscriptions',
       'organizations_url': 'https://api.github.com/users/jorisvandenbossche/orgs',
       'repos_url': 'https://api.github.com/users/jorisvandenbossche/repos',
       'events_url': 'https://api.github.com/users/jorisvandenbossche/events{/privacy}',
       'received_events_url': 'https://api.github.com/users/jorisvandenbossche/received_events',
       'type': 'User',
       'site_admin': False},
      'open_issues': 33,
      'closed_issues': 1611,
      'state': 'open',
      'created_at': '2018-03-29T12:00:12Z',
      'updated_at': '2018-12-18T06:22:35Z',
      'due_on': '2018-12-31T08:00:00Z',
      'closed_at': None},
     'comments': 3,
     'created_at': '2018-12-18T06:16:42Z',
     'updated_at': '2018-12-18T06:56:49Z',
     'closed_at': None,
     'author_association': 'MEMBER',
     'pull_request': {'url': 'https://api.github.com/repos/pandas-dev/pandas/pulls/24330',
      'html_url': 'https://github.com/pandas-dev/pandas/pull/24330',
      'diff_url': 'https://github.com/pandas-dev/pandas/pull/24330.diff',
      'patch_url': 'https://github.com/pandas-dev/pandas/pull/24330.patch'},
     'body': '- [x] tests added / passed\r\n- [x] passes `git diff upstream/master -u -- "*.py" | flake8 --diff`\r\n\r\nGiven that we want to ship 0.24.0 soon and that converting `\'D\'` and `Day` to always act as calendar day warrants more game-planning, this PR just simply removes `CalendarDay` and reverts `\'D\'` and `Day` to their prior behavior. '}
    

    data中的每一个元素都是一个dict,这个dict就是在github上找到的issue页面上的信息。我们可以把data传给DataFrame并提取感兴趣的部分:

    In [19]: issues = pd.DataFrame(data, columns=['number', 'title',
        ...:                                     'labels', 'state'])
        ...: issues
        ...:
    Out[19]:
        number                                              title                                             labels state
    0    24330                            API: Remove CalendarDay                                                 []  open
    1    24329  BUG: Timestamp(Timestamp(Ambiguous time)) modi...  [{'id': 76811, 'node_id': 'MDU6TGFiZWw3NjgxMQ=...  open
    2    24328   Fixed PEP8 errors in doc/source/whatsnew/v0.15.*                                                 []  open
    3    24327  ERR: Improve error message for cut with infini...  [{'id': 42670965, 'node_id': 'MDU6TGFiZWw0MjY3...  open
    4    24326  Fixtures making IntNA tests difficult to run i...                                                 []  open
    5    24325         pandas.Series docstrings dtype information  [{'id': 134699, 'node_id': 'MDU6TGFiZWwxMzQ2OT...  open
    6    24324  REF/TST: Add more pytest idiom to resample/tes...                                                 []  open
    7    24323  API: Remove 'codes' parameter from MultiIndex ...                                                 []  open
    8    24322  DOC: Fixes flake8 issues in whatsnew v0.13.* #...  [{'id': 211029535, 'node_id': 'MDU6TGFiZWwyMTE...  open
    9    24321  Doc on pandas.read_parquet says path is a stri...  [{'id': 134699, 'node_id': 'MDU6TGFiZWwxMzQ2OT...  open
    10   24319     BUG: Ensure incomplete stata files are deleted                                                 []  open
    11   24318  hash_pandas_object fails on empty dataframe wi...  [{'id': 76811, 'node_id': 'MDU6TGFiZWw3NjgxMQ=...  open
    12   24314        Numpy 'inf' values cause pandas.cut to fail  [{'id': 42670965, 'node_id': 'MDU6TGFiZWw0MjY3...  open
    13   24312  Segmentation fault while loading csv into data...  [{'id': 31932467, 'node_id': 'MDU6TGFiZWwzMTkz...  open
    14   24310       BUG: Series.__repr__ crashing with tzlocal()                                                 []  open
    15   24305  DOC/API: PeriodIndex sub/add with Integers: up...  [{'id': 35818298, 'node_id': 'MDU6TGFiZWwzNTgx...  open
    16   24303       DOC: Fix flake8 issues with whatsnew v0.18.*  [{'id': 211029535, 'node_id': 'MDU6TGFiZWwyMTE...  open
    17   24302    Series.value_counts: Preserve original ordering                                                 []  open
    18   24301                    EA ops alignment with DataFrame                                                 []  open
    19   24295  Elegant way to generate indexable sliding wind...                                                 []  open
    20   24294  BUG: Fix index bug due to parse_time_string GH...  [{'id': 76811, 'node_id': 'MDU6TGFiZWw3NjgxMQ=...  open
    21   24293  standardize signature for Index reductions, im...  [{'id': 35818298, 'node_id': 'MDU6TGFiZWwzNTgx...  open
    22   24288  DOC: Fix docstrings with the sections in the w...  [{'id': 134699, 'node_id': 'MDU6TGFiZWwxMzQ2OT...  open
    23   24282                 WIP: decorator for ops boilerplate  [{'id': 211029535, 'node_id': 'MDU6TGFiZWwyMTE...  open
    24   24280  DOC: Fix docstrings with the sections in the w...  [{'id': 211029535, 'node_id': 'MDU6TGFiZWwyMTE...  open
    25   24279                Added extra info section to EA repr  [{'id': 849023693, 'node_id': 'MDU6TGFiZWw4NDk...  open
    26   24278  Add an "extra info" section to the base Extens...  [{'id': 849023693, 'node_id': 'MDU6TGFiZWw4NDk...  open
    27   24275        GH24241 make Categorical.map transform nans  [{'id': 78527356, 'node_id': 'MDU6TGFiZWw3ODUy...  open
    28   24274  BLD: for C extension builds on mac, target mac...  [{'id': 129350, 'node_id': 'MDU6TGFiZWwxMjkzNT...  open
    29   24271  sort_index not sorting when multi-index made b...                                                 []  open
    

    通过一些体力活,我们可以构建一些高层级的界面,让web API直接返回DataFrame格式,以便于分析。

    6.4 Interacting with Databases(与数据库的交互)

    如果在工作中,大部分数据并不会以text或excel的格式存储。最广泛使用的是SQL-based的关系型数据库(SQL Server,PostgreSQL,MySQL)。选择数据库通常取决于性能,数据整合性,实际应用的可扩展性。

    读取SQL到DataFrame非常直观,pandas中有一些函数能简化这个过程。举个例子,这里创建一个SQLite数据库,通过使用python内建的sqlite3 driver:

    In [20]: import sqlite3
        ...: import pandas as pd
        ...:
        ...:
    
    In [21]: query = """
        ...: CREATE TABLE test
        ...: (a VARCHAR(20), b VARCHAR(20),
        ...:  c REAL,        d INTEGER
        ...: );"""
    
    In [22]: con = sqlite3.connect('mydata.sqlite')
    
    In [23]: con.execute(query)
    Out[23]: <sqlite3.Cursor at 0x1aa05e626c0>
    
    In [24]: con.commit()
    

    然后插入几行数据:

    In [25]: data = [('Atlanta', 'Georgia', 1.25, 6),
        ...:         ('Tallahassee', 'Florida', 2.6, 3),
        ...:         ('Sacramento', 'California', 1.7, 5)]
        ...:
    
    In [26]: stmt = "INSERT INTO test VALUES(?, ?, ?, ?)"
    
    In [27]: con.executemany(stmt, data)
    Out[27]: <sqlite3.Cursor at 0x1aa02d1f340>
    
    In [28]: con.commit()
    

    从表中选取数据时,大部分Python SQL驱动器(PyODBC、psycopg2、MySQLdb、pymssql等)都会返回一个元组列表:

    In [29]: cursor = con.execute('select * from test')
    
    In [30]: rows = cursor.fetchall()
    
    In [31]: rows
    Out[31]:
    [('Atlanta', 'Georgia', 1.25, 6),
     ('Tallahassee', 'Florida', 2.6, 3),
     ('Sacramento', 'California', 1.7, 5)]
    

    你可以将这个元组列表传给DataFrame构造器,但还需要列名(位于光标的description属性中):

    In [32]: cursor.description
    Out[32]:
    (('a', None, None, None, None, None, None),
     ('b', None, None, None, None, None, None),
     ('c', None, None, None, None, None, None),
     ('d', None, None, None, None, None, None))
    
    In [33]: pd.DataFrame(rows, columns=[x[0] for x in cursor.description])
    Out[33]:
                 a           b     c  d
    0      Atlanta     Georgia  1.25  6
    1  Tallahassee     Florida  2.60  3
    2   Sacramento  California  1.70  5
    

    我们不希望每次询问数据库的时候都重复以上步骤,这样对计算机很不好(mung,【计】逐步对计算机系统或文件做小改动导致大的损害)。SQLAlchemy计划是一个六星的Python SQL工具箱,它能抽象出不同SQL数据库之间的不同。pandas有一个read_sql函数,能让我们从SQLAlchemy connection从读取数据。这里我们用SQLAlchemy连接到同一个SQLite数据库,并从之前创建的表格读取数据:

    In [135]: import sqlalchemy as sqla
    
    In [136]: db = sqla.create_engine('sqlite:///mydata.sqlite')
    
    In [137]: pd.read_sql('select * from test', db)
    Out[137]: 
                 a           b     c  d
    0      Atlanta     Georgia  1.25  6
    1  Tallahassee     Florida  2.60  3
    2   Sacramento  California  1.70  5
    

    相关文章

      网友评论

          本文标题:12-16、17第06章 数据加载、存储与文件格式

          本文链接:https://www.haomeiwen.com/subject/vvxqkqtx.html