上一篇我们了解了 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 进行数据分析》读书笔记系列: