Python 数据处理 —— pandas 索引和选择

前言

pandas 对象的轴标签信息有很多用途,如

  • 利用标识符来标识数据
  • 能够显式的和自动对齐数据
  • 获取和设置数据子集

在本节中,我们主要关注最后一点,如何对数据切片以及获取和设置数据对象(SeriesDataFrame)的子集。

1 索引方法

pandas 目前支持三种多轴索引:

  1. .loc:

主要是配合标签使用,但是也可与布尔数组一起使用。.loc 会在找不到对应的数据项目时引发 KeyError

允许的输入有

  • 单个标签,如 5a注意,这里的 5 是索引标签的值,并不是 0 起始的整数位置索引)。
  • 列表或数组,如 ['a', 'b', 'c']
  • 标签的切片对象,如 'A':'f'注意,与 Python 切片不一样,这个切片会包含开始和结束位置的值)
  • 布尔数组(注意任何 NA 值都将被视为 False
  • 带参数的回调函数,能够返回有效的索引值
  1. .iloc:

主要基于整数位置(0length-1),也可以和布尔数组一起使用。除了允许越界的切片索引器之外,如果索引越界,那么将会引发 IndexError

允许的输入有

  • 一个整数,如 5
  • 整数列表或数组,如 [4, 3, 0]
  • 切片对象,如 1:7
  • 布尔数组(注意任何 NA 值都将被视为 False
  • 带参数的回调函数,能够返回有效的索引值
  1. .loc, .iloc, 和 [] 能够接受一个回调函数作为索引器

lociloc 的访问方式是一样的,例如

  • Series:
    • s.loc[indexer]
  • DataFrame:
    • df.loc[row_indexer,column_indexer]
      同样适用于 iloc

其中,未列出来的轴被假定为 :,也就是选取该轴的所有数据

2 基础

使用 [] 可以获取数据的低维度切片,使用方式为
image.png

我们来举例说明

In [1]: dates = pd.date_range('1/1/2000', periods=8)

In [2]: df = pd.DataFrame(np.random.randn(8, 4),
   ...:                   index=dates, columns=['A', 'B', 'C', 'D'])
   ...: 

In [3]: df
Out[3]: 
                   A         B         C         D
2000-01-01  0.469112 -0.282863 -1.509059 -1.135632
2000-01-02  1.212112 -0.173215  0.119209 -1.044236
2000-01-03 -0.861849 -2.104569 -0.494929  1.071804
2000-01-04  0.721555 -0.706771 -1.039575  0.271860
2000-01-05 -0.424972  0.567020  0.276232 -1.087401
2000-01-06 -0.673690  0.113648 -1.478427  0.524988
2000-01-07  0.404705  0.577046 -1.715002 -1.039268
2000-01-08 -0.370647 -1.157892 -1.344312  0.844885

注意:这些索引函数并不是只能用于时间序列数据,除非有特别说明

使用 [] 实现最基本的索引

In [4]: s = df['A']

In [5]: s[dates[5]]
Out[5]: -0.6736897080883706

你可以将一个列名列表传递给 [],便能够按这个顺序返回对应的列。

如果传入了不包含在 DataFrame 中的列,将会引发一个异常。

也可以使用这种方式设置多个列。

In [6]: df
Out[6]: 
                   A         B         C         D
2000-01-01  0.469112 -0.282863 -1.509059 -1.135632
2000-01-02  1.212112 -0.173215  0.119209 -1.044236
2000-01-03 -0.861849 -2.104569 -0.494929  1.071804
2000-01-04  0.721555 -0.706771 -1.039575  0.271860
2000-01-05 -0.424972  0.567020  0.276232 -1.087401
2000-01-06 -0.673690  0.113648 -1.478427  0.524988
2000-01-07  0.404705  0.577046 -1.715002 -1.039268
2000-01-08 -0.370647 -1.157892 -1.344312  0.844885

In [7]: df[['B', 'A']] = df[['A', 'B']]

In [8]: df
Out[8]: 
                   A         B         C         D
2000-01-01 -0.282863  0.469112 -1.509059 -1.135632
2000-01-02 -0.173215  1.212112  0.119209 -1.044236
2000-01-03 -2.104569 -0.861849 -0.494929  1.071804
2000-01-04 -0.706771  0.721555 -1.039575  0.271860
2000-01-05  0.567020 -0.424972  0.276232 -1.087401
2000-01-06  0.113648 -0.673690 -1.478427  0.524988
2000-01-07  0.577046  0.404705 -1.715002 -1.039268
2000-01-08 -1.157892 -0.370647 -1.344312  0.844885

这对于原地交换数据列很有用

当从 .loc.iloc 设置 SeriesDataFrame 时,pandas 将自动对齐所有的列

下面的代码不会修改 df,因为列对齐发生在赋值之前。

In [9]: df[['A', 'B']]
Out[9]: 
                   A         B
2000-01-01 -0.282863  0.469112
2000-01-02 -0.173215  1.212112
2000-01-03 -2.104569 -0.861849
2000-01-04 -0.706771  0.721555
2000-01-05  0.567020 -0.424972
2000-01-06  0.113648 -0.673690
2000-01-07  0.577046  0.404705
2000-01-08 -1.157892 -0.370647

In [10]: df.loc[:, ['B', 'A']] = df[['A', 'B']]

In [11]: df[['A', 'B']]
Out[11]: 
                   A         B
2000-01-01 -0.282863  0.469112
2000-01-02 -0.173215  1.212112
2000-01-03 -2.104569 -0.861849
2000-01-04 -0.706771  0.721555
2000-01-05  0.567020 -0.424972
2000-01-06  0.113648 -0.673690
2000-01-07  0.577046  0.404705
2000-01-08 -1.157892 -0.370647

交换列值的正确方法是使用原始值:

In [12]: df.loc[:, ['B', 'A']] = df[['A', 'B']].to_numpy()

In [13]: df[['A', 'B']]
Out[13]: 
                   A         B
2000-01-01  0.469112 -0.282863
2000-01-02  1.212112 -0.173215
2000-01-03 -0.861849 -2.104569
2000-01-04  0.721555 -0.706771
2000-01-05 -0.424972  0.567020
2000-01-06 -0.673690  0.113648
2000-01-07  0.404705  0.577046
2000-01-08 -0.370647 -1.157892

3 属性访问

你可以直接把列名作为一个属性,并以访问属性的方式获取对应的列:

In [14]: sa = pd.Series([1, 2, 3], index=list('abc'))

In [15]: dfa = df.copy()
In [16]: sa.b
Out[16]: 2

In [17]: dfa.A
Out[17]: 
2000-01-01    0.469112
2000-01-02    1.212112
2000-01-03   -0.861849
2000-01-04    0.721555
2000-01-05   -0.424972
2000-01-06   -0.673690
2000-01-07    0.404705
2000-01-08   -0.370647
Freq: D, Name: A, dtype: float64
In [18]: sa.a = 5

In [19]: sa
Out[19]: 
a    5
b    2
c    3
dtype: int64

# 需要保证 A 列存在
In [20]: dfa.A = list(range(len(dfa.index)))

In [21]: dfa
Out[21]: 
            A         B         C         D
2000-01-01  0 -0.282863 -1.509059 -1.135632
2000-01-02  1 -0.173215  0.119209 -1.044236
2000-01-03  2 -2.104569 -0.494929  1.071804
2000-01-04  3 -0.706771 -1.039575  0.271860
2000-01-05  4  0.567020  0.276232 -1.087401
2000-01-06  5  0.113648 -1.478427  0.524988
2000-01-07  6  0.577046 -1.715002 -1.039268
2000-01-08  7 -1.157892 -1.344312  0.844885

# 可以使用这种方式添加新列
In [22]: dfa['A'] = list(range(len(dfa.index))) 

In [23]: dfa
Out[23]: 
            A         B         C         D
2000-01-01  0 -0.282863 -1.509059 -1.135632
2000-01-02  1 -0.173215  0.119209 -1.044236
2000-01-03  2 -2.104569 -0.494929  1.071804
2000-01-04  3 -0.706771 -1.039575  0.271860
2000-01-05  4  0.567020  0.276232 -1.087401
2000-01-06  5  0.113648 -1.478427  0.524988
2000-01-07  6  0.577046 -1.715002 -1.039268
2000-01-08  7 -1.157892 -1.344312  0.844885

警告

  • 只有当 index 元素是有效的 Python 标识符时,才能使用此方式访问,例如不允许使用 s.1
  • 如果该属性与现有方法名冲突,则该属性不可用,例如不允许使用 s.min,但可以使用 s['min']
  • 如果该属性与以下任何列表冲突,则该属性将不可用:indexmajor_axisminor_axisitem

如果使用的是 IPython 环境,可以使用 <Tab> 补全来查看属性

你也可以给将一个字典赋值给 DataFrame 的某行

In [24]: x = pd.DataFrame({'x': [1, 2, 3], 'y': [3, 4, 5]})

In [25]: x.iloc[1] = {'x': 9, 'y': 99}

In [26]: x
Out[26]: 
   x   y
0  1   3
1  9  99
2  3   5

你可以使用访问属性的方式来修改 Series 的元素或 DataFrame 的列,

但要注意,如果你试图使用访问属性的方式来创建一个新的列,它会创建一个新的属性而不是一个新列。在 0.21.0 及更高版本中,这将引发一个 UserWarning

In [1]: df = pd.DataFrame({'one': [1., 2., 3.]})
In [2]: df.two = [4, 5, 6]
UserWarning: Pandas doesn't allow Series to be assigned into nonexistent columns - see https://pandas.pydata.org/pandas-docs/stable/indexing.html#attribute_access
In [3]: df
Out[3]:
   one
0  1.0
1  2.0
2  3.0

访问属性

>>> df.two
[4, 5, 6]

4 切片范围

我们使用 [] 操作符来解释切片的用法

对于 Series,该语法与 ndarray 的工作原理完全相同,返回值的一个切片和相应的标签

In [27]: s[:5]
Out[27]: 
2000-01-01    0.469112
2000-01-02    1.212112
2000-01-03   -0.861849
2000-01-04    0.721555
2000-01-05   -0.424972
Freq: D, Name: A, dtype: float64

In [28]: s[::2]
Out[28]: 
2000-01-01    0.469112
2000-01-03   -0.861849
2000-01-05   -0.424972
2000-01-07    0.404705
Freq: 2D, Name: A, dtype: float64

In [29]: s[::-1]
Out[29]: 
2000-01-08   -0.370647
2000-01-07    0.404705
2000-01-06   -0.673690
2000-01-05   -0.424972
2000-01-04    0.721555
2000-01-03   -0.861849
2000-01-02    1.212112
2000-01-01    0.469112
Freq: -1D, Name: A, dtype: float64

也可以使用如下方式设置值

In [30]: s2 = s.copy()

In [31]: s2[:5] = 0

In [32]: s2
Out[32]: 
2000-01-01    0.000000
2000-01-02    0.000000
2000-01-03    0.000000
2000-01-04    0.000000
2000-01-05    0.000000
2000-01-06   -0.673690
2000-01-07    0.404705
2000-01-08   -0.370647
Freq: D, Name: A, dtype: float64

DataFrame中[] 切片是对行进行的。这主要是为了方便,因为这是一种非常常见的操作

In [33]: df[:3]
Out[33]: 
                   A         B         C         D
2000-01-01  0.469112 -0.282863 -1.509059 -1.135632
2000-01-02  1.212112 -0.173215  0.119209 -1.044236
2000-01-03 -0.861849 -2.104569 -0.494929  1.071804

In [34]: df[::-1]
Out[34]: 
                   A         B         C         D
2000-01-08 -0.370647 -1.157892 -1.344312  0.844885
2000-01-07  0.404705  0.577046 -1.715002 -1.039268
2000-01-06 -0.673690  0.113648 -1.478427  0.524988
2000-01-05 -0.424972  0.567020  0.276232 -1.087401
2000-01-04  0.721555 -0.706771 -1.039575  0.271860
2000-01-03 -0.861849 -2.104569 -0.494929  1.071804
2000-01-02  1.212112 -0.173215  0.119209 -1.044236
2000-01-01  0.469112 -0.282863 -1.509059 -1.135632

5 根据标签选择(.loc)

当您提供与索引类型不兼容(或可转换)的切片器时,.loc 是严格的。例如,在 DatetimeIndex 中使用整数将引发 TypeError

In [35]: dfl = pd.DataFrame(np.random.randn(5, 4),
   ....:                    columns=list('ABCD'),
   ....:                    index=pd.date_range('20130101', periods=5))
   ....: 

In [36]: dfl
Out[36]: 
                   A         B         C         D
2013-01-01  1.075770 -0.109050  1.643563 -1.469388
2013-01-02  0.357021 -0.674600 -1.776904 -0.968914
2013-01-03 -1.294524  0.413738  0.276662 -0.472035
2013-01-04 -0.013960 -0.362543 -0.006154 -0.923061
2013-01-05  0.895717  0.805244 -1.206412  2.565646
In [4]: dfl.loc[2:3]
TypeError: cannot do slice indexing on <class 'pandas.tseries.index.DatetimeIndex'> with these indexers [2] of <type 'int'>

切片中的字符串类型可以转换为索引类型

In [37]: dfl.loc['20130102':'20130104']
Out[37]: 
                   A         B         C         D
2013-01-02  0.357021 -0.674600 -1.776904 -0.968914
2013-01-03 -1.294524  0.413738  0.276662 -0.472035
2013-01-04 -0.013960 -0.362543 -0.006154 -0.923061

pandas 提供了一套方法,以实现纯粹基于标签的索引,这是一个严格的包含协议

要求的每个标签都必须在索引中,否则将引发 KeyError

在切片时,索引的起始和终止边界都会包含。整数切片指的是标签而不是位置索引

例如,对于 Series

In [38]: s1 = pd.Series(np.random.randn(6), index=list('abcdef'))

In [39]: s1
Out[39]: 
a    1.431256
b    1.340309
c   -1.170299
d   -0.226169
e    0.410835
f    0.813850
dtype: float64

In [40]: s1.loc['c':]
Out[40]: 
c   -1.170299
d   -0.226169
e    0.410835
f    0.813850
dtype: float64

In [41]: s1.loc['b']
Out[41]: 1.3403088497993827

设置值

In [42]: s1.loc['c':] = 0

In [43]: s1
Out[43]: 
a    1.431256
b    1.340309
c    0.000000
d    0.000000
e    0.000000
f    0.000000
dtype: float64

对于 DataFrame

In [44]: df1 = pd.DataFrame(np.random.randn(6, 4),
   ....:                    index=list('abcdef'),
   ....:                    columns=list('ABCD'))
   ....: 

In [45]: df1
Out[45]: 
          A         B         C         D
a  0.132003 -0.827317 -0.076467 -1.187678
b  1.130127 -1.436737 -1.413681  1.607920
c  1.024180  0.569605  0.875906 -2.211372
d  0.974466 -2.006747 -0.410001 -0.078638
e  0.545952 -1.219217 -1.226825  0.769804
f -1.281247 -0.727707 -0.121306 -0.097883

In [46]: df1.loc[['a', 'b', 'd'], :]
Out[46]: 
          A         B         C         D
a  0.132003 -0.827317 -0.076467 -1.187678
b  1.130127 -1.436737 -1.413681  1.607920
d  0.974466 -2.006747 -0.410001 -0.078638

标签切片

In [47]: df1.loc['d':, 'A':'C']
Out[47]: 
          A         B         C
d  0.974466 -2.006747 -0.410001
e  0.545952 -1.219217 -1.226825
f -1.281247 -0.727707 -0.121306

使用标签获取横截面(相当于 df.xs('a')

In [48]: df1.loc['a']
Out[48]: 
A    0.132003
B   -0.827317
C   -0.076467
D   -1.187678
Name: a, dtype: float64

使用布尔数组获取值

In [49]: df1.loc['a'] > 0
Out[49]: 
A     True
B    False
C    False
D    False
Name: a, dtype: bool

In [50]: df1.loc[:, df1.loc['a'] > 0]
Out[50]: 
          A
a  0.132003
b  1.130127
c  1.024180
d  0.974466
e  0.545952
f -1.281247

布尔数组中的 NA 值作为 False

In [51]: mask = pd.array([True, False, True, False, pd.NA, False], dtype="boolean")

In [52]: mask
Out[52]: 
<BooleanArray>
[True, False, True, False, <NA>, False]
Length: 6, dtype: boolean

In [53]: df1[mask]
Out[53]: 
          A         B         C         D
a  0.132003 -0.827317 -0.076467 -1.187678
c  1.024180  0.569605  0.875906 -2.211372

显式地获取一个值:

# 相当于 ``df1.at['a','A']``
In [54]: df1.loc['a', 'A']
Out[54]: 0.13200317033032932
5.1 根据标签切片

当使用 .loc 进行切片时,如果开始和停止标签都存在于索引中,则返回位于这两个标签之间的元素(包括它们)

In [55]: s = pd.Series(list('abcde'), index=[0, 3, 2, 5, 4])

In [56]: s.loc[3:5]
Out[56]: 
3    b
2    c
5    d
dtype: object

如果这两个标签中至少有一个不存在,但索引已经排过序,并且可以与开始和结束标签进行比较,那么切片仍然可以按照预期工作,将会选择在这两个标签值范围之间的标签:

In [57]: s.sort_index()
Out[57]: 
0    a
2    c
3    b
4    e
5    d
dtype: object

In [58]: s.sort_index().loc[1:6]
Out[58]: 
2    c
3    b
4    e
5    d
dtype: object

但是,如果两个标签中至少有一个不存在且索引未排序,则会引发一个错误

例如,对于上面的例子,s.loc[1:6] 将会抛出 KeyError 异常

如果索引存在重复标签

In [59]: s = pd.Series(list('abcdef'), index=[0, 3, 2, 5, 4, 2])

In [60]: s.loc[3:5]
Out[60]: 
3    b
2    c
5    d
dtype: object

如果索引切片的开始或结束位置是重复的标签,那么将会抛出 KeyError 异常

In [8]: s.loc[2:5]
---------------------------------------------------------------------------
KeyError
...

6 根据位置选择(.iloc)

Pandas 提供了一套方法,以获得纯粹基于整数的索引。语义跟 PythonNumPy 切片一致。

这些都是 0 起始的索引,在切片时,包含起始但不包括结束位置。

尝试使用非整数,甚至是有效的标签都会引发一个 IndexError 异常

对于 Series

In [61]: s1 = pd.Series(np.random.randn(5), index=list(range(0, 10, 2)))

In [62]: s1
Out[62]: 
0    0.695775
2    0.341734
4    0.959726
6   -1.110336
8   -0.619976
dtype: float64

In [63]: s1.iloc[:3]
Out[63]: 
0    0.695775
2    0.341734
4    0.959726
dtype: float64

In [64]: s1.iloc[3]
Out[64]: -1.110336102891167

设置值

In [65]: s1.iloc[:3] = 0

In [66]: s1
Out[66]: 
0    0.000000
2    0.000000
4    0.000000
6   -1.110336
8   -0.619976
dtype: float64

对于 DataFrame

In [67]: df1 = pd.DataFrame(np.random.randn(6, 4),
   ....:                    index=list(range(0, 12, 2)),
   ....:                    columns=list(range(0, 8, 2)))
   ....: 

In [68]: df1
Out[68]: 
           0         2         4         6
0   0.149748 -0.732339  0.687738  0.176444
2   0.403310 -0.154951  0.301624 -2.179861
4  -1.369849 -0.954208  1.462696 -1.743161
6  -0.826591 -0.345352  1.314232  0.690579
8   0.995761  2.396780  0.014871  3.357427
10 -0.317441 -1.236269  0.896171 -0.487602

通过整数切片选择

In [69]: df1.iloc[:3]
Out[69]: 
          0         2         4         6
0  0.149748 -0.732339  0.687738  0.176444
2  0.403310 -0.154951  0.301624 -2.179861
4 -1.369849 -0.954208  1.462696 -1.743161

In [70]: df1.iloc[1:5, 2:4]
Out[70]: 
          4         6
2  0.301624 -2.179861
4  1.462696 -1.743161
6  1.314232  0.690579
8  0.014871  3.357427

通过整数列表选择:

In [71]: df1.iloc[[1, 3, 5], [1, 3]]
Out[71]: 
           2         6
2  -0.154951 -2.179861
6  -0.345352  0.690579
10 -1.236269 -0.487602
In [72]: df1.iloc[1:3, :]
Out[72]: 
          0         2         4         6
2  0.403310 -0.154951  0.301624 -2.179861
4 -1.369849 -0.954208  1.462696 -1.743161
In [73]: df1.iloc[:, 1:3]
Out[73]: 
           2         4
0  -0.732339  0.687738
2  -0.154951  0.301624
4  -0.954208  1.462696
6  -0.345352  1.314232
8   2.396780  0.014871
10 -1.236269  0.896171
# 相当于 df1.iat[1,1]
In [74]: df1.iloc[1, 1]
Out[74]: -0.1549507744249032

使用整数位置获取截面(等同于 df.xs(1))

In [75]: df1.iloc[1]
Out[75]: 
0    0.403310
2   -0.154951
4    0.301624
6   -2.179861
Name: 2, dtype: float64

超出范围的分片索引会像在 PythonNumPy 中一样被优雅地处理。

# these are allowed in Python/NumPy.
In [76]: x = list('abcdef')

In [77]: x
Out[77]: ['a', 'b', 'c', 'd', 'e', 'f']

In [78]: x[4:10]
Out[78]: ['e', 'f']

In [79]: x[8:10]
Out[79]: []

In [80]: s = pd.Series(x)

In [81]: s
Out[81]: 
0    a
1    b
2    c
3    d
4    e
5    f
dtype: object

In [82]: s.iloc[4:10]
Out[82]: 
4    e
5    f
dtype: object

In [83]: s.iloc[8:10]
Out[83]: Series([], dtype: object)

请注意,使用超出边界的切片可能会导致一个空轴(例如,返回一个空的 DataFrame

In [84]: dfl = pd.DataFrame(np.random.randn(5, 2), columns=list('AB'))

In [85]: dfl
Out[85]: 
          A         B
0 -0.082240 -2.182937
1  0.380396  0.084844
2  0.432390  1.519970
3 -0.493662  0.600178
4  0.274230  0.132885

In [86]: dfl.iloc[:, 2:3]
Out[86]: 
Empty DataFrame
Columns: []
Index: [0, 1, 2, 3, 4]

In [87]: dfl.iloc[:, 1:3]
Out[87]: 
          B
0 -2.182937
1  0.084844
2  1.519970
3  0.600178
4  0.132885

In [88]: dfl.iloc[4:6]
Out[88]: 
         A         B
4  0.27423  0.132885

单个索引器或索引器列表中有任何元素超出边界,会引发 IndexError

>>> dfl.iloc[[4, 5, 6]]
IndexError: positional indexers are out-of-bounds

>>> dfl.iloc[:, 4]
IndexError: single positional indexer is out-of-bounds

7 根据可调用函数选择

.loc, .iloc, 和 [] 能够接受一个可调用函数作为索引器

可调用对象必须是具有一个参数(SeriesDataFrame)的函数,该函数返回有效的输出以进行索引

In [89]: df1 = pd.DataFrame(np.random.randn(6, 4),
   ....:                    index=list('abcdef'),
   ....:                    columns=list('ABCD'))
   ....: 

In [90]: df1
Out[90]: 
          A         B         C         D
a -0.023688  2.410179  1.450520  0.206053
b -0.251905 -2.213588  1.063327  1.266143
c  0.299368 -0.863838  0.408204 -1.048089
d -0.025747 -0.988387  0.094055  1.262731
e  1.289997  0.082423 -0.055758  0.536580
f -0.489682  0.369374 -0.034571 -2.484478

In [91]: df1.loc[lambda df: df['A'] > 0, :]
Out[91]: 
          A         B         C         D
c  0.299368 -0.863838  0.408204 -1.048089
e  1.289997  0.082423 -0.055758  0.536580

In [92]: df1.loc[:, lambda df: ['A', 'B']]
Out[92]: 
          A         B
a -0.023688  2.410179
b -0.251905 -2.213588
c  0.299368 -0.863838
d -0.025747 -0.988387
e  1.289997  0.082423
f -0.489682  0.369374

In [93]: df1.iloc[:, lambda df: [0, 1]]
Out[93]: 
          A         B
a -0.023688  2.410179
b -0.251905 -2.213588
c  0.299368 -0.863838
d -0.025747 -0.988387
e  1.289997  0.082423
f -0.489682  0.369374

In [94]: df1[lambda df: df.columns[0]]
Out[94]: 
a   -0.023688
b   -0.251905
c    0.299368
d   -0.025747
e    1.289997
f   -0.489682
Name: A, dtype: float64

也可以在 Series 中使用

In [95]: df1['A'].loc[lambda s: s > 0]
Out[95]: 
c    0.299368
e    1.289997
Name: A, dtype: float64

使用这些方法和索引器,你可以在不使用临时变量的情况下对数据选择操作进行串联

In [96]: bb = pd.read_csv('data/baseball.csv', index_col='id')

In [97]: (bb.groupby(['year', 'team']).sum()
   ....:    .loc[lambda df: df['r'] > 100])
   ....: 
Out[97]: 
           stint    g    ab    r    h  X2b  X3b  hr    rbi    sb   cs   bb     so   ibb   hbp    sh    sf  gidp
year team                                                                                                      
2007 CIN       6  379   745  101  203   35    2  36  125.0  10.0  1.0  105  127.0  14.0   1.0   1.0  15.0  18.0
     DET       5  301  1062  162  283   54    4  37  144.0  24.0  7.0   97  176.0   3.0  10.0   4.0   8.0  28.0
     HOU       4  311   926  109  218   47    6  14   77.0  10.0  4.0   60  212.0   3.0   9.0  16.0   6.0  17.0
     LAN      11  413  1021  153  293   61    3  36  154.0   7.0  5.0  114  141.0   8.0   9.0   3.0   8.0  29.0
     NYN      13  622  1854  240  509  101    3  61  243.0  22.0  4.0  174  310.0  24.0  23.0  18.0  15.0  48.0
     SFN       5  482  1305  198  337   67    6  40  171.0  26.0  7.0  235  188.0  51.0   8.0  16.0   6.0  41.0
     TEX       2  198   729  115  200   40    4  28  115.0  21.0  4.0   73  140.0   4.0   5.0   2.0   8.0  16.0
     TOR       4  459  1408  187  378   96    2  58  223.0   4.0  2.0  190  265.0  16.0  12.0   4.0  16.0  38.0

8 组合位置和标签索引

如果你想获取 'A' 列的第 0 和第 2 个元素,你可以这样做:

In [98]: dfd = pd.DataFrame({'A': [1, 2, 3],
   ....:                     'B': [4, 5, 6]},
   ....:                    index=list('abc'))
   ....: 

In [99]: dfd
Out[99]: 
   A  B
a  1  4
b  2  5
c  3  6

In [100]: dfd.loc[dfd.index[[0, 2]], 'A']
Out[100]: 
a    1
c    3
Name: A, dtype: int64

这也可以用 .iloc 获取,通过使用位置索引来选择内容

In [101]: dfd.iloc[[0, 2], dfd.columns.get_loc('A')]
Out[101]: 
a    1
c    3
Name: A, dtype: int64

可以使用 .get_indexer 获取多个索引:

In [101]: dfd.iloc[[0, 2], dfd.columns.get_loc('A')]
Out[101]: 
a    1
c    3
Name: A, dtype: int64

9 列表中包含缺失标签的索引已被弃用

警告

对于包含一个或多个缺失标签的列表,使用 .loc[] 将不再重新索引,而是使用 .reindex

在以前的版本中,只要索引列表中存在至少一个有效标签,就可以使用 .loc[list-of-labels]

但是现在,只要索引列表中存在缺失的标签将引发 KeyError。推荐的替代方法是使用 .reindex()

例如

In [103]: s = pd.Series([1, 2, 3])

In [104]: s
Out[104]: 
0    1
1    2
2    3
dtype: int64

索引列表的标签都存在

In [105]: s.loc[[1, 2]]
Out[105]: 
1    2
2    3
dtype: int64

先前的版本

In [4]: s.loc[[1, 2, 3]]
Out[4]:
1    2.0
2    3.0
3    NaN
dtype: float64

但是,现在

In [4]: s.loc[[1, 2, 3]]
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-8-28925a59f003> in <module>
----> 1 s.loc[[1, 2, 3]]
...
KeyError: "Passing list-likes to .loc or [] with any missing labels is no longer supported. The following labels were missing: Int64Index([3], dtype='int64'). See https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#deprecate-loc-reindex-listlike"
reindex

索引标签列表中包含不存在的标签,使用 reindex

In [106]: s.reindex([1, 2, 3])
Out[106]: 
1    2.0
2    3.0
3    NaN
dtype: float64

另外,如果你只想选择有效的键,可以使用下面的方法,同时保留了数据的 dtype

In [107]: labels = [1, 2, 3]

In [108]: s.loc[s.index.intersection(labels)]
Out[108]: 
1    2
2    3
dtype: int64

对于 .reindex(),如果有重复的索引将会引发异常

In [109]: s = pd.Series(np.arange(4), index=['a', 'a', 'b', 'c'])

In [110]: labels = ['c', 'd']

In [17]: s.reindex(labels)
ValueError: cannot reindex from a duplicate axis

通常,您可以将所需的标签与当前轴做交集,然后重新索引

In [111]: s.loc[s.index.intersection(labels)].reindex(labels)
Out[111]: 
c    3.0
d    NaN
dtype: float64

但是,如果你的索引结果包含重复标签,还是会引发异常

In [41]: labels = ['a', 'd']

In [42]: s.loc[s.index.intersection(labels)].reindex(labels)
ValueError: cannot reindex from a duplicate axis

10 随机抽样

使用 sample() 方法可以从 SeriesDataFrame 中随机选择行或列。

该方法默认会对行进行采样,并接受一个特定的行数、列数,或数据子集。

In [112]: s = pd.Series([0, 1, 2, 3, 4, 5])

# 不传递参数,返回任意一行
In [113]: s.sample()
Out[113]: 
4    4
dtype: int64

# 选择指定数量的行
In [114]: s.sample(n=3)
Out[114]: 
0    0
4    4
1    1
dtype: int64

# 返回百分比的数据
In [115]: s.sample(frac=0.5)
Out[115]: 
5    5
3    3
1    1
dtype: int64

默认情况下,sample 每行最多返回一次,但也可以使用 replace 参数进行替换采样

In [116]: s = pd.Series([0, 1, 2, 3, 4, 5])

# Without replacement (default):
In [117]: s.sample(n=6, replace=False)
Out[117]: 
0    0
1    1
5    5
3    3
2    2
4    4
dtype: int64

# With replacement:
In [118]: s.sample(n=6, replace=True)
Out[118]: 
0    0
4    4
3    3
2    2
4    4
4    4
dtype: int64

默认情况下,每一行被选中的概率相等,但是如果你想让每一行有不同的概率,你可以为 sample 函数的 weights 参数设置抽样权值

In [119]: s = pd.Series([0, 1, 2, 3, 4, 5])

In [120]: example_weights = [0, 0, 0.2, 0.2, 0.2, 0.4]

In [121]: s.sample(n=3, weights=example_weights)
Out[121]: 
5    5
4    4
3    3
dtype: int64

# Weights will be re-normalized automatically
In [122]: example_weights2 = [0.5, 0, 0, 0, 0, 0]

In [123]: s.sample(n=1, weights=example_weights2)
Out[123]: 
0    0
dtype: int64

这些权重可以是一个列表、一个 NumPy 数组或一个 Series,但它们的长度必须与你要抽样的对象相同。

缺失的值将被视为权重为零,并且不允许使用 inf 值。如果权重之和不等于 1,则将所有权重除以权重之和,将其重新归一化。例如

In [119]: s = pd.Series([0, 1, 2, 3, 4, 5])

In [120]: example_weights = [0, 0, 0.2, 0.2, 0.2, 0.4]

In [121]: s.sample(n=3, weights=example_weights)
Out[121]: 
5    5
4    4
3    3
dtype: int64

# Weights will be re-normalized automatically
In [122]: example_weights2 = [0.5, 0, 0, 0, 0, 0]

In [123]: s.sample(n=1, weights=example_weights2)
Out[123]: 
0    0
dtype: int64

当应用于 DataFrame 时,您可以通过简单地将列名作为字符串传递给 weights 作为采样权重(前提是您要采样的是行而不是列)。

In [124]: df2 = pd.DataFrame({'col1': [9, 8, 7, 6],
   .....:                     'weight_column': [0.5, 0.4, 0.1, 0]})
   .....: 

In [125]: df2.sample(n=3, weights='weight_column')
Out[125]: 
   col1  weight_column
1     8            0.4
0     9            0.5
2     7            0.1

sample 还允许用户使用 axis 参数对列进行抽样。

In [126]: df3 = pd.DataFrame({'col1': [1, 2, 3], 'col2': [2, 3, 4]})

In [127]: df3.sample(n=1, axis=1)
Out[127]: 
   col1
0     1
1     2
2     3

最后,我们还可以使用 random_state 参数为 sample 的随机数生成器设置一个种子,它将接受一个整数(作为种子)或一个 NumPy RandomState 对象

In [128]: df4 = pd.DataFrame({'col1': [1, 2, 3], 'col2': [2, 3, 4]})

# With a given seed, the sample will always draw the same rows.
In [129]: df4.sample(n=2, random_state=2)
Out[129]: 
   col1  col2
2     3     4
1     2     3

In [130]: df4.sample(n=2, random_state=2)
Out[130]: 
   col1  col2
2     3     4
1     2     3

当为该轴设置一个不存在的键时,.loc/[] 操作可以执行放大

Series 的情况下,这实际上是一个追加操作

In [131]: se = pd.Series([1, 2, 3])

In [132]: se
Out[132]: 
0    1
1    2
2    3
dtype: int64

In [133]: se[5] = 5.

In [134]: se
Out[134]: 
0    1.0
1    2.0
2    3.0
5    5.0
dtype: float64

可以通过 .loc 在任一轴上放大 DataFrame

In [135]: dfi = pd.DataFrame(np.arange(6).reshape(3, 2),
   .....:                    columns=['A', 'B'])
   .....: 

In [136]: dfi
Out[136]: 
   A  B
0  0  1
1  2  3
2  4  5

In [137]: dfi.loc[:, 'C'] = dfi.loc[:, 'A']

In [138]: dfi
Out[138]: 
   A  B  C
0  0  1  0
1  2  3  2
2  4  5  4

这就像 DataFrameappend 操作

In [139]: dfi.loc[3] = 5

In [140]: dfi
Out[140]: 
   A  B  C
0  0  1  0
1  2  3  2
2  4  5  4
3  5  5  5

11 快速获取和设置标量值

由于用 [] 做索引必须处理很多情况(单标签访问、分片、布尔索引等),所以需要一些开销来搞清楚你的意图

如果你只想访问一个标量值,最快的方法是使用 atiat 方法,这两个方法在所有的数据结构上都实现了

loc 类似,at 提供了基于标签的标量查找,而 iat 提供了基于整数的查找,与 iloc 类似

In [141]: s.iat[5]
Out[141]: 5

In [142]: df.at[dates[5], 'A']
Out[142]: -0.6736897080883706

In [143]: df.iat[3, 0]
Out[143]: 0.7215551622443669

同时,你也可以根据这些索引进行设置值

In [144]: df.at[dates[5], 'E'] = 7

In [145]: df.iat[3, 0] = 7

如果索引标签不存在,会放大数据

In [146]: df.at[dates[-1] + pd.Timedelta('1 day'), 0] = 7

In [147]: df
Out[147]: 
                   A         B         C         D    E    0
2000-01-01  0.469112 -0.282863 -1.509059 -1.135632  NaN  NaN
2000-01-02  1.212112 -0.173215  0.119209 -1.044236  NaN  NaN
2000-01-03 -0.861849 -2.104569 -0.494929  1.071804  NaN  NaN
2000-01-04  7.000000 -0.706771 -1.039575  0.271860  NaN  NaN
2000-01-05 -0.424972  0.567020  0.276232 -1.087401  NaN  NaN
2000-01-06 -0.673690  0.113648 -1.478427  0.524988  7.0  NaN
2000-01-07  0.404705  0.577046 -1.715002 -1.039268  NaN  NaN
2000-01-08 -0.370647 -1.157892 -1.344312  0.844885  NaN  NaN
2000-01-09       NaN       NaN       NaN       NaN  NaN  7.0

12 布尔索引

另一种常见的操作是使用布尔向量来过滤数据。运算符包括:

|(or)&(and)~ (not)

这些必须用括号来分组,因为默认情况下,Python 会将 df['A'] > 2 & df['B'] < 3 这样的表达式评估为 df['A'] > (2 & df['B']) < 3,而理想的执行顺序是 (df['A'] > 2) & (df['B'] < 3)

使用一个布尔向量来索引一个 Series,其工作原理和 NumPy ndarray 一样。

In [148]: s = pd.Series(range(-3, 4))

In [149]: s
Out[149]: 
0   -3
1   -2
2   -1
3    0
4    1
5    2
6    3
dtype: int64

In [150]: s[s > 0]
Out[150]: 
4    1
5    2
6    3
dtype: int64

In [151]: s[(s < -1) | (s > 0.5)]
Out[151]: 
0   -3
1   -2
4    1
5    2
6    3
dtype: int64

In [152]: s[~(s < 0)]
Out[152]: 
3    0
4    1
5    2
6    3
dtype: int64

您可以使用一个与 DataFrame 的索引长度相同的布尔向量从 DataFrame 中选择行

In [153]: df[df['A'] > 0]
Out[153]: 
                   A         B         C         D   E   0
2000-01-01  0.469112 -0.282863 -1.509059 -1.135632 NaN NaN
2000-01-02  1.212112 -0.173215  0.119209 -1.044236 NaN NaN
2000-01-04  7.000000 -0.706771 -1.039575  0.271860 NaN NaN
2000-01-07  0.404705  0.577046 -1.715002 -1.039268 NaN NaN

列表推导式和 Seriesmap 函数可用于产生更复杂的标准

In [154]: df2 = pd.DataFrame({'a': ['one', 'one', 'two', 'three', 'two', 'one', 'six'],
   .....:                     'b': ['x', 'y', 'y', 'x', 'y', 'x', 'x'],
   .....:                     'c': np.random.randn(7)})
   .....: 

# only want 'two' or 'three'
In [155]: criterion = df2['a'].map(lambda x: x.startswith('t'))

In [156]: df2[criterion]
Out[156]: 
       a  b         c
2    two  y  0.041290
3  three  x  0.361719
4    two  y -0.238075

# equivalent but slower
In [157]: df2[[x.startswith('t') for x in df2['a']]]
Out[157]: 
       a  b         c
2    two  y  0.041290
3  three  x  0.361719
4    two  y -0.238075

# Multiple criteria
In [158]: df2[criterion & (df2['b'] == 'x')]
Out[158]: 
       a  b         c
3  three  x  0.361719

我们可以使用布尔向量结合其他索引表达式,在多个轴上索引

In [159]: df2.loc[criterion & (df2['b'] == 'x'), 'b':'c']
Out[159]: 
   b         c
3  x  0.361719

iloc 支持两种布尔索引。如果索引器是一个布尔值 Series,就会引发异常。

例如,在下面的例子中,df.iloc[s.values, 1] 是正确的。但是 df.iloc[s,1] 会引发 ValueError

In [160]: df = pd.DataFrame([[1, 2], [3, 4], [5, 6]],
   .....:                   index=list('abc'),
   .....:                   columns=['A', 'B'])
   .....: 

In [161]: s = (df['A'] > 2)

In [162]: s
Out[162]: 
a    False
b     True
c     True
Name: A, dtype: bool

In [163]: df.loc[s, 'B']
Out[163]: 
b    4
c    6
Name: B, dtype: int64

In [164]: df.iloc[s.values, 1]
Out[164]: 
b    4
c    6
Name: B, dtype: int64

13 使用 isin 索引

isin() 方法,顾名思义,就是判断 pandas 对象的每个元素是否存在传入的对象(SeriesDataFramdict 以及可迭代对象)中,返回一个布尔值 DataFrame

In [165]: s = pd.Series(np.arange(5), index=np.arange(5)[::-1], dtype='int64')

In [166]: s
Out[166]: 
4    0
3    1
2    2
1    3
0    4
dtype: int64

In [167]: s.isin([2, 4, 6])
Out[167]: 
4    False
3    False
2     True
1    False
0     True
dtype: bool

In [168]: s[s.isin([2, 4, 6])]
Out[168]: 
2    2
0    4
dtype: int64

同样,该方法也适用于 Index 对象,当你不知道所要寻找的标签中哪些是真实存在的时候,这一方法是很有用的

In [169]: s[s.index.isin([2, 4, 6])]
Out[169]: 
4    0
2    2
dtype: int64

# 注意与下面代码的区别
In [170]: s.reindex([2, 4, 6])
Out[170]: 
2    2.0
4    0.0
6    NaN
dtype: float64

对于 MultiIndex,还可以指定索引的级别

In [171]: s_mi = pd.Series(np.arange(6),
   .....:                  index=pd.MultiIndex.from_product([[0, 1], ['a', 'b', 'c']]))
   .....: 

In [172]: s_mi
Out[172]: 
0  a    0
   b    1
   c    2
1  a    3
   b    4
   c    5
dtype: int64

In [173]: s_mi.iloc[s_mi.index.isin([(1, 'a'), (2, 'b'), (0, 'c')])]
Out[173]: 
0  c    2
1  a    3
dtype: int64

In [174]: s_mi.iloc[s_mi.index.isin(['a', 'c', 'e'], level=1)]
Out[174]: 
0  a    0
   c    2
1  a    3
   c    5
dtype: int64

DataFrame 也有 isin() 方法,如果传递的是数组、列表或序列,会返回一个与原 DataFrame 大小相同的布尔 DataFrame

In [175]: df = pd.DataFrame({'vals': [1, 2, 3, 4], 'ids': ['a', 'b', 'f', 'n'],
   .....:                    'ids2': ['a', 'n', 'c', 'n']})
   .....: 

In [176]: values = ['a', 'b', 1, 3]

In [177]: df.isin(values)
Out[177]: 
    vals    ids   ids2
0   True   True   True
1  False   True  False
2   True  False  False
3  False  False  False

通常,您希望将特定的值与特定的列相匹配。只需将 values 设置为一个字典,其中键是列名,值是要检查的值的列表。

In [178]: values = {'ids': ['a', 'b'], 'vals': [1, 3]}

In [179]: df.isin(values)
Out[179]: 
    vals    ids   ids2
0   True   True  False
1  False   True  False
2   True  False  False
3  False  False  False

DataFrameisinany()all() 方法结合起来,可以快速选择满足给定条件的数据子集。

In [180]: values = {'ids': ['a', 'b'], 'ids2': ['a', 'c'], 'vals': [1, 3]}

In [181]: row_mask = df.isin(values).all(1)

In [182]: df[row_mask]
Out[182]: 
   vals ids ids2
0     1   a    a

14 where()mask() 方法

用布尔向量从 Series 中选择值,一般会返回数据的一个子集。为了保证选择的数据与原始数据的形状相同,可以使用 SeriesDataFrame 中的 where 方法

返回选定的行

In [183]: s[s > 0]
Out[183]: 
3    1
2    2
1    3
0    4
dtype: int64

返回一个与原始数据形状相同的 Series

In [184]: s.where(s > 0)
Out[184]: 
4    NaN
3    1.0
2    2.0
1    3.0
0    4.0
dtype: float64

下面的代码与 df.where(df < 0) 功能一样

In [185]: df[df < 0]
Out[185]: 
                   A         B         C         D
2000-01-01 -2.104139 -1.309525       NaN       NaN
2000-01-02 -0.352480       NaN -1.192319       NaN
2000-01-03 -0.864883       NaN -0.227870       NaN
2000-01-04       NaN -1.222082       NaN -1.233203
2000-01-05       NaN -0.605656 -1.169184       NaN
2000-01-06       NaN -0.948458       NaN -0.684718
2000-01-07 -2.670153 -0.114722       NaN -0.048048
2000-01-08       NaN       NaN -0.048788 -0.808838

此外,where 接受一个可选的 other 参数,用于指定条件为 False 的值的替换值

In [186]: df.where(df < 0, -df)
Out[186]: 
                   A         B         C         D
2000-01-01 -2.104139 -1.309525 -0.485855 -0.245166
2000-01-02 -0.352480 -0.390389 -1.192319 -1.655824
2000-01-03 -0.864883 -0.299674 -0.227870 -0.281059
2000-01-04 -0.846958 -1.222082 -0.600705 -1.233203
2000-01-05 -0.669692 -0.605656 -1.169184 -0.342416
2000-01-06 -0.868584 -0.948458 -2.297780 -0.684718
2000-01-07 -2.670153 -0.114722 -0.168904 -0.048048
2000-01-08 -0.801196 -1.392071 -0.048788 -0.808838

您可能希望根据一些布尔条件设置值

In [187]: s2 = s.copy()

In [188]: s2[s2 < 0] = 0

In [189]: s2
Out[189]: 
4    0
3    1
2    2
1    3
0    4
dtype: int64

In [190]: df2 = df.copy()

In [191]: df2[df2 < 0] = 0

In [192]: df2
Out[192]: 
                   A         B         C         D
2000-01-01  0.000000  0.000000  0.485855  0.245166
2000-01-02  0.000000  0.390389  0.000000  1.655824
2000-01-03  0.000000  0.299674  0.000000  0.281059
2000-01-04  0.846958  0.000000  0.600705  0.000000
2000-01-05  0.669692  0.000000  0.000000  0.342416
2000-01-06  0.868584  0.000000  2.297780  0.000000
2000-01-07  0.000000  0.000000  0.168904  0.000000
2000-01-08  0.801196  1.392071  0.000000  0.000000

默认情况下,where 返回数据的拷贝后的修改数据。这里有一个可选参数 inplace,可以在不创建副本的情况下修改原始数据。

In [193]: df_orig = df.copy()

In [194]: df_orig.where(df > 0, -df, inplace=True)

In [195]: df_orig
Out[195]: 
                   A         B         C         D
2000-01-01  2.104139  1.309525  0.485855  0.245166
2000-01-02  0.352480  0.390389  1.192319  1.655824
2000-01-03  0.864883  0.299674  0.227870  0.281059
2000-01-04  0.846958  1.222082  0.600705  1.233203
2000-01-05  0.669692  0.605656  1.169184  0.342416
2000-01-06  0.868584  0.948458  2.297780  0.684718
2000-01-07  2.670153  0.114722  0.168904  0.048048
2000-01-08  0.801196  1.392071  0.048788  0.808838

注意

pandasdf1.where(m, df2)numpynp.where(m, df1, df2) 相等

In [196]: df.where(df < 0, -df) == np.where(df < 0, df, -df)
Out[196]: 
               A     B     C     D
2000-01-01  True  True  True  True
2000-01-02  True  True  True  True
2000-01-03  True  True  True  True
2000-01-04  True  True  True  True
2000-01-05  True  True  True  True
2000-01-06  True  True  True  True
2000-01-07  True  True  True  True
2000-01-08  True  True  True  True
对齐

此外,where 会与布尔输入对齐,与 .loc 的部分选择和部分设置类似

In [197]: df2 = df.copy()

In [198]: df2[df2[1:4] > 0] = 3

In [199]: df2
Out[199]: 
                   A         B         C         D
2000-01-01 -2.104139 -1.309525  0.485855  0.245166
2000-01-02 -0.352480  3.000000 -1.192319  3.000000
2000-01-03 -0.864883  3.000000 -0.227870  3.000000
2000-01-04  3.000000 -1.222082  3.000000 -1.233203
2000-01-05  0.669692 -0.605656 -1.169184  0.342416
2000-01-06  0.868584 -0.948458  2.297780 -0.684718
2000-01-07 -2.670153 -0.114722  0.168904 -0.048048
2000-01-08  0.801196  1.392071 -0.048788 -0.808838

where 还可以接受 axislevel 参数来对齐输入

In [200]: df2 = df.copy()

In [201]: df2.where(df2 > 0, df2['A'], axis='index')
Out[201]: 
                   A         B         C         D
2000-01-01 -2.104139 -2.104139  0.485855  0.245166
2000-01-02 -0.352480  0.390389 -0.352480  1.655824
2000-01-03 -0.864883  0.299674 -0.864883  0.281059
2000-01-04  0.846958  0.846958  0.600705  0.846958
2000-01-05  0.669692  0.669692  0.669692  0.342416
2000-01-06  0.868584  0.868584  2.297780  0.868584
2000-01-07 -2.670153 -2.670153  0.168904 -2.670153
2000-01-08  0.801196  1.392071  0.801196  0.801196

这等效于下面的代码,但是速度更快

In [202]: df2 = df.copy()

In [203]: df.apply(lambda x, y: x.where(x > 0, y), y=df['A'])
Out[203]: 
                   A         B         C         D
2000-01-01 -2.104139 -2.104139  0.485855  0.245166
2000-01-02 -0.352480  0.390389 -0.352480  1.655824
2000-01-03 -0.864883  0.299674 -0.864883  0.281059
2000-01-04  0.846958  0.846958  0.600705  0.846958
2000-01-05  0.669692  0.669692  0.669692  0.342416
2000-01-06  0.868584  0.868584  2.297780  0.868584
2000-01-07 -2.670153 -2.670153  0.168904 -2.670153
2000-01-08  0.801196  1.392071  0.801196  0.801196

where 的条件参数和 other 参数接受一个可调用函数,函数必须传入一个参数,并返回有效的输出作为条件和 other 参数。

In [204]: df3 = pd.DataFrame({'A': [1, 2, 3],
   .....:                     'B': [4, 5, 6],
   .....:                     'C': [7, 8, 9]})
   .....: 

In [205]: df3.where(lambda x: x > 4, lambda x: x + 10)
Out[205]: 
    A   B  C
0  11  14  7
1  12   5  8
2  13   6  9
mask

mask()where 的逆布尔运算

In [206]: s.mask(s >= 0)
Out[206]: 
4   NaN
3   NaN
2   NaN
1   NaN
0   NaN
dtype: float64

In [207]: df.mask(df >= 0)
Out[207]: 
                   A         B         C         D
2000-01-01 -2.104139 -1.309525       NaN       NaN
2000-01-02 -0.352480       NaN -1.192319       NaN
2000-01-03 -0.864883       NaN -0.227870       NaN
2000-01-04       NaN -1.222082       NaN -1.233203
2000-01-05       NaN -0.605656 -1.169184       NaN
2000-01-06       NaN -0.948458       NaN -0.684718
2000-01-07 -2.670153 -0.114722       NaN -0.048048
2000-01-08       NaN       NaN -0.048788 -0.808838

15 使用 numpy 函数进行有条件的放大设置

where() 的替代方法是使用 numpy.where()。通过与设置新列相结合,可以对数据进行放大,其中的值是根据条件确定的。

In [208]: df = pd.DataFrame({'col1': list('ABBC'), 'col2': list('ZZXY')})

In [209]: df['color'] = np.where(df['col2'] == 'Z', 'green', 'red')

In [210]: df
Out[210]: 
  col1 col2  color
0    A    Z  green
1    B    Z  green
2    B    X    red
3    C    Y    red

考虑到在下面的数据,你有两个选择。你想在第二列有 'Z' 的时候,将新的列颜色设置为 'green'。你可以执行以下操作

In [208]: df = pd.DataFrame({'col1': list('ABBC'), 'col2': list('ZZXY')})

In [209]: df['color'] = np.where(df['col2'] == 'Z', 'green', 'red')

In [210]: df
Out[210]: 
  col1 col2  color
0    A    Z  green
1    B    Z  green
2    B    X    red
3    C    Y    red

如果有多个条件,可以使用 numpy.select()

例如,如果说对应三个条件有三种颜色可以选择,第四种颜色作为备用,可以做如下处理

In [211]: conditions = [
   .....:     (df['col2'] == 'Z') & (df['col1'] == 'A'),
   .....:     (df['col2'] == 'Z') & (df['col1'] == 'B'),
   .....:     (df['col1'] == 'B')
   .....: ]
   .....: 

In [212]: choices = ['yellow', 'blue', 'purple']

In [213]: df['color'] = np.select(conditions, choices, default='black')

In [214]: df
Out[214]: 
  col1 col2   color
0    A    Z  yellow
1    B    Z    blue
2    B    X  purple
3    C    Y   black

16 重复数据

如果你想识别和删除 DataFrame 中的重复行,可以使用下面两个方法:

  • duplicate: 返回一个布尔向量,其长度与数据行数一样,指示对应的行是否重复
  • drop_duplicates: 删除重复的行

默认情况下,第一个观察到的行被认为是唯一的(即保留重复的第一个),但是每个方法都有一个 keep 参数来指定要保存的目标

  • keep='first'(default): 保留重复数据的第一个
  • keep='last': 保留重复数据的最后一个
  • keep=False: 删除所有存在重复的行
In [281]: df2 = pd.DataFrame({'a': ['one', 'one', 'two', 'two', 'two', 'three', 'four'],
   .....:                     'b': ['x', 'y', 'x', 'y', 'x', 'x', 'x'],
   .....:                     'c': np.random.randn(7)})
   .....: 

In [282]: df2
Out[282]: 
       a  b         c
0    one  x -1.067137
1    one  y  0.309500
2    two  x -0.211056
3    two  y -1.842023
4    two  x -0.390820
5  three  x -1.964475
6   four  x  1.298329

In [283]: df2.duplicated('a')
Out[283]: 
0    False
1     True
2    False
3     True
4     True
5    False
6    False
dtype: bool

In [284]: df2.duplicated('a', keep='last')
Out[284]: 
0     True
1    False
2     True
3     True
4    False
5    False
6    False
dtype: bool

In [285]: df2.duplicated('a', keep=False)
Out[285]: 
0     True
1     True
2     True
3     True
4     True
5    False
6    False
dtype: bool

In [286]: df2.drop_duplicates('a')
Out[286]: 
       a  b         c
0    one  x -1.067137
2    two  x -0.211056
5  three  x -1.964475
6   four  x  1.298329

In [287]: df2.drop_duplicates('a', keep='last')
Out[287]: 
       a  b         c
1    one  y  0.309500
4    two  x -0.390820
5  three  x -1.964475
6   four  x  1.298329

In [288]: df2.drop_duplicates('a', keep=False)
Out[288]: 
       a  b         c
5  three  x -1.964475
6   four  x  1.298329

此外,还可以传递列名列表来标识重复行

In [289]: df2.duplicated(['a', 'b'])
Out[289]: 
0    False
1    False
2    False
3    False
4     True
5    False
6    False
dtype: bool

In [290]: df2.drop_duplicates(['a', 'b'])
Out[290]: 
       a  b         c
0    one  x -1.067137
1    one  y  0.309500
2    two  x -0.211056
3    two  y -1.842023
5  three  x -1.964475
6   four  x  1.298329

要按索引值删除重复的内容,请使用 Index.deproicated 然后执行切片。也包含同样的 keep 参数

In [291]: df3 = pd.DataFrame({'a': np.arange(6),
   .....:                     'b': np.random.randn(6)},
   .....:                    index=['a', 'a', 'b', 'c', 'b', 'a'])
   .....: 

In [292]: df3
Out[292]: 
   a         b
a  0  1.440455
a  1  2.456086
b  2  1.038402
c  3 -0.894409
b  4  0.683536
a  5  3.082764

In [293]: df3.index.duplicated()
Out[293]: array([False,  True, False, False,  True,  True])

In [294]: df3[~df3.index.duplicated()]
Out[294]: 
   a         b
a  0  1.440455
b  2  1.038402
c  3 -0.894409

In [295]: df3[~df3.index.duplicated(keep='last')]
Out[295]: 
   a         b
c  3 -0.894409
b  4  0.683536
a  5  3.082764

In [296]: df3[~df3.index.duplicated(keep=False)]
Out[296]: 
   a         b
c  3 -0.894409

17 类似字典个 get 方法

每个 SeriesDataFrame 都有一个可以返回默认值的 get 方法

In [297]: s = pd.Series([1, 2, 3], index=['a', 'b', 'c'])

In [298]: s.get('a')  # equivalent to s['a']
Out[298]: 1

In [299]: s.get('x', default=-1)
Out[299]: -1

18 使用索引或列标签查找值

有时,您希望提取一组给定行标签和列标签序列的值,这可以通过使用 DataFrame.meltDataFrame.loc 来实现

In [300]: df = pd.DataFrame({'col': ["A", "A", "B", "B"],
   .....:                    'A': [80, 23, np.nan, 22],
   .....:                    'B': [80, 55, 76, 67]})
   .....: 

In [301]: df
Out[301]: 
  col     A   B
0   A  80.0  80
1   A  23.0  55
2   B   NaN  76
3   B  22.0  67

In [302]: melt = df.melt('col')

In [303]: melt = melt.loc[melt['col'] == melt['variable'], 'value']

In [304]: melt.reset_index(drop=True)
Out[304]: 
0    80.0
1    23.0
2    76.0
3    67.0
Name: value, dtype: float64

19 query() 方法

DataFrame 对象有一个 query() 方法,它允许使用表达式进行选择。

例如,想要后去 b 列值位于 a 列 和 c 列之间的行

In [215]: n = 10

In [216]: df = pd.DataFrame(np.random.rand(n, 3), columns=list('abc'))

In [217]: df
Out[217]: 
          a         b         c
0  0.438921  0.118680  0.863670
1  0.138138  0.577363  0.686602
2  0.595307  0.564592  0.520630
3  0.913052  0.926075  0.616184
4  0.078718  0.854477  0.898725
5  0.076404  0.523211  0.591538
6  0.792342  0.216974  0.564056
7  0.397890  0.454131  0.915716
8  0.074315  0.437913  0.019794
9  0.559209  0.502065  0.026437

# pure python
In [218]: df[(df['a'] < df['b']) & (df['b'] < df['c'])]
Out[218]: 
          a         b         c
1  0.138138  0.577363  0.686602
4  0.078718  0.854477  0.898725
5  0.076404  0.523211  0.591538
7  0.397890  0.454131  0.915716

# query
In [219]: df.query('(a < b) & (b < c)')
Out[219]: 
          a         b         c
1  0.138138  0.577363  0.686602
4  0.078718  0.854477  0.898725
5  0.076404  0.523211  0.591538
7  0.397890  0.454131  0.915716

如果不存在名为 a 的列,则会使用名为 a 的命名索引

In [220]: df = pd.DataFrame(np.random.randint(n / 2, size=(n, 2)), columns=list('bc'))

In [221]: df.index.name = 'a'

In [222]: df
Out[222]: 
   b  c
a      
0  0  4
1  0  1
2  3  4
3  4  3
4  1  4
5  0  3
6  0  1
7  3  4
8  2  3
9  1  1

In [223]: df.query('a < b and b < c')
Out[223]: 
   b  c
a      
2  3  4

如果你不想或不能命名索引,你可以在查询表达式中使用 index 指代

In [224]: df = pd.DataFrame(np.random.randint(n, size=(n, 2)), columns=list('bc'))

In [225]: df
Out[225]: 
   b  c
0  3  1
1  3  0
2  5  6
3  5  2
4  7  4
5  0  1
6  2  5
7  0  1
8  6  0
9  7  9

In [226]: df.query('index < b < c')
Out[226]: 
   b  c
2  5  6

注意

如果索引的名称与列名重叠,则列名优先。例如

In [227]: df = pd.DataFrame({'a': np.random.randint(5, size=5)})

In [228]: df.index.name = 'a'

In [229]: df.query('a > 2')  # 使用列而不是索引
Out[229]: 
   a
a   
1  3
3  3

你仍然可以使用 index 来引用索引

In [230]: df.query('index > 2')
Out[230]: 
   a
a   
3  3
4  2

如果出于某些原因,您有一个名为 index 的列,那么您也可以使用 ilevel_0 来引用索引,但此时您应该考虑将您的列重命名为不那么模糊的名称

19.1 MultiIndex query() 语法

对包含多级索引的 DataFrame 查询,可以直接使用 level 名称,就像使用列名一样方便

In [231]: n = 10

In [232]: colors = np.random.choice(['red', 'green'], size=n)

In [233]: foods = np.random.choice(['eggs', 'ham'], size=n)

In [234]: colors
Out[234]: 
array(['red', 'red', 'red', 'green', 'green', 'green', 'green', 'green',
       'green', 'green'], dtype='<U5')

In [235]: foods
Out[235]: 
array(['ham', 'ham', 'eggs', 'eggs', 'eggs', 'ham', 'ham', 'eggs', 'eggs',
       'eggs'], dtype='<U4')

In [236]: index = pd.MultiIndex.from_arrays([colors, foods], names=['color', 'food'])

In [237]: df = pd.DataFrame(np.random.randn(n, 2), index=index)

In [238]: df
Out[238]: 
                   0         1
color food                    
red   ham   0.194889 -0.381994
      ham   0.318587  2.089075
      eggs -0.728293 -0.090255
green eggs -0.748199  1.318931
      eggs -2.029766  0.792652
      ham   0.461007 -0.542749
      ham  -0.305384 -0.479195
      eggs  0.095031 -0.270099
      eggs -0.707140 -0.773882
      eggs  0.229453  0.304418

In [239]: df.query('color == "red"')
Out[239]: 
                   0         1
color food                    
red   ham   0.194889 -0.381994
      ham   0.318587  2.089075
      eggs -0.728293 -0.090255

如果 MultiIndexlevel 没有命名,你可以使用特殊的名称来引用它们

In [240]: df.index.names = [None, None]

In [241]: df
Out[241]: 
                   0         1
red   ham   0.194889 -0.381994
      ham   0.318587  2.089075
      eggs -0.728293 -0.090255
green eggs -0.748199  1.318931
      eggs -2.029766  0.792652
      ham   0.461007 -0.542749
      ham  -0.305384 -0.479195
      eggs  0.095031 -0.270099
      eggs -0.707140 -0.773882
      eggs  0.229453  0.304418

In [242]: df.query('ilevel_0 == "red"')
Out[242]: 
                 0         1
red ham   0.194889 -0.381994
    ham   0.318587  2.089075
    eggs -0.728293 -0.090255

通常 ilevel_0 的意思是索引级别为 0,代表 index 的第 1level

19.2 query() 示例

当你有多个 DataFrame 对象,且它们之间存在共同的列(或索引),你可以将相同的查询同时应用于这些对象,而不需要指定一一指定

In [243]: df = pd.DataFrame(np.random.rand(n, 3), columns=list('abc'))

In [244]: df
Out[244]: 
          a         b         c
0  0.224283  0.736107  0.139168
1  0.302827  0.657803  0.713897
2  0.611185  0.136624  0.984960
3  0.195246  0.123436  0.627712
4  0.618673  0.371660  0.047902
5  0.480088  0.062993  0.185760
6  0.568018  0.483467  0.445289
7  0.309040  0.274580  0.587101
8  0.258993  0.477769  0.370255
9  0.550459  0.840870  0.304611

In [245]: df2 = pd.DataFrame(np.random.rand(n + 2, 3), columns=df.columns)

In [246]: df2
Out[246]: 
           a         b         c
0   0.357579  0.229800  0.596001
1   0.309059  0.957923  0.965663
2   0.123102  0.336914  0.318616
3   0.526506  0.323321  0.860813
4   0.518736  0.486514  0.384724
5   0.190804  0.505723  0.614533
6   0.891939  0.623977  0.676639
7   0.480559  0.378528  0.460858
8   0.420223  0.136404  0.141295
9   0.732206  0.419540  0.604675
10  0.604466  0.848974  0.896165
11  0.589168  0.920046  0.732716

In [247]: expr = '0.0 <= a <= c <= 0.5'

In [248]: map(lambda frame: frame.query(expr), [df, df2])
Out[248]: <map at 0x7f7110fdd910>
19.3 query() 的 Python 和 pandas 语法比较

类似 numpy 的语法

In [249]: df = pd.DataFrame(np.random.randint(n, size=(n, 3)), columns=list('abc'))

In [250]: df
Out[250]: 
   a  b  c
0  7  8  9
1  1  0  7
2  2  7  2
3  6  2  2
4  2  6  3
5  3  8  2
6  1  7  2
7  5  1  5
8  9  8  0
9  1  5  0

In [251]: df.query('(a < b) & (b < c)')
Out[251]: 
   a  b  c
0  7  8  9

In [252]: df[(df['a'] < df['b']) & (df['b'] < df['c'])]
Out[252]: 
   a  b  c
0  7  8  9

去掉括号会更简单一些

In [253]: df.query('a < b & b < c')
Out[253]: 
   a  b  c
0  7  8  9

使用英语替代符号

In [254]: df.query('a < b and b < c')
Out[254]: 
   a  b  c
0  7  8  9

更简洁的方式是

In [255]: df.query('a < b < c')
Out[255]: 
   a  b  c
0  7  8  9
19.4 in 和 not in 操作

query() 还支持 Pythoninnot 比较运算符的使用,是 SeriesDataFrameisin 方法的一个简单替代

In [256]: df = pd.DataFrame({'a': list('aabbccddeeff'), 'b': list('aaaabbbbcccc'),
   .....:                    'c': np.random.randint(5, size=12),
   .....:                    'd': np.random.randint(9, size=12)})
   .....: 

In [257]: df
Out[257]: 
    a  b  c  d
0   a  a  2  6
1   a  a  4  7
2   b  a  1  6
3   b  a  2  1
4   c  b  3  6
5   c  b  0  2
6   d  b  3  3
7   d  b  2  1
8   e  c  4  3
9   e  c  2  0
10  f  c  0  6
11  f  c  1  2

In [258]: df.query('a in b')
Out[258]: 
   a  b  c  d
0  a  a  2  6
1  a  a  4  7
2  b  a  1  6
3  b  a  2  1
4  c  b  3  6
5  c  b  0  2

# 使用纯 Python 语法
In [259]: df[df['a'].isin(df['b'])]
Out[259]: 
   a  b  c  d
0  a  a  2  6
1  a  a  4  7
2  b  a  1  6
3  b  a  2  1
4  c  b  3  6
5  c  b  0  2

In [260]: df.query('a not in b')
Out[260]: 
    a  b  c  d
6   d  b  3  3
7   d  b  2  1
8   e  c  4  3
9   e  c  2  0
10  f  c  0  6
11  f  c  1  2

# 纯 Python
In [261]: df[~df['a'].isin(df['b'])]
Out[261]: 
    a  b  c  d
6   d  b  3  3
7   d  b  2  1
8   e  c  4  3
9   e  c  2  0
10  f  c  0  6
11  f  c  1  2

可以将这一语法与其他表达式结合起来,组合成更复杂的查询

# 提取 a 列的值在 b 列出现,且 c 列的值小于 d 列的值的行
In [262]: df.query('a in b and c < d')
Out[262]: 
   a  b  c  d
0  a  a  2  6
1  a  a  4  7
2  b  a  1  6
4  c  b  3  6
5  c  b  0  2

# 纯 Python
In [263]: df[df['b'].isin(df['a']) & (df['c'] < df['d'])]
Out[263]: 
    a  b  c  d
0   a  a  2  6
1   a  a  4  7
2   b  a  1  6
4   c  b  3  6
5   c  b  0  2
10  f  c  0  6
11  f  c  1  2

注意

因为 numexpr 不支持 innot in 操作,所以会被 Python 执行。例如

df.query('a in b + c + d')

(b + c + d) 会被 numexpr 执行,而 in 操作是被 Python 执行。通常其他任何操作都可以使用 numexpr 执行

19.5 == 和 list 的使用

使用 ==!= 对比列和值列表的方式与 in/not in 类似

In [264]: df.query('b == ["a", "b", "c"]')
Out[264]: 
    a  b  c  d
0   a  a  2  6
1   a  a  4  7
2   b  a  1  6
3   b  a  2  1
4   c  b  3  6
5   c  b  0  2
6   d  b  3  3
7   d  b  2  1
8   e  c  4  3
9   e  c  2  0
10  f  c  0  6
11  f  c  1  2

# pure Python
In [265]: df[df['b'].isin(["a", "b", "c"])]
Out[265]: 
    a  b  c  d
0   a  a  2  6
1   a  a  4  7
2   b  a  1  6
3   b  a  2  1
4   c  b  3  6
5   c  b  0  2
6   d  b  3  3
7   d  b  2  1
8   e  c  4  3
9   e  c  2  0
10  f  c  0  6
11  f  c  1  2

In [266]: df.query('c == [1, 2]')
Out[266]: 
    a  b  c  d
0   a  a  2  6
2   b  a  1  6
3   b  a  2  1
7   d  b  2  1
9   e  c  2  0
11  f  c  1  2

In [267]: df.query('c != [1, 2]')
Out[267]: 
    a  b  c  d
1   a  a  4  7
4   c  b  3  6
5   c  b  0  2
6   d  b  3  3
8   e  c  4  3
10  f  c  0  6

# using in/not in
In [268]: df.query('[1, 2] in c')
Out[268]: 
    a  b  c  d
0   a  a  2  6
2   b  a  1  6
3   b  a  2  1
7   d  b  2  1
9   e  c  2  0
11  f  c  1  2

In [269]: df.query('[1, 2] not in c')
Out[269]: 
    a  b  c  d
1   a  a  4  7
4   c  b  3  6
5   c  b  0  2
6   d  b  3  3
8   e  c  4  3
10  f  c  0  6

# pure Python
In [270]: df[df['c'].isin([1, 2])]
Out[270]: 
    a  b  c  d
0   a  a  2  6
2   b  a  1  6
3   b  a  2  1
7   d  b  2  1
9   e  c  2  0
11  f  c  1  2
19.6 布尔操作

可以用 not~ 运算符对布尔表达式取反

In [271]: df = pd.DataFrame(np.random.rand(n, 3), columns=list('abc'))

In [272]: df['bools'] = np.random.rand(len(df)) > 0.5

In [273]: df.query('~bools')
Out[273]: 
          a         b         c  bools
2  0.697753  0.212799  0.329209  False
7  0.275396  0.691034  0.826619  False
8  0.190649  0.558748  0.262467  False

In [274]: df.query('not bools')
Out[274]: 
          a         b         c  bools
2  0.697753  0.212799  0.329209  False
7  0.275396  0.691034  0.826619  False
8  0.190649  0.558748  0.262467  False

In [275]: df.query('not bools') == df[~df['bools']]
Out[275]: 
      a     b     c  bools
2  True  True  True   True
7  True  True  True   True
8  True  True  True   True

当然,表达式也可以任意的复杂的组合

# 简洁的 query 语法
In [276]: shorter = df.query('a < b < c and (not bools) or bools > 2')

# 纯 Python 实现
In [277]: longer = df[(df['a'] < df['b'])
   .....:             & (df['b'] < df['c'])
   .....:             & (~df['bools'])
   .....:             | (df['bools'] > 2)]
   .....: 

In [278]: shorter
Out[278]: 
          a         b         c  bools
7  0.275396  0.691034  0.826619  False

In [279]: longer
Out[279]: 
          a         b         c  bools
7  0.275396  0.691034  0.826619  False

In [280]: shorter == longer
Out[280]: 
      a     b     c  bools
7  True  True  True   True
19.7 query() 的性能

query 使用 numexpr 会比纯 Python 在大数据框的查询上速度更快一些

image.png

20 索引对象

索引几乎都是不可变的,但是可以设置和更改它们的 name 属性。您可以使用 renameset_names 直接设置这些属性,它们默认会返回一个拷贝

In [315]: ind = pd.Index([1, 2, 3])

In [316]: ind.rename("apple")
Out[316]: Int64Index([1, 2, 3], dtype='int64', name='apple')

In [317]: ind
Out[317]: Int64Index([1, 2, 3], dtype='int64')

In [318]: ind.set_names(["apple"], inplace=True)

In [319]: ind.name = "bob"

In [320]: ind
Out[320]: Int64Index([1, 2, 3], dtype='int64', name='bob')

set_namesset_levelsset_codes 还有一个可选的 level 参数

In [321]: index = pd.MultiIndex.from_product([range(3), ['one', 'two']], names=['first', 'second'])

In [322]: index
Out[322]: 
MultiIndex([(0, 'one'),
            (0, 'two'),
            (1, 'one'),
            (1, 'two'),
            (2, 'one'),
            (2, 'two')],
           names=['first', 'second'])

In [323]: index.levels[1]
Out[323]: Index(['one', 'two'], dtype='object', name='second')

In [324]: index.set_levels(["a", "b"], level=1)
Out[324]: 
MultiIndex([(0, 'a'),
            (0, 'b'),
            (1, 'a'),
            (1, 'b'),
            (2, 'a'),
            (2, 'b')],
           names=['first', 'second'])
20.1 索引对象的集合操作

索引对象的集合操作包括 unionintersectiondifference

例如,差集

In [325]: a = pd.Index(['c', 'b', 'a'])

In [326]: b = pd.Index(['c', 'e', 'd'])

In [327]: a.difference(b)
Out[327]: Index(['a', 'b'], dtype='object')

并集和交集

>>> a.union(b)
Index(['a', 'b', 'c', 'd', 'e'], dtype='object')

>>> a.intersection(b)
Index(['c'], dtype='object')

还可以使用 symmetric_difference 操作,它返回出现在 idx1idx2 中的元素,但不同时出现在两个索引中的元素。这相当于

idx1.difference(idx2).union(idx2.difference(idx1))

In [328]: idx1 = pd.Index([1, 2, 3, 4])

In [329]: idx2 = pd.Index([2, 3, 4, 5])

In [330]: idx1.symmetric_difference(idx2)
Out[330]: Int64Index([1, 5], dtype='int64')

注意:集合操作产生的索引将按升序排序

当在具有不同 dtype 的索引之间执行 Index.union() 时,索引必须能转换为一个公共 dtype。通常是 object dtype

比如,整数索引和浮点索引取并集

In [331]: idx1 = pd.Index([0, 1, 2])

In [332]: idx2 = pd.Index([0.5, 1.5])

In [333]: idx1.union(idx2)
Out[333]: Float64Index([0.0, 0.5, 1.0, 1.5, 2.0], dtype='float64')
20.2 缺失值

提示:虽然索引也支持缺失值(NaN),但是最好不要使用,因为有些操作默认会忽略缺失值

可以使用 Index.fillna 用于指定的标量值来填充缺失值

In [334]: idx1 = pd.Index([1, np.nan, 3, 4])

In [335]: idx1
Out[335]: Float64Index([1.0, nan, 3.0, 4.0], dtype='float64')

In [336]: idx1.fillna(2)
Out[336]: Float64Index([1.0, 2.0, 3.0, 4.0], dtype='float64')

In [337]: idx2 = pd.DatetimeIndex([pd.Timestamp('2011-01-01'),
   .....:                          pd.NaT,
   .....:                          pd.Timestamp('2011-01-03')])
   .....: 

In [338]: idx2
Out[338]: DatetimeIndex(['2011-01-01', 'NaT', '2011-01-03'], dtype='datetime64[ns]', freq=None)

In [339]: idx2.fillna(pd.Timestamp('2011-01-02'))
Out[339]: DatetimeIndex(['2011-01-01', '2011-01-02', '2011-01-03'], dtype='datetime64[ns]', freq=None)
21 设置和重置索引

有时,你会从数据集中加载或创建 DataFrame,并希望添加一些索引。

一般有以下几种方法

21.1 设置索引

DataFrame 有一个 set_index() 方法,它接受一个列名或列名列表,来创建一个新的索引或重新创建索引

In [340]: data
Out[340]: 
     a    b  c    d
0  bar  one  z  1.0
1  bar  two  y  2.0
2  foo  one  x  3.0
3  foo  two  w  4.0

In [341]: indexed1 = data.set_index('c')

In [342]: indexed1
Out[342]: 
     a    b    d
c               
z  bar  one  1.0
y  bar  two  2.0
x  foo  one  3.0
w  foo  two  4.0

In [343]: indexed2 = data.set_index(['a', 'b'])

In [344]: indexed2
Out[344]: 
         c    d
a   b          
bar one  z  1.0
    two  y  2.0
foo one  x  3.0
    two  w  4.0

append 关键字参数允许你保留现有的索引,并将给定的列追加到索引

In [345]: frame = data.set_index('c', drop=False)

In [346]: frame = frame.set_index(['a', 'b'], append=True)

In [347]: frame
Out[347]: 
           c    d
c a   b          
z bar one  z  1.0
y bar two  y  2.0
x foo one  x  3.0
w foo two  w  4.0

set_index 还有其他参数,允许你不删除索引列或原地添加索引(不创建新的对象)

In [348]: data.set_index('c', drop=False)
Out[348]: 
     a    b  c    d
c                  
z  bar  one  z  1.0
y  bar  two  y  2.0
x  foo  one  x  3.0
w  foo  two  w  4.0

In [349]: data.set_index(['a', 'b'], inplace=True)

In [350]: data
Out[350]: 
         c    d
a   b          
bar one  z  1.0
    two  y  2.0
foo one  x  3.0
    two  w  4.0
21.2 重置索引

为了方便起见,DataFrame 上有一个名为 reset_index() 的函数,它将索引值添加到 DataFrame 的列中,并设置一个简单的整数索引。相当于 set_index() 的逆操作

In [351]: data
Out[351]: 
         c    d
a   b          
bar one  z  1.0
    two  y  2.0
foo one  x  3.0
    two  w  4.0

In [352]: data.reset_index()
Out[352]: 
     a    b  c    d
0  bar  one  z  1.0
1  bar  two  y  2.0
2  foo  one  x  3.0
3  foo  two  w  4.0

新列的列名为索引的 name 属性,你可以使用 level 关键字删除部分索引

In [353]: frame
Out[353]: 
           c    d
c a   b          
z bar one  z  1.0
y bar two  y  2.0
x foo one  x  3.0
w foo two  w  4.0

In [354]: frame.reset_index(level=1)
Out[354]: 
         a  c    d
c b               
z one  bar  z  1.0
y two  bar  y  2.0
x one  foo  x  3.0
w two  foo  w  4.0

reset_index 接受一个可选参数 drop,如果为真,则直接丢弃索引,而不是将索引值放入 DataFrame 的列中

为索引赋值

data.index = index

22 返回视图或拷贝

在设置 pandas 对象中的值时,必须小心避免所谓的链接索引。

对于下面的一个例子

In [355]: dfmi = pd.DataFrame([list('abcd'),
   .....:                      list('efgh'),
   .....:                      list('ijkl'),
   .....:                      list('mnop')],
   .....:                     columns=pd.MultiIndex.from_product([['one', 'two'],
   .....:                                                         ['first', 'second']]))
   .....: 

In [356]: dfmi
Out[356]: 
    one          two       
  first second first second
0     a      b     c      d
1     e      f     g      h
2     i      j     k      l
3     m      n     o      p

比较这两种访问方式

In [357]: dfmi['one']['second']
Out[357]: 
0    b
1    f
2    j
3    n
Name: second, dtype: object
In [358]: dfmi.loc[:, ('one', 'second')]
Out[358]: 
0    b
1    f
2    j
3    n
Name: (one, second), dtype: object

这两者产生相同的结果,那么您应该使用哪一种呢?应该如何理解这些操作的顺序呢?

对于第一种方法,dfmi['one'] 先获取第一个 level 的数据,然后 ['second'] 是对前一步的结果进一步取值的。调用了两次获取属性的方法 __getitem__ ,这些步骤是分开的,像是链条一样一个接着一个

df.loc[:,('one','second')] 会解析为嵌套元组 (slice(None),('one','second')) 并调用一次 __getitem__,这种操作的速度会更快。

22.1 执行顺序

使用链式索引时,索引操作的类型和顺序部分决定了返回的结果是原始对象的切片还是切片的拷贝。

pandas 之所以有 SettingWithCopyWarning,是因为通常为一个切片的拷贝赋值是无意识的,而是链式索引返回一个切片的拷贝所造成的错误

如果你想让 pandas 或多或少地支持对链式索引表达式的赋值,可以将 mode.chained_assignment 设置为下面的值

  • 'warn': 默认值,打印出 SettingWithCopyWarning 信息
  • 'raise': 引发 SettingWithCopyException 异常
  • None: 完全消除警告
In [359]: dfb = pd.DataFrame({'a': ['one', 'one', 'two',
   .....:                           'three', 'two', 'one', 'six'],
   .....:                     'c': np.arange(7)})
   .....: 

# 将会显示 SettingWithCopyWarning
# 但是设置值的操作还是会生效
In [360]: dfb['c'][dfb['a'].str.startswith('o')] = 42

但是在切片拷贝上操作不会生效

>>> pd.set_option('mode.chained_assignment','warn')
>>> dfb[dfb['a'].str.startswith('o')]['c'] = 42
Traceback (most recent call last)
     ...
SettingWithCopyWarning:
     A value is trying to be set on a copy of a slice from a DataFrame.
     Try using .loc[row_index,col_indexer] = value instead

注意:这些设置规则适用于所有 .loc/.iloc

以下是推荐使用的访问方法,在 .loc 中使用固定索引来访问单个或多个数据

In [361]: dfc = pd.DataFrame({'a': ['one', 'one', 'two',
   .....:                           'three', 'two', 'one', 'six'],
   .....:                     'c': np.arange(7)})
   .....: 

In [362]: dfd = dfc.copy()

# 设置多个值
In [363]: mask = dfd['a'].str.startswith('o')

In [364]: dfd.loc[mask, 'c'] = 42

In [365]: dfd
Out[365]: 
       a   c
0    one  42
1    one  42
2    two   2
3  three   3
4    two   4
5    one  42
6    six   6

# 设置单个值
In [366]: dfd = dfc.copy()

In [367]: dfd.loc[2, 'a'] = 11

In [368]: dfd
Out[368]: 
       a  c
0    one  0
1    one  1
2     11  2
3  three  3
4    two  4
5    one  5
6    six  6

下面的方法只是偶尔发挥作用,所以应该避免使用

In [369]: dfd = dfc.copy()

In [370]: dfd['a'][2] = 111

In [371]: dfd
Out[371]: 
       a  c
0    one  0
1    one  1
2    111  2
3  three  3
4    two  4
5    one  5
6    six  6

最后的例子根本不起作用,所以应该避免

>>> pd.set_option('mode.chained_assignment','raise')
>>> dfd.loc[0]['a'] = 1111
Traceback (most recent call last)
     ...
SettingWithCopyException:
     A value is trying to be set on a copy of a slice from a DataFrame.
     Try using .loc[row_index,col_indexer] = value instead
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

名本无名

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值