Skip to content

Pandas

Pandas 中文

Pandas对象简介

Pandas的Series对象

Pandas 的 Series 对象是一个带索引数据构成的一维数组。可以用一个数组创建 Series 对象:

1
2
3
4
5
6
7
8
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 数组类似:

1
2
In [62]: data.values
Out[62]: array([0.25, 0.5 , 0.75, 1.  ])

index 属性返回的结果是一个类型为 pd.Index 的类数组对象

1
2
In [63]: data.index
Out[63]: RangeIndex(start=0, stop=4, step=1)

和 NumPy 数组一样,数据可以通过 Python 中的括号索引标签获取

1
2
3
4
5
6
7
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 数组

到目前为止,我们可能觉得 Series 对象和一维 NumPy 数组基本可以等价交换,但两者间的本质差异其实是索引:NumPy 数组通过隐式定义的整数索引获取数值,而 Pandas 的 Series 对象用一种显式定义的索引与数值关联。

显式索引的定义让 Series 对象拥有了更强的能力。例如,索引不再仅仅是整数,还可以是任意想要的类型。如果需要,完全可以用字符串定义索引

1
2
3
4
5
6
7
8
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

获取数值的方式与之前一样

1
2
In [68]: data['b']
Out[68]: 0.5

也可以使用不连续或不按顺序的索引:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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
  • Series 是特殊的字典

你可以把 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 对象时,其索引默认按照顺序排列。典型的字典数组获取方式仍然有效:

1
2
In [75]: population['California']
Out[75]: 38332521

和字典不同,Series 对象还支持数组形式的操作,比如切片:

1
2
3
4
5
6
In [77]: population['California':'New York']
Out[77]:
California    38332521
Texas         26448193
New York      19651127
dtype: int64
  • 创建 Series 对象

我们已经见过几种创建 Pandas 的 Series 对象的方法,都是像 pd.Series(data, index=index) 这样的形式:

其中,index 是一个可选参数,data 参数支持多种数据类型

例如,data 可以是列表或 NumPy 数组,这时 index 默认值为整数序列

1
2
3
4
5
6
In [78]: pd.Series([2, 4, 6])
Out[78]:
0    2
1    4
2    6
dtype: int64

data 也可以是一个标量,创建 Series 对象时会重复填充到每个索引上:

1
2
3
4
5
6
In [79]: pd.Series(5, index=[100, 200, 300])
Out[79]:
100    5
200    5
300    5
dtype: int64

data 还可以是一个字典

1
2
3
4
5
6
In [80]: pd.Series({2:'a', 1:'b', 3:'c'})
Out[80]:
2    a
1    b
3    c
dtype: object

每一种形式都可以通过显式指定索引筛选需要的结果

1
2
3
4
5
In [81]: pd.Series({2:'a', 1:'b', 3:'c'}, index=[3, 2])
Out[81]:
3    c
2    a
dtype: object

这里需要注意的是,Series 对象只会保留显式定义的键值对。

Pandas的DataFrame对象

  • DataFrame是通用的NumPy数组

如果将 Series 类比为带灵活索引的一维数组,那么 DataFrame 就可以看作是一种既有灵活的行索引,又有灵活列名的二维数组。就像你可以把二维数组看成是有序排列的一维数组一样,你也可以把 DataFrame 看成是有序排列的若干 Series 对象。这里的“排列”指的是它们拥有共同的索引

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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 对象,用一个字典创建一个包含这些信息的二维对象

1
2
3
4
5
6
7
8
9
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 属性可以获取索引标签

1
2
In [10]: states.index
Out[10]: Index(['California', 'Texas', 'New York', 'Florida', 'Illinois'], dtype='object')

另外,DataFrame 还有一个 columns 属性,是存放列标签的 Index 对象

1
2
In [11]: states.columns
Out[11]: Index(['population', 'area'], dtype='object')

因此 DataFrame 可以看作一种通用的 NumPy 二维数组,它的行与列都可以通过索引获取

  • DataFrame是特殊的字典

与 Series 类似,我们可以把 DataFrame 看成一种特殊的字典。字典是一个键映射一个值,而 DataFrame 是一列映射一个 Series 的数据。例如,通过 'area' 的列属性可以返回包含面积数据的 Series 对象:

1
2
3
4
5
6
7
8
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 看成一种通用字典,而不是通用数组,即使这两种看法在不同情况下都是有用的。

  • 创建 DataFrame 对象

(1) 通过单个 Series 对象创建。DataFrame 是一组 Series 对象的集合,可以用单个 Series 创建一个单列的 DataFrame

1
2
3
4
5
6
7
8
In [13]: pd.DataFrame(population, columns=['population'])
Out[13]:
            population
California    38332521
Texas         26448193
New York      19651127
Florida       19552860
Illinois      12882135

(2) 通过字典列表创建。任何元素是字典的类标都可以变成 DataFrame。用一个简单的列表综合来创建一些数据

1
2
3
4
5
6
7
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)来表示:

1
2
3
4
5
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 对象构成的字典创建

1
2
3
4
5
6
7
8
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。如果不指定行列索引值,那么行列默认都是整数索引值

1
2
3
4
5
6
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 对象:

1
2
3
In [23]: ind = pd.Index([2, 3, 5, 7, 11])
In [24]: ind
Out[24]: Int64Index([2, 3, 5, 7, 11], dtype='int64')
  • 将 Index 看作不可变数组

Index 对象的许多操作都像数组。例如,可以通过标准 Python 的取值方式获取数值,也可以通过切片获取数值

1
2
3
4
In [25]: ind[1]
Out[25]: 3
In [26]: ind[::2]
Out[26]: Int64Index([2, 5, 11], dtype='int64')

Index 对象还有许多与 NumPy 数组相似的属性

1
2
In [27]: print(ind.size, ind.shape, ind.ndim, ind.dtype)
5 (5,) 1 int64

Index 对象与 NumPy 数组之间的不同在于,Index 对象的索引是不可变的,也就是说不能通过通常的方式进行调整

1
In [28]: ind[1] = 0 # TypeError: Index does not support mutable operations

Index 对象的不可变特征使得多个 DataFrame 和数组之间进行索引共享时更加安全,尤其是可以避免因修改索引时粗心大意而导致的副作用

  • 将 Index 看作有序集合

Pandas 对象被设计用于实现许多操作,如连接(join)数据集,其中会涉及许多集合操作。Index 对象遵循 Python 标准库的集合(set)数据结构的许多习惯用法,包括并集、交集、差集等:

1
2
3
4
5
6
7
8
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')

这些操作还可以通过调用对象方法来实现,例如

1
2
In [35]: indA.intersection(indB)
Out[35]: Int64Index([3, 5, 7], dtype='int64')

数据取值与选择

Series数据选择方法

  • 将Series看作字典
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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 字典的表达式和方法来检测键/索引的值:

1
2
3
4
5
6
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

1
2
3
4
5
6
7
8
9
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 在底层已经为可能发生的内存布局和数据复制自动决策,用户不需要担心这些问题

  • 将 Series 看作一维数组
 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]) 作切片时,结果不包含最后一个索引

  • 索引器:loc、iloc

这些切片和取值的习惯用法经常会造成混乱。例如,如果你的 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 属性,表示取值和切片都是显式的

1
2
3
4
5
6
7
In [55]: data.loc[1]
Out[55]: 'a'
In [56]: data.loc[1:3]
Out[56]:
1    a
3    b
dtype: object

第二种是 iloc 属性,表示取值和切片都是 Python 形式的隐式索引

1
2
3
4
5
6
7
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看作字典

第一种类比是把 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 的一列,可以通过对列名进行字典形式的取值获取数据

1
2
3
4
5
6
7
8
In [67]: data['area']
Out[67]:
California    423967
Texas         695662
New York      141297
Florida       170312
Illinois      149995
Name: area, dtype: int64

同样,也可以用属性形式选择纯字符串列名的数据

1
2
3
4
5
6
7
8
In [68]: data.area
Out[68]:
California    423967
Texas         695662
New York      141297
Florida       170312
Illinois      149995
Name: area, dtype: int64

对同一个对象进行属性形式与字典形式的列数据,结果是相同的

1
2
In [69]: data.area is data['area']
Out[69]: True

虽然属性形式的数据选择方法很方便,但是它并不是通用的。如果列名不是纯字符串,或者列名与 DataFrame 的方法同名,那么就不能用属性索引。例如,DataFrame 有一个 pop() 方法,如果 data.pop 就不会获取 'pop' 列,而是显示为方法

