利用 Python 进行数据分析 —— 3 pandas 入门

上一篇我们了解了 NumPy 的基本功能,本篇引入一个新的 python 数据处理库——pandas。

NumPy 更适合处理统一的数值数组数据。Pandas 是基于 NumPy 数组构建的,专门处理表格和混杂数据。接下来,让我们来了解一下 pandas 的基本使用吧。

首先让我们导入 pandas 库:

import pandas as pd
import numpy as np  # 后续也会使用到 numpy,在此先导入进来

3.1 pandas 的数据结构

pandas 有两个基本的数据结构:Series 和 DataFrame。

3.1.1 Series

我们可以把 Series 理解为带有索引的一维 np 数组。让我们创建出来对比一下:

 in: obj = pd.Series([4, 7, -5, 3])  # 可以将一个 list 转换为 Series
     obj
out: 0    4
     1    7
     2   -5
     3    3
     dtype: int64

 in: np_obj = np.array([4, 7, -5, 3])
     np_obj
out: array([ 4,  7, -5,  3])

可以看到,用 pd.Series() 创建的 Series 比 np.array() 创建的 ndarray 多了索引值——0, 1, 2, 3。

Series 的两个属性也可以在此印证这个事实:

 in: obj.values  # Series 的值:确实是个 np 数组
out: array([ 4,  7, -5,  3], dtype=int64)  

 in: obj.index  # Series 的索引(index)
out: RangeIndex(start=0, stop=4, step=1)  

如果不想要 pandas 自动生成的索引,也可以指定:

 in: obj2 = pd.Series([4, 7, -5, 3], index=['d', 'b', 'a', 'c'])
     obj2
out: d    4
     b    7
     a   -5
     c    3
     dtype: int64

如何从 Series 中选取出一个值或一组值呢?

 in: obj2['a']
out: -5

 in: obj2[['a', 'b']]  # 取出一组值时,注意方括号的使用
out: a   -5
     b    7
     dtype: int64  # 取出一组值时,取出的也是一个 Series 

Series 如何运算呢?它可以像 NumPy 那样运算。

obj2[obj2 > 2]  # 用布尔值索引选取
obj2 * 2  # 元素级乘法运算
np.exp(obj2)

Series 有索引也有值,就像字典的“键-值”一样。Series 的一些用法也像字典一样:

  in: "a" in obj2  # 像是在检查某个值是否属于字典的键
 out: True

刚才在创建 Series 时,我们是将一个 list 转换为 Series。其实,也可以将一个 dict 转换为 Series(此时,字典的键就是 Series 的索引):

 in: sdata = {'Ohio': 35000, 'Texas': 71000, 'Oregon': 16000, 'Utah': 5000}
     obj3 = pd.Series(sdata)
     obj3
out: Ohio      35000
     Texas     71000
     Oregon    16000
     Utah       5000
     dtype: int64

在已经有索引的情况下,如果再指定索引,会发生什么呢?

 in: states = ['California', 'Ohio', 'Oregon', 'Texas']
     obj4 = pd.Series(sdata, index=states)
     obj4 
out: California        NaN  # 原索引被新索引代替了,新索引中的 California 找不到对应的原索引,因此赋空值 NaN
     Ohio          35000.0
     Oregon        16000.0
     Texas         71000.0
     dtype: float64  # 新索引中没有 Utah 了,此处可以看到也没有出现 Utah 的数据

我们在上一个代码块中看到了缺失值的存在。我们在处理数据时常常碰到有缺失数据的情况。那么如何检测数据中是否存在缺失数据呢?

 in: pd.isnull(obj4)  # 和下面效果一样
     obj4.isnull()  # 和上面效果一样
out: California     True
     Ohio          False
     Oregon        False
     Texas         False
     dtype: bool
        
 in: pd.notnull(obj4)  # 也可以检测非空值
out: California    False
     Ohio           True
     Oregon         True
     Texas          True
     dtype: bool

最后,再介绍一个 Series 的重要功能:根据运算的索引标签自动对齐数据。

 in: obj3 + obj4  # 两个 Series 的索引存在不同,而加法运算自动将能对应上的索引值相加
