使用链式索引时为什么赋值失败?
警告
写时复制 将成为 pandas 3.0 的新默认值。这意味着链式索引永远不会起作用。因此,SettingWithCopyWarning
将不再必要。有关更多上下文,请参见此部分。我们建议打开写时复制以利用改进
pd.options.mode.copy_on_write = True
即使在 pandas 3.0 可用之前。
前面部分的问题只是一个性能问题。关于SettingWithCopy
警告是什么?当你做一些可能花费几毫秒额外时间的事情时,我们通常不会发出警告!
但事实证明,将链式索引的乘积分配给结果本质上是不可预测的。要理解这一点,请思考 Python 解释器如何执行这段代码:
dfmi.loc[:, ('one', 'second')] = value # becomes dfmi.loc.__setitem__((slice(None), ('one', 'second')), value)
但这段代码处理方式不同:
dfmi['one']['second'] = value # becomes dfmi.__getitem__('one').__setitem__('second', value)
看到里面的__getitem__
了吗?除了简单情况外,很难预测它是否会返回视图或副本(它取决于数组的内存布局,关于这一点,pandas 不做任何保证),因此__setitem__
是否会修改dfmi
或立即被丢弃的临时对象。这就是SettingWithCopy
警告您的内容!
注意
您可能想知道我们是否应该关注第一个示例中的loc
属性。但是保证dfmi.loc
是dfmi
本身,并具有修改后的索引行为,因此dfmi.loc.__getitem__
/ dfmi.loc.__setitem__
直接在dfmi
上操作。当然,dfmi.loc.__getitem__(idx)
可能是dfmi
的视图或副本。
有时会在没有明显的链式索引的情况下出现SettingWithCopy
警告。这些就是SettingWithCopy
旨在捕捉的错误!pandas 可能正试图警告您已经做了这件事:
def do_something(df): foo = df[['bar', 'baz']] # Is foo a view? A copy? Nobody knows! # ... many lines here ... # We don't know whether this will modify df or not! foo['quux'] = value return foo
哎呀!
评估顺序很重要
警告
写时复制 将成为 pandas 3.0 的新默认值。这意味着链式索引将永远不会起作用。因此,SettingWithCopyWarning
将不再需要。有关更多上下文,请参阅此部分。我们建议开启写时复制以利用改进。
pd.options.mode.copy_on_write = True
在 pandas 3.0 发布之前就已经可用。
当你使用链式索引时,索引操作的顺序和类型部分地确定结果是原始对象的切片,还是切片的副本。
pandas 有 SettingWithCopyWarning
,因为在切片的副本上赋值通常不是有意的,而是由于链式索引返回了一个副本而预期的是一个切片引起的错误。
如果你希望 pandas 对链式索引表达式的赋值更加信任或不信任,你可以将选项 mode.chained_assignment
设置为以下值之一:
'warn'
,默认值,表示会打印出SettingWithCopyWarning
。'raise'
表示 pandas 将引发一个SettingWithCopyError
让你处理。None
将完全抑制警告。
In [382]: dfb = pd.DataFrame({'a': ['one', 'one', 'two', .....: 'three', 'two', 'one', 'six'], .....: 'c': np.arange(7)}) .....: # This will show the SettingWithCopyWarning # but the frame values will be set In [383]: dfb['c'][dfb['a'].str.startswith('o')] = 42
然而,这是在副本上操作的,不会起作用。
In [384]: with pd.option_context('mode.chained_assignment','warn'): .....: dfb[dfb['a'].str.startswith('o')]['c'] = 42 .....:
链式赋值也可能出现在设置混合类型数据帧时。
注意
这些设置规则适用于所有 .loc/.iloc
。
以下是使用 .loc
获取多个项(使用 mask
)和使用固定索引获取单个项的推荐访问方法:
In [385]: dfc = pd.DataFrame({'a': ['one', 'one', 'two', .....: 'three', 'two', 'one', 'six'], .....: 'c': np.arange(7)}) .....: In [386]: dfd = dfc.copy() # Setting multiple items using a mask In [387]: mask = dfd['a'].str.startswith('o') In [388]: dfd.loc[mask, 'c'] = 42 In [389]: dfd Out[389]: a c 0 one 42 1 one 42 2 two 2 3 three 3 4 two 4 5 one 42 6 six 6 # Setting a single item In [390]: dfd = dfc.copy() In [391]: dfd.loc[2, 'a'] = 11 In [392]: dfd Out[392]: a c 0 one 0 1 one 1 2 11 2 3 three 3 4 two 4 5 one 5 6 six 6
以下内容 有时 可以工作,但不能保证,因此应该避免使用:
In [393]: dfd = dfc.copy() In [394]: dfd['a'][2] = 111 In [395]: dfd Out[395]: a c 0 one 0 1 one 1 2 111 2 3 three 3 4 two 4 5 one 5 6 six 6
最后,下面的示例将完全不会起作用,因此应该避免使用:
In [396]: with pd.option_context('mode.chained_assignment','raise'): .....: dfd.loc[0]['a'] = 1111 .....: --------------------------------------------------------------------------- SettingWithCopyError Traceback (most recent call last) <ipython-input-396-32ce785aaa5b> in ?() 1 with pd.option_context('mode.chained_assignment','raise'): ----> 2 dfd.loc[0]['a'] = 1111 ~/work/pandas/pandas/pandas/core/series.py in ?(self, key, value) 1284 ) 1285 1286 check_dict_or_set_indexers(key) 1287 key = com.apply_if_callable(key, self) -> 1288 cacher_needs_updating = self._check_is_chained_assignment_possible() 1289 1290 if key is Ellipsis: 1291 key = slice(None) ~/work/pandas/pandas/pandas/core/series.py in ?(self) 1489 ref = self._get_cacher() 1490 if ref is not None and ref._is_mixed_type: 1491 self._check_setitem_copy(t="referent", force=True) 1492 return True -> 1493 return super()._check_is_chained_assignment_possible() ~/work/pandas/pandas/pandas/core/generic.py in ?(self) 4395 single-dtype meaning that the cacher should be updated following 4396 setting. 4397 """ 4398 if self._is_copy: -> 4399 self._check_setitem_copy(t="referent") 4400 return False ~/work/pandas/pandas/pandas/core/generic.py in ?(self, t, force) 4469 "indexing.html#returning-a-view-versus-a-copy" 4470 ) 4471 4472 if value == "raise": -> 4473 raise SettingWithCopyError(t) 4474 if value == "warn": 4475 warnings.warn(t, SettingWithCopyWarning, stacklevel=find_stack_level()) SettingWithCopyError: A value is trying to be set on a copy of a slice from a DataFrame See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
警告
链式赋值警告/异常旨在通知用户可能无效的赋值。可能会出现虚警;意外报告链式赋值的情况。## 索引的不同选择
为了支持更明确的基于位置的索引,对象选择已经增加了一些用户请求的添加。pandas 现在支持三种类型的多轴索引。
.loc
主要基于标签,但也可以与布尔数组一起使用。当找不到项目时,.loc
将引发KeyError
。允许的输入是:
- 单个标签,例如
5
或'a'
(请注意5
被解释为索引的 标签。这种用法 不是 沿索引的整数位置。)。- 标签列表或数组
['a', 'b', 'c']
。- 使用标签
'a':'f'
的切片对象(请注意,与通常的 Python 切片相反,在索引中同时包括起始和停止点!请参阅 使用标签进行切片 和 端点是包含的。)- 布尔数组(任何
NA
值都将被视为False
)。- 带有一个参数(调用系列或数据帧)并返回索引的有效输出(上述之一)的
callable
函数。- 一个包含整数的元组,其元素是上述输入之一。
- 更多信息请参见通过标签进行选择。
.iloc
主要基于整数位置(从轴的0
到length-1
),但也可以与布尔数组一起使用。如果请求的索引器超出范围,.iloc
将引发IndexError
,除了切片索引器允许超出范围的索引(这符合 Python/NumPy 的切片语义)。允许的输入为:
- 一个整数,例如
5
。- 一个整数列表或数组
[4, 3, 0]
。- 一个包含整数
1:7
的切片对象。- 一个布尔数组(任何
NA
值都将被视为False
)。- 一个具有一个参数(调用的 Series 或 DataFrame)的
callable
函数,并返回用于索引的有效输出(上述之一)。- 一个元组,包含行(和列)索引,其元素是上述输入之一。
- 更多信息请参见通过位置进行选择、高级索引以及高级分层。
.loc
、.iloc
,以及[]
索引都可以接受callable
作为索引器。更多信息请参见通过可调用对象进行选择。
注意
在应用可调用对象之前,将元组键解构为行(和列)索引,因此无法从可调用对象中返回元组以索引行和列。
从具有多轴选择的对象中获取值使用以下表示法(以.loc
为例,但.iloc
也适用)。规范中的任何轴访问器都可以是空切片:
。省略规范中的轴被假定为:
,例如p.loc['a']
等同于p.loc['a', :]
。
对象类型 | 索引器 |
Series | s.loc[indexer] |
DataFrame | df.loc[row_indexer,column_indexer] |
基础知识
如在上一节介绍数据结构时提到的,使用[]
(即__getitem__
,对于熟悉在 Python 中实现类行为的人)进行索引的主要功能是选择较低维度的切片。以下表格显示了使用[]
索引 pandas 对象时的返回类型值:
对象类型 | 选择 | 返回值类型 |
Series | series[label] |
标量值 |
DataFrame | frame[colname] |
对应于 colname 的 Series |
在这里,我们构建了一个简单的时间序列数据集,用于说明索引功能:
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
您可能会发现这对于对列的子集应用转换(就地)很有用。
警告
pandas 在从.loc
设置Series
和DataFrame
时会对齐所有轴。
这不会修改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
然而,当使用.iloc
从Series
和DataFrame
设置时,pandas 不会对齐轴,因为.iloc
是按位置操作的。
这将修改df
,因为在赋值之前列对齐。
In [14]: df[['A', 'B']] Out[14]: 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 In [15]: df.iloc[:, [1, 0]] = df[['A', 'B']] In [16]: df[['A','B']] Out[16]: 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
属性访问
您可以直接将Series
上的索引或DataFrame
上的列作为属性访问:
In [17]: sa = pd.Series([1, 2, 3], index=list('abc')) In [18]: dfa = df.copy()
In [19]: sa.b Out[19]: 2 In [20]: dfa.A Out[20]: 2000-01-01 -0.282863 2000-01-02 -0.173215 2000-01-03 -2.104569 2000-01-04 -0.706771 2000-01-05 0.567020 2000-01-06 0.113648 2000-01-07 0.577046 2000-01-08 -1.157892 Freq: D, Name: A, dtype: float64
In [21]: sa.a = 5 In [22]: sa Out[22]: a 5 b 2 c 3 dtype: int64 In [23]: dfa.A = list(range(len(dfa.index))) # ok if A already exists In [24]: dfa Out[24]: A B C D 2000-01-01 0 0.469112 -1.509059 -1.135632 2000-01-02 1 1.212112 0.119209 -1.044236 2000-01-03 2 -0.861849 -0.494929 1.071804 2000-01-04 3 0.721555 -1.039575 0.271860 2000-01-05 4 -0.424972 0.276232 -1.087401 2000-01-06 5 -0.673690 -1.478427 0.524988 2000-01-07 6 0.404705 -1.715002 -1.039268 2000-01-08 7 -0.370647 -1.344312 0.844885 In [25]: dfa['A'] = list(range(len(dfa.index))) # use this form to create a new column In [26]: dfa Out[26]: A B C D 2000-01-01 0 0.469112 -1.509059 -1.135632 2000-01-02 1 1.212112 0.119209 -1.044236 2000-01-03 2 -0.861849 -0.494929 1.071804 2000-01-04 3 0.721555 -1.039575 0.271860 2000-01-05 4 -0.424972 0.276232 -1.087401 2000-01-06 5 -0.673690 -1.478427 0.524988 2000-01-07 6 0.404705 -1.715002 -1.039268 2000-01-08 7 -0.370647 -1.344312 0.844885
警告
- 只有当索引元素是有效的 Python 标识符时,才能使用此访问,例如,
s.1
是不允许的。请参阅此处以了解有效标识符的解释。 - 如果属性与现有方法名冲突,则该属性将不可用,例如,
s.min
是不允许的,但s['min']
是可能的。 - 同样,如果属性与以下列表中的任何内容冲突,则该属性将不可用:
index
、major_axis
、minor_axis
、items
。 - 在任何这些情况下,标准索引仍将起作用,例如,
s['1']
、s['min']
和s['index']
将访问相应的元素或列。
如果您正在使用 IPython 环境,还可以使用制表符补全来查看这些可访问的属性。
您还可以将dict
分配给DataFrame
的一行:
In [27]: x = pd.DataFrame({'x': [1, 2, 3], 'y': [3, 4, 5]}) In [28]: x.iloc[1] = {'x': 9, 'y': 99} In [29]: x Out[29]: x y 0 1 3 1 9 99 2 3 5
您可以使用属性访问来修改 Series 或 DataFrame 的现有元素,但要小心;如果尝试使用属性访问来创建新列,则会创建新属性而不是新列,并将引发UserWarning
:
In [30]: df_new = pd.DataFrame({'one': [1., 2., 3.]}) In [31]: df_new.two = [4, 5, 6] In [32]: df_new Out[32]: one 0 1.0 1 2.0 2 3.0
切片范围
沿着任意轴切片范围的最稳健和一致的方法在按位置选择部分详细描述了.iloc
方法。现在,我们解释使用[]
运算符的切片语义。
对于 Series,语法与 ndarray 完全相同,返回值的切片和相应的标签:
In [33]: s[:5] Out[33]: 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 [34]: s[::2] Out[34]: 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 [35]: s[::-1] Out[35]: 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 [36]: s2 = s.copy() In [37]: s2[:5] = 0 In [38]: s2 Out[38]: 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 [39]: df[:3] Out[39]: 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 In [40]: df[::-1] Out[40]: A B C D 2000-01-08 -1.157892 -0.370647 -1.344312 0.844885 2000-01-07 0.577046 0.404705 -1.715002 -1.039268 2000-01-06 0.113648 -0.673690 -1.478427 0.524988 2000-01-05 0.567020 -0.424972 0.276232 -1.087401 2000-01-04 -0.706771 0.721555 -1.039575 0.271860 2000-01-03 -2.104569 -0.861849 -0.494929 1.071804 2000-01-02 -0.173215 1.212112 0.119209 -1.044236 2000-01-01 -0.282863 0.469112 -1.509059 -1.135632
按标签选择
警告
在设置操作中返回副本还是引用可能取决于上下文。有时这被称为chained assignment
,应该避免。请参阅返回视图与副本。
警告
当您提供与索引类型不兼容(或可转换)的切片器时,
.loc
是严格的。例如,在DatetimeIndex
中使用整数。这将引发TypeError
。
In [41]: dfl = pd.DataFrame(np.random.randn(5, 4), ....: columns=list('ABCD'), ....: index=pd.date_range('20130101', periods=5)) ....: In [42]: dfl Out[42]: 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 [43]: dfl.loc[2:3] --------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In[43], line 1 ----> 1 dfl.loc[2:3] File ~/work/pandas/pandas/pandas/core/indexing.py:1191, in _LocationIndexer.__getitem__(self, key) 1189 maybe_callable = com.apply_if_callable(key, self.obj) 1190 maybe_callable = self._check_deprecated_callable_usage(key, maybe_callable) -> 1191 return self._getitem_axis(maybe_callable, axis=axis) File ~/work/pandas/pandas/pandas/core/indexing.py:1411, in _LocIndexer._getitem_axis(self, key, axis) 1409 if isinstance(key, slice): 1410 self._validate_key(key, axis) -> 1411 return self._get_slice_axis(key, axis=axis) 1412 elif com.is_bool_indexer(key): 1413 return self._getbool_axis(key, axis=axis) File ~/work/pandas/pandas/pandas/core/indexing.py:1443, in _LocIndexer._get_slice_axis(self, slice_obj, axis) 1440 return obj.copy(deep=False) 1442 labels = obj._get_axis(axis) -> 1443 indexer = labels.slice_indexer(slice_obj.start, slice_obj.stop, slice_obj.step) 1445 if isinstance(indexer, slice): 1446 return self.obj._slice(indexer, axis=axis) File ~/work/pandas/pandas/pandas/core/indexes/datetimes.py:682, in DatetimeIndex.slice_indexer(self, start, end, step) 674 # GH#33146 if start and end are combinations of str and None and Index is not 675 # monotonic, we can not use Index.slice_indexer because it does not honor the 676 # actual elements, is only searching for start and end 677 if ( 678 check_str_or_none(start) 679 or check_str_or_none(end) 680 or self.is_monotonic_increasing 681 ): --> 682 return Index.slice_indexer(self, start, end, step) 684 mask = np.array(True) 685 in_index = True File ~/work/pandas/pandas/pandas/core/indexes/base.py:6662, in Index.slice_indexer(self, start, end, step) 6618 def slice_indexer( 6619 self, 6620 start: Hashable | None = None, 6621 end: Hashable | None = None, 6622 step: int | None = None, 6623 ) -> slice: 6624 """ 6625 Compute the slice indexer for input labels and step. 6626 (...) 6660 slice(1, 3, None) 6661 """ -> 6662 start_slice, end_slice = self.slice_locs(start, end, step=step) 6664 # return a slice 6665 if not is_scalar(start_slice): File ~/work/pandas/pandas/pandas/core/indexes/base.py:6879, in Index.slice_locs(self, start, end, step) 6877 start_slice = None 6878 if start is not None: -> 6879 start_slice = self.get_slice_bound(start, "left") 6880 if start_slice is None: 6881 start_slice = 0 File ~/work/pandas/pandas/pandas/core/indexes/base.py:6794, in Index.get_slice_bound(self, label, side) 6790 original_label = label 6792 # For datetime indices label may be a string that has to be converted 6793 # to datetime boundary according to its resolution. -> 6794 label = self._maybe_cast_slice_bound(label, side) 6796 # we need to look up the label 6797 try: File ~/work/pandas/pandas/pandas/core/indexes/datetimes.py:642, in DatetimeIndex._maybe_cast_slice_bound(self, label, side) 637 if isinstance(label, dt.date) and not isinstance(label, dt.datetime): 638 # Pandas supports slicing with dates, treated as datetimes at midnight. 639 # https://github.com/pandas-dev/pandas/issues/31501 640 label = Timestamp(label).to_pydatetime() --> 642 label = super()._maybe_cast_slice_bound(label, side) 643 self._data._assert_tzawareness_compat(label) 644 return Timestamp(label) File ~/work/pandas/pandas/pandas/core/indexes/datetimelike.py:378, in DatetimeIndexOpsMixin._maybe_cast_slice_bound(self, label, side) 376 return lower if side == "left" else upper 377 elif not isinstance(label, self._data._recognized_scalars): --> 378 self._raise_invalid_indexer("slice", label) 380 return label File ~/work/pandas/pandas/pandas/core/indexes/base.py:4301, in Index._raise_invalid_indexer(self, form, key, reraise) 4299 if reraise is not lib.no_default: 4300 raise TypeError(msg) from reraise -> 4301 raise TypeError(msg) TypeError: cannot do slice indexing on DatetimeIndex with these indexers [2] of type int
切片中的字符串可以转换为索引类型,并导致自然切片。
In [44]: dfl.loc['20130102':'20130104'] Out[44]: 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
。在切片时,如果存在于索引中,则起始边界和停止边界都将包括。整数是有效标签,但它们指的是标签而不是位置。
.loc
属性是主要访问方法。以下是有效输入:
- 单个标签,例如
5
或'a'
(请注意,5
被解释为索引的标签。此用法不是索引上的整数位置)。 - 标签列表或数组
['a', 'b', 'c']
。 - 具有标签
'a':'f'
的切片对象(请注意,与通常的 Python 切片相反,当存在于索引中时,起始和停止都包括在内!请参见使用标签切片)。 - 一个布尔数组。
callable
,参见通过可调用进行选择。
In [45]: s1 = pd.Series(np.random.randn(6), index=list('abcdef')) In [46]: s1 Out[46]: a 1.431256 b 1.340309 c -1.170299 d -0.226169 e 0.410835 f 0.813850 dtype: float64 In [47]: s1.loc['c':] Out[47]: c -1.170299 d -0.226169 e 0.410835 f 0.813850 dtype: float64 In [48]: s1.loc['b'] Out[48]: 1.3403088497993827
请注意,设置也有效:
In [49]: s1.loc['c':] = 0 In [50]: s1 Out[50]: a 1.431256 b 1.340309 c 0.000000 d 0.000000 e 0.000000 f 0.000000 dtype: float64
使用 DataFrame:
In [51]: df1 = pd.DataFrame(np.random.randn(6, 4), ....: index=list('abcdef'), ....: columns=list('ABCD')) ....: In [52]: df1 Out[52]: 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 [53]: df1.loc[['a', 'b', 'd'], :] Out[53]: 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 [54]: df1.loc['d':, 'A':'C'] Out[54]: 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 [55]: df1.loc['a'] Out[55]: A 0.132003 B -0.827317 C -0.076467 D -1.187678 Name: a, dtype: float64
通过布尔数组获取值:
In [56]: df1.loc['a'] > 0 Out[56]: A True B False C False D False Name: a, dtype: bool In [57]: df1.loc[:, df1.loc['a'] > 0] Out[57]: A a 0.132003 b 1.130127 c 1.024180 d 0.974466 e 0.545952 f -1.281247
布尔数组中的 NA 值会传播为False
:
In [58]: mask = pd.array([True, False, True, False, pd.NA, False], dtype="boolean") In [59]: mask Out[59]: <BooleanArray> [True, False, True, False, <NA>, False] Length: 6, dtype: boolean In [60]: df1[mask] Out[60]: A B C D a 0.132003 -0.827317 -0.076467 -1.187678 c 1.024180 0.569605 0.875906 -2.211372
明确获取一个值:
# this is also equivalent to ``df1.at['a','A']`` In [61]: df1.loc['a', 'A'] Out[61]: 0.13200317033032932
使用标签切片
使用切片时,如果索引中同时存在起始和停止标签,则返回介于两者之间(包括它们)的元素:
In [62]: s = pd.Series(list('abcde'), index=[0, 3, 2, 5, 4]) In [63]: s.loc[3:5] Out[63]: 3 b 2 c 5 d dtype: object
如果两者中至少有一个缺失,但索引已排序,并且可以与起始和停止标签进行比较,则切片仍将按预期工作,通过选择介于两者之间的标签:
In [64]: s.sort_index() Out[64]: 0 a 2 c 3 b 4 e 5 d dtype: object In [65]: s.sort_index().loc[1:6] Out[65]: 2 c 3 b 4 e 5 d dtype: object
但是,如果两者中至少有一个缺失且索引未排序,则会引发错误(因为否则会在计算上昂贵,以及对于混合类型索引可能会产生歧义)。例如,在上面的示例中,s.loc[1:6]
将引发KeyError
。
有关此行为背后的原理,请参见端点是包容的。
In [66]: s = pd.Series(list('abcdef'), index=[0, 3, 2, 5, 4, 2]) In [67]: s.loc[3:5] Out[67]: 3 b 2 c 5 d dtype: object
此外,如果索引具有重复标签且起始或停止标签重复,则会引发错误。例如,在上面的示例中,s.loc[2:5]
将引发KeyError
。
有关重复标签的更多信息,请参见重复标签。### 使用标签切片
使用切片时,如果索引中同时存在起始和停止标签,则返回介于两者之间(包括它们)的元素:
In [62]: s = pd.Series(list('abcde'), index=[0, 3, 2, 5, 4]) In [63]: s.loc[3:5] Out[63]: 3 b 2 c 5 d dtype: object
如果两者中至少有一个缺失,但索引已排序,并且可以与起始和停止标签进行比较,则切片仍将按预期工作,通过选择介于两者之间的标签:
In [64]: s.sort_index() Out[64]: 0 a 2 c 3 b 4 e 5 d dtype: object In [65]: s.sort_index().loc[1:6] Out[65]: 2 c 3 b 4 e 5 d dtype: object
但是,如果两者中至少有一个缺失且索引未排序,则会引发错误(因为否则会在计算上昂贵,以及对于混合类型索引可能会产生歧义)。例如,在上面的示例中,s.loc[1:6]
会引发KeyError
。
有关此行为背后的原理,请参见端点是包含的。
In [66]: s = pd.Series(list('abcdef'), index=[0, 3, 2, 5, 4, 2]) In [67]: s.loc[3:5] Out[67]: 3 b 2 c 5 d dtype: object
此外,如果索引具有重复标签且开始或停止标签重复,则会引发错误。例如,在上面的示例中,s.loc[2:5]
会引发KeyError
。
有关重复标签的更多信息,请参见重复标签。
按位置选择
警告
对于设置操作,返回副本还是引用可能取决于上下文。有时被称为链式赋值
,应该避免。请参见返回视图与副本。
pandas 提供了一套方法,以便获得纯整数索引。语义紧随 Python 和 NumPy 的切片。这些是基于 0 的
索引。在切片时,起始边界是包含的,而上限是不包含的。尝试使用非整数,即使是有效标签也会引发IndexError
。
.iloc
属性是主要访问方法。以下是有效的输入:
- 一个整数例如
5
。 - 一个整数数组或列表
[4, 3, 0]
。 - 一个带有整数
1:7
的切片对象。 - 一个布尔数组。
- 一个
callable
,请参见通过可调用进行选择。 - 一个行(和列)索引的元组,其元素是上述类型之一。
In [68]: s1 = pd.Series(np.random.randn(5), index=list(range(0, 10, 2))) In [69]: s1 Out[69]: 0 0.695775 2 0.341734 4 0.959726 6 -1.110336 8 -0.619976 dtype: float64 In [70]: s1.iloc[:3] Out[70]: 0 0.695775 2 0.341734 4 0.959726 dtype: float64 In [71]: s1.iloc[3] Out[71]: -1.110336102891167
请注意,设置也可以:
In [72]: s1.iloc[:3] = 0 In [73]: s1 Out[73]: 0 0.000000 2 0.000000 4 0.000000 6 -1.110336 8 -0.619976 dtype: float64
与 DataFrame 一起:
In [74]: df1 = pd.DataFrame(np.random.randn(6, 4), ....: index=list(range(0, 12, 2)), ....: columns=list(range(0, 8, 2))) ....: In [75]: df1 Out[75]: 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 [76]: df1.iloc[:3] Out[76]: 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 [77]: df1.iloc[1:5, 2:4] Out[77]: 4 6 2 0.301624 -2.179861 4 1.462696 -1.743161 6 1.314232 0.690579 8 0.014871 3.357427
通过整数列表选择:
In [78]: df1.iloc[[1, 3, 5], [1, 3]] Out[78]: 2 6 2 -0.154951 -2.179861 6 -0.345352 0.690579 10 -1.236269 -0.487602
In [79]: df1.iloc[1:3, :] Out[79]: 0 2 4 6 2 0.403310 -0.154951 0.301624 -2.179861 4 -1.369849 -0.954208 1.462696 -1.743161
In [80]: df1.iloc[:, 1:3] Out[80]: 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
# this is also equivalent to ``df1.iat[1,1]`` In [81]: df1.iloc[1, 1] Out[81]: -0.1549507744249032
要使用整数位置获取交叉部分(等同于df.xs(1)
):
In [82]: df1.iloc[1] Out[82]: 0 0.403310 2 -0.154951 4 0.301624 6 -2.179861 Name: 2, dtype: float64
超出范围的切片索引会像在 Python/NumPy 中一样得到很好的处理。
# these are allowed in Python/NumPy. In [83]: x = list('abcdef') In [84]: x Out[84]: ['a', 'b', 'c', 'd', 'e', 'f'] In [85]: x[4:10] Out[85]: ['e', 'f'] In [86]: x[8:10] Out[86]: [] In [87]: s = pd.Series(x) In [88]: s Out[88]: 0 a 1 b 2 c 3 d 4 e 5 f dtype: object In [89]: s.iloc[4:10] Out[89]: 4 e 5 f dtype: object In [90]: s.iloc[8:10] Out[90]: Series([], dtype: object)
请注意,使用超出范围的切片可能导致一个空轴(例如返回一个空的 DataFrame)。
In [91]: dfl = pd.DataFrame(np.random.randn(5, 2), columns=list('AB')) In [92]: dfl Out[92]: 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 [93]: dfl.iloc[:, 2:3] Out[93]: Empty DataFrame Columns: [] Index: [0, 1, 2, 3, 4] In [94]: dfl.iloc[:, 1:3] Out[94]: B 0 -2.182937 1 0.084844 2 1.519970 3 0.600178 4 0.132885 In [95]: dfl.iloc[4:6] Out[95]: A B 4 0.27423 0.132885
一个超出范围的单个索引器将引发IndexError
。任何元素超出范围的索引器列表将引发IndexError
。
In [96]: dfl.iloc[[4, 5, 6]] --------------------------------------------------------------------------- IndexError Traceback (most recent call last) File ~/work/pandas/pandas/pandas/core/indexing.py:1714, in _iLocIndexer._get_list_axis(self, key, axis) 1713 try: -> 1714 return self.obj._take_with_is_copy(key, axis=axis) 1715 except IndexError as err: 1716 # re-raise with different error message, e.g. test_getitem_ndarray_3d File ~/work/pandas/pandas/pandas/core/generic.py:4153, in NDFrame._take_with_is_copy(self, indices, axis) 4144 """ 4145 Internal version of the `take` method that sets the `_is_copy` 4146 attribute to keep track of the parent dataframe (using in indexing (...) 4151 See the docstring of `take` for full explanation of the parameters. 4152 """ -> 4153 result = self.take(indices=indices, axis=axis) 4154 # Maybe set copy if we didn't actually change the index. File ~/work/pandas/pandas/pandas/core/generic.py:4133, in NDFrame.take(self, indices, axis, **kwargs) 4129 indices = np.arange( 4130 indices.start, indices.stop, indices.step, dtype=np.intp 4131 ) -> 4133 new_data = self._mgr.take( 4134 indices, 4135 axis=self._get_block_manager_axis(axis), 4136 verify=True, 4137 ) 4138 return self._constructor_from_mgr(new_data, axes=new_data.axes).__finalize__( 4139 self, method="take" 4140 ) File ~/work/pandas/pandas/pandas/core/internals/managers.py:891, in BaseBlockManager.take(self, indexer, axis, verify) 890 n = self.shape[axis] --> 891 indexer = maybe_convert_indices(indexer, n, verify=verify) 893 new_labels = self.axes[axis].take(indexer) File ~/work/pandas/pandas/pandas/core/indexers/utils.py:282, in maybe_convert_indices(indices, n, verify) 281 if mask.any(): --> 282 raise IndexError("indices are out-of-bounds") 283 return indices IndexError: indices are out-of-bounds The above exception was the direct cause of the following exception: IndexError Traceback (most recent call last) Cell In[96], line 1 ----> 1 dfl.iloc[[4, 5, 6]] File ~/work/pandas/pandas/pandas/core/indexing.py:1191, in _LocationIndexer.__getitem__(self, key) 1189 maybe_callable = com.apply_if_callable(key, self.obj) 1190 maybe_callable = self._check_deprecated_callable_usage(key, maybe_callable) -> 1191 return self._getitem_axis(maybe_callable, axis=axis) File ~/work/pandas/pandas/pandas/core/indexing.py:1743, in _iLocIndexer._getitem_axis(self, key, axis) 1741 # a list of integers 1742 elif is_list_like_indexer(key): -> 1743 return self._get_list_axis(key, axis=axis) 1745 # a single integer 1746 else: 1747 key = item_from_zerodim(key) File ~/work/pandas/pandas/pandas/core/indexing.py:1717, in _iLocIndexer._get_list_axis(self, key, axis) 1714 return self.obj._take_with_is_copy(key, axis=axis) 1715 except IndexError as err: 1716 # re-raise with different error message, e.g. test_getitem_ndarray_3d -> 1717 raise IndexError("positional indexers are out-of-bounds") from err IndexError: positional indexers are out-of-bounds
In [97]: dfl.iloc[:, 4] --------------------------------------------------------------------------- IndexError Traceback (most recent call last) Cell In[97], line 1 ----> 1 dfl.iloc[:, 4] File ~/work/pandas/pandas/pandas/core/indexing.py:1184, in _LocationIndexer.__getitem__(self, key) 1182 if self._is_scalar_access(key): 1183 return self.obj._get_value(*key, takeable=self._takeable) -> 1184 return self._getitem_tuple(key) 1185 else: 1186 # we by definition only have the 0th axis 1187 axis = self.axis or 0 File ~/work/pandas/pandas/pandas/core/indexing.py:1690, in _iLocIndexer._getitem_tuple(self, tup) 1689 def _getitem_tuple(self, tup: tuple): -> 1690 tup = self._validate_tuple_indexer(tup) 1691 with suppress(IndexingError): 1692 return self._getitem_lowerdim(tup) File ~/work/pandas/pandas/pandas/core/indexing.py:966, in _LocationIndexer._validate_tuple_indexer(self, key) 964 for i, k in enumerate(key): 965 try: --> 966 self._validate_key(k, i) 967 except ValueError as err: 968 raise ValueError( 969 "Location based indexing can only have " 970 f"[{self._valid_types}] types" 971 ) from err File ~/work/pandas/pandas/pandas/core/indexing.py:1592, in _iLocIndexer._validate_key(self, key, axis) 1590 return 1591 elif is_integer(key): -> 1592 self._validate_integer(key, axis) 1593 elif isinstance(key, tuple): 1594 # a tuple should already have been caught by this point 1595 # so don't treat a tuple as a valid indexer 1596 raise IndexingError("Too many indexers") File ~/work/pandas/pandas/pandas/core/indexing.py:1685, in _iLocIndexer._validate_integer(self, key, axis) 1683 len_axis = len(self.obj._get_axis(axis)) 1684 if key >= len_axis or key < -len_axis: -> 1685 raise IndexError("single positional indexer is out-of-bounds") IndexError: single positional indexer is out-of-bounds
通过可调用进行选择
.loc
、.iloc
,以及[]
索引可以接受一个callable
作为索引器。这个callable
必须是一个带有一个参数(调用的 Series 或 DataFrame)的函数,返回用于索引的有效输出。
注意
对于.iloc
索引,不支持从可调用返回元组,因为在应用可调用之前会发生行和列索引的元组解构。
In [98]: df1 = pd.DataFrame(np.random.randn(6, 4), ....: index=list('abcdef'), ....: columns=list('ABCD')) ....: In [99]: df1 Out[99]: 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 [100]: df1.loc[lambda df: df['A'] > 0, :] Out[100]: A B C D c 0.299368 -0.863838 0.408204 -1.048089 e 1.289997 0.082423 -0.055758 0.536580 In [101]: df1.loc[:, lambda df: ['A', 'B']] Out[101]: 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 [102]: df1.iloc[:, lambda df: [0, 1]] Out[102]: 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 [103]: df1[lambda df: df.columns[0]] Out[103]: a -0.023688 b -0.251905 c 0.299368 d -0.025747 e 1.289997 f -0.489682 Name: A, dtype: float64
您可以在Series
中使用可调用的索引。
In [104]: df1['A'].loc[lambda s: s > 0] Out[104]: c 0.299368 e 1.289997 Name: A, dtype: float64
使用这些方法/索引器,您可以在不使用临时变量的情况下链接数据选择操作。
In [105]: bb = pd.read_csv('data/baseball.csv', index_col='id') In [106]: (bb.groupby(['year', 'team']).sum(numeric_only=True) .....: .loc[lambda df: df['r'] > 100]) .....: Out[106]: stint g ab r h X2b ... so ibb hbp sh sf gidp year team ... 2007 CIN 6 379 745 101 203 35 ... 127.0 14.0 1.0 1.0 15.0 18.0 DET 5 301 1062 162 283 54 ... 176.0 3.0 10.0 4.0 8.0 28.0 HOU 4 311 926 109 218 47 ... 212.0 3.0 9.0 16.0 6.0 17.0 LAN 11 413 1021 153 293 61 ... 141.0 8.0 9.0 3.0 8.0 29.0 NYN 13 622 1854 240 509 101 ... 310.0 24.0 23.0 18.0 15.0 48.0 SFN 5 482 1305 198 337 67 ... 188.0 51.0 8.0 16.0 6.0 41.0 TEX 2 198 729 115 200 40 ... 140.0 4.0 5.0 2.0 8.0 16.0 TOR 4 459 1408 187 378 96 ... 265.0 16.0 12.0 4.0 16.0 38.0 [8 rows x 18 columns]
Pandas 2.2 中文官方教程和指南(十一·二)(2)https://developer.aliyun.com/article/1509870