Python For Data Analysis-六章第一节

《Python For Data Analysis》的第六章主要讨论的是从各类数据源将数据变成dataframe。第六章的第一节主要围绕读取各种格式的文本文件数据的函数展开。

11.1 读取csv文本文件数据

csv文件的特点是数据有规律且多行,每行用特定的符号(一般是逗号,也用用分号和冒号、空格的)间隔各个字段数据。pandas提供了read_csv、readtable等函数可以直接将csv数据文件里的数据读取到dataframe文件里去,就不用python先用readlines读再处理那么麻烦了。 下面以机器学习领域里的一个知名的csv数据集文件iris.data为例使用一下这些函数。首先可以从该文件的官网下载此文件,然后保存到和程序同目录下。

  • read_csv读取csv文件到dataframe里
import pandas as pd
import numpy as np
d = pd.read_csv("./iris.data")
print type(d), "# type(d)"
print d.iloc[:10], "# d.iloc[:10]"

执行结果:

<class 'pandas.core.frame.DataFrame'> # type(d)
   5.1  3.5  1.4  0.2  Iris-setosa
0  4.9  3.0  1.4  0.2  Iris-setosa
1  4.7  3.2  1.3  0.2  Iris-setosa
2  4.6  3.1  1.5  0.2  Iris-setosa
3  5.0  3.6  1.4  0.2  Iris-setosa
4  5.4  3.9  1.7  0.4  Iris-setosa
5  4.6  3.4  1.4  0.3  Iris-setosa
6  5.0  3.4  1.5  0.2  Iris-setosa
7  4.4  2.9  1.4  0.2  Iris-setosa
8  4.9  3.1  1.5  0.1  Iris-setosa
9  5.4  3.7  1.5  0.2  Iris-setosa # d.iloc[:10]

这里read_csv将iris.data文件里的多行多列数据直接读取到dataframe数据d里了!但是第一行的结果不太满意,是数据而不是字段名,不急等演示完read_table函数后再处理。

  • read_table函数读取csv文件。
import pandas as pd
import numpy as np
d = pd.read_table("./iris.data")
print type(d), "# type(d)"
print d.iloc[:10], "# d.iloc[:10]"

程序的执行结果和效果和read_csv函数一样。

  • header形参可以说明数据文件是否有文件头,即数据文件的第一行不是数据而是数据的字段名信息。上边的两个程序分别使用了read_csv和read_table函数,都读取了iris.data文件到dataframe数据里了,但第一行(5.1 3.5 1.4 0.2 Iris-setosa)没有行标签,被认为是列(字段)的名字(参看另一个csv文件winequality-red.csv),那么可知两个函数均将第一行当作了非数据了(有很多的csv文件的前几行是文件的说明,有时数据上一行是各个字段名)视为字段名数据了。这时可以使用参数header,告诉函数此csv文件全都是数据,没有字段信息数据,即每行都是数据(如果非要给数据配上字段名可以用names指定字段名)。
import pandas as pd
import numpy as np
d = pd.read_csv("./iris.data", header = None)
print d.iloc[:10], "# d.iloc[:10]"

执行结果:

     0    1    2    3            4
0  5.1  3.5  1.4  0.2  Iris-setosa
1  4.9  3.0  1.4  0.2  Iris-setosa
2  4.7  3.2  1.3  0.2  Iris-setosa
3  4.6  3.1  1.5  0.2  Iris-setosa
4  5.0  3.6  1.4  0.2  Iris-setosa
5  5.4  3.9  1.7  0.4  Iris-setosa
6  4.6  3.4  1.4  0.3  Iris-setosa
7  5.0  3.4  1.5  0.2  Iris-setosa
8  4.4  2.9  1.4  0.2  Iris-setosa
9  4.9  3.1  1.5  0.1  Iris-setosa # d.iloc[:10]

结果好些了,但是还是有些问题, (1). 列名字都是整形数据不太清楚是啥,解决很简单可以指定names来修改。 (2). 如果.csv文件头几行有数据描述和字段名,又如何处理?

  • names形参指定字段名,对于上边程序出现的第一个问题,每行就用数字标识还可以,毕竟是一条条的记录,但列名字还是写出来比较好,用names形参为dataframe指定列的字段名字信息col。在iris.data提供的网站有说明文件的各个字段是什么,请自行前往阅读。