out: California         NaN  # 而对应不上的值直接赋空值
     Ohio           70000.0  
     Oregon         32000.0
     Texas         142000.0
     Utah               NaN  # 对应不上,赋空值,不论其是否在某一个 Series 中有值
     dtype: float64

3.1.2 DataFrame

DataFrame 是一个表格型数据结构,既有行索引也有列索引。一列的值类型是相同的,但不同列可以有不同的值类型。

首先让我们创建一个 DataFrame。

 in: data = {'state': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada', 'Nevada'],
             'year': [2000, 2001, 2002, 2001, 2002, 2003],
             'pop': [1.5, 1.7, 3.6, 2.4, 2.9, 3.2]}  # 建立一个值为等长 list 的字典
     frame = pd.DataFrame(data)  # 将上述字典转为 DataFrame
     frame
out:     state  year  pop
     0    Ohio  2000  1.5
     1    Ohio  2001  1.7
     2    Ohio  2002  3.6
     3  Nevada  2001  2.4
     4  Nevada  2002  2.9
     5  Nevada  2003  3.2  # 可以看到这个 DataFrame 使用了字典的键作为列索引,行索引是自动创建的

我们时常会处理很大数据量的 DataFrame,当想查看这个表格结构时,并不想将整个表格都输出。我们可以用 head() 查看一个 DataFrame 的前几行:

 in: frame.head()
out:     state  year  pop
     0    Ohio  2000  1.5
     1    Ohio  2001  1.7
     2    Ohio  2002  3.6
     3  Nevada  2001  2.4
     4  Nevada  2002  2.9  # 默认输出前5行

 in: frame.head(3)  # 可以设置输出的行数
out:     state  year  pop
     0    Ohio  2000  1.5
     1    Ohio  2001  1.7
     2    Ohio  2002  3.6

那如何取出一列呢?

 in: frame['state']  # 像取字典的值那样就可以,我们取出了一个 Series
     frame.state  # 这样也可以,效果和上一行是一样的,因为 DataFrame 有一个 name 属性
out: 0      Ohio
     1      Ohio
     2      Ohio
     3    Nevada
     4    Nevada
     5    Nevada
     Name: state, dtype: object

在创建 DataFrame 时,我们还可以指定列(字段)的排列顺序,让表格按我们想要的顺序排列:

 in: pd.DataFrame(data, columns=['year', 'state', 'pop'])
out:    year   state  pop
     0  2000    Ohio  1.5
     1  2001    Ohio  1.7
     2  2002    Ohio  3.6
     3  2001  Nevada  2.4
     4  2002  Nevada  2.9
     5  2003  Nevada  3.2

但如果我们指定了匹配不上原数据的字段名,那么会产生缺失值:

 in: frame2 = pd.DataFrame(data, columns=['year', 'state', 'pop', 'debt'],
                      index=['one', 'two', 'three', 'four', 'five', 'six'])
     frame2
out:        year   state  pop debt  # 可以看到 debt 列都是缺失值
     one    2000    Ohio  1.5  NaN
     two    2001    Ohio  1.7  NaN
     three  2002    Ohio  3.6  NaN
     four   2001  Nevada  2.4  NaN
     five   2002  Nevada  2.9  NaN
     six    2003  Nevada  3.2  NaN

缺失值的这列数据该如何处理呢?

 in: frame2['debt'] = 12  # 可以直接赋一个整数,这会被广播到整列
out:        year   state  pop  debt
     one    2000    Ohio  1.5    12
     two    2001    Ohio  1.7    12
     three  2002    Ohio  3.6    12
     four   2001  Nevada  2.4    12
     five   2002  Nevada  2.9    12
     six    2003  Nevada  3.2    12
        
 in: frame2['debt'] = np.arange(6.)  # 也可以加入一个与表格长度等长的序列
out:        year   state  pop  debt
     one    2000    Ohio  1.5   0.0
     two    2001    Ohio  1.7   1.0
     three  2002    Ohio  3.6   2.0
     four   2001  Nevada  2.4   3.0
     five   2002  Nevada  2.9   4.0
     six    2003  Nevada  3.2   5.0
 
 in: val = pd.Series([-1.2, -1.5, -1.7], index=['two', 'four', 'five'])
     frame2['debt'] = val  # 如果将一个带有能匹配上的索引的 Series 赋值给缺失值列的话
     frame2
out:        year   state  pop  debt
     one    2000    Ohio  1.5   NaN
     two    2001    Ohio  1.7  -1.2
     three  2002    Ohio  3.6   NaN
     four   2001  Nevada  2.4  -1.5
     five   2002  Nevada  2.9  -1.7
     six    2003  Nevada  3.2   NaN  # 可以看到匹配上索引的赋值了,没匹配上的仍是缺失值

如果我们不想要 debt 这列了,如何删除呢?

 in: del frame2['debt']
     frame2.columns  # 来查看一下还有哪几列
out: Index(['year', 'state', 'pop'], dtype='object')

3.2 pandas 基本功能

3.2.1 插入新数据:使用“重新索引”

有时我们希望往 Series 或 DataFrame 中插入新数据,这可以用重新索引的方式实现。对于 Series 而言:

 in: obj = pd.Series(['blue', 'purple', 'yellow'], index=[0, 2, 4])
     obj
out: 0      blue
     2    purple
     4    yellow
     dtype: object
    
 in: obj.reindex(range(6))  # 我们通过改变并覆盖原来的索引,达到插入新行的目的
out: 0      blue
     1       NaN
     2    purple
     3       NaN
     4    yellow
     5       NaN
     dtype: object
        
 in: obj.reindex(range(6),method='ffill')  # 指定 method 参数,还可以实现诸如插值处理的操作
out: 0      blue
     1      blue  # 不再是空值了,而是按照前一个值来填充
     2    purple
     3    purple
     4    yellow
     5    yellow
     dtype: object

DataFrame 有两个索引,因此我们可以通过重新索引向表格中插入行和列。

 in: frame = pd.DataFrame(np.arange(9).reshape((3, 3)),
                     index=['a', 'c', 'd'],
                     columns=['Ohio', 'Texas', 'California'])
     frame
out:    Ohio  Texas  California
     a     0      1           2
     c     3      4           5
     d     6      7           8

 in: frame2 = frame.reindex(['a', 'b', 'c', 'd'])  # 默认重新索引行,用一个 list 表述新的索引序列。
     frame2
out:    Ohio  Texas  California  # 可以看到匹配上索引的行还是原来的数据,没匹配上的赋空值
     a   0.0    1.0         2.0
     b   NaN    NaN         NaN
     c   3.0    4.0         5.0
     d   6.0    7.0         8.0

 in: states = ['Texas', 'Utah', 'California']
     frame2.reindex(columns=states)   # 指明 columns 参数,即可重新索引列
out:    Texas  Utah  California  # 原数据中,没匹配索引的列被删除了
     a    1.0   NaN         2.0
     b    NaN   NaN         NaN
     c    4.0   NaN         5.0
     d    7.0   NaN         8.0

3.2.2 丢弃指定轴上的项

我们可以指定索引,来丢弃指定轴上的项。

对 Series 来说,只有一个索引,可以这么做:

 in: obj = pd.Series(np.arange(5.))
     obj.drop([1, 3])
out: 0    0.0
     2    2.0
     4    4.0
     dtype: float64

对于 DataFrame 来说,索引有两个,drop() 默认对行删除。注意,drop() 的删除是一个视图,并不是直接在原数据上操作的。如果想直接删除的话,需要使用 del。

 in: data = pd.DataFrame(np.arange(16).reshape((4, 4)),
                         index=['Ohio', 'Colorado', 'Utah', 'New York'],
                         columns=['one', 'two', 'three', 'four'])
     data.drop('Ohio')
out:           one  two  three  four
     Colorado    4    5      6     7
     Utah        8    9     10    11
     New York   12   13     14    15
    
 in: data.drop(['one', 'two'], axis=1)  # 指定 axis=1 可以对列进行删除