1
2
In [71]: data.pop is data['pop']
Out[71]: False

另外,还应该避免对用属性形式选择的列直接赋值(即可以用 data['pop'] = z,但不要用 data.pop = z)

还可以用字典形式的语法调整对象,如果要增加一列可以这样做

1
2
3
4
5
6
7
8
9
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看作二维数组

可以把 DataFrame 看成是一个增强版的二维数组,用 values 属性按行查看数组数据

1
2
3
4
5
6
7
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 进行列转置

1
2
3
4
5
6
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 数组中使用单个行索引获取一行数据时

1
2
In [76]: data.values[0]
Out[76]: array([4.23967000e+05, 3.83325210e+07, 9.04139261e+01])

而获取一列数据就需要向 DataFrame 传递单个列索引

1
2
3
4
5
6
7
8
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 索引器中结合使用掩码与花哨的索引方法

1
2
3
4
5
In [82]: data.loc[data.density > 100, ['pop', 'density']]
Out[82]:
               pop     density
New York  19651127  139.076746
Florida   19552860  114.806121

任何一种取值方法都可以用于调整数据,这一点和 NumPy 的常用方法是相同的

1
2
3
4
5
6
7
8
9
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
  • 其他取值方法

还有一些取值方法和前面介绍过的方法不太一样。它们虽然看着有点奇怪,但是在实践中还是很好用的。首先,如果对单个标签取值就选择列,而对多个标签用切片就选择行

1
2
3
4
5
In [85]: data['Florida':'Illinois']
Out[85]:
            area       pop     density
Florida   170312  19552860  114.806121
Illinois  149995  12882135   85.883763

切片也可以不用索引值,而直接用行数来实现

1
2
3
4
5
In [86]: data[1:3]
Out[86]:
            area       pop     density
Texas     695662  26448193   38.018740
New York  141297  19651127  139.076746

与之类似,掩码操作也可以直接对每一行进行过滤,而不需要使用 loc 索引器

1
2
3
4
5
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()设置索引

1
2
3
4
5
6
7
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()重置索引

1
2
3
4
5
6
7
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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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

1
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.

1
2
3
4
5
6
7
8
9
>>> 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}}
1
2
3
4
>>> 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)]))])
1
2
3
4
>>> 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
"""

去重

1
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 对象

1
2
3
4
5
6
7
In [15]: np.exp(ser)
Out[15]:
0     403.428793
1      20.085537
2    1096.633158
3      54.598150
dtype: float64

或者,再做一个比较复杂的运算

1
2
3
4
5
6
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 会在计算过程中对齐两个对象的索引。当处理不完整的数据时,这一点非常方便

  • Series索引对齐

假如你要整合两个数据源的数据,其中一个是美国面积最大的三个州的面积数据,另一个是美国人口最多的三个州的人口数据

1
2
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')

来看看如果用人口除以面积会得到什么样的结果:

1
2
3
4
5
6
7
In [20]: population / area
Out[20]:
Alaska              NaN
California    90.413926
New York            NaN
Texas         38.018740
dtype: float64

结果数组的索引是两个输入数组索引的并集。我们也可以用 Python 标准库的集合运算法则来获得这个索引

1
2
In [21]: area.index | population.index
Out[21]: Index(['Alaska', 'California', 'New York', 'Texas'], dtype='object')

对于缺失位置的数据,Pandas 会用 NaN 填充,表示“此处无数”。这是 Pandas 表示缺失值的方法。这种索引对齐方式是通过 Python 内置的集合运算规则实现的,任何缺失值默认都用 NaN 填充:

1
2
3
4
5
6
7
8
9
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 缺失的数据

1
2
3
4
5
6
7
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 索引对齐

在计算两个 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 将二维数组压缩成一维数组):

1
2
3
4
5
6
7
8
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 中二维数组与一维数组的运算规则是一样的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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 里默认也是按行计算的

1
2
3
4
5
6
7
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 参数设置

1
2
3
4
5
6
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的缺失值

  • None: Python对象类型的缺失值

Pandas 可以使用的第一种缺失值标签是 None,它是一个 Python 的单体对象,经常在代码中表示缺失值。由于 None 是一个 Python 对象,所以不能作为任何 NumPy/Pandas 数组类型的缺失值,只能用于 'object' 数组类型(即由 Python 对象构成的数组)

1
2
3
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 层面完成,但是在常见的快速操作时,这种类型比其他原生类型数组要消耗更多的资源

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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: 数值类型的缺失值

另一种缺失值的标签是 Nan(全称 Not a Number, 不是一个数字),是一种按照 IEEE 浮点数标准设计、在任何系统中都兼容的特殊浮点数

1
2
3
In [47]: vals2 = np.array([1, np.nan, 3, 4])
In [48]: vals2.dtype
Out[48]: dtype('float64')

请注意,NumPy 会为这个数组选择一个原生浮点类型,这意味着和之前的 object 类型数组不同,这个数组会被编译成 C 代码从而实现快速操作。你可以把 NaN 看作是一个数据类病毒--它会将与它接触过的数据同化。无论和 NaN 进行何种操作,最终结果都是 NaN

1
2
3
4
In [49]: 1 + np.nan
Out[49]: nan
In [50]: 0 * np.nan
Out[50]: nan

虽然这些累计操作的结果定义是合理的(即不会抛出异常),但是并非总是有效的:

1
2
In [51]: vals2.sum(), vals2.min(), vals2.max()
Out[51]: (nan, nan, nan)

NumPy 也提供了一些特殊的累计函数,它们可以忽略缺失值的影响:

1
2
In [53]: np.nansum(vals2), np.nanmin(vals2), np.nanmax(vals2)
Out[53]: (8.0, 1.0, 4.0)

谨记,NaN 是一种特殊的浮点数,不是整数、字符串以及其他数据类型

  • Pandas中NaN与None的差异

虽然 NaN 与 None 各有各的用处,但是 Pandas 把它们看成是可以等价交换的,在适当的时候会将两者进行替换

1
2
3
4
5
6
7
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()。每种方法都返回布尔类型的掩码数据,例如

1
2
3
4
5
6
7
8
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 的索引使用:

1
2
3
4
5
In [66]: data[data.notnull()]
Out[66]:
0        1
2    hello
dtype: object

在 Series 里使用的 isnull() 和 notnull() 同样适用于 DataFrame,产生的结果同样是布尔类型

  • 剔除缺失值

除了前面介绍的掩码方法,还有两种很好用的缺失值处理方法,分别是 dropna()(剔除缺失值)和 fillna()(填充缺失值)。在 Series 上使用这些方法非常简单

1
2
3
4
5
In [67]: data.dropna()
Out[67]:
0        1
2    hello
dtype: object

而在 DataFrame 上使用它们时需要设置一些参数,例如下面的 DataFrame

1
2
3
4
5
6
7
8
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() 会剔除任何包含缺失值的整行数据

1
2
3
4
In [70]: df.dropna()
Out[70]:
     0    1  2
1  2.0  3.0  5

可以设置按不同的坐标轴剔除缺失值,比如 axis=1(或 axis='columss')会剔除任何包含缺失值的整列数据

1
2
3
4
5
6
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 参数设置行或列中非缺失值的最小数量,从而实现更加个性化的配置

1
2
3
4
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() 方法,它将返回填充了缺失值后的数组副本

1
2
3
4
5
6
7
8
9
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

1
2
3
4
5
6
7
8
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)

1
2
3
4
5
6
7
8
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)

1
2
3
4
5
6
7
8
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

1
2
3
4
5
6
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 实现简易合并

pd.concat() 可以简单地合并一维的 Series 或 DataFrame 对象,与 np.concatenate() 合并数组一样

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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 也可以设置合并坐标轴,例如下面的示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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,合并时若有索引重复就会触发异常。

1
2
3
4
5
6
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
  • 类似 join 的合并

前面介绍的简单示例都有一个共同特点,那就是合并的 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 的合并功能可以满足你在合并两个数据集时的许多需求,操作时请记住这一点

  • append() 方法

因为直接进行数组合并的需求非常普遍,所以 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写数据库

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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.

1
2
3
4
5
6
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')

1
2
3
4
5
6
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')

1
2
3
4
5
6
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   小蓝   男
"""
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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   小红   女
"""