import pandas as pd
import numpy as np
col = "sepal_length,sepal_width,petal_length,petal_width,class".split(",")
d = pd.read_csv("./iris.data", header = None, names = col)
print "-" * 40
print d.iloc[:10], "# d.iloc[:10]"
print "-" * 40
print d["sepal_width"][:10], '# d["sepal_width"][:10]'
print "-" * 40
print d.columns, "# d.columns"
print "-" * 40

执行结果:

----------------------------------------
   sepal_length  sepal_width     ...       petal_width        class
0           5.1          3.5     ...               0.2  Iris-setosa
1           4.9          3.0     ...               0.2  Iris-setosa
2           4.7          3.2     ...               0.2  Iris-setosa
3           4.6          3.1     ...               0.2  Iris-setosa
4           5.0          3.6     ...               0.2  Iris-setosa
5           5.4          3.9     ...               0.4  Iris-setosa
6           4.6          3.4     ...               0.3  Iris-setosa
7           5.0          3.4     ...               0.2  Iris-setosa
8           4.4          2.9     ...               0.2  Iris-setosa
9           4.9          3.1     ...               0.1  Iris-setosa

[10 rows x 5 columns] # d.iloc[:10]
----------------------------------------
0    3.5
1    3.0
2    3.2
3    3.1
4    3.6
5    3.9
6    3.4
7    3.4
8    2.9
9    3.1
Name: sepal_width, dtype: float64 # d["sepal_width"][:10]
----------------------------------------
Index([u'sepal_length', u'sepal_width', u'petal_length', u'petal_width',
       u'class'],
      dtype='object') # d.columns
----------------------------------------

总之,read_csv和read_table会将数据文件第一行认做数据的字段名集合,如果不是用header=None告诉两个函数。如果数据文件第一行是字段信息那么就不用header好了。使用names可以为纯数据文本文件配以字段名信息。

  • seq形参,可以指定数据文本文件不是以逗号间隔的数据行,默认情况下read_csv和read_table读取每行文件时为了区分得到各个字段的值,是以逗号为默认值找到各个字段的值的,但有的数据文件则是以其他分割符号分割数据的各个字段的,下面的这个经典数据集文件winequality-red.csv就是以分号间隔每行各个字段的值,那么在使用read_csv和read_table函数时可以使用sep形参告知函数,字段分割符是什么。
import pandas as pd
import numpy as np
col = "sepal_length,sepal_width,petal_length,petal_width,class".split(",")
d = pd.read_csv("./winequality-red.csv", sep = ";")
print "-" * 40
print d.iloc[:10], "# d.iloc[:10]"
print "-" * 40
print d.columns, "# d.columns"
print "-" * 40

执行结果:

----------------------------------------
   fixed acidity  volatile acidity   ...     alcohol  quality
0            7.4              0.70   ...         9.4        5
1            7.8              0.88   ...         9.8        5
2            7.8              0.76   ...         9.8        5
3           11.2              0.28   ...         9.8        6
4            7.4              0.70   ...         9.4        5
5            7.4              0.66   ...         9.4        5
6            7.9              0.60   ...         9.4        5
7            7.3              0.65   ...        10.0        7
8            7.8              0.58   ...         9.5        7
9            7.5              0.50   ...        10.5        5

[10 rows x 12 columns] # d.iloc[:10]
----------------------------------------
Index([u'fixed acidity', u'volatile acidity', u'citric acid',
       u'residual sugar', u'chlorides', u'free sulfur dioxide',
       u'total sulfur dioxide', u'density', u'pH', u'sulphates', u'alcohol',
       u'quality'],
      dtype='object') # d.columns
----------------------------------------

winequality-red.csv数据文件的第一行是字段信息,所以就不用header形参了,其下各行是数据,行内字段值间用分号(;)间隔而不是逗号(,),所以需要使用sep参数告知函数d = pd.read_csv("./winequality-red.csv", sep = ";")

  • skiprows形参可以忽略掉数据文件的若干行数据。有点时候,数据文件在有效果数据前会有一些信息来说明、介绍这个数据文件,这些信息是不应当被读入到dataframe数据里去的,应该去掉这些信息,例如下面这个数据文件:
# 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
....

那么第0、2、3行文字,可以视为注释或者说明,不是数据,是没有用的,不需要读取到dataframe里去的,可以使用skiprows参数去掉这几行不读入到dataframe里。假设这个数据文件为“ts.csv”和程序文件在同一个目录下

import pandas as pd
import numpy as np
d = pd.read_csv("./ts.csv", skiprows = [0,2,3])
print "-" * 40
print d.iloc[:10], "# d.iloc[:10]"
print "-" * 40
print d.columns, "# d.columns"
print "-" * 40