out:           three  four
     Ohio          2     3
     Colorado      6     7
     Utah         10    11
     New York     14    15  

在实际操作中,我通常设置横轴的索引,而是用默认自动创建的序号索引(0, 1, 2, ...)。

这样的话,drop() 后,index 就会有残缺。

 in: data = pd.DataFrame(np.arange(16).reshape((4, 4)),
                         columns=['one', 'two', 'three', 'four'])
     data = data.drop(index=(data.loc[(data['one']>5) & (data['one']<10)]).index)
     data
out:    one  two  three  four
     0    0    1      2     3
     1    4    5      6     7
     3   12   13     14    15  # 缺失了 index = 2 的行

这时,我们需要重新设置 index:

 in: data = data.reset_index(drop=True)  # 指定 drop=True 可以不保留原来的 index
out:    one  two  three  four
     0    0    1      2     3
     1    4    5      6     7
     2   12   13     14    15

3.2.3 选取 Series 中的一些数据

可以像字典那样,写出键,就可以选取:

 in: obj = pd.Series(np.arange(4.), index=['a', 'b', 'c', 'd'])
     obj
out: a    0.0
     b    1.0
     c    2.0
     d    3.0
     dtype: float64

 in: obj['b']
     obj[1]  # 即使我们设置了 index 的名称,仍然可以用默认的数字 index 进行索引,和上一行产生同样的效果
out: 1.0

 in: obj[0:2]  # 可以利用切片选取连续的几个值
out: a    0.0
     b    1.0
     dtype: float64
        
 in: obj[[0, 2]]  # 也可以选取不连续的几个值
out: a    0.0
     c    2.0
     dtype: float64
        
 in: obj[obj<2]  # 布尔值也可以索引,像 ndarray 一样
out: a    0.0
     b    1.0
     dtype: float64

这里用切片选取时,有个有意思的特点:用普通的 python 切片得到的数据是不包含末端的,但用我们设置的 index 标签来切片,就会包含末端数据。

 in: obj['a':'c']
out: a    0.0
     b    1.0
     c    2.0
     dtype: float64  # 可以对比上面的 obj[0:2]

3.2.4 选取 DataFrame 中的一些数据

可以直接用类似上面的方法选取:

 in: data
out:    one  two  three  four
     0    0    1      2     3
     1    4    5      6     7
     2   12   13     14    15
    
 in: data[['one', 'three']]  # 选取指定的两列数据
out:    one  three
     0    0      2
     1    4      6
     2   12     14
 
 in: data[:2]  # 利用切片按行选取
out:    one  two  three  four
     0    0    1      2     3
     1    4    5      6     7

常用的选取方法是用布尔值选取,以达到按条件选取的目的:

 in: data['three'] > 5  # 这样可以返回一个布尔值序列
out: 0    False
     1     True
     2     True
     Name: three, dtype: bool

 in: data[data['three'] > 5]  # 用这样的布尔值选取,我们就可以实现按条件选取(选出 three 列中值 > 5 的行)
out:    one  two  three  four
     1    4    5      6     7
     2   12   13     14    15

此外,另外一种常见的选取方式是通过轴标签(loc)或整数索引(iloc)来选取:

 in: data = pd.DataFrame(np.arange(16).reshape((4, 4)),
                         index=['Ohio', 'Colorado', 'Utah', 'New York'],
                         columns=['one', 'two', 'three', 'four'])
     data
out:           one  two  three  four
     Ohio        0    1      2     3
     Colorado    4    5      6     7
     Utah        8    9     10    11
     New York   12   13     14    15
 
 in: data.loc['Utah', ['one', 'four']]  # 指定行和列
     data.iloc[2,[0,3]]  # 和上式达到相同效果
out: one      8
     four    11
     Name: Utah, dtype: int32
 
 in: data.loc[:'Utah', 'two']  # loc 和 iloc 也可以用切片索引
