Pandas
Pandas 中文
Pandas对象简介
Pandas的Series对象
Pandas 的 Series 对象是一个带索引数据构成的一维数组。可以用一个数组创建 Series 对象:
| In [60]: data = pd.Series([0.25, 0.5, 0.75, 1.0])
In [61]: data
Out[61]:
0 0.25
1 0.50
2 0.75
3 1.00
dtype: float64
|
从上面的结果中,你会发现 Series 对象将一组数据和一组索引绑定在一起,我们可以通过 values 属性和 index 属性获取数据。values 属性返回的结果与 NumPy 数组类似:
| In [62]: data.values
Out[62]: array([0.25, 0.5 , 0.75, 1. ])
|
index 属性返回的结果是一个类型为 pd.Index 的类数组对象
| In [63]: data.index
Out[63]: RangeIndex(start=0, stop=4, step=1)
|
和 NumPy 数组一样,数据可以通过 Python 中的括号索引标签获取
| In [64]: data[1]
Out[64]: 0.5
In [65]: data[1:3]
Out[65]:
1 0.50
2 0.75
dtype: float64
|
但是我们将会看到,Pandas 的 Series 对象比它模仿的一维 NumPy 数组更为通用、灵活
到目前为止,我们可能觉得 Series 对象和一维 NumPy 数组基本可以等价交换,但两者间的本质差异其实是索引:NumPy 数组通过隐式定义的整数索引获取数值,而 Pandas 的 Series 对象用一种显式定义的索引与数值关联。
显式索引的定义让 Series 对象拥有了更强的能力。例如,索引不再仅仅是整数,还可以是任意想要的类型。如果需要,完全可以用字符串定义索引
| In [66]: data = pd.Series([0.25, 0.5, 0.75, 1.0], index=['a', 'b', 'c', 'd'])
In [67]: data
Out[67]:
a 0.25
b 0.50
c 0.75
d 1.00
dtype: float64
|
获取数值的方式与之前一样
| In [68]: data['b']
Out[68]: 0.5
|
也可以使用不连续或不按顺序的索引:
| In [69]: data = pd.Series([0.25, 0.5, 0.75, 1.0], index=[2, 5, 3, 7])
In [70]: data
Out[70]:
2 0.25
5 0.50
3 0.75
7 1.00
dtype: float64
In [71]: data[5]
Out[71]: 0.5
|
你可以把 Pandas 的 Series 对象看成一种特殊的 Python 字典。字典是一种将任意键映射到一组任意值的数据结构,而 Series 对象其实是一种将类型键映射到一组类型值的数据结构。类型至关重要:就像 NumPy 数组背后特定类型的经过编译的代码使得它在某些操作上比普通的 Python 列表更加高效一样,Pandas Series 的类型信息使得它在某些操作上比 Python 的字典更高效
我们可以直接用 Python 的字典创建一个 Series 对象,让 Series 对象与字典的类比更加清晰
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | In [72]: population_dict = {'California': 38332521,
'Texas': 26448193,
'New York': 19651127,
'Florida': 19552860,
'Illinois': 12882135}
In [73]: population = pd.Series(population_dict)
In [74]: population
Out[74]:
California 38332521
Texas 26448193
New York 19651127
Florida 19552860
Illinois 12882135
dtype: int64
|
用字典创建 Series 对象时,其索引默认按照顺序排列。典型的字典数组获取方式仍然有效:
| In [75]: population['California']
Out[75]: 38332521
|
和字典不同,Series 对象还支持数组形式的操作,比如切片:
| In [77]: population['California':'New York']
Out[77]:
California 38332521
Texas 26448193
New York 19651127
dtype: int64
|
我们已经见过几种创建 Pandas 的 Series 对象的方法,都是像 pd.Series(data, index=index) 这样的形式:
其中,index 是一个可选参数,data 参数支持多种数据类型
例如,data 可以是列表或 NumPy 数组,这时 index 默认值为整数序列
| In [78]: pd.Series([2, 4, 6])
Out[78]:
0 2
1 4
2 6
dtype: int64
|
data 也可以是一个标量,创建 Series 对象时会重复填充到每个索引上:
| In [79]: pd.Series(5, index=[100, 200, 300])
Out[79]:
100 5
200 5
300 5
dtype: int64
|
data 还可以是一个字典
| In [80]: pd.Series({2:'a', 1:'b', 3:'c'})
Out[80]:
2 a
1 b
3 c
dtype: object
|
每一种形式都可以通过显式指定索引筛选需要的结果
| In [81]: pd.Series({2:'a', 1:'b', 3:'c'}, index=[3, 2])
Out[81]:
3 c
2 a
dtype: object
|
这里需要注意的是,Series 对象只会保留显式定义的键值对。
Pandas的DataFrame对象
如果将 Series 类比为带灵活索引的一维数组,那么 DataFrame 就可以看作是一种既有灵活的行索引,又有灵活列名的二维数组。就像你可以把二维数组看成是有序排列的一维数组一样,你也可以把 DataFrame 看成是有序排列的若干 Series 对象。这里的“排列”指的是它们拥有共同的索引
| In [3]: area_dict = {'California': 423967, 'Texas': 695662, 'New York': 141297, 'Florida': 170312, 'Illinois': 149995}
In [4]: area = pd.Series(area_dict)
In [5]: area
Out[5]:
California 423967
Texas 695662
New York 141297
Florida 170312
Illinois 149995
dtype: int64
|
再结合之前创建的 population 的 Series 对象,用一个字典创建一个包含这些信息的二维对象
| In [8]: states = pd.DataFrame({'population': population, 'area': area})
In [9]: states
Out[9]:
population area
California 38332521 423967
Texas 26448193 695662
New York 19651127 141297
Florida 19552860 170312
Illinois 12882135 149995
|
和 Series 对象一样,DataFrame 也有一个 index 属性可以获取索引标签
| In [10]: states.index
Out[10]: Index(['California', 'Texas', 'New York', 'Florida', 'Illinois'], dtype='object')
|
另外,DataFrame 还有一个 columns 属性,是存放列标签的 Index 对象
| In [11]: states.columns
Out[11]: Index(['population', 'area'], dtype='object')
|
因此 DataFrame 可以看作一种通用的 NumPy 二维数组,它的行与列都可以通过索引获取
与 Series 类似,我们可以把 DataFrame 看成一种特殊的字典。字典是一个键映射一个值,而 DataFrame 是一列映射一个 Series 的数据。例如,通过 'area' 的列属性可以返回包含面积数据的 Series 对象:
| In [12]: states['area']
Out[12]:
California 423967
Texas 695662
New York 141297
Florida 170312
Illinois 149995
Name: area, dtype: int64
|
这里需要注意的是,在 NumPy 的二维数组里,data[0] 返回第一行,而在 DataFrame 中,data['col0'] 返回第一列。因此,最好把 DataFrame 看成一种通用字典,而不是通用数组,即使这两种看法在不同情况下都是有用的。
(1) 通过单个 Series 对象创建。DataFrame 是一组 Series 对象的集合,可以用单个 Series 创建一个单列的 DataFrame
| In [13]: pd.DataFrame(population, columns=['population'])
Out[13]:
population
California 38332521
Texas 26448193
New York 19651127
Florida 19552860
Illinois 12882135
|
(2) 通过字典列表创建。任何元素是字典的类标都可以变成 DataFrame。用一个简单的列表综合来创建一些数据
| In [14]: data = [{'a': i, 'b': 2 * i} for i in range(3)]
In [15]: pd.DataFrame(data)
Out[15]:
a b
0 0 0
1 1 2
2 2 4
|
即使字典中有些键不存在,Pandas 也会用缺失值 NaN(不是数字, not a number)来表示:
| In [16]: pd.DataFrame([{'a': 1, 'b':2}, {'b': 3, 'c':4}])
Out[16]:
a b c
0 1.0 2 NaN
1 NaN 3 4.0
|
(3) 通过 Series 对象字典创建。就像之前见过的那样,DataFrame 也可以用一个由 Series 对象构成的字典创建
| In [19]: pd.DataFrame({'population': population, 'area': area})
Out[19]:
population area
California 38332521 423967
Texas 26448193 695662
New York 19651127 141297
Florida 19552860 170312
Illinois 12882135 149995
|
(4) 通过 NumPy 二维数组创建。假如有一个二维数组,就可以创建一个可以指定行列索引值的 DataFrame。如果不指定行列索引值,那么行列默认都是整数索引值
| In [22]: pd.DataFrame(np.random.rand(3, 2), columns=['foo', 'bar'], index=['a', 'b', 'c'])
Out[22]:
foo bar
a 0.586608 0.696365
b 0.054114 0.385159
c 0.179520 0.543672
|
Pandas的Index对象
Series 和 DataFrame 对象都使用便于引用和调整的显式索引。Pandas 的 Index 对象是一个很有趣的数据结构,可以将它看作是一个不可变数组或有序集合(实际上是一个多集,因为 Index 对象可能会包含重复值)。这两种观点使得 Index 对象能呈现一些有趣的功能。
用一个简单的整数列表来创建一个 Index 对象:
| In [23]: ind = pd.Index([2, 3, 5, 7, 11])
In [24]: ind
Out[24]: Int64Index([2, 3, 5, 7, 11], dtype='int64')
|
Index 对象的许多操作都像数组。例如,可以通过标准 Python 的取值方式获取数值,也可以通过切片获取数值
| In [25]: ind[1]
Out[25]: 3
In [26]: ind[::2]
Out[26]: Int64Index([2, 5, 11], dtype='int64')
|
Index 对象还有许多与 NumPy 数组相似的属性
| In [27]: print(ind.size, ind.shape, ind.ndim, ind.dtype)
5 (5,) 1 int64
|
Index 对象与 NumPy 数组之间的不同在于,Index 对象的索引是不可变的,也就是说不能通过通常的方式进行调整
| In [28]: ind[1] = 0 # TypeError: Index does not support mutable operations
|
Index 对象的不可变特征使得多个 DataFrame 和数组之间进行索引共享时更加安全,尤其是可以避免因修改索引时粗心大意而导致的副作用
Pandas 对象被设计用于实现许多操作,如连接(join)数据集,其中会涉及许多集合操作。Index 对象遵循 Python 标准库的集合(set)数据结构的许多习惯用法,包括并集、交集、差集等:
| In [29]: indA = pd.Index([1, 3, 5, 7, 9])
In [31]: indB = pd.Index([2, 3, 5, 7, 11])
In [32]: indA & indB # 交集
Out[32]: Int64Index([3, 5, 7], dtype='int64')
In [33]: indA | indB # 并集
Out[33]: Int64Index([1, 2, 3, 5, 7, 9, 11], dtype='int64')
In [34]: indA ^ indB # 异或
Out[34]: Int64Index([1, 2, 9, 11], dtype='int64')
|
这些操作还可以通过调用对象方法来实现,例如
| In [35]: indA.intersection(indB)
Out[35]: Int64Index([3, 5, 7], dtype='int64')
|
数据取值与选择
Series数据选择方法
| In [36]: data = pd.Series([0.25, 0.5, 0.75, 1.0], index=['a', 'b', 'c', 'd'])
In [37]: data
Out[37]:
a 0.25
b 0.50
c 0.75
d 1.00
dtype: float64
In [39]: data['b']
Out[39]: 0.5
|
我们还可以用 Python 字典的表达式和方法来检测键/索引的值:
| In [41]: 'a' in data
Out[41]: True
In [42]: data.keys()
Out[42]: Index(['a', 'b', 'c', 'd'], dtype='object')
In [43]: list(data.items())
Out[43]: [('a', 0.25), ('b', 0.5), ('c', 0.75), ('d', 1.0)]
|
Series 对象还可以用字典语法调整数据。就像你可以通过增加新的键扩展字典一样,你也可以通过增加新的索引值扩展 Series
| In [44]: data['e'] = 1.25
In [45]: data
Out[45]:
a 0.25
b 0.50
c 0.75
d 1.00
e 1.25
dtype: float64
|
Series 对象的可变性是一个非常方便的特性:Pandas 在底层已经为可能发生的内存布局和数据复制自动决策,用户不需要担心这些问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | In [46]: data['a':'c'] # 将显式索引作为切片
Out[46]:
a 0.25
b 0.50
c 0.75
dtype: float64
In [47]: data[0:2] # 将隐式整数索引作为切片
Out[47]:
a 0.25
b 0.50
dtype: float64
In [49]: data[(0.3 < data) & (data < 0.8)] # 掩码
Out[49]:
b 0.50
c 0.75
dtype: float64
In [50]: data[['a', 'e']] # 花哨的索引
Out[50]:
a 0.25
e 1.25
dtype: float64
|
在以上示例中,切片是绝大部分混乱之源。需要注意的是,当使用显式索引(即 data['a':'c']) 作切片时,结果包含最后一个索引,而当使用隐式索引(即 data[0:2]) 作切片时,结果不包含最后一个索引
这些切片和取值的习惯用法经常会造成混乱。例如,如果你的 Series 是显式整数索引,那么 data[1] 这样的取值操作会使用显式索引,而 data[1:3] 这样的切片操作却会使用隐式索引
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | In [51]: data = pd.Series(['a', 'b', 'c'], index=[1, 3, 5])
In [52]: data
Out[52]:
1 a
3 b
5 c
dtype: object
In [53]: data[1] # 取值操作是显式索引
Out[53]: 'a'
In [54]: data[1:3] # 切片操作是隐式索引
Out[54]:
3 b
5 c
dtype: object
|
由于整数索引很容易造成混淆,所以 Pandas 提供了一些索引器属性来作为取值的方法。它们不是 Series 对象的函数方法,而是暴露切片接口的属性
第一种索引器是 loc 属性,表示取值和切片都是显式的
| In [55]: data.loc[1]
Out[55]: 'a'
In [56]: data.loc[1:3]
Out[56]:
1 a
3 b
dtype: object
|
第二种是 iloc 属性,表示取值和切片都是 Python 形式的隐式索引
| In [57]: data.iloc[1]
Out[57]: 'b'
In [58]: data.iloc[1:3]
Out[58]:
3 b
5 c
dtype: object
|
Python 代码的设计原则之一是“显式优于隐式”。使用 loc 和 iloc 可以让代码更容易维护,可读性更高。特别是在处理整数索引的对象时,强烈推荐使用这两种索引器。它们既可以让代码阅读和理解起来更容易,也能避免因误用索引/切片而产生的小 bug
DataFrame数据选择方法
第一种类比是把 DataFrame 当作一个由若干 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 | In [63]: area
Out[63]:
California 423967
Texas 695662
New York 141297
Florida 170312
Illinois 149995
dtype: int64
In [64]: pop
Out[64]:
California 38332521
Texas 26448193
New York 19651127
Florida 19552860
Illinois 12882135
dtype: int64
In [65]: data = pd.DataFrame({'area':area, 'pop':pop})
In [66]: data
Out[66]:
area pop
California 423967 38332521
Texas 695662 26448193
New York 141297 19651127
Florida 170312 19552860
Illinois 149995 12882135
|
两个 Series 分别构成 DataFrame 的一列,可以通过对列名进行字典形式的取值获取数据
| In [67]: data['area']
Out[67]:
California 423967
Texas 695662
New York 141297
Florida 170312
Illinois 149995
Name: area, dtype: int64
|
同样,也可以用属性形式选择纯字符串列名的数据
| In [68]: data.area
Out[68]:
California 423967
Texas 695662
New York 141297
Florida 170312
Illinois 149995
Name: area, dtype: int64
|
对同一个对象进行属性形式与字典形式的列数据,结果是相同的
| In [69]: data.area is data['area']
Out[69]: True
|
虽然属性形式的数据选择方法很方便,但是它并不是通用的。如果列名不是纯字符串,或者列名与 DataFrame 的方法同名,那么就不能用属性索引。例如,DataFrame 有一个 pop() 方法,如果 data.pop 就不会获取 'pop' 列,而是显示为方法
| In [71]: data.pop is data['pop']
Out[71]: False
|
另外,还应该避免对用属性形式选择的列直接赋值(即可以用 data['pop'] = z,但不要用 data.pop = z)
还可以用字典形式的语法调整对象,如果要增加一列可以这样做
| In [72]: data['density'] = data['pop'] / data['area']
In [73]: data
Out[73]:
area pop density
California 423967 38332521 90.413926
Texas 695662 26448193 38.018740
New York 141297 19651127 139.076746
Florida 170312 19552860 114.806121
Illinois 149995 12882135 85.883763
|
可以把 DataFrame 看成是一个增强版的二维数组,用 values 属性按行查看数组数据
| In [74]: data.values
Out[74]:
array([[4.23967000e+05, 3.83325210e+07, 9.04139261e+01],
[6.95662000e+05, 2.64481930e+07, 3.80187404e+01],
[1.41297000e+05, 1.96511270e+07, 1.39076746e+02],
[1.70312000e+05, 1.95528600e+07, 1.14806121e+02],
[1.49995000e+05, 1.28821350e+07, 8.58837628e+01]])
|
理解列这一点,就可以把许多数组操作方式用在 DataFrame 上。例如,可以用 DataFrame 进行列转置
| In [75]: data.T
Out[75]:
California Texas New York Florida Illinois
area 4.239670e+05 6.956620e+05 1.412970e+05 1.703120e+05 1.499950e+05
pop 3.833252e+07 2.644819e+07 1.965113e+07 1.955286e+07 1.288214e+07
density 9.041393e+01 3.801874e+01 1.390767e+02 1.148061e+02 8.588376e+01
|
通过字典形式对列进行取值显然会限制我们把 DataFrame 作为 NumPy 数组可以获得的能力,尤其是当我们在 DataFrame 数组中使用单个行索引获取一行数据时
| In [76]: data.values[0]
Out[76]: array([4.23967000e+05, 3.83325210e+07, 9.04139261e+01])
|
而获取一列数据就需要向 DataFrame 传递单个列索引
| In [77]: data['area']
Out[77]:
California 423967
Texas 695662
New York 141297
Florida 170312
Illinois 149995
Name: area, dtype: int64
|
因此,在进行数组形式的取值时,我们就需要另一种方法--前面介绍过的 Pandas 索引器 loc 和 iloc 了。通过 iloc 索引器,我们就可以像对待 NumPy 数组一样索引 Pandas 的底层数组(Python 的隐式索引),DataFrame 的行列标签会自动保留在结果中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | In [80]: data.iloc[:3, :2]
Out[80]:
area pop
California 423967 38332521
Texas 695662 26448193
New York 141297 19651127
In [81]: data.loc[:'Illinois', :'pop']
Out[81]:
area pop
California 423967 38332521
Texas 695662 26448193
New York 141297 19651127
Florida 170312 19552860
Illinois 149995 12882135
|
任何用于处理 NumPy 形式数据的方法都可以用于这些索引器。例如,可以在 loc 索引器中结合使用掩码与花哨的索引方法
| In [82]: data.loc[data.density > 100, ['pop', 'density']]
Out[82]:
pop density
New York 19651127 139.076746
Florida 19552860 114.806121
|
任何一种取值方法都可以用于调整数据,这一点和 NumPy 的常用方法是相同的
| In [83]: data.iloc[0, 2] = 90
In [84]: data
Out[84]:
area pop density
California 423967 38332521 90.000000
Texas 695662 26448193 38.018740
New York 141297 19651127 139.076746
Florida 170312 19552860 114.806121
Illinois 149995 12882135 85.883763
|
还有一些取值方法和前面介绍过的方法不太一样。它们虽然看着有点奇怪,但是在实践中还是很好用的。首先,如果对单个标签取值就选择列,而对多个标签用切片就选择行
| In [85]: data['Florida':'Illinois']
Out[85]:
area pop density
Florida 170312 19552860 114.806121
Illinois 149995 12882135 85.883763
|
切片也可以不用索引值,而直接用行数来实现
| In [86]: data[1:3]
Out[86]:
area pop density
Texas 695662 26448193 38.018740
New York 141297 19651127 139.076746
|
与之类似,掩码操作也可以直接对每一行进行过滤,而不需要使用 loc 索引器
| In [87]: data[data.density > 100]
Out[87]:
area pop density
New York 141297 19651127 139.076746
Florida 170312 19552860 114.806121
|
这两种操作方法其实与 NumPy 数组的语法类似,虽然它们与 Pandas 的操作习惯不太一致,但是在实践中非常好用
DataFrame数据过滤方法
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 | In [38]: df = pd.DataFrame(np.random.randn(6,4), columns=list('ABCD'))
In [39]: df
Out[39]:
A B C D
0 0.935414 0.627536 -0.259355 -0.715265
1 -0.849681 -0.776875 -0.771094 -0.376245
2 0.851099 -0.511299 1.431271 1.821167
3 -0.651782 0.527855 1.140843 -0.006482
4 -0.414684 0.199196 -0.745896 0.557545
5 0.252019 -0.624213 -1.903845 1.148376
In [40]: df[df.C > 1]
Out[40]:
A B C D
2 0.851099 -0.511299 1.431271 1.821167
3 -0.651782 0.527855 1.140843 -0.006482
In [41]: df[df > 1]
Out[41]:
A B C D
0 NaN NaN NaN NaN
1 NaN NaN NaN NaN
2 NaN NaN 1.431271 1.821167
3 NaN NaN 1.140843 NaN
4 NaN NaN NaN NaN
5 NaN NaN NaN 1.148376
In [43]: df[df.A + df.D > 1.4]
Out[43]:
A B C D
2 0.851099 -0.511299 1.431271 1.821167
5 0.252019 -0.624213 -1.903845 1.148376
|
使用isin
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 | In [47]: df['E']=['one', 'one','two','three','four','three']
In [48]: df
Out[48]:
A B C D E
0 0.935414 0.627536 -0.259355 -0.715265 one
1 -0.849681 -0.776875 -0.771094 -0.376245 one
2 0.851099 -0.511299 1.431271 1.821167 two
3 -0.651782 0.527855 1.140843 -0.006482 three
4 -0.414684 0.199196 -0.745896 0.557545 four
5 0.252019 -0.624213 -1.903845 1.148376 three
In [51]: df.E.isin(['one'])
Out[51]:
0 True
1 True
2 False
3 False
4 False
5 False
Name: E, dtype: bool
In [49]: df[df.E.isin(['one'])]
Out[49]:
A B C D E
0 0.935414 0.627536 -0.259355 -0.715265 one
1 -0.849681 -0.776875 -0.771094 -0.376245 one
|
设置索引
使用set_index()
设置索引
| df.set_index(
keys,
drop=True,
append=False,
inplace=False,
verify_integrity=False,
)
|
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 | In [16]: df = pd.DataFrame([[1, 2, 3], [4, 5 ,6]], columns=list('ABC'))
In [17]: df
Out[17]:
A B C
0 1 2 3
1 4 5 6
In [18]: df.set_index('A')
Out[18]:
B C
A
1 2 3
4 5 6
In [29]: df.set_index('A', drop=False)
Out[29]:
A B C
A
1 1 2 3
4 4 5 6
In [30]: df.set_index('A', inplace=True)
In [31]: df
Out[31]:
B C
A
1 2 3
4 5 6
In [32]: df.set_index('B', inplace=True)
In [33]: df
Out[33]:
C
B
2 3
5 6
In [35]: df.index.names
Out[35]: FrozenList(['B'])
|
使用reset_index()
重置索引
| df.reset_index(
level=None,
drop=False,
inplace=False,
col_level=0,
col_fill='',
)
|
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 | In [8]: df = pd.DataFrame(np.arange(20).reshape(5,4),index=[1,3,4,6,8])
In [9]: df
Out[9]:
0 1 2 3
1 0 1 2 3
3 4 5 6 7
4 8 9 10 11
6 12 13 14 15
8 16 17 18 19
In [12]: df.reset_index()
Out[12]:
index 0 1 2 3
0 1 0 1 2 3
1 3 4 5 6 7
2 4 8 9 10 11
3 6 12 13 14 15
4 8 16 17 18 19
In [13]: df.reset_index(drop=True)
Out[13]:
0 1 2 3
0 0 1 2 3
1 4 5 6 7
2 8 9 10 11
3 12 13 14 15
4 16 17 18 19
In [14]: df.reset_index(drop=True, inplace=True)
In [15]: df
Out[15]:
0 1 2 3
0 0 1 2 3
1 4 5 6 7
2 8 9 10 11
3 12 13 14 15
4 16 17 18 19
|
操作DataFrame的行和列
map()
方法
map()是Series对象的一个函数,DataFrame中没有map(),map()的功能是将一个自定义函数作用于Series对象的每个元素
df['seconds'] = df['time'].map(lambda x: x % 100 + x // 100 % 100 * 60 + x // 10000 * 3600)
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
72 | In [57]: toward_dict = {1: '东', 2: '南', 3: '西', 4: '北'}
In [58]: df = pd.DataFrame({'house' : list('AABCEFG'),
...: 'price' : [100, 90, '', 50, 120, 150, 200],
...: 'toward' : ['1','1','2','3','','3','2']})
In [59]: df
Out[59]:
house price toward
0 A 100 1
1 A 90 1
2 B 2
3 C 50 3
4 E 120
5 F 150 3
6 G 200 2
In [60]: df['朝向'] = df.toward.map(toward_dict)
In [61]: df
Out[61]:
house price toward 朝向
0 A 100 1 NaN
1 A 90 1 NaN
2 B 2 NaN
3 C 50 3 NaN
4 E 120 NaN
5 F 150 3 NaN
6 G 200 2 NaN
In [69]: toward_dict2 = dict((str(key), val) for key, val in toward_dict.items())
In [72]: df['朝向'] = df['toward'].map(toward_dict2)
In [73]: df
Out[73]:
house price toward 朝向
0 A 100 1 东
1 A 90 1 东
2 B 2 南
3 C 50 3 西
4 E 120 NaN
5 F 150 3 西
6 G 200 2 南
In [74]: df.toward = df.toward.map(lambda x: np.nan if x == '' else x).map(int,na_action='ignore')
...: df['朝向2'] = df.toward.map(toward_dict)
In [75]: df
Out[75]:
house price toward 朝向 朝向2
0 A 100 1.0 东 东
1 A 90 1.0 东 东
2 B 2.0 南 南
3 C 50 3.0 西 西
4 E 120 NaN NaN NaN
5 F 150 3.0 西 西
6 G 200 2.0 南 南
In [5]: df = pd.DataFrame({'key1':['a','b','c','d'], 'key2':['one','two','three'
...: ,'four'], 'data1':np.arange(4), 'data2':np.arange(5,9)})
In [6]: df
Out[6]:
key1 key2 data1 data2
0 a one 0 5
1 b two 1 6
2 c three 2 7
3 d four 3 8
In [7]: df['data1'] = df['data1'].map(lambda x : "%.3f"%x)
In [8]: df
Out[8]:
key1 key2 data1 data2
0 a one 0.000 5
1 b two 1.000 6
2 c three 2.000 7
3 d four 3.000 8
|
使用apply
apply()函数的功能是将一个自定义函数作用于DataFrame的行或者列
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 | In [16]: df['data1'] = df['data1'].map(lambda x : float(x))
In [17]: df
Out[17]:
key1 key2 data1 data2
0 a one 0.0 5
1 b two 1.0 6
2 c three 2.0 7
3 d four 3.0 8
In [18]: df['total'] = df[['data1','data2']].apply(lambda x : x.sum(), axis = 1)
In [19]: df
Out[19]:
key1 key2 data1 data2 total
0 a one 0.0 5 5.0
1 b two 1.0 6 7.0
2 c three 2.0 7 9.0
3 d four 3.0 8 11.0
In [20]: df.loc['total'] = df[['data1','data2']].apply(lambda x : x.sum(), axis = 0) #注意索引行必须用loc
In [21]: df
Out[21]:
key1 key2 data1 data2 total
0 a one 0.0 5.0 5.0
1 b two 1.0 6.0 7.0
2 c three 2.0 7.0 9.0
3 d four 3.0 8.0 11.0
total NaN NaN 6.0 26.0 NaN
|
使用applymap
| In [22]: def add(n):
...: return '#' + str(n)
In [23]: df.applymap(add)
Out[23]:
key1 key2 data1 data2 total
0 #a #one #0.0 #5.0 #5.0
1 #b #two #1.0 #6.0 #7.0
2 #c #three #2.0 #7.0 #9.0
3 #d #four #3.0 #8.0 #11.0
total #nan #nan #6.0 #26.0 #nan
|
使用astype
df["code"] = df["code"].astype(str).str.pad(6, side='left', fillchar='0')
数据类型的转换:
将某一列的数据类型转换为 int64
| df['column name'] = df['column name'].astype(int64)
|
与字典的转换
df.to_dict(orient='dict', into=<class 'dict'>)
orient : str {'dict', 'list', 'series', 'split', 'records', 'index'}
- 'dict' (default) : dict like {column -> {index -> value}}
- 'list' : dict like {column -> [values]}
- 'series' : dict like {column -> Series(values)}
- 'split' : dict like
{'index' -> [index], 'columns' -> [columns], 'data' -> [values]}
- 'records' : list like
[{column -> value}, ... , {column -> value}]
- 'index' : dict like {index -> {column -> value}}
Abbreviations are allowed. s
indicates series
and sp
indicates split
.
| >>> df = pd.DataFrame({'col1': [1, 2],
... 'col2': [0.5, 0.75]},
... index=['row1', 'row2'])
>>> df
col1 col2
row1 1 0.50
row2 2 0.75
>>> df.to_dict()
{'col1': {'row1': 1, 'row2': 2}, 'col2': {'row1': 0.5, 'row2': 0.75}}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | >>> df.to_dict('series')
{'col1': row1 1
row2 2
Name: col1, dtype: int64,
'col2': row1 0.50
row2 0.75
Name: col2, dtype: float64}
>>> df.to_dict('split')
{'index': ['row1', 'row2'], 'columns': ['col1', 'col2'],
'data': [[1, 0.5], [2, 0.75]]}
>>> df.to_dict('records')
[{'col1': 1, 'col2': 0.5}, {'col1': 2, 'col2': 0.75}]
>>> df.to_dict('index')
{'row1': {'col1': 1, 'col2': 0.5}, 'row2': {'col1': 2, 'col2': 0.75}}
|
| >>> from collections import OrderedDict, defaultdict
>>> df.to_dict(into=OrderedDict)
OrderedDict([('col1', OrderedDict([('row1', 1), ('row2', 2)])),
('col2', OrderedDict([('row1', 0.5), ('row2', 0.75)]))])
|
| >>> dd = defaultdict(list)
>>> df.to_dict('records', into=dd)
[defaultdict(<class 'list'>, {'col1': 1, 'col2': 0.5}),
defaultdict(<class 'list'>, {'col1': 2, 'col2': 0.75})]
|
pmv_pre_dict[code] = self.tick_df[self.tick_df["code"]==code].set_index('seconds').to_dict()['pmv']
times_dict = times.set_index('time').to_dict()['reform_time']
遍历方法
DataFrame.iterrows()
1
2
3
4
5
6
7
8
9
10
11
12 | import time
import numpy as np
import pandas as pd
data = pd.DataFrame({'data1':np.random.randn(30000),'data2':np.random.randn(30000),'data3':np.random.randn(30000)})
df = pd.DataFrame()
start_time = time.time()
for index, row in data.iterrows():
df=df.append(row)
print(f"use time = {time.time() - start_time} s")
"""
use time = 23.419179677963257 s
"""
|
1
2
3
4
5
6
7
8
9
10
11
12 | import time
import numpy as np
import pandas as pd
data = pd.DataFrame({'data1':np.random.randn(30000),'data2':np.random.randn(30000),'data3':np.random.randn(30000)})
l = []
start_time = time.time()
for index, row in data.iterrows():
l.append(row)
print(f"use time = {time.time() - start_time} s")
"""
use time = 1.2698729038238525 s
"""
|
DataFrame.iloc()
1
2
3
4
5
6
7
8
9
10
11
12 | import time
import numpy as np
import pandas as pd
data = pd.DataFrame({'data1':np.random.randn(30000),'data2':np.random.randn(30000),'data3':np.random.randn(30000)})
df = pd.DataFrame()
start_time = time.time()
for i in range(len(data)):
df=df.append(data.iloc[i])
print(f"use time = {time.time() - start_time} s")
"""
use time = 25.31654405593872 s
"""
|
1
2
3
4
5
6
7
8
9
10
11
12 | import time
import numpy as np
import pandas as pd
data = pd.DataFrame({'data1':np.random.randn(30000),'data2':np.random.randn(30000),'data3':np.random.randn(30000)})
l = []
start_time = time.time()
for i in range(len(data)):
l.append(data.iloc[i])
print(f"use time = {time.time() - start_time} s")
"""
use time = 3.5785369873046875 s
"""
|
DataFrame.itertuples()
1
2
3
4
5
6
7
8
9
10
11
12 | import time
import numpy as np
import pandas as pd
data = pd.DataFrame({'data1':np.random.randn(30000),'data2':np.random.randn(30000),'data3':np.random.randn(30000)})
l = []
start_time = time.time()
for row in data.itertuples():
l.append((row[1], row[2], row[3]))
print(f"use time = {time.time() - start_time} s")
"""
use time = 0.04286384582519531 s
"""
|
row[0]
为索引
zip()方法
1
2
3
4
5
6
7
8
9
10
11
12 | import time
import numpy as np
import pandas as pd
data = pd.DataFrame({'data1':np.random.randn(30000),'data2':np.random.randn(30000),'data3':np.random.randn(30000)})
l = []
start_time = time.time()
for row in zip(data['data1'], data['data2'], data['data3']):
l.append((row[0], row[1], row[2]))
print(f"use time = {time.time() - start_time} s")
"""
use time = 0.02226996421813965 s
"""
|
去重
| df.drop_duplicates(subset=None, keep='first', inplace=False)
|
Pandas数值运算方法
NumPy 的基本能力之一是快速对每个元素进行运算,既包括基本算术运算(加、减、乘、除),也包括更复杂的运算(三角函数、指数函数和对数函数等)。Pandas 继承了 NumPy 的功能
但是 Pandas 也实现了一些高效技巧:对于一元运算(像函数与三角函数),这些通用函数将在数据结果中保留索引和列标签,而对于二元运算(如加法和乘法),Pandas 在传递通用函数时会自动对齐索引进行计算。
通用函数:保留索引
因为 Pandas 是建立在 NumPy 基础之上的,所以 NumPy 的通用函数同样适用于 Pandas 的 Series 和 DataFrame 对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | In [6]: rng = np.random.RandomState(42)
In [7]: ser = pd.Series(rng.randint(0, 10, 4))
In [8]: ser
Out[8]:
0 6
1 3
2 7
3 4
dtype: int64
In [13]: df = pd.DataFrame(rng.randint(0, 10, (3,4)), columns=['A', 'B', 'C', 'D'])
In [14]: df
Out[14]:
A B C D
0 6 3 7 4
1 6 9 2 6
2 7 4 3 7
|
如果对这两个对象的其中一个使用 NumPy 通用函数,生成的结果是另一个保留索引的 Pandas 对象
| In [15]: np.exp(ser)
Out[15]:
0 403.428793
1 20.085537
2 1096.633158
3 54.598150
dtype: float64
|
或者,再做一个比较复杂的运算
| In [16]: np.sin(df * np.pi / 4)
Out[16]:
A B C D
0 -1.000000 7.071068e-01 -0.707107 1.224647e-16
1 -1.000000 7.071068e-01 1.000000 -1.000000e+00
2 -0.707107 1.224647e-16 0.707107 -7.071068e-01
|
通用函数:索引对齐
当在两个 Series 或 DataFrame 对象上进行二元计算时,Pandas 会在计算过程中对齐两个对象的索引。当处理不完整的数据时,这一点非常方便
假如你要整合两个数据源的数据,其中一个是美国面积最大的三个州的面积数据,另一个是美国人口最多的三个州的人口数据
| In [18]: area = pd.Series({'Alaska': 1723337, 'Texas': 695662, 'California': 423967}, name='area')
In [19]: population = pd.Series({'California': 38332521, 'Texas': 26448193, 'New York': 19651127}, name='population')
|
来看看如果用人口除以面积会得到什么样的结果:
| In [20]: population / area
Out[20]:
Alaska NaN
California 90.413926
New York NaN
Texas 38.018740
dtype: float64
|
结果数组的索引是两个输入数组索引的并集。我们也可以用 Python 标准库的集合运算法则来获得这个索引
| In [21]: area.index | population.index
Out[21]: Index(['Alaska', 'California', 'New York', 'Texas'], dtype='object')
|
对于缺失位置的数据,Pandas 会用 NaN 填充,表示“此处无数”。这是 Pandas 表示缺失值的方法。这种索引对齐方式是通过 Python 内置的集合运算规则实现的,任何缺失值默认都用 NaN 填充:
| In [22]: A = pd.Series([2, 4, 6], index=[0, 1, 2])
In [23]: B = pd.Series([1, 3, 5], index=[1, 2, 3])
In [24]: A + B
Out[24]:
0 NaN
1 5.0
2 9.0
3 NaN
dtype: float64
|
如果用 NaN 值不是我们想要的结果,那么可以用适当的对象方法替代运算符。例如,A.add(B) 等价于 A + B,也可以设置参数自定义 A 或 B 缺失的数据
| In [25]: A.add(B, fill_value=0)
Out[25]:
0 2.0
1 5.0
2 9.0
3 5.0
dtype: float64
|
在计算两个 DataFrame 时,类似的索引对齐规则也同样会出现在共同(并集)列中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | In [26]: A = pd.DataFrame(rng.randint(0, 20, (2, 2)), columns=list('AB'))
In [27]: A
Out[27]:
A B
0 2 1
1 11 5
In [28]: B = pd.DataFrame(rng.randint(0, 10, (3, 3)), columns=list('BAC'))
In [29]: B
Out[29]:
B A C
0 1 4 0
1 9 5 8
2 0 9 2
In [30]: A + B
Out[30]:
A B C
0 6.0 2.0 NaN
1 16.0 14.0 NaN
2 NaN NaN NaN
|
你会发现,两个对象的行列索引可以是不同顺序的,结果的索引会自动按顺序排列。在 Series 中,我们可以通过运算符方法的 fill_value 参数自定义缺失值。这里我们将用 A 中所有值的均值来填充缺失值(计算 A 的均值需要用 stack 将二维数组压缩成一维数组):
| In [31]: fill = A.stack().mean()
In [32]: A.add(B, fill_value=fill)
Out[32]:
A B C
0 6.00 2.00 4.75
1 16.00 14.00 12.75
2 13.75 4.75 6.75
|
Python 运算符于 Pandas 方法的映射关系
Python运算符 |
Pandas方法 |
+ |
add() |
- |
sub()、subtract() |
* |
mul()、multiply() |
/ |
truediv()、div()、divide() |
// |
floordiv() |
% |
mod() |
** |
pow() |
通用函数:DataFrame与Series的运算
我们经常需要对一个 DataFrame 和一个 Series 进行计算,行列对齐方式与之前类似。也就是说,DataFrame 和 Series 的运算规则,与 NumPy 中二维数组与一维数组的运算规则是一样的。
| In [35]: A = rng.randint(10, size=(3, 4))
In [36]: A
Out[36]:
array([[6, 3, 8, 2],
[4, 2, 6, 4],
[8, 6, 1, 3]])
In [37]: A - A[0]
Out[37]:
array([[ 0, 0, 0, 0],
[-2, -1, -2, 2],
[ 2, 3, -7, 1]])
|
根据 NumPy 的广播规则,让二维数组减自身的一行数据会按行计算。
在 Pandas 里默认也是按行计算的
| In [38]: df = pd.DataFrame(A, columns=list('QRST'))
In [39]: df - df.iloc[0]
Out[39]:
Q R S T
0 0 0 0 0
1 -2 -1 -2 2
2 2 3 -7 1
|
如果想按列计算,那么就需要利用前面介绍过的运算方法,通过 axis 参数设置
| In [40]: df.subtract(df['R'], axis=0)
Out[40]:
Q R S T
0 3 0 5 -1
1 2 0 4 2
2 2 0 -5 -3
|
你会发现 DataFrame/Series 的运算与前面介绍的运算一样,结果的索引都会自动对齐:
1
2
3
4
5
6
7
8
9
10
11
12 | In [41]: halfrow = df.iloc[0, ::2]
In [42]: halfrow
Out[42]:
Q 6
S 8
Name: 0, dtype: int64
In [43]: df - halfrow
Out[43]:
Q R S T
0 0.0 NaN 0.0 NaN
1 -2.0 NaN -2.0 NaN
2 2.0 NaN -7.0 NaN
|
这些行列索引的保留与对齐方法说明 Pandas 在运算时会一直保存这些数据内容,从而避免在处理数据类型有差异和/或维度不一致的 NumPy 数组时可能遇到的问题
处理缺失值
Pandas的缺失值
Pandas 可以使用的第一种缺失值标签是 None,它是一个 Python 的单体对象,经常在代码中表示缺失值。由于 None 是一个 Python 对象,所以不能作为任何 NumPy/Pandas 数组类型的缺失值,只能用于 'object' 数组类型(即由 Python 对象构成的数组)
| In [44]: vals1 = np.array([1, None, 3, 4])
In [45]: vals1
Out[45]: array([1, None, 3, 4], dtype=object)
|
这里 dtype=object 表示 NumPy 认为由于这个数组是 Python 对象构成的,因此将其类型判断为 object。虽然这种类型在某些情景中非常有用,对数据的任何操作最终都会在 Python 层面完成,但是在常见的快速操作时,这种类型比其他原生类型数组要消耗更多的资源
| In [46]: for dtype in ['object', 'int']:
print("dtype = ", dtype)
%timeit np.arange(1E6, dtype=dtype).sum()
print()
dtype = object
62.8 ms ± 371 µs per loop
dtype = int
1.6 ms ± 6.06 µs per loop
|
使用 Python 对象构成的数组就意味着如果你对一个包含 None 的数组进行累计操作,如 sum() 或者 min(),那么通常会出现类型错误
这就是说,在 Python 中没有定义整数与 None 之间的加法运算
另一种缺失值的标签是 Nan(全称 Not a Number, 不是一个数字),是一种按照 IEEE 浮点数标准设计、在任何系统中都兼容的特殊浮点数
| In [47]: vals2 = np.array([1, np.nan, 3, 4])
In [48]: vals2.dtype
Out[48]: dtype('float64')
|
请注意,NumPy 会为这个数组选择一个原生浮点类型,这意味着和之前的 object 类型数组不同,这个数组会被编译成 C 代码从而实现快速操作。你可以把 NaN 看作是一个数据类病毒--它会将与它接触过的数据同化。无论和 NaN 进行何种操作,最终结果都是 NaN
| In [49]: 1 + np.nan
Out[49]: nan
In [50]: 0 * np.nan
Out[50]: nan
|
虽然这些累计操作的结果定义是合理的(即不会抛出异常),但是并非总是有效的:
| In [51]: vals2.sum(), vals2.min(), vals2.max()
Out[51]: (nan, nan, nan)
|
NumPy 也提供了一些特殊的累计函数,它们可以忽略缺失值的影响:
| In [53]: np.nansum(vals2), np.nanmin(vals2), np.nanmax(vals2)
Out[53]: (8.0, 1.0, 4.0)
|
谨记,NaN 是一种特殊的浮点数,不是整数、字符串以及其他数据类型
虽然 NaN 与 None 各有各的用处,但是 Pandas 把它们看成是可以等价交换的,在适当的时候会将两者进行替换
| In [59]: pd.Series([1, np.nan, 2, None])
Out[59]:
0 1.0
1 NaN
2 2.0
3 NaN
dtype: float64
|
Pandas 会将没有标签值的数据类型自动转换为 NA。例如,当我们将整型数组中的一个值设置为 np.nan 时,这个值就会强制转换成浮点数缺失值 NA.
1
2
3
4
5
6
7
8
9
10
11
12 | In [60]: x = pd.Series(range(2), dtype=int)
In [61]: x
Out[61]:
0 0
1 1
dtype: int64
In [62]: x[0] = None
In [63]: x
Out[63]:
0 NaN
1 1.0
dtype: float64
|
请注意,除了将整型数组的缺失值强制转换为浮点数,Pandas 还会自动将 None 转换为 NaN。
Pandas 对不同类型缺失值的转换规则
类型 |
缺失值转换规则 |
NA标签值 |
floating 浮点型 |
无变化 |
np.nan |
object 对象类型 |
无变化 |
None 或 np.nan |
integer 整数类型 |
强制转换为 float64 |
np.nan |
boolean 布尔类型 |
强制转换为 object |
None 或 np.nan |
需要注意的是,Pandas 中字符串类型的数据通常是用 object 类型存储的
处理缺失值
Pandas 基本上把 None 和 NaN 看成是可以等价交换的缺失值形式。为了完成这种交换过程,Pandas 提供了一些方法来发现、剔除、替换数据结构中的缺失值,主要包括以下几种
isnull()
:创建一个布尔类型的掩码标签缺失值
notnull()
:与 isnull() 操作相反
dropna()
:返回一个剔除缺失值的数据
fillna()
:返回一个填充了缺失值的数据副本
Pandas 数据结构有两种有效的方法可以发现缺失值:isnull() 和 notnull()。每种方法都返回布尔类型的掩码数据,例如
| In [64]: data = pd.Series([1, np.nan, 'hello', None])
In [65]: data.isnull()
Out[65]:
0 False
1 True
2 False
3 True
dtype: bool
|
布尔类型掩码数组可以直接作为 Series 或 DataFrame 的索引使用:
| In [66]: data[data.notnull()]
Out[66]:
0 1
2 hello
dtype: object
|
在 Series 里使用的 isnull() 和 notnull() 同样适用于 DataFrame,产生的结果同样是布尔类型
除了前面介绍的掩码方法,还有两种很好用的缺失值处理方法,分别是 dropna()(剔除缺失值)和 fillna()(填充缺失值)。在 Series 上使用这些方法非常简单
| In [67]: data.dropna()
Out[67]:
0 1
2 hello
dtype: object
|
而在 DataFrame 上使用它们时需要设置一些参数,例如下面的 DataFrame
| In [68]: df = pd.DataFrame([[1, np.nan, 2], [2, 3, 5], [np.nan, 4, 6]])
In [69]: df
Out[69]:
0 1 2
0 1.0 NaN 2
1 2.0 3.0 5
2 NaN 4.0 6
|
我们没法从 DataFrame 中单独剔除一个值,要么是剔除缺失值所在的整行,要么是整列。根据实际需求,有时你需要剔除整行,有时可能是列,DataFrame 中的 dropna() 会有一些参数可以配置
默认情况下,dropna() 会剔除任何包含缺失值的整行数据
| In [70]: df.dropna()
Out[70]:
0 1 2
1 2.0 3.0 5
|
可以设置按不同的坐标轴剔除缺失值,比如 axis=1(或 axis='columss')会剔除任何包含缺失值的整列数据
| In [71]: df.dropna(axis='columns')
Out[71]:
2
0 2
1 5
2 6
|
但是这么做也会把非缺失值一并剔除,因为可能有时候只需要剔除全部是缺失值的行或列,或者绝大多数是缺失值的行或列。这些需求可以通过设置 how 或 thresh 参数来满足,它们可以设置剔除行或列缺失值的数量阈值
默认设置是 how='any',也就是说只要有缺失值就是剔除整行或整列(通过 axis 设置坐标轴)。你还可以设置 how='all',这样就只会剔除全部是缺失值的行或列了
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | In [72]: df[3] = np.nan
In [73]: df
Out[73]:
0 1 2 3
0 1.0 NaN 2 NaN
1 2.0 3.0 5 NaN
2 NaN 4.0 6 NaN
In [74]: df.dropna(axis='columns', how='all')
Out[74]:
0 1 2
0 1.0 NaN 2
1 2.0 3.0 5
2 NaN 4.0 6
|
还可以通过 thresh 参数设置行或列中非缺失值的最小数量,从而实现更加个性化的配置
| In [75]: df.dropna(axis='rows', thresh=3)
Out[75]:
0 1 2 3
1 2.0 3.0 5 NaN
|
第 1 行与第 3 行被剔除了,因为它们只包含两个非缺失值
有时候你可能并不想移除缺失值,而是想把它们替换成有效的数值。有效的值可能是像 0、1、2 那样单独的值,也可能是经过填充或转换得到的。虽然你可以通过 isnull() 方法建立掩码来填充缺失值,但是 Pandas 为此专门提供了一个 fillna() 方法,它将返回填充了缺失值后的数组副本
| In [76]: data = pd.Series([1, np.nan, 2, None, 3], index=list('abcde'))
In [77]: data
Out[77]:
a 1.0
b NaN
c 2.0
d NaN
e 3.0
dtype: float64
|
我们将用一个单独的值来填充缺失值,例如用 0
| In [78]: data.fillna(0)
Out[78]:
a 1.0
b 0.0
c 2.0
d 0.0
e 3.0
dtype: float64
|
可以用缺失值前面的有效值来从前往后填充(forward-fill)
| In [79]: data.fillna(method='ffill')
Out[79]:
a 1.0
b 1.0
c 2.0
d 2.0
e 3.0
dtype: float64
|
也可以用缺失值后面的有效值来从后往前填充(back-fill)
| In [80]: data.fillna(method='bfill')
Out[80]:
a 1.0
b 2.0
c 2.0
d 3.0
e 3.0
dtype: float64
|
DataFrame 的操作方法与 Series 类似,只是在填充时需要设置坐标轴参数 axis
| In [82]: df.fillna(method='ffill', axis=1)
Out[82]:
0 1 2 3
0 1.0 1.0 2.0 2.0
1 2.0 3.0 5.0 5.0
2 NaN 4.0 6.0 6.0
|
需要注意的是,假如在从前往后填充时,需要填充的缺失值前面没有值,那么它就仍然是缺失值
合并数据集:Concat与Append操作
将不同的数据源进行合并是数据科学中最有趣的事情之一,这既包括将两个不同的数据集非常简单地拼接在一起,也包括用数据库那样的连接(join)与合并(merge)操作处理有重叠字段的数据集。Series 与 DataFrame 都具备这类操作,Pandas 的函数与方法让数据合并变得快速简单
1
2
3
4
5
6
7
8
9
10
11
12
13 | 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 [4]: make_df('ABC', range(3))
Out[4]:
A B C
0 A0 B0 C0
1 A1 B1 C1
2 A2 B2 C2
|
pd.concat() 可以简单地合并一维的 Series 或 DataFrame 对象,与 np.concatenate() 合并数组一样
| In [6]: ser1 = pd.Series(['A', 'B', 'C'], index=[1, 2, 3])
In [7]: ser2 = pd.Series(['D', 'E', 'F'], index=[4, 5, 6])
In [8]: pd.concat([ser1, ser2])
Out[8]:
1 A
2 B
3 C
4 D
5 E
6 F
dtype: object
|
它也可以用来合并高维数据,例如下面的 DataFrame:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | In [9]: df1 = make_df('AB', [1, 2])
In [10]: df2 = make_df('AB', [3, 4])
In [11]: print(df1); print(df2); print(pd.concat([df1, df2]))
A B
1 A1 B1
2 A2 B2
A B
3 A3 B3
4 A4 B4
A B
1 A1 B1
2 A2 B2
3 A3 B3
4 A4 B4
|
默认情况下,DataFrame 的合并都是逐行进行的(默认设置是 axis=0)。与 np.concatenate() 一样,pd.concat 也可以设置合并坐标轴,例如下面的示例:
| In [15]: print(df3); print(df4); print(pd.concat([df3, df4], axis=1))
A B
0 A0 B0
1 A1 B1
C D
0 C0 D0
1 C1 D1
A B C D
0 A0 B0 C0 D0
1 A1 B1 C1 D1
|
np.concatenate 与 pd.concat 最主要的差异之一就是 Pandas 在合并时会保留索引,即使索引时重复的!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | In [17]: x = make_df('AB', [0, 1])
In [18]: y = make_df('AB', [2, 3])
In [19]: y.index = x.index # 复制索引
In [20]: print(x); print(y); print(pd.concat([x, y]))
A B
0 A0 B0
1 A1 B1
A B
0 A2 B2
1 A3 B3
A B
0 A0 B0
1 A1 B1
0 A2 B2
1 A3 B3
|
你会发现结果中的索引是重复的。虽然 DataFrame 允许这么做,但结果并不是我们想要的。pd.concat() 提供了一些解决这个问题的方法
(1)捕捉索引重复的错误。如果你想要检测 pd.concat() 合并的结果中是否出现了重复的索引,可以设置 verify_integrity 参数。将参数设置为 True,合并时若有索引重复就会触发异常。
| try:
pd.concat([x, y], verify_integrity=True)
except ValueError as e:
print("ValueError:", e)
ValueError: Indexes have overlapping values: Int64Index([0, 1], dtype='int64')
|
(2)忽略索引。有时索引无关紧要,那么合并时就可以忽略它们,可以通过设置 ignore_index 参数来实现。如果将参数设置为 True,那么合并时将会创建一个新的整数索引
1
2
3
4
5
6
7
8
9
10
11
12 | In [23]: print(x); print(y); print(pd.concat([x, y], ignore_index=True))
A B
0 A0 B0
1 A1 B1
A B
0 A2 B2
1 A3 B3
A B
0 A0 B0
1 A1 B1
2 A2 B2
3 A3 B3
|
前面介绍的简单示例都有一个共同特点,那就是合并的 DataFrame 都是同样的列名。而在实际工作中,需要合并的数据往往带有不同的列名,而 pd.concat 提供了一些选项来解决这类合并问题。
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | In [24]: df5 = make_df('ABC', [1, 2])
In [26]: df6 = make_df('BCD', [3, 4])
In [27]: print(df5); print(df6); print(pd.concat([df5, df6]))
A B C
1 A1 B1 C1
2 A2 B2 C2
B C D
3 B3 C3 D3
4 B4 C4 D4
A B C D
1 A1 B1 C1 NaN
2 A2 B2 C2 NaN
3 NaN B3 C3 D3
4 NaN B4 C4 D4
|
默认情况下,某个位置上缺失的数据会用 NaN 表示。如果不想这样,可以用 join 和 join_axes 参数设置合并方法。默认的合并方式是对所有输入列进行并集合并(join='outer'),当然也可以用 join='inner' 实现对输入列的交集合并
1
2
3
4
5
6
7
8
9
10
11
12 | In [28]: print(df5); print(df6); print(pd.concat([df5, df6], join='inner'))
A B C
1 A1 B1 C1
2 A2 B2 C2
B C D
3 B3 C3 D3
4 B4 C4 D4
B C
1 B1 C1
2 B2 C2
3 B3 C3
4 B4 C4
|
另一种合并方式是直接确定结果使用的列名,设置 join_axes 参数,里面是索引对象构成的列表(是列表的列表)
1
2
3
4
5
6
7
8
9
10
11
12 | In [29]: print(df5); print(df6); print(pd.concat([df5, df6], join_axes=[df5.columns]))
A B C
1 A1 B1 C1
2 A2 B2 C2
B C D
3 B3 C3 D3
4 B4 C4 D4
A B C
1 A1 B1 C1
2 A2 B2 C2
3 NaN B3 C3
4 NaN B4 C4
|
pd.concat 的合并功能可以满足你在合并两个数据集时的许多需求,操作时请记住这一点
因为直接进行数组合并的需求非常普遍,所以 Series 和 DataFrame 对象都支持 append 方法,让你通过最少的代码实现合并功能。例如,你可以使用 df1.append(df2),效果与 pd.concat([df1, df2]) 一样
1
2
3
4
5
6
7
8
9
10
11
12 | In [30]: print(df1); print(df2); print(df1.append(df2))
A B
1 A1 B1
2 A2 B2
A B
3 A3 B3
4 A4 B4
A B
1 A1 B1
2 A2 B2
3 A3 B3
4 A4 B4
|
需要主要的是,与 Python 列表中的 append() 和 extend() 方法不同,Pandas 的 append() 不直接更新原有对象的值,而是为合并后的数据创建一个新对象。因此,它不能被称之为一个非常高效的解决方案,因为每次合并都需要重新创建索引和数据缓存。总之,如果你需要进行多个 append 操作,还是建议先创建一个 DataFrame 列表,然后用 concat() 函数一次性解决所有合并任务
合并数据集:合并与连接
Pandas 的基本特性之一就是高性能的内存式数据连接(join)与合并(merge)操作。
pd.merge() 实现的功能基于关系代数的一部分。关系代数是处理关系型数据的通用理论,绝大部分数据库的可用操作都以此为理论基础。关系代数方法论的强大之处在于,它提出的若干简单操作规则经过组合就可以为任意数据集构建十分复杂的操作。借助在数据库或程序里已经高效实现的基本操作规则,你可以完成许多非常复杂的操作
Pandas 在 pd.merge() 函数与 Series 和 DataFrame 的 join() 方法里实现了这些基本操作规则。
数据连接的类型
数据输入输出
1
2
3
4
5
6
7
8
9
10
11
12
13 | pd.read_csv('train.csv')
pd.read_csv(path,index_col=0)
# csv 字符串转 pandas
pd.read_csv(io.StringIO(xxx))
to_csv(None) 返回 csv 格式字符串
to_csv() 返回 csv 格式字符串
df.to_csv('foo.csv')
ans.to_csv('ans.csv', index=False)
|
操作数据库
写数据库
用to_sql
写数据库
| df.to_sql(
name,
con,
schema=None,
if_exists='fail',
index=True,
index_label=None,
chunksize=None,
dtype=None,
method=None,
)
|
name
: string, Name of SQL table.
con
: sqlalchemy.engine.Engine or sqlite3.Connection
index
: bool, default True, Write DataFrame index as a column
if_exists
: {'fail', 'replace', 'append'}, default 'fail'
How to behave if the table already exists.
fail: Raise a ValueError.
replace: Drop the table before inserting new values.
append: Insert new values to the existing table.
| import pandas as pd
import sqlalchemy
engine = sqlalchemy.create_engine('mysql+pymysql://root:123456@localhost:3306/test_db')
df = pd.DataFrame({'name': ['小红', '小蓝'], 'sex': ['女', '男']})
df.to_sql(name='test_table', con=engine, index=False, if_exists='append')
|
| import pandas as pd
import sqlalchemy
engine = sqlalchemy.create_engine('postgresql+psycopg2://nocilantro:123456@localhost:5432/test_db')
df = pd.DataFrame({'name': ['😂', '不要香菜'], 'sex': ['女', '男']})
df.to_sql(name='test_table', con=engine, index=False, if_exists='append')
|
| import sqlite3
import pandas as pd
conn = sqlite3.connect('test.db')
df = pd.DataFrame({'name': ['不要香菜1号', '😂'], '年龄': [999, 0]})
df.to_sql('test_table', con=conn)
|
读数据库
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 | import pandas as pd
import sqlalchemy
engine = sqlalchemy.create_engine('mysql+pymysql://root:123456@localhost:3306/test_db')
df = pd.read_sql_table('test_table', engine)
print(df)
df = pd.read_sql_table('test_table', engine, columns=['name', 'sex'])
print(df)
df = pd.read_sql_table('test_table', engine, columns=['name'])
print(df)
df = pd.read_sql_query('select * from test_table', engine)
print(df)
df = pd.read_sql_query('select * from test_table where sex = "男"', engine)
print(df)
"""
name sex
0 小红 女
1 小蓝 男
name sex
0 小红 女
1 小蓝 男
name
0 小红
1 小蓝
name sex
0 小红 女
1 小蓝 男
name sex
0 小蓝 男
"""
|
| df = pd.read_sql('test_table', engine)
print(df)
df = pd.read_sql('select * from test_table where sex = "女"', engine)
print(df)
"""
name sex
0 小红 女
1 小蓝 男
name sex
0 小红 女
"""
|