执行结果:

----------------------------------------
   a   b   c   d message
0  1   2   3   4   hello
1  5   6   7   8   world
2  9  10  11  12     foo # d.iloc[:10]
----------------------------------------
Index([u'a', u'b', u'c', u'd', u'message'], dtype='object') # d.columns
----------------------------------------
  • na_values形参,可以进行数据缺失处理和无效数据预处理。(1).有的时候,csv等数据文件的每行不是都有各个字段值的,即字段缺失数据,不是错是真没有。函数read_csv在读取数据文件时,会自动对列上的缺失数据进行必要的处理设定NaN。(2).有的时候,数据文件某列下出现了某值(不该出现这个值,无效),也可以在读取时被处理成NaN,可以使用na_values参数完成。某列有可能程序比较多的无效数据,可以采用字典给出各个字段在出现某些值时要被视为NaN。以下面的数据文件为例:
# 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
3,31,,the
11,12,,cruel
5,6,7,8,world
9,,11,12,xaxa

数据文件(ts.csv)里有些行的列上是无数据,例如3,31,,the却c列的数据,函数会自动处理为NaN。 如果b列下出现了6、a列里有3、c列各行有7或者11,如果应用程序认为这些值不该出现在各自的列里,那么可以用na_values来处理读取时也设置为NaN。

import pandas as pd
import numpy as np
deal = {"a": [3], "b":[6], "c" : [7, 11], "d" :[400], "message" : "the" }
d = pd.read_csv("./ts.csv", skiprows = [0,2,3], na_values = deal)
print "-" * 40
print d.iloc[:10], "# d.iloc[:10]"
print "-" * 40
print d.columns, "# d.columns"
print "-" * 40
print d.isnull(), "#d.isnull()"

执行结果:

----------------------------------------
      a     b    c     d message
0   1.0   2.0  3.0   4.0   hello
1   NaN  31.0  NaN   NaN     NaN
2  11.0  12.0  NaN   NaN   cruel
3   5.0   NaN  NaN   8.0   world
4   9.0   NaN  NaN  12.0    xaxa # d.iloc[:10]
----------------------------------------
Index([u'a', u'b', u'c', u'd', u'message'], dtype='object') # d.columns
----------------------------------------
       a      b      c      d  message
0  False  False  False  False    False
1   True  False   True   True     True
2  False  False   True   True    False
3  False   True   True  False    False
4  False   True   True  False    False #d.isnull()

语句pd.read_csv("./ts.csv", skiprows = [0,2,3], na_values = deal)na_values = deal的意思是当读取数据文件时, a列字段值如果3,认为数据不合理,处理成NaN,c列下的值如果出现7或者11被处理成NaN。

  • nrows可以实现只读数据文件的多少行数据。
import pandas as pd
import numpy as np
col = "sepal_length,sepal_width,petal_length,petal_width,class".split(",")
d0 = pd.read_csv("./iris.data", header = None, names = col, nrows = 5)
print d0, "# d0"

执行结果:

   sepal_length  sepal_width     ...       petal_width        class
0           5.1          3.5     ...               0.2  Iris-setosa
1           4.9          3.0     ...               0.2  Iris-setosa
2           4.7          3.2     ...               0.2  Iris-setosa
3           4.6          3.1     ...               0.2  Iris-setosa
4           5.0          3.6     ...               0.2  Iris-setosa

[5 rows x 5 columns] # d0
  • chunksize可以将一个大的数据文件按chunksize大小分割成若干个小块,一块一块的读数据文件,通过迭代器读取完数据文件。chunksize可以解决内存小、数据文件大的问题,分块读取,小马拉大车。
import pandas as pd
import numpy as np
col = "sepal_length,sepal_width,petal_length,petal_width,class".split(",")
dc = pd.read_csv("./iris.data", chunksize = 50, names = col)
d = pd.DataFrame({}, columns = col)
print "-" * 40
print d, "# d empty!"
print "-" * 40
i = 0
for v in dc:
    d = d.append(v,sort=True)
    print v.iloc[:5], "# v times is",i, type(v)
    i += 1 
print "-" * 40
print d.iloc[:10], "# d.iloc[:10]"
print "-" * 40
print d.iloc[-10:], "# d empty?"
print "-" * 40

irir.data文件共150行数据,通过语句pd.read_csv("./iris.data", chunksize = 50, names = col)里的chunksize = 50设置每次读取50行,那么读几次呢? $\frac{150}{50} = 3$次。再通过迭代读取每块的数据到dataframe数据d里:

for v in dc:
    d = d.append(v,sort=True)

迭代3次把文件全部读完,每次读取都是一个长度为50条记录的dataframe。执行结果如下:

----------------------------------------
Empty DataFrame
Columns: [sepal_length, sepal_width, petal_length, petal_width, class]
Index: [] # d empty!
----------------------------------------
   sepal_length  sepal_width     ...       petal_width        class
0           5.1          3.5     ...               0.2  Iris-setosa
1           4.9          3.0     ...               0.2  Iris-setosa
2           4.7          3.2     ...               0.2  Iris-setosa
3           4.6          3.1     ...               0.2  Iris-setosa
4           5.0          3.6     ...               0.2  Iris-setosa

[5 rows x 5 columns] # v times is 0 <class 'pandas.core.frame.DataFrame'>
    sepal_length  sepal_width       ...         petal_width            class
50           7.0          3.2       ...                 1.4  Iris-versicolor
51           6.4          3.2       ...                 1.5  Iris-versicolor
52           6.9          3.1       ...                 1.5  Iris-versicolor
53           5.5          2.3       ...                 1.3  Iris-versicolor
54           6.5          2.8       ...                 1.5  Iris-versicolor

[5 rows x 5 columns] # v times is 1 <class 'pandas.core.frame.DataFrame'>
     sepal_length  sepal_width       ...        petal_width           class
100           6.3          3.3       ...                2.5  Iris-virginica
101           5.8          2.7       ...                1.9  Iris-virginica
102           7.1          3.0       ...                2.1  Iris-virginica
103           6.3          2.9       ...                1.8  Iris-virginica
104           6.5          3.0       ...                2.2  Iris-virginica

[5 rows x 5 columns] # v times is 2 <class 'pandas.core.frame.DataFrame'>
----------------------------------------
         class  petal_length     ...       sepal_length  sepal_width
0  Iris-setosa           1.4     ...                5.1          3.5
1  Iris-setosa           1.4     ...                4.9          3.0
2  Iris-setosa           1.3     ...                4.7          3.2
3  Iris-setosa           1.5     ...                4.6          3.1
4  Iris-setosa           1.4     ...                5.0          3.6
5  Iris-setosa           1.7     ...                5.4          3.9
6  Iris-setosa           1.4     ...                4.6          3.4
7  Iris-setosa           1.5     ...                5.0          3.4
8  Iris-setosa           1.4     ...                4.4          2.9
9  Iris-setosa           1.5     ...                4.9          3.1

[10 rows x 5 columns] # d.iloc[:10]
----------------------------------------
              class  petal_length     ...       sepal_length  sepal_width
140  Iris-virginica           5.6     ...                6.7          3.1
141  Iris-virginica           5.1     ...                6.9          3.1
142  Iris-virginica           5.1     ...                5.8          2.7
143  Iris-virginica           5.9     ...                6.8          3.2
144  Iris-virginica           5.7     ...                6.7          3.3
145  Iris-virginica           5.2     ...                6.7          3.0
146  Iris-virginica           5.0     ...                6.3          2.5
147  Iris-virginica           5.2     ...                6.5          3.0
148  Iris-virginica           5.4     ...                6.2          3.4
149  Iris-virginica           5.1     ...                5.9          3.0

[10 rows x 5 columns] # d empty?
----------------------------------------

chunksize参数可以实现解决大数据文件的分块读写的功能。

11.2 数据写入文本文件

dataframe数据可以调用to_csv函数将数据输出写入到对于类似于csv的文本文件。

import pandas as pd
import numpy as np
col = "sepal_length,sepal_width,petal_length,petal_width,class".split(",")
d0 = pd.read_csv("./iris.data", header = None, names = col, nrows = 5)
print d0, "# d0"
d0.to_csv("./iris_out.csv", index=False, header=False)

11.3 csv模块读数据文件

本节的内容是书中《Working with Delimited Formats》节次的笔记。内容本身不难,就是用csv读数据文件,然后组成dataframe,但其中有些知识点儿很有参考价值。

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

这句话很有内容。 * zip()可以将长度为m的n个的序列,整合成m个长度为n个的元组的列表。

i = range(1,11)
c = list("abcdefghij")
t = list("klmnopqrst")
print "i->", i
print "c->", c
print "t->", t
m_ict = zip(i, c, t)

这里有3个长度为10的列表,通过zip得到的m_ict是由10个长度为3的元组组成的列表。