out: Ohio        1
     Colorado    5
     Utah        9
     Name: two, dtype: int32

 in: data.iloc[:, :3][data.three > 5]  # 还可以加条件,通过在最后加一个方括号包含的布尔值序列的方式
out:           one  two  three
     Colorado    4    5      6
     Utah        8    9     10
     New York   12   13     14

3.2.5 算数运算和数据对齐

pandas 的一个重要特性是,它支持对两个索引不同的对象做运算,并进行数据对齐——运算时,索引匹配上的值会进行运算,索引不匹配的值会直接赋值为空。

 in: s1 = pd.Series([7.3, -2.5, 3.4, 1.5], index=['a', 'c', 'd', 'e'])
     s2 = pd.Series([-2.1, 3.6, -1.5, 4, 3.1], index=['a', 'c', 'e', 'f', 'g'])
     s1 + s2
out: a    5.2
     c    1.1
     d    NaN
     e    0.0
     f    NaN
     g    NaN  # 即使 s2 中有值,但 s1 中不存在这个索引,运算后该索引的值仍为空
     dtype: float64

DataFrame 有两个索引,数据对齐会同时发生在行和列上。

 in: df1 = pd.DataFrame(np.arange(9.).reshape((3, 3)),
                        columns=list('bcd'),
                        index=['Ohio', 'Texas', 'Colorado'])
     df2 = pd.DataFrame(np.arange(12.).reshape((4, 3)),
                        columns=list('bde'),
                        index=['Utah', 'Ohio', 'Texas', 'Oregon'])
     df1 + df2
out:             b   c     d   e
     Colorado  NaN NaN   NaN NaN
     Ohio      3.0 NaN   6.0 NaN
     Oregon    NaN NaN   NaN NaN
     Texas     9.0 NaN  12.0 NaN
     Utah      NaN NaN   NaN NaN

 in: df1.add(df2, fill_value=0)  # 如果不想要空值,可以使用 add(),并指明 fill_value=0
out:             b    c     d     e
     Colorado  6.0  7.0   8.0   NaN  # 可以看到还是存在空值,这是因为当两个表中都不存在值时不能填充
     Ohio      3.0  1.0   6.0   5.0
     Oregon    9.0  NaN  10.0  11.0
     Texas     9.0  4.0  12.0   8.0
     Utah      0.0  NaN   1.0   2.0  

如果是 DataFrame 和 Series 进行运算,运算会被逐行广播:

 in: frame = pd.DataFrame(np.arange(12.).reshape((4, 3)),
                          columns=list('bde'),
                          index=['Utah', 'Ohio', 'Texas', 'Oregon'])
     series = frame.iloc[0]
     frame
     series
out:           b     d     e
     Utah    0.0   1.0   2.0
     Ohio    3.0   4.0   5.0
     Texas   6.0   7.0   8.0
     Oregon  9.0  10.0  11.0
     
     b    0.0
     d    1.0
     e    2.0
 in: frame - series  # 表格从第一行开始减 series,一直向下广播,减到最后一行
out:           b    d    e
     Utah    0.0  0.0  0.0
     Ohio    3.0  3.0  3.0
     Texas   6.0  6.0  6.0
     Oregon  9.0  9.0  9.0

3.2.6 函数应用

numpy 的 ufuncs(元素级数组方法)也可用于操作 pandas 对象。而 pandas 对象也内置了许多常用的统计方法,可以直接调用。

 in: frame = pd.DataFrame(np.random.randn(4, 3),
                          columns=list('bde'),
                          index=['Utah', 'Ohio', 'Texas', 'Oregon'])
     frame
out:                b         d         e
     Utah   -0.661018  0.130761  0.508856
     Ohio    0.174303 -1.288930 -0.384396
     Texas  -0.523870 -0.657847 -0.822806
     Oregon  0.969613  0.894881 -0.623135

 in: np.abs(frame)
     frame.abs()  # DataFrame 已经内置了 abs() 方法,该行和上一行会产生相同的效果
out: Utah    0.661018  0.130761  0.508856
     Ohio    0.174303  1.288930  0.384396
     Texas   0.523870  0.657847  0.822806
     Oregon  0.969613  0.894881  0.623135

