简易目录:
1 Pandas对象及其数据取值与选择:Series、DataFrame和Index
2 Pandas数值运算方法
3 处理缺失值
4 层级索引:层级索引的创建、取值与切片、行列转换
5 合并数据集:pd.concat()、append()、pd.merge()
6 累计与分组:GroupBy——分割、应用和组合
7 数据透视表:pivot_table()
8 向量化字符串操作
9 处理时间序列
Pandas是在NumPy基础上建立的新程序库,提供了一种高效的DataFrame数据结构。
DataFrame本质上是一种带行标签和列标签、支持相同类型数据和缺失值的多维数组。
建立在NumPy数组结构上的Pandas,尤其是它的Series和DataFrame对象,为处理更灵活的数据任务(如为数据添加标签、处理缺失值等),或者需要做一些不是对每个元素都进行广播映射的计算(如分组、透视表等)等“数据清理”任务提供了捷径。
1 Pandas对象及其数据取值与选择
Pandas的三个基本数据结构:Series、DataFrame和Index。
1.1 Series
1.1.1 Series对象简介及其创建
Series对象将一组数据和一组索引绑定在一起,可以通过values属性和index属性获取数据。
和NumPy数组一样,数据可以通过Python的中括号索引标签获取。1
2
3
4
5
6
7
8
9
10
11
12In[1]: data = pd.Series([0.25, 0.5, 0.75, 1.0])
data
Out[1]: 0 0.25
1 0.50
2 0.75
3 1.00
dtype: float64
In[2]: data.values
Out[2]: array([ 0.25, 0.5 , 0.75, 1. ])
In[3]: data.index
Out[3]: RangeIndex(start=0, stop=4, step=1)
1.Series是通用的NumPy数组
NumPy 数组通过隐式定义的整数索引获取数值,而Pandas 的Series 对象用一种显式定义的索引与数值关联。索引不仅仅是整数,还可以是任意想要的类型,如字符串。
2.Series是特殊的字典
字典是一种将任意键映射到一组任意值的数据结构,而Series 对象其实是一种将类型键映射到一组类型值的数据结构。和字典不同,Series 对象还支持数组形式的操作,比如切片。
3.创建Series对象pd.Series(data, index=index)
,其中index是可选参数,data参数支持多种数据类型。例如:
data可以是列表或NumPy数组,这时index默认值为整数序列。
data也可以是一个标量,创建Series对象时会重复填充到每个索引上。
data还可以是一个字典,index默认是排序的字典键。
每一种形式都可以通过显式指定索引筛选需要的结果,此时Series对象只会保留显式定义的键值对。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36#Series是通用的Numpy数组
In[4]: data = pd.Series([0.25, 0.5, 0.75, 1.0],
index = ['a', 'b', 'c', 'd'])
data
Out[4]: a 0.25
b 0.50
c 0.75
d 1.00
dtype: float64
In[5]: data['b']
Out[5]: 0.5
#Series是特殊的字典
In[6]: population_dict = {'California': 38332521,
'Texas': 26448193,
'New York': 19651127,
'Florida': 19552860,
'Illinois': 12882135}
pop = pd.Series(population_dict)
pop
Out[6]: California 38332521
Florida 19552860
Illinois 12882135
New York 19651127
Texas 26448193
dtype: int64
In[7]: pop['California':'Illinois']
Out[7]: California 38332521
Florida 19552860
Illinois 12882135
dtype: int64
#data是标量
In[8]: pd.Series(5, index=[100, 200, 300])
Out[8]: 100 5
200 5
300 5
dtype: int64
1.1.2 Series数据取值与选择
和字典一样,Series对象提供了键值对的映射,也可以用字典语法调整数据,如增加新的索引值扩展Series。
和NumPy数组一样,Series对象具备数据选择功能,包括索引、掩码、花哨的索引等操作。
需要注意的是,当使用显式索引(即data[‘a’:’c’])作切片时,结果包含最后一个索引;而当使用隐式索引(即data[0:2])作切片时,结果不包含最后一个索引。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35In[9]: data.keys()
Out[9]: Index(['a', 'b', 'c', 'd'], dtype='object')
In[10]: list(data.items())
Out[10]: [('a', 0.25), ('b', 0.5), ('c', 0.75), ('d', 1.0)]
In[11]: data['e'] = 1.25
data
Out[11]: a 0.25
b 0.50
c 0.75
d 1.00
e 1.25
dtype: float64
#掩码
In[12]: data[(data > 0.3) & (data < 0.8)]
Out[12]: b 0.50
c 0.75
dtype: float64
#花哨的索引
In[13]: data[['a', 'e']]
Out[13]: a 0.25
e 1.25
dtype: float64
#将显式索引作为切片
In[14]: data['a':'c']
Out[14]: a 0.25
b 0.50
c 0.75
dtype: float64
#将隐式整数索引作为切片
In[15]: data[0:2]
Out[15]: a 0.25
b 0.50
dtype: float64
索引器:loc
属性:表示取值和切片都是显式的iloc
属性:表示取值和切片都是Python形式的隐式索引1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20In[16]: data = pd.Series(['a', 'b', 'c'], index=[1, 3, 5])
data
Out[16]: 1 a
3 b
5 c
dtype: object
#loc:
In[17]: data.loc[1]
Out[17]: 'a'
In[18]: data.loc[1:3]
Out[18]: 1 a
3 b
dtype: object
#iloc:
In[19]: data.iloc[1]
Out[19]: 'b'
In[20]: data.iloc[1:3]
Out[20]: 3 b
5 c
dtype: object
1.2 DataFrame
1.2.1 DataFrame对象简介及其创建
1.DataFrame是通用的NumPy数组
如果将Series类比为带灵活索引的一维数组,那么DataFrame就可以看作是一种既有灵活的行索引,又有灵活列名的二维数组。DataFrame 有一个index属性可以获取索引标签,一个columns属性,是存放列标签的Index对象。
2.DataFrame是特殊的字典
字典是一个键映射一个值,而DataFrame是一列映射一个Series的数据。
3.创建DataFrame对象
(1)通过单个Series对象创建,DataFrame 是一组Series 对象的集合
(2)通过字典列表创建
(3)通过Series对象字典创建
(4)通过NumPy二维数组创建
(5)通过NumPy结构化数组创建1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42#单个Series对象创建DataFrame
In[21]: pd.DataFrame(pop, columns=['pop'])
Out[21]: pop
California 38332521
Florida 19552860
Illinois 12882135
New York 19651127
Texas 26448193
#字典列表创建
In[22]: data = [{'a': i, 'b': 2 * i} for i in range(3)]
pd.DataFrame(data)
Out[22]: a b
0 0 0
1 1 2
2 2 4
#Series对象字典创建
In[23]: data2 = pd.DataFrame({'pop': pop, 'area': area})
data2
Out[23]: area pop
California 423967 38332521
Florida 170312 19552860
Illinois 149995 12882135
New York 141297 19651127
Texas 695662 26448193
#NumPy二维数组创建
In[24]: pd.DataFrame(np.random.rand(3, 2),
columns=['foo', 'bar'],
index=['a', 'b', 'c'])
Out[24]: foo bar
a 0.865257 0.213169
b 0.442759 0.108267
c 0.047110 0.905718
#NumPy结构化数组创建
In[25]: A = np.zeros(3, dtype=[('A', 'i8'), ('B', 'f8')])
A
Out[25]: array([(0, 0.0), (0, 0.0), (0, 0.0)],
dtype=[('A', '<i8'), ('B', '<f8')])
In[26]: pd.DataFrame(A)
Out[26]: A B
0 0 0.0
1 0 0.0
2 0 0.0
1.2.2 DataFrame数据选择方法
1.将DataFrame看作字典
可以通过对列名进行字典形式的取值获取数据或调整对象,如增加列等。
也可以用属性形式选择纯字符串列名的数据,但该方法不是通用的,如果列名不是纯字符串,或者列名与DataFrame的方法同名,就不能用属性索引。例如,DataFrame有一个pop()方法,如果用data.pop 就不会获取’pop’ 列,而是显示为方法。
另外,应该避免对属性形式选择的列直接赋值。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19In[27]: data2['area'] #字典形式与属性形式的结果是一样的
In[28]: data2.area
Out[28]: California 423967
Florida 170312
Illinois 149995
New York 141297
Texas 695662
Name: area, dtype: int64
In[29]: data2['density'] = data2['pop'] / data2['area'] #字典形式增加列
data2
Out[29]: area pop density
California 423967 38332521 90.413926
Florida 170312 19552860 114.806121
Illinois 149995 12882135 85.883763
New York 141297 19651127 139.076746
Texas 695662 26448193 38.018740
In[30]: data2.pop is data2['pop']
Out[30]: False
2.将DataFrame看作二维数组
可以把许多数组操作方式用在DataFrame上,例如进行行列转置。
索引器:loc
(显式索引)、iloc
(隐式索引)、ix
(混合)
任何用于处理NumPy形式数据的方法都可以用于这些索引器。例如,可以在loc
索引器中结合使用掩码与花哨的索引方法。
任何一种取值方法都可以用于调整数据,这一点和NumPy 的常用方法是相同的。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28In[31]:data2.T
Out[31]: California Florida Illinois New York Texas
area 4.239670e+05 1.703120e+05 1.499950e+05 1.412970e+05 6.956620e+05
pop 3.833252e+07 1.955286e+07 1.288214e+07 1.965113e+07 2.644819e+07
density 9.041393e+01 1.148061e+02 8.588376e+01 1.390767e+02 3.801874e+01
In[32]: data2.loc[:'Illinois', :'pop']
Out[32]: area pop
California 423967 38332521
Florida 170312 19552860
Illinois 149995 12882135
In[33]: data.iloc[:3, :2]
Out[33]: area pop
California 423967 38332521
Florida 170312 19552860
Illinois 149995 12882135
In[34]: data2.ix[:3, :'pop']
Out[34]: area pop
California 423967 38332521
Florida 170312 19552860
Illinois 149995 12882135
In[35]: data2.loc[data2.density > 100, ['pop', 'density']]
Out[35]: pop density
Florida 19552860 114.806121
New York 19651127 139.076746
3.其他取值方法
对单个标签取值就选择列,对多个标签用切片就选择行
切片也可以不用索引值,而直接用行数来实现
掩码操作也可以直接对每一行进行过滤,而不需要使用loc索引器1
2
3
4
5
6
7
8
9
10
11
12
13
14In[36]: data2['Florida':'Illinois']
Out[36]: area pop density
Florida 170312 19552860 114.806121
Illinois 149995 12882135 85.883763
In[37]: data[1:3]
Out[37]: area pop density
Florida 170312 19552860 114.806121
Illinois 149995 12882135 85.883763
In[38]: data2[data.denstiy > 100]
Out[38]: area pop density
Florida 170312 19552860 114.806121
New York 141297 19651127 139.076746
1.3 Index
1.将Index看作不可变数组
Index对象的许多操作都像数组。例如,可以通过标准Python的取值方法获取数值,也可以通过切片获取数值。但不同的是,Index对象的索引是不可变的,也就是说不能通过通常的方式进行调整。
Index对象的不可变特征使得多个DataFrame和数组之间进行索引共享时更加安全,尤其是可以避免因修改索引时粗心大意而导致的副作用。
2.将Index看作有序集合
Index对象遵循Python标准库的集合(set)数据结构的许多习惯用法,包括并集|
、交集&
、差集^
等。1
2
3
4
5
6
7
8
9
10
11
12
13
14In[39]: ind = pd.Index([2, 3, 5, 7, 11])
ind
Out[39]: Int64Index([2, 3, 5, 7, 11], dtype='int64')
In[40]: ind[::2]
Out[40]: Int64Index([2, 5, 11], dtype='int64')
In[41]: indA = pd.Index([1, 3, 5, 7, 9])
indB = pd.Index([2, 3, 5, 7, 11])
In[42]: indA & indB #交集
Out[42]: Int64Index([3, 5, 7], dtype='int64')
In[43]: indA ^ indB #异或
Out[43]: Int64Index([1, 2, 9, 11], dtype='int64')
2 Pandas数值运算方法
对于一元运算(像函数与三角函数),这些通用函数将在输出结果中保留索引和列标签;而对于二元运算(如加法和乘法),Pandas在传递通用函数时会自动对齐索引进行计算。
对于缺失位置的数据,Pandas 会用NaN 填充,表示“此处无数”,可以通过设置fill_value
参数处理缺失的数据。
按行或按列计算则通过axis
参数设置。
3 处理缺失值
3.1 缺失值的类型
缺失值主要有三种形式:null、NaN或NA。
识别缺失值的方法一般有两种:
一是通过一个覆盖全局的掩码表示缺失值(掩码可能是一个与原数组维度相同的完整布尔类型数组,也可能是用一个比特(0 或1)表示有缺失值的局部状态),
另一种方法是用一个标签值表示缺失值(例如用-9999 表示缺失的整数,用NaN表示缺失的浮点数)。
Pandas最终选择用标签方法表示缺失值,包括两种Python原有的缺失值:浮点数据类型的NaN值,以及Python的None对象。
但是,在Python中没有定义整数与None之间的加法运算,这意味着如果你对一个包含None 的数组进行累计操作,如sum() 或者min(),那么通常会出现类型错误。而NaN可以看作是一个数据类病毒——它会将与它接触过的数据同化,无论和NaN进行何种操作,最终结果都是NaN。
谨记,NaN 是一种特殊的浮点数,不是整数、字符串以及其他数据类型。
3.2 缺失值的处理
1.发现缺失值:isnull()
创建一个布尔类型的掩码标签缺失值。notnull()
与isnull() 操作相反。
2.剔除缺失值:dropna()
返回一个剔除缺失值的数据。
注意:没法从DataFrame 中单独剔除一个值,要么是剔除缺失值所在的整行,要么是整列。
可以设置按不同的坐标轴剔除缺失值,比如axis=1(或axis=’columns’)会剔除任何包含缺失值的整列数据。
如果只需要剔除全部是缺失值的行或列,或者绝大多数是缺失值的行或列,可以通过设置how或thresh参数来满足,如设置how='all'
就只会剔除全部是缺失值的行或列;thresh=3
表示行或列中非缺失值的最小数量,如df.dropna(axis='rows', thresh=3)
保留非缺失值大于等于3的行。
3.填充缺失值:fillna()
返回一个填充了缺失值的数据副本。
用一个单独的值填充缺失值:data.fillna(0)
用缺失值前面的有效值来从前往后填充:data.fillna(method = 'ffill')
用缺失值后面的有效值来从后往前填充:data.fillna(method = 'bfill')
DataFrame 在填充时需要设置坐标轴参数axis。
4 层级索引
可以用含多级索引的一维Series 数据表示二维数据,也可以用Series 或DataFrame 表示三维甚至更高维度的数据。多级索引每增加一级,就表示数据增加一维,利用这一特点就可以轻松表示任意维度的数据。
4.1 多级索引的创建
1.最直接的方法:将index 参数设置为至少二维的索引数组,或者把元组作为键的字典传递给Pandas。
2.显式地创建:用pd.MultiIndex
中的类方法,例如
通过一个有不同等级的若干简单数组组成的列表来构建,pd.MultiIndex.from_arrays
通过包含多个索引值的元组构成的列表创建,pd.MultiIndex.from_tuples
用两个索引的笛卡尔积创建,pd.MultiIndex.from_product
直接提供levels
(包含每个等级的索引值列表的列表)和labels
(包含每个索引值标签列表的列表)创建
在创建Series或DataFrame时,可以将这些对象作为index参数,或者通过reindex
方法更新索引。
给MultiIndex的等级加上名称会为一些操作提供便利。可以在前面任何一个MultiIndex构造器中通过names参数设置等级名称,也可以在创建之后通过索引的names属性来修改名称
既有多级行索引,也可以有多级列索引1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71#最直接的方法
In[44]: df = pd.DataFrame(np.random.rand(4, 2),
index=[['a', 'a', 'b', 'b'], [1, 2, 1, 2]],
columns=['data1', 'data2'])
df
Out[44]: data1 data2
a 1 0.554233 0.356072
2 0.925244 0.219474
b 1 0.441759 0.610054
2 0.171495 0.886688
In[45]: data = {('California', 2000): 33871648,
('California', 2010): 37253956,
('Texas', 2000): 20851820,
('Texas', 2010): 25145561,
('New York', 2000): 18976457,
('New York', 2010): 19378102}
pd.Series(data)
Out[45]: California 2000 33871648
2010 37253956
New York 2000 18976457
2010 19378102
Texas 2000 20851820
2010 25145561
dtype: int64
#显式地创建
In[46]: pd.MultiIndex.from_arrays([['a', 'a', 'b', 'b'], [1, 2, 1, 2]])
Out[46]: MultiIndex(levels=[['a', 'b'], [1, 2]],
labels=[[0, 0, 1, 1], [0, 1, 0, 1]])
In[47]: pd.MultiIndex.from_tuples([('a', 1), ('a', 2), ('b', 1), ('b', 2)])
Out[47]: MultiIndex(levels=[['a', 'b'], [1, 2]],
labels=[[0, 0, 1, 1], [0, 1, 0, 1]])
In[48]: pd.MultiIndex.from_product([['a', 'b'], [1, 2]])
Out[48]: MultiIndex(levels=[['a', 'b'], [1, 2]],
labels=[[0, 0, 1, 1], [0, 1, 0, 1]])
In[49]: pd.MultiIndex(levels=[['a', 'b'], [1, 2]],
labels=[[0, 0, 1, 1], [0, 1, 0, 1]])
Out[49]: MultiIndex(levels=[['a', 'b'], [1, 2]],
labels=[[0, 0, 1, 1], [0, 1, 0, 1]])
#给多级索引加上名称
In[50]: pop.index.names = ['state', 'year']
pop
Out[50]: state year
California 2000 33871648
2010 37253956
New York 2000 18976457
2010 19378102
Texas 2000 20851820
2010 25145561
dtype: int64
#多级列索引
In[51]: index = pd.MultiIndex.from_product([[2013, 2014], [1, 2]],
names = ['year', 'visit'])
columns = pd.MultiIndex.from_product([['Bob', 'Guido', 'Sue'],
['HR', 'Temp']],
names = ['subject', 'type'])
data = np.round(np.random.randn(4, 6), 1)
data[:, ::2] *= 10
data += 37
health_data = pd.DataFrame(data, index = index, columns = columns)
health_data
Out[51]: subject Bob Guido Sue
type HR Temp HR Temp HR Temp
year visit
2013 1 31.0 38.7 32.0 36.7 35.0 37.2
2 44.0 37.7 50.0 35.0 29.0 36.7
2014 1 30.0 37.4 39.0 37.8 61.0 36.9
2 47.0 37.8 48.0 37.3 51.0 36.5
4.2 多级索引的取值与切片
1.Series多级索引
可以通过对多个级别索引值获取单个元素,如pop['California', 2000]
局部取值,即只取索引的某一个层级,如pop['California']
局部切片,不过要求MultiIndex 是按顺序排列的,如pop.loc['California':'New York']
如果索引已经排序,用较低层级的索引取值,第一层级的索引可以用空切片,如pop[:, 2000]
通过布尔掩码选择数据,如pop[pop > 22000000]
用花哨的索引选择数据,如pop[['California', 'Texas']]
2.DataFrame多级索引
DataFrame的基本索引是列索引,如health_data['Guido', 'HR']
索引器loc、iloc 和ix,如health_data.iloc[:2, :2]
、
索引器传递多个层级的索引元组,如health_data.loc[:, ('Bob', 'HR')]
索引器获取切片pd.IndexSlice
对象1
2
3
4
5
6
7In[52]: idx = pd.IndexSlice
health_data.loc[idx[:, 1], idx[:, 'HR']]
Out[52]: subject Bob Guido Sue
type HR HR HR
year visit
2013 1 31.0 32.0 35.0
2014 1 30.0 39.0 61.0
4.3 多级索引行列转换
1.有序的索引和无序的索引
如果MultiIndex 不是有序的索引(有序即按照字典顺序由A至Z),那么大多数切片操作都会失败。
可以用sort_index()
和sortlevel()
方法对index进行排序。
2.转换索引层级
将一个多级索引数据集转换成简单的二维形式:stack()
、unstack()
,可以通过level参数设置转换的索引层级
3.索引的设置与重置
行列标签转换:reset_index
,可以用数据的name属性为列设置名称
将原始输入数据的列直接转换成多级索引:set_index()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31In[53]: pop.unstack(level=0)
Out[53]: state California New York Texas
year
2000 33871648 18976457 20851820
2010 37253956 19378102 25145561
In[54]: pop.unstack(level=1)
Out[54]: year 2000 2010
state
California 33871648 37253956
New York 18976457 19378102
Texas 20851820 25145561
In[55]: pop_flat = pop.reset_index(name='population')
pop_flat
Out[55]: state year population
0 California 2000 33871648
1 California 2010 37253956
2 New York 2000 18976457
3 New York 2010 19378102
4 Texas 2000 20851820
5 Texas 2010 25145561
In[56]: pop_flat.set_index(['state', 'year'])
Out[56]: population
state year
California 2000 33871648
2010 37253956
New York 2000 18976457
2010 19378102
Texas 2000 20851820
2010 25145561
5 合并数据集
5.1 Concat和Append操作
1.pd.concat简易合并pd.concat()
简单地合并一维的Series 或高维的DataFrame 对象
默认情况下,DataFrame 对象的合并是逐行进行的(即axis=0),也可以设置合并坐标轴,如axis='col'
(1)重复索引:合并时仍会保留重复索引
ignore_index参数如ignore_index=True
忽略重复参数,在合并时将会创建一个新的整数索引。
keys参数为数据源设置多级索引标签,这样结果数据就会带上多级索引。
(2)类似join的合并:列名部分相同时
join参数:join='outer'
对输入列并集合并,join='inner'
对输入列交集合并。
join_axes参数:里面是索引对象构成的列表(是列表的列表)。
2.append()方法df1.append(df2)
效果与pd.concat([df1, df2])
一样。
需要注意的是,与Python列表中的append()和extend()方法不同,Pandas的append()不直接更新原有对象的值,而是为合并后的数据创建一个新对象。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39In[57]: def make_df(cols, ind):
"""一个简单的DataFrame"""
data = {c: [str(c) + str(i) for i in ind] for c in cols}
return pd.DataFrame(data, ind)
In[58]: x = make_df('AB', [0, 1])
y = make_df('AB', [2, 3])
y.index = x.index # 复制索引
print(x); print(y); print(pd.concat([x, y]));
x y
A B A B A B
0 A0 B0 0 A2 B2 0 A0 B0
1 A1 B1 1 A3 B3 1 A1 B1
0 A2 B2
1 A3 B3
In[59]: print(pd.concat([x, y], ignore_index=True));
print(pd.concat([x, y], keys=['x', 'y']))
A B A B
0 A0 B0 x 0 A0 B0
1 A1 B1 1 A1 B1
2 A2 B2 y 0 A2 B2
3 A3 B3 1 A3 B3
In[60]: df5 = make_df('ABC', [1, 2])
df6 = make_df('BCD', [3, 4])
print(df5); print(df6); print(pd.concat([df5, df6])
df5 df6
A B C B C D A B C D
1 A1 B1 C1 3 B3 C3 D3 1 A1 B1 C1 NaN
2 A2 B2 C2 4 B4 C4 D4 2 A2 B2 C2 NaN
3 NaN B3 C3 D3
4 NaN B4 C4 D4
In[61]: print(pd.concat([df5, df6], join='inner'));
print(pd.concat([df5, df6], join_axes=[df5.columns]))
B C A B C
1 B1 C1 1 A1 B1 C1
2 B2 C2 2 A2 B2 C2
3 B3 C3 3 NaN B3 C3
4 B4 C4 4 NaN B4 C4
5.2 合并与连接:pd.merge()
1.合并:有共同的列或者列名不同但值相同时
(1)pd.merge()
实现三种数据连接的类型:
一对一:按列合并,以共同列(键)进行连接
多对一:在需要连接的两个列中,有一列的值有重复,获得的结果DataFrame将会保留重复值
多对多:左右两个输入的共同列都包含重复值
(2)合并不同名的列
on参数:只能在两个DataFrame有共同列名的时候才可以使用
left_on与right_on参数:两个列名不同,获取的结果中会有一个多余的列,可以通过DataFrame的drop()
方法将这列去掉
(3)合并索引:left_index与right_index参数或直接DataFrame的join()
方法
(4)将索引与列混合使用:结合left_index 与right_on,或者结合left_on 与right_index1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58In[62]: df1 = pd.DataFrame({'employee': ['Bob', 'Jake', 'Lisa', 'Sue'],
'group':['Accounting','Engineering','Engineering','HR']})
df2 = pd.DataFrame({'employee': ['Lisa', 'Bob', 'Jake', 'Sue'],
'hire_date': [2004, 2008, 2012, 2014]})
print(df1); print(df2)
df1 df2
employee group employee hire_date
0 Bob Accounting 0 Lisa 2004
1 Jake Engineering 1 Bob 2008
2 Lisa Engineering 2 Jake 2012
3 Sue HR 3 Sue 2014
In[63]: print(pd.merge(df1, df2, on='employee'))
employee group hire_date
0 Bob Accounting 2008
1 Jake Engineering 2012
2 Lisa Engineering 2004
3 Sue HR 2014
In[64]: df3 = pd.DataFrame({'name': ['Bob', 'Jake', 'Lisa', 'Sue'],
'salary': [70000, 80000, 120000, 90000]})
print(df3); print(pd.merge(df1, df3, left_on="employee", right_on="name"))
df3
name salary employee group name salary
0 Bob 70000 0 Bob Accounting Bob 70000
1 Jake 80000 1 Jake Engineering Jake 80000
2 Lisa 120000 2 Lisa Engineering Lisa 120000
3 Sue 90000 3 Sue HR Sue 90000
In[65]: pd.merge(df1, df3, left_on="employee", right_on="name").drop('name', axis=1)
Out[65]: employee group salary
0 Bob Accounting 70000
1 Jake Engineering 80000
2 Lisa Engineering 120000
3 Sue HR 90000
In[66]: df1a = df1.set_index('employee')
df2a = df2.set_index('employee')
print(df1a); print(df2a);
print(pd.merge(df1a, df2a, left_index=True, right_index=True))
df1a df2a
group hire_date group hire_date
employee employee employee
Bob Accounting Lisa 2004 Lisa Engineering 2004
Jake Engineering Bob 2008 Bob Accounting 2008
Lisa Engineering Jake 2012 Jake Engineering 2012
Sue HR Sue 2014 Sue HR 2014
In[67]: print(df1a.join(df2a))
group hire_date
employee
Bob Accounting 2008
Jake Engineering 2012
Lisa Engineering 2004
Sue HR 2014
In[68]: pd.merge(df1a, df3, left_index=True, right_on='name')
group name salary
0 Accounting Bob 70000
1 Engineering Jake 80000
2 Engineering Lisa 120000
3 HR Sue 90000
2.连接:两列的值不一样时
当一个值出现在一列,却没有出现在另一列时,就需要考虑集合操作规则。
可以用how参数设置连接方式:
内连接:默认情况下,结果中只会包含两个输入集合的交集,如pd.merge(df1, df2, how='inner')
外连接:返回两个输入列的交集,所有缺失值都用NaN 填充,如pd.merge(df1, df2, how='outer')
左连接:输出的行中只包含左边输入列的值,如how = 'left'
右连接:输出的行中只包含右边输入列的值,如how = 'right'
3.重复列名:suffixes参数
当两个输入DataFrame有重名列时,输出的结果会自动增加后缀_x 或_y,可以通过suffixes参数自定义后缀名。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34In[69]: df4 = pd.DataFrame({'name': ['Peter', 'Paul', 'Mary'],
'food': ['fish', 'beans', 'bread']},
columns=['name', 'food'])
df5 = pd.DataFrame({'name': ['Mary', 'Joseph'], 'drink': ['wine', 'beer']},
columns=['name', 'drink'])
print(df4); print(df5); print(pd.merge(df4, df5))
df4 df5 pd.merge(df4, df5)
name food name drink name food drink
0 Peter fish 0 Mary wine 0 Mary bread wine
1 Paul beans 1 Joseph beer
2 Mary bread
In[70]: print(pd.merge(df4, df5, how='right'));
print(pd.merge(df4, df5, how='left'))
name food drink name food drink
0 Mary bread wine 0 Peter fish NaN
1 Joseph NaN beer 1 Paul beans NaN
2 Mary bread wine
In[71]: df6 = pd.DataFrame({'name':['Bob','Jake','Lisa','Sue'], 'rank':[1,2,3,4]})
df7 = pd.DataFrame({'name':['Bob','Jake','Lisa','Sue'], 'rank':[3,1,4,2]})
print(df6); print(df7); print(pd.merge(df6, df7, on="name"))
df6 df7
name rank name rank name rank_x rank_y
0 Bob 1 0 Bob 3 0 Bob 1 3
1 Jake 2 1 Jake 1 1 Jake 2 1
2 Lisa 3 2 Lisa 4 2 Lisa 3 4
3 Sue 4 3 Sue 2 3 Sue 4 2
In[72]: pd.merge(df6, df7, on="name", suffixes=["_L", "_R"])
name rank_L rank_R
0 Bob 1 3
1 Jake 2 1
2 Lisa 3 4
3 Sue 4 2
6 累计与分组
6.1 累计
DataFrame和Series对象支持Python的所有累计方法:
6.2 GroupBy:分割、应用和组合
GroupBy只需要一行代码,就可以计算每组的和、均值、计数、最小值以及其他累计值。
分割步骤将DataFrame 按照指定的键分割成若干组。
应用步骤对每个组应用函数,通常是累计、转换或过滤函数。
组合步骤将每一组的结果合并成一个输出数组。
下图展示了df.groupby('key').sum()
的实现过程:
1.GroupBy中最重要的操作:
累计aggregate()
:支持字符串、函数或者函数列表,能一次性计算所有累计值;或者通过字典指定不同列需要累计的函数。
过滤filter()
:按照分组的属性丢弃若干数据。
转换transform()
:数据经过转换之后,其形状与原来的输入数据是一样的。
应用apply()
:在每个组上应用任意方法。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58#累计aggregate()
In[73]: rng = np.random.RandomState(0)
df = pd.DataFrame({'key': ['A', 'B', 'C', 'A', 'B', 'C'],
'data1': range(6),
'data2': rng.randint(0, 10, 6)},
columns = ['key', 'data1', 'data2'])
df
Out[73]: key data1 data2
0 A 0 5
1 B 1 0
2 C 2 3
3 A 3 3
4 B 4 7
5 C 5 9
In[74]: df.groupby('key').aggregate(['min', np.median, max])
Out[74]: data1 data2
min median max min median max
key
A 0 1.5 3 3 4.0 5
B 1 2.5 4 0 3.5 7
C 2 3.5 5 3 6.0 9
In[75]: df.groupby('key').aggregate({'data1': 'min', 'data2': 'max'})
Out[75]: data1 data2
key
A 0 5
B 1 7
C 2 9
#过滤filter()
In[76]: def filter_func(x):
return x['data2'].std() > 4
print(df.groupby('key').std()); print(df.groupby('key').filter(filter_func))
key data1 data2 key data1 data2
A 2.12132 1.414214 1 B 1 0
B 2.12132 4.949747 2 C 2 3
C 2.12132 4.242641 4 B 4 7
5 C 5 9
#转换transform()
In[77]: df.groupby('key').transform(lambda x: x - x.mean())
Out[77]: data1 data2
0 -1.5 1.0
1 -1.5 -3.5
2 -1.5 -3.0
3 1.5 -1.0
4 1.5 3.5
5 1.5 3.0
#应用apply()
In[78]: def norm_by_data2(x):
# x是一个分组数据的DataFrame
x['data1'] /= x['data2'].sum()
return x
print(df.groupby('key').apply(norm_by_data2))
key data1 data2
0 A 0.000000 5
1 B 0.142857 0
2 C 0.166667 3
3 A 0.375000 3
4 B 0.571429 7
5 C 0.416667 9
2.设置分割的键
前面的例子是列名分割DataFrame,但还有其他的分组操作:
(1)将列表、数组、Series或索引作为分组键。可以是长度与DataFrame匹配的任意Series或列表
(2)用字典或Series将索引映射到分组名称。
(3)任意Python函数。函数映射到索引,然后新的分组输出。
(4)多个有效键构成的列表。返回一个多级索引的分组结果。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29In[79]: L = [0, 1, 0, 1, 2, 0]
print(df.groupby(L).sum())
data1 data2
0 7 17
1 4 3
2 4 7
In[80]: df2 = df.set_index('key')
mapping = {'A': 'vowel', 'B': 'consonant', 'C': 'consonant'}
print(df2); print(df2.groupby(mapping).sum())
key data1 data2 data1 data2
A 0 5 consonant 12 19
B 1 0 vowel 3 8
C 2 3
A 3 3
B 4 7
C 5 9
In[81]: print(df2.groupby(str.lower).mean())
data1 data2
a 1.5 4.0
b 2.5 3.5
c 3.5 6.0
In[82]: df2.groupby([str.lower, mapping]).mean()
Out[82]: data1 data2
a vowel 1.5 4.0
b consonant 2.5 3.5
c consonant 3.5 6.0
7 数据透视表
数据透视表将每一列数据作为输入,输出将数据不断细分成多个维度累计信息的二维数据表,可以看做是一种多维的GroupBy累计操作。
用pivot_table
快速解决多维的累计分析任务:df.pivot_table(data, values=None, index=None, columns=None, aggfunc='mean', fill_value=None, margins=False, dropna=True, margins_name='All')
(1)aggfunc参数用于设置累计函数类型,默认值是均值(mean)
累计函数可以用一些常见的字符串(’sum’、’mean’、’count’、’min’、’max’ 等)表示,也可以用标准的累计函数(np.sum()、min()、sum() 等)表示。
另外,还可以通过字典为不同的列指定不同的累计函数。当为aggfunc指定映射关系的时候,待透视的数值就已经确定了,此时不需要设置参数values。
(2)margins参数:计算每一组的总数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38In[83]: import seaborn as sns
titanic = sns.load_dataset('titanic')
#这份数据包含了惨遭厄运的每位乘客的大量信息,包括性别(gender)、年龄(age)、船舱等级(class)和船票价格(fare paid)等
In[84]: titanic.groupby(['sex', 'class'])['survived'].aggregate('mean').unstack()
Out[84]: Class First Second Third
sex
female 0.968085 0.921053 0.500000
male 0.368852 0.157407 0.135447
In[85]: titanic.pivot_table('survived', index='sex', columns='class')
Out[85]: Class First Second Third
sex
female 0.968085 0.921053 0.500000
male 0.368852 0.157407 0.135447
In[86]: age = pd.cut(titanic['age'], [0, 18, 80])
titanic.pivot_table('survived', ['sex', age], 'class')
Out[86]: Class First Second Third
sex age
female (0, 18] 0.909091 1.000000 0.511628
(18, 80] 0.972973 0.900000 0.423729
male (0, 18] 0.800000 0.600000 0.215686
(18, 80] 0.375000 0.071429 0.133663
In[87]: titanic.pivot_table(index='sex', columns='class',
aggfunc={'survived':sum, 'fare':'mean'})
Out[87]: fare survived
Class First Second Third First Second Third
sex
female 106.125798 21.970121 16.118810 91.0 70.0 72.0
male 67.226127 19.741782 12.661633 45.0 17.0 47.0
In[88]: titanic.pivot_table('survived', index='sex', columns='class', margins=True)
Out[88]: Class First Second Third All
sex
female 0.968085 0.921053 0.500000 0.742038
male 0.368852 0.157407 0.135447 0.188908
All 0.629630 0.472826 0.242363 0.383838
8 向量化字符串操作
1.与Python字符串方法相似的str 方法
2.使用正则表达式的方法
3.其他字符串方法
9 处理时间序列
时间戳表示某个具体的时间点(例如2015 年7 月4 日上午7 点)。
时间间隔与周期表示开始时间点与结束时间点之间的时间长度,例如2015年(指的是2015年1月1日至2015年12月31日这段时间间隔)。周期通常是指一种特殊形式的时间间隔,每个间隔长度相同,彼此之间不会重叠(例如,以24小时为周期构成每一天)。
时间增量或持续时间表示精确的时间长度(例如,某程序运行持续时间22.56 秒)。
9.1 Python的日期与时间工具
1.原生Python的日期与时间工具
datetime模块:Python 基本的日期与时间功能都在此模块中,如datetime(year=2015, month=7, day=4)
dateutil模块:对各种字符串格式的日期进行正确解析,如date=parser.parse("4th of July, 2015")
2.时间类型数组
NumPy的datetime64类型,需要在设置日期时确定具体的输入类型,如date=np.array('2015-07-04', dtype=np.datetime64)
因为NumPy 的datetime64 数组内元素的类型是统一的,所以这种数组的运算速度会比Python 的datetime 对象的运算速度快很多,尤其是在处理较大数组时。
3.Pandas的日期与时间工具
Pandas所有关于日期与时间的处理方法全部都是通过Timestamp
对象实现的,它利用numpy.datetime64 的有效存储和向量化接口将datetime 和dateutil 的易用性有机结合起来。
Pandas通过一组Timestamp
对象就可以创建一个可以作为Series 或DataFrame 索引的DatetimeIndex
,即带时间戳的索引数据。1
2
3
4
5
6
7In[89]: import pandas as pd
date = pd.to_datetime("4th of July, 2015")
date
Out[89]: Timestamp('2015-07-04 00:00:00')
In[90]: date.strftime('%A')
Out[90]: 'Saturday'
9.2 Pandas时间序列数据结构
时间戳数据:Timestamp
类型,对应的索引数据结构是DatetimeIndex
。
时间周期数据:Period
类型,对应的索引数据结构是PeriodIndex
。
时间增量或持续时间:Timedelta
类型,对应的索引数据结构是TimedeltaIndex
。
1.最基础的时间对象:Timestamp 和DatetimeIndexpd.to_datetime()
函数可以解析许多日期与时间格式
任何DatetimeIndex 类型都可以通过to_period()
方法和一个频率代码转换成PeriodIndex 类型,如date.to_period('D')
2.有规律的时间序列:pd.date_range()
处理时间戳,通过传递日期范围创建一个有规律的日期序列pd.period_range()
可以处理有规律的周期pd.timedelta_range()
可以处理时间间隔1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28#pd.date_range()
#通过开始日期、结束日期和频率代码(同样是可选的)创建一个有规律的日期序列
In[91]: pd.date_range('2015-07-03', '2015-07-10')
Out[91]: DatetimeIndex(['2015-07-03', '2015-07-04', '2015-07-05', '2015-07-06',
'2015-07-07', '2015-07-08', '2015-07-09', '2015-07-10'],
dtype='datetime64[ns]', freq='D')
#通过开始时间与周期数periods创建
In[92]: pd.date_range('2015-07-03', periods=8)
Out[92]: DatetimeIndex(['2015-07-03', '2015-07-04', '2015-07-05', '2015-07-06',
'2015-07-07', '2015-07-08', '2015-07-09', '2015-07-10'],
dtype='datetime64[ns]', freq='D')
#通过freq 参数改变时间间隔,默认值是D
In[93]: pd.date_range('2015-07-03', periods=8, freq='H')
Out[93]: DatetimeIndex(['2015-07-03 00:00:00', '2015-07-03 01:00:00',
'2015-07-03 02:00:00', '2015-07-03 03:00:00',
'2015-07-03 04:00:00', '2015-07-03 05:00:00',
'2015-07-03 06:00:00', '2015-07-03 07:00:00'],
dtype='datetime64[ns]', freq='H')
#pd.period_range()创建一个以月为周期的序列
In[94]: pd.period_range('2015-07', periods=8, freq='M')
Out[94]: PeriodIndex(['2015-07', '2015-08', '2015-09', '2015-10', '2015-11',
'2015-12', '2016-01', '2016-02'],
dtype='int64', freq='M')
#pd.timedelta_range()创建一个以小时递增的序列
In[95]: pd.timedelta_range(0, periods=10, freq='H')
Out[95]: TimedeltaIndex(['00:00:00', '01:00:00', '02:00:00', '03:00:00', '04:00:00',
'05:00:00', '06:00:00', '07:00:00', '08:00:00', '09:00:00'],
dtype='timedelta64[ns]', freq='H')
3.时间频率/偏移量
带开始索引S的频率代码:
可以在频率代码后面加三位月份缩写字母来改变季、年频率的开始时间:Q-JAN、BQ-FEB、QS-MAR、BQS-APR 等
也可以在后面加三位星期缩写字母来改变一周的开始时间:W-SUN、W-MON、W-TUE、W-WED 等
9.3 重新取样、迁移和窗口
重新取样:resample()
以数据累计为基础,asfreq()
以数据选择为基础
时间迁移:shift()
迁移数据,tshift()
迁移索引
移动时间窗口:rolling()
移动统计值
- 本笔记参考书目:Jake VanderPlas的《Python数据科学手册》