i-> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
c-> ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
t-> ['k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't']
[(1, 'a', 'k'), (2, 'b', 'l'), (3, 'c', 'm'), (4, 'd', 'n'), (5, 'e', 'o'), (6, 'f', 'p'), (7, 'g', 'q'), (8, 'h', 'r'), (9, 'i', 's'), (10, 'j', 't')] # m_ict
  • zip(*)可以将元素为n长度的长度为m的二维列表,展开成n个长度为m元的二维列表,可以理解为是zip()的逆过程。
i = range(1,11)
c = list("abcdefghij")
t = list("klmnopqrst")
#print "i->", i
#print "c->", c
#print "t->", t
m_ict = zip(i, c, t)
print m_ict, "# m_ict = zip(i, c, t)"
print zip(*m_ict), "# zip(*m_ict)"

结果:

[(1, 'a', 'k'), (2, 'b', 'l'), (3, 'c', 'm'), (4, 'd', 'n'), (5, 'e', 'o'), (6, 'f', 'p'), (7, 'g', 'q'), (8, 'h', 'r'), (9, 'i', 's'), (10, 'j', 't')] # m_ict = zip(i, c, t)
[(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'), ('k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't')] # zip(*m_ict)

结果的每个元素就是zip()里的每个列表i、c、t。

  • {h: v for h, v in zip(header, zip(*values))}语句的理解,之前见过列表解析:
li = [x for x in range(10)]

和这个很像,h:v类似于列表解析里方括号后的x。那么这个表示实际就是构造了一个字典,用header里的每个值做键,用zip(*values))里的每个数据做值。

k = ["c", "t"]
c = list("abcdefghij")
t = list("klmnopqrst")
v = zip(c, t)
d = {kk : vv for kk, vv in zip(k, zip(*v))}
print "-" * 40
print d, "# d"
print "-" * 40
df = pd.DataFrame(d)
print df, "# df"
print "-" * 40
print zip(*v), "# zip(*v)"
print "-" * 40

执行结果如下:

----------------------------------------
{'c': ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'), 't': ('k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't')} # d
----------------------------------------
   c  t
0  a  k
1  b  l
2  c  m
3  d  n
4  e  o
5  f  p
6  g  q
7  h  r
8  i  s
9  j  t # df
----------------------------------------
[('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'), ('k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't')]# zip(*v)
----------------------------------------

11.4 JSON数据

JSON数据是http服务器和各类与web相关应用间可传递数据的格式,形式很像字典,但要求key必须是字符串,而值values可以是任意的数据类型。Python有json模块,可以直接读取json数据成Python的字典数据。

import json
j = '''
{"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"]}]
}
'''
ret = json.loads(j)
print "-" * 40
print ret,type(ret)
print "-" * 40
print pd.DataFrame(ret['siblings'])
print "-" * 40

执行结果:

----------------------------------------
{u'pet': None, u'siblings': [{u'age': 30, u'name': u'Scott', u'pets': [u'Zeus', u'Zuko']}, {u'age': 38, u'name': u'Katie', u'pets': [u'Sixes', u'Stache', u'Cisco']}], u'name': u'Wes', u'places_lived': [u'United States', u'Spain', u'Germany']} <type 'dict'>
----------------------------------------
   age   name                    pets
0   30  Scott            [Zeus, Zuko]
1   38  Katie  [Sixes, Stache, Cisco]
----------------------------------------

11.5 在线读表格数据

pandas的read_html函数可以读取网页里的<table>标签表格数据,一个网页里也许有多个表格,那么函数的返回值就是一个列表,每个元素就是一个表格的数据并以DataFrame形式存在。

import pandas as pd
url = 'http://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.html'
df = pd.read_html(url)
print "-" * 40
print type(df), len(df), "# type(df), len(df)"
print type(df[1]), len(df[1]), "# type(df[1]), len(df[1])"
print "-" * 40
print df[1][:5]
print "-" * 40
print df[1].columns,df[1].index
print "-" * 40

执行结果:

----------------------------------------
<type 'list'> 4 # type(df), len(df)
<class 'pandas.core.frame.DataFrame'> 19 # type(df[1]), len(df[1])
----------------------------------------
         0                                                  1
0        T                       Transpose index and columns.
1       at  Access a single value for a row/column label p...
2     axes  Return a list representing the axes of the Dat...
3   blocks  (DEPRECATED) Internal property, property synon...
4  columns                The column labels of the DataFrame.
----------------------------------------
Int64Index([0, 1], dtype='int64') RangeIndex(start=0, stop=19, step=1)
----------------------------------------