复杂的计算可以使用 lambda 函数,并用 apply() 将函数应用到由各列或行所形成的一维数组上。

 in: frame.apply(lambda x: x.max() - x.min())  # 对每列进行函数运算
out: b    1.630631
     d    2.183811
     e    1.331663
     dtype: float64

 in: frame.apply(lambda x: x.max() - x.min(), axis=1)  # 匹配列,对每行进行函数运算
out: Utah      1.169874
     Ohio      1.463233
     Texas     0.298937
     Oregon    1.592748
     dtype: float64

3.2.7 排序

对于 DataFrame 来说,排序可以按行索引排,也可以按列索引排。

 in: frame = pd.DataFrame(np.arange(8).reshape((2, 4)),
                     index=['three', 'one'],
                     columns=['d', 'a', 'b', 'c'])
     frame
out:        d  a  b  c
     three  0  1  2  3
     one    4  5  6  7

 in: frame.sort_index()  # 按行排
out:        d  a  b  c
     one    4  5  6  7
     three  0  1  2  3
        
 in: frame.sort_index(axis=1, ascending=False)  # 按列排,降序排
out:        d  c  b  a
     three  0  3  2  1
     one    4  7  6  5

此外,还可以按值排序:

 in: frame.sort_values(by='a',ascending=False)  # 也可以按值排列
out:        d  a  b  c
     one    4  5  6  7
     three  0  1  2  3

 in: me.sort_values(by=['a','b'])  # 按多个列的值来排
out:        d  a  b  c
     three  0  1  2  3
     one    4  5  6  7

5.3 描述性统计

先创建一个 DataFrame 用于举例:

 in: df = pd.DataFrame([[1.4, np.nan], [7.1, -4.5],[np.nan, np.nan], [0.75, -1.3]],
                       index=['a', 'b', 'c', 'd'],
                       columns=['one', 'two'])
     df
out:     one  two
     a  1.40  NaN
     b  7.10 -4.5
     c   NaN  NaN
     d  0.75 -1.3                

描述性统计的方向默认是以列为单位,统计行数据的:

 in: df.sum()
out: one    9.25
     two   -5.80
     dtype: float64    

 in: df.mean(axis=1)  # 也可以按行统计
out: a    1.400  # 包括一个空值时,空值被跳过了
     b    1.300
     c      NaN  # 都是空值时,返回的也是空值
     d   -0.275
     dtype: float64

可以直接使用 describe() 返回描述性统计结果:

 in: df.describe()
out:             one       two
     count  3.000000  2.000000
     mean   3.083333 -2.900000
     std    3.493685  2.262742
     min    0.750000 -4.500000
     25%    1.075000 -3.700000
     50%    1.400000 -2.900000
     75%    4.250000 -2.100000
     max    7.100000 -1.300000

还可以计算相关系数:

 in: df.corr()  # 默认为 pearson 相关系数
out:      one  two
     one  1.0 -1.0
     two -1.0  1.0

还有涉及到唯一值、值计数以及成员资格的统计:

 in: obj = pd.Series(['c', 'a', 'd', 'a', 'a', 'b', 'b', 'c', 'c'])
     obj
out: 0    c
     1    a
     2    d
     3    a
     4    a
     5    b
     6    b
     7    c
     8    c
     dtype: object

 in: obj.unique()  # 返回一个唯一值数组
out: array(['c', 'a', 'd', 'b'], dtype=object)

 in: obj.value_counts()  # 计算一个值出现的频率
out: a    3
     c    3
     b    2
     d    1
     dtype: int64
 
 in: obj.isin(['b', 'c'])  # 是否在 [] 中
out: 0     True
     1    False
     2    False
     3    False
     4    False
     5     True
     6     True
     7     True
     8     True
     dtype: bool
注:转载请注明出处。

本文属于《利用 Python 进行数据分析》读书笔记系列:

你可能感兴趣的:(利用 Python 进行数据分析 —— 3 pandas 入门)