- 一个 Python 的数据分析库
- 关于 Pandas
- 获取 Pandas
- v0.25.0 版本特性(2019年7月18日)
- 安装
- 快速入门
- Pandas 用户指南目录
- IO工具(文本,CSV,HDF5,…)
- 索引和数据选择器
- 多层级索引和高级索引
- Merge, join, and concatenate
- Reshaping and pivot tables
- Pandas 处理文本字符串
- Working with missing data
- Categorical data
- Nullable 整型数据类型
- Visualization
- Computational tools
- Group By: split-apply-combine
- 时间序列与日期用法
- 时间差
- Styling
- Options and settings
- Enhancing performance
- Sparse data structures
- Frequently Asked Questions (FAQ)
- 烹饪指南
- Pandas 生态圈
- API 参考手册
- 开发者文档
- 发布日志
多层级索引和高级索引
请参阅 索引与选择数据来获得更多的通用索引方面的帮助文档
警告
基于实际的使用场景不同,返回的内容也会不尽相同(返回一个数据的副本,或者返回数据的引用)。有时,这种情况被称作连锁赋值,但是这种情况应当被尽力避免。参见返回视图or返回副本。
参见 cookbook,获取更多高级的使用技巧。
分层索引(多层级索引)
分层/多级索引在处理复杂的数据分析和数据操作方面为开发者奠定了基础,尤其是在处理高纬度数据处理上。本质上,它使您能够在较低维度的数据结构(如 Series
(1d)和DataFrame
(2d))中存储和操作任意维数的数据。
在本节中,我们将展示“层次”索引的确切含义,以及它如何与上面和前面部分描述的所有panda索引功能集成。稍后,在讨论分组 和数据透视与重塑性数据时,我们将展示一些重要的应用程序,以说明它如何帮助构建分析数据的结构。
请参阅cookbook,查看一些高级策略.
在0.24.0版本中的改变:MultIndex.labels
被更名为MultiIndex.codes
open in new window ,同时 MultiIndex.set_labels
更名为 MultiIndex.set_codes
open in new window.
创建多级索引和分层索引对象
MultiIndex
open in new window 对象是标准索引对象
open in new window 的分层模拟,标准索引对象通常将axis标签存储在panda对象中。您可以将MultiIndex
看作一个元组数组,其中每个元组都是惟一的。可以从数组列表(使用 MultiIndex.from_arrays()
open in new window)、元组数组(使用MultiIndex.from_tuples()
open in new window)或交叉迭代器集(使用MultiIndex.from_product()
open in new window)或者将一个 DataFrame
open in new window(使用using MultiIndex.from_frame()
open in new window)创建多索引。当传递一个元组列表时,索引
构造函数将尝试返回一个MultiIndex
。下面的示例演示了初始化多索引的不同方法。
In [1]: arrays = [['bar', 'bar', 'baz', 'baz', 'foo', 'foo', 'qux', 'qux'],
...: ['one', 'two', 'one', 'two', 'one', 'two', 'one', 'two']]
...:
In [2]: tuples = list(zip(*arrays))
In [3]: tuples
Out[3]:
[('bar', 'one'),
('bar', 'two'),
('baz', 'one'),
('baz', 'two'),
('foo', 'one'),
('foo', 'two'),
('qux', 'one'),
('qux', 'two')]
In [4]: index = pd.MultiIndex.from_tuples(tuples, names=['first', 'second'])
In [5]: index
Out[5]:
MultiIndex([('bar', 'one'),
('bar', 'two'),
('baz', 'one'),
('baz', 'two'),
('foo', 'one'),
('foo', 'two'),
('qux', 'one'),
('qux', 'two')],
names=['first', 'second'])
In [6]: s = pd.Series(np.random.randn(8), index=index)
In [7]: s
Out[7]:
first second
bar one 0.469112
two -0.282863
baz one -1.509059
two -1.135632
foo one 1.212112
two -0.173215
qux one 0.119209
two -1.044236
dtype: float64
当您想要在两个迭代器中对每个元素进行配对时,可以更容易地使用 MultiIndex.from_product()
open in new window方法:
In [8]: iterables = [['bar', 'baz', 'foo', 'qux'], ['one', 'two']]
In [9]: pd.MultiIndex.from_product(iterables, names=['first', 'second'])
Out[9]:
MultiIndex([('bar', 'one'),
('bar', 'two'),
('baz', 'one'),
('baz', 'two'),
('foo', 'one'),
('foo', 'two'),
('qux', 'one'),
('qux', 'two')],
names=['first', 'second'])
还可以使用 MultiIndex.from_frame()
open in new window方法直接将一个DataFrame
对象构造一个多索引
。这是MultiIndex.to_frame()
open in new window的一个补充方法。
0.24.0版本新增。
In [10]: df = pd.DataFrame([['bar', 'one'], ['bar', 'two'],
....: ['foo', 'one'], ['foo', 'two']],
....: columns=['first', 'second'])
....:
In [11]: pd.MultiIndex.from_frame(df)
Out[11]:
MultiIndex([('bar', 'one'),
('bar', 'two'),
('foo', 'one'),
('foo', 'two')],
names=['first', 'second'])
为了方便,您可以将数组列表直接传递到Series
或DataFrame
中,从而自动构造一个MultiIndex
:
In [12]: arrays = [np.array(['bar', 'bar', 'baz', 'baz', 'foo', 'foo', 'qux', 'qux']),
....: np.array(['one', 'two', 'one', 'two', 'one', 'two', 'one', 'two'])]
....:
In [13]: s = pd.Series(np.random.randn(8), index=arrays)
In [14]: s
Out[14]:
bar one -0.861849
two -2.104569
baz one -0.494929
two 1.071804
foo one 0.721555
two -0.706771
qux one -1.039575
two 0.271860
dtype: float64
In [15]: df = pd.DataFrame(np.random.randn(8, 4), index=arrays)
In [16]: df
Out[16]:
0 1 2 3
bar one -0.424972 0.567020 0.276232 -1.087401
two -0.673690 0.113648 -1.478427 0.524988
baz one 0.404705 0.577046 -1.715002 -1.039268
two -0.370647 -1.157892 -1.344312 0.844885
foo one 1.075770 -0.109050 1.643563 -1.469388
two 0.357021 -0.674600 -1.776904 -0.968914
qux one -1.294524 0.413738 0.276662 -0.472035
two -0.013960 -0.362543 -0.006154 -0.923061
所MultiIndex
构造函数都接受names
参数,该参数存储级别本身的字符串名称。如果没有提供name
属性,将分配None
:
In [17]: df.index.names
Out[17]: FrozenList([None, None])
此索引可以支持panda对象的任何轴,索引的级别由开发者决定:
In [18]: df = pd.DataFrame(np.random.randn(3, 8), index=['A', 'B', 'C'], columns=index)
In [19]: df
Out[19]:
first bar baz foo qux
second one two one two one two one two
A 0.895717 0.805244 -1.206412 2.565646 1.431256 1.340309 -1.170299 -0.226169
B 0.410835 0.813850 0.132003 -0.827317 -0.076467 -1.187678 1.130127 -1.436737
C -1.413681 1.607920 1.024180 0.569605 0.875906 -2.211372 0.974466 -2.006747
In [20]: pd.DataFrame(np.random.randn(6, 6), index=index[:6], columns=index[:6])
Out[20]:
first bar baz foo
second one two one two one two
first second
bar one -0.410001 -0.078638 0.545952 -1.219217 -1.226825 0.769804
two -1.281247 -0.727707 -0.121306 -0.097883 0.695775 0.341734
baz one 0.959726 -1.110336 -0.619976 0.149748 -0.732339 0.687738
two 0.176444 0.403310 -0.154951 0.301624 -2.179861 -1.369849
foo one -0.954208 1.462696 -1.743161 -0.826591 -0.345352 1.314232
two 0.690579 0.995761 2.396780 0.014871 3.357427 -0.317441
我们已经“稀疏化”了更高级别的索引,使控制台的输出更容易显示。注意,可以使用pandas.set_options()
中的multi_sparse
选项控制索引的显示方式:
In [21]: with pd.option_context('display.multi_sparse', False):
....: df
....:
值得记住的是,没有什么可以阻止您使用元组作为轴上的原子标签:
In [22]: pd.Series(np.random.randn(8), index=tuples)
Out[22]:
(bar, one) -1.236269
(bar, two) 0.896171
(baz, one) -0.487602
(baz, two) -0.082240
(foo, one) -2.182937
(foo, two) 0.380396
(qux, one) 0.084844
(qux, two) 0.432390
dtype: float64
MultiIndex
之所以重要,是因为它允许您进行分组、选择和重新构造操作,我们将在下面的文档和后续部分中进行描述。正如您将在后面的部分中看到的,您可以发现自己使用分层索引的数据,而不需要显式地创建一个MultiIndex
。然而,当从文件中加载数据时,您可能希望在准备数据集时生成自己的MultiIndex
。
重构层次标签
get_level_values()
open in new window方法将返回特定级别每个位置的标签向量:
In [23]: index.get_level_values(0)
Out[23]: Index(['bar', 'bar', 'baz', 'baz', 'foo', 'foo', 'qux', 'qux'], dtype='object', name='first')
In [24]: index.get_level_values('second')
Out[24]: Index(['one', 'two', 'one', 'two', 'one', 'two', 'one', 'two'], dtype='object', name='second')
基本索引轴上的多索引
层次索引的一个重要特性是,您可以通过partial
标签来选择数据,该标签标识数据中的子组。局部 选择“降低”层次索引的级别,其结果完全类似于在常规数据Dataframe中选择列:
In [25]: df['bar']
Out[25]:
second one two
A 0.895717 0.805244
B 0.410835 0.813850
C -1.413681 1.607920
In [26]: df['bar', 'one']
Out[26]:
A 0.895717
B 0.410835
C -1.413681
Name: (bar, one), dtype: float64
In [27]: df['bar']['one']
Out[27]:
A 0.895717
B 0.410835
C -1.413681
Name: one, dtype: float64
In [28]: s['qux']
Out[28]:
one -1.039575
two 0.271860
dtype: float64
有关如何在更深层次上进行选择,请参见具有层次索引的横切。
定义不同层次索引
MultiIndex
open in new window 保存了所有被定义的索引层级,即使它们实际上没有被使用。在切割索引时,您可能会注意到这一点。例如:
In [29]: df.columns.levels # original MultiIndex
Out[29]: FrozenList([['bar', 'baz', 'foo', 'qux'], ['one', 'two']])
In [30]: df[['foo','qux']].columns.levels # sliced
Out[30]: FrozenList([['bar', 'baz', 'foo', 'qux'], ['one', 'two']])
这样做是为了避免重新计算级别,从而使切片具有很高的性能。如果只想查看使用的级别,可以使用remove_unused_levels()
open in new window方法。
In [31]: df[['foo', 'qux']].columns.to_numpy()
Out[31]:
array([('foo', 'one'), ('foo', 'two'), ('qux', 'one'), ('qux', 'two')],
dtype=object)
# for a specific level
In [32]: df[['foo', 'qux']].columns.get_level_values(0)
Out[32]: Index(['foo', 'foo', 'qux', 'qux'], dtype='object', name='first')
若要仅使用已使用的级别来重构MultiIndex
,可以使用remove_unused_levels()
open in new window方法。
0.20.0.中更新。
In [33]: new_mi = df[['foo', 'qux']].columns.remove_unused_levels()
In [34]: new_mi.levels
Out[34]: FrozenList([['foo', 'qux'], ['one', 'two']])
数据对齐和使用 reindex
在轴上具有MultiIndex
的不同索引对象之间的操作将如您所期望的那样工作;数据对齐的工作原理与元组索引相同:
In [35]: s + s[:-2]
Out[35]:
bar one -1.723698
two -4.209138
baz one -0.989859
two 2.143608
foo one 1.443110
two -1.413542
qux one NaN
two NaN
dtype: float64
In [36]: s + s[::2]
Out[36]:
bar one -1.723698
two NaN
baz one -0.989859
two NaN
foo one 1.443110
two NaN
qux one -2.079150
two NaN
dtype: float64
Series/DataFrames
对象的 reindex()
open in new window 方法可以调用另一个MultiIndex
,甚至一个列表或数组元组:
In [37]: s.reindex(index[:3])
Out[37]:
first second
bar one -0.861849
two -2.104569
baz one -0.494929
dtype: float64
In [38]: s.reindex([('foo', 'two'), ('bar', 'one'), ('qux', 'one'), ('baz', 'one')])
Out[38]:
foo two -0.706771
bar one -0.861849
qux one -1.039575
baz one -0.494929
dtype: float64
具有层次索引的高级索引方法
语法上,使用.loc
方法,在高级索引中加入 MultiIndex
(多层索引)是有一些挑战的,但是我们一直在尽己所能地去实现这个功能。简单来说,多层索引的索引键(keys)来自元组的格式。例如,下列代码将会按照你的期望工作:
In [39]: df = df.T
In [40]: df
Out[40]:
A B C
first second
bar one 0.895717 0.410835 -1.413681
two 0.805244 0.813850 1.607920
baz one -1.206412 0.132003 1.024180
two 2.565646 -0.827317 0.569605
foo one 1.431256 -0.076467 0.875906
two 1.340309 -1.187678 -2.211372
qux one -1.170299 1.130127 0.974466
two -0.226169 -1.436737 -2.006747
In [41]: df.loc[('bar', 'two')]
Out[41]:
A 0.805244
B 0.813850
C 1.607920
Name: (bar, two), dtype: float64
注意 df.loc['bar', 'two']
也将会在这个用例中正常工作,但是这种便捷的简写方法总的来说是容易产生歧义的。
如果你也希望使用 .loc
对某个特定的列进行索引,你需要使用如下的元组样式:
In [42]: df.loc[('bar', 'two'), 'A']
Out[42]: 0.8052440253863785
你可以只输入元组的第一个元素,而不需要写出所有的多级索引的每一个层级。例如,你可以使用“局部”索引,来获得所有在第一层为bar
的元素,参见下例:
df.loc[‘bar’]
这种方式是对于更为冗长的方式df.loc[('bar',),]
的一个简写(在本例中,等同于df.loc['bar',]
)
您也可以类似地使用“局部”切片。
In [43]: df.loc['baz':'foo']
Out[43]:
A B C
first second
baz one -1.206412 0.132003 1.024180
two 2.565646 -0.827317 0.569605
foo one 1.431256 -0.076467 0.875906
two 1.340309 -1.187678 -2.211372
您可以通过使用一个元组的切片,提供一个值的范围(a ‘range’ of values),来进行切片
In [44]: df.loc[('baz', 'two'):('qux', 'one')]
Out[44]:
A B C
first second
baz two 2.565646 -0.827317 0.569605
foo one 1.431256 -0.076467 0.875906
two 1.340309 -1.187678 -2.211372
qux one -1.170299 1.130127 0.974466
In [45]: df.loc[('baz', 'two'):'foo']
Out[45]:
A B C
first second
baz two 2.565646 -0.827317 0.569605
foo one 1.431256 -0.076467 0.875906
two 1.340309 -1.187678 -2.211372
类似于重命名索引(reindexing),您可以通过输入一个标签的元组来实现:
In [46]: df.loc[[('bar', 'two'), ('qux', 'one')]]
Out[46]:
A B C
first second
bar two 0.805244 0.813850 1.607920
qux one -1.170299 1.130127 0.974466
小技巧
在pandas中,元组和列表,在索引时,是有区别的。一个元组会被识别为一个多层级的索引值(key),而列表被用于表明多个不同的索引值(several keys)。换句话说,元组是按照横向展开的,即水平层级(trasvering levels),而列表是纵向的,即扫描层级(scanning levels)。
注意,一个元组构成的列表提供的是完整的多级索引,而一个列表构成的元组提供的是同一个级别中的多个值:
In [47]: s = pd.Series([1, 2, 3, 4, 5, 6],
....: index=pd.MultiIndex.from_product([["A", "B"], ["c", "d", "e"]]))
....:
In [48]: s.loc[[("A", "c"), ("B", "d")]] # list of tuples
Out[48]:
A c 1
B d 5
dtype: int64
In [49]: s.loc[(["A", "B"], ["c", "d"])] # tuple of lists
Out[49]:
A c 1
d 2
B c 4
d 5
dtype: int64
使用切片器
你可以使用多级索引器来切片一个``多级索引
你可以提供任意的选择器,就仿佛你按照标签索引一样,参见按照标签索引, 包含切片,标签构成的列表,标签,和布尔值索引器。
你可以使用slice(None)
来选择所有的该级别的内容。你不需要指明所有的深层级别,他们将按照slice(None)
的方式来做默认推测。
一如既往,切片器的两侧都会被包含进来,因为这是按照标签索引的方式进行的。
警告
你需要在.loc
中声明所有的维度,这意味着同时包含行索引以及列索引。在一些情况下,索引器中的数据有可能会被错误地识别为在两个维度同时进行索引,而不是只对行进行多层级索引。
建议使用下列的方式:
df.loc[(slice('A1', 'A3'), ...), :] # noqa: E999
不建议使用下列的方式:
df.loc[(slice('A1', 'A3'), ...)] # noqa: E999
In [50]: def mklbl(prefix, n):
....: return ["%s%s" % (prefix, i) for i in range(n)]
....:
In [51]: miindex = pd.MultiIndex.from_product([mklbl('A', 4),
....: mklbl('B', 2),
....: mklbl('C', 4),
....: mklbl('D', 2)])
....:
In [52]: micolumns = pd.MultiIndex.from_tuples([('a', 'foo'), ('a', 'bar'),
....: ('b', 'foo'), ('b', 'bah')],
....: names=['lvl0', 'lvl1'])
....:
In [53]: dfmi = pd.DataFrame(np.arange(len(miindex) * len(micolumns))
....: .reshape((len(miindex), len(micolumns))),
....: index=miindex,
....: columns=micolumns).sort_index().sort_index(axis=1)
....:
In [54]: dfmi
Out[54]:
lvl0 a b
lvl1 bar foo bah foo
A0 B0 C0 D0 1 0 3 2
D1 5 4 7 6
C1 D0 9 8 11 10
D1 13 12 15 14
C2 D0 17 16 19 18
... ... ... ... ...
A3 B1 C1 D1 237 236 239 238
C2 D0 241 240 243 242
D1 245 244 247 246
C3 D0 249 248 251 250
D1 253 252 255 254
[64 rows x 4 columns]
使用切片,列表和标签来进行简单的多层级切片
In [55]: dfmi.loc[(slice('A1', 'A3'), slice(None), ['C1', 'C3']), :]
Out[55]:
lvl0 a b
lvl1 bar foo bah foo
A1 B0 C1 D0 73 72 75 74
D1 77 76 79 78
C3 D0 89 88 91 90
D1 93 92 95 94
B1 C1 D0 105 104 107 106
... ... ... ... ...
A3 B0 C3 D1 221 220 223 222
B1 C1 D0 233 232 235 234
D1 237 236 239 238
C3 D0 249 248 251 250
D1 253 252 255 254
[24 rows x 4 columns]
你可以使用 pandas.IndexSlice
open in new window,即使用:
,一个更为符合习惯的语法,而不是使用slice(None)。
In [56]: idx = pd.IndexSlice
In [57]: dfmi.loc[idx[:, :, ['C1', 'C3']], idx[:, 'foo']]
Out[57]:
lvl0 a b
lvl1 foo foo
A0 B0 C1 D0 8 10
D1 12 14
C3 D0 24 26
D1 28 30
B1 C1 D0 40 42
... ... ...
A3 B0 C3 D1 220 222
B1 C1 D0 232 234
D1 236 238
C3 D0 248 250
D1 252 254
[32 rows x 2 columns]
您可以使用这种方法在两个维度上同时实现非常复杂的选择。
In [58]: dfmi.loc['A1', (slice(None), 'foo')]
Out[58]:
lvl0 a b
lvl1 foo foo
B0 C0 D0 64 66
D1 68 70
C1 D0 72 74
D1 76 78
C2 D0 80 82
... ... ...
B1 C1 D1 108 110
C2 D0 112 114
D1 116 118
C3 D0 120 122
D1 124 126
[16 rows x 2 columns]
In [59]: dfmi.loc[idx[:, :, ['C1', 'C3']], idx[:, 'foo']]
Out[59]:
lvl0 a b
lvl1 foo foo
A0 B0 C1 D0 8 10
D1 12 14
C3 D0 24 26
D1 28 30
B1 C1 D0 40 42
... ... ...
A3 B0 C3 D1 220 222
B1 C1 D0 232 234
D1 236 238
C3 D0 248 250
D1 252 254
[32 rows x 2 columns]
使用布尔索引器,您可以对数值进行选择。
In [60]: mask = dfmi[('a', 'foo')] > 200
In [61]: dfmi.loc[idx[mask, :, ['C1', 'C3']], idx[:, 'foo']]
Out[61]:
lvl0 a b
lvl1 foo foo
A3 B0 C1 D1 204 206
C3 D0 216 218
D1 220 222
B1 C1 D0 232 234
D1 236 238
C3 D0 248 250
D1 252 254
您也可以使用.loc
来明确您所希望的维度
(axis
),从而只在一个维度上来进行切片。
In [62]: dfmi.loc(axis=0)[:, :, ['C1', 'C3']]
Out[62]:
lvl0 a b
lvl1 bar foo bah foo
A0 B0 C1 D0 9 8 11 10
D1 13 12 15 14
C3 D0 25 24 27 26
D1 29 28 31 30
B1 C1 D0 41 40 43 42
... ... ... ... ...
A3 B0 C3 D1 221 220 223 222
B1 C1 D0 233 232 235 234
D1 237 236 239 238
C3 D0 249 248 251 250
D1 253 252 255 254
[32 rows x 4 columns]
进一步,您可以使用下列的方式来赋值
In [63]: df2 = dfmi.copy()
In [64]: df2.loc(axis=0)[:, :, ['C1', 'C3']] = -10
In [65]: df2
Out[65]:
lvl0 a b
lvl1 bar foo bah foo
A0 B0 C0 D0 1 0 3 2
D1 5 4 7 6
C1 D0 -10 -10 -10 -10
D1 -10 -10 -10 -10
C2 D0 17 16 19 18
... ... ... ... ...
A3 B1 C1 D1 -10 -10 -10 -10
C2 D0 241 240 243 242
D1 245 244 247 246
C3 D0 -10 -10 -10 -10
D1 -10 -10 -10 -10
[64 rows x 4 columns]
您也可以在等号的右侧使用一个可以被“重命名”的对象来赋值
In [66]: df2 = dfmi.copy()
In [67]: df2.loc[idx[:, :, ['C1', 'C3']], :] = df2 * 1000
In [68]: df2
Out[68]:
lvl0 a b
lvl1 bar foo bah foo
A0 B0 C0 D0 1 0 3 2
D1 5 4 7 6
C1 D0 9000 8000 11000 10000
D1 13000 12000 15000 14000
C2 D0 17 16 19 18
... ... ... ... ...
A3 B1 C1 D1 237000 236000 239000 238000
C2 D0 241 240 243 242
D1 245 244 247 246
C3 D0 249000 248000 251000 250000
D1 253000 252000 255000 254000
[64 rows x 4 columns]
交叉选择
DataFrame
的 xs()
open in new window方法接受一个额外的参数,从而可以简便地在某个特定的多级索引
中的某一个层级进行数据的选取。
In [69]: df
Out[69]:
A B C
first second
bar one 0.895717 0.410835 -1.413681
two 0.805244 0.813850 1.607920
baz one -1.206412 0.132003 1.024180
two 2.565646 -0.827317 0.569605
foo one 1.431256 -0.076467 0.875906
two 1.340309 -1.187678 -2.211372
qux one -1.170299 1.130127 0.974466
two -0.226169 -1.436737 -2.006747
In [70]: df.xs('one', level='second')
Out[70]:
A B C
first
bar 0.895717 0.410835 -1.413681
baz -1.206412 0.132003 1.024180
foo 1.431256 -0.076467 0.875906
qux -1.170299 1.130127 0.974466
# using the slicers
In [71]: df.loc[(slice(None), 'one'), :]
Out[71]:
A B C
first second
bar one 0.895717 0.410835 -1.413681
baz one -1.206412 0.132003 1.024180
foo one 1.431256 -0.076467 0.875906
qux one -1.170299 1.130127 0.974466
您也可以用xs()
并填写坐标参数来选择列。
In [72]: df = df.T
In [73]: df.xs('one', level='second', axis=1)
Out[73]:
first bar baz foo qux
A 0.895717 -1.206412 1.431256 -1.170299
B 0.410835 0.132003 -0.076467 1.130127
C -1.413681 1.024180 0.875906 0.974466
# using the slicers
In [74]: df.loc[:, (slice(None), 'one')]
Out[74]:
first bar baz foo qux
second one one one one
A 0.895717 -1.206412 1.431256 -1.170299
B 0.410835 0.132003 -0.076467 1.130127
C -1.413681 1.024180 0.875906 0.974466
xs
也接受多个键(keys)来进行选取
In [75]: df.xs(('one', 'bar'), level=('second', 'first'), axis=1)
Out[75]:
first bar
second one
A 0.895717
B 0.410835
C -1.413681
# using the slicers
In [76]: df.loc[:, ('bar', 'one')]
Out[76]:
A 0.895717
B 0.410835
C -1.413681
Name: (bar, one), dtype: float64
您可以向xs
传入 drop_level=False
来保留那些已经选取的层级。
In [77]: df.xs('one', level='second', axis=1, drop_level=False)
Out[77]:
first bar baz foo qux
second one one one one
A 0.895717 -1.206412 1.431256 -1.170299
B 0.410835 0.132003 -0.076467 1.130127
C -1.413681 1.024180 0.875906 0.974466
请比较上面,使用 drop_level=True
(默认值)的结果。
In [78]: df.xs('one', level='second', axis=1, drop_level=True)
Out[78]:
first bar baz foo qux
A 0.895717 -1.206412 1.431256 -1.170299
B 0.410835 0.132003 -0.076467 1.130127
C -1.413681 1.024180 0.875906 0.974466
高级重命名索引及对齐
level
参数已经被加入到pandas对象中的 reindex()
open in new window 和 align()
open in new window 方法中。这将有助于沿着一个层级来广播值(broadcast values)。例如:
In [79]: midx = pd.MultiIndex(levels=[['zero', 'one'], ['x', 'y']],
....: codes=[[1, 1, 0, 0], [1, 0, 1, 0]])
....:
In [80]: df = pd.DataFrame(np.random.randn(4, 2), index=midx)
In [81]: df
Out[81]:
0 1
one y 1.519970 -0.493662
x 0.600178 0.274230
zero y 0.132885 -0.023688
x 2.410179 1.450520
In [82]: df2 = df.mean(level=0)
In [83]: df2
Out[83]:
0 1
one 1.060074 -0.109716
zero 1.271532 0.713416
In [84]: df2.reindex(df.index, level=0)
Out[84]:
0 1
one y 1.060074 -0.109716
x 1.060074 -0.109716
zero y 1.271532 0.713416
x 1.271532 0.713416
# aligning
In [85]: df_aligned, df2_aligned = df.align(df2, level=0)
In [86]: df_aligned
Out[86]:
0 1
one y 1.519970 -0.493662
x 0.600178 0.274230
zero y 0.132885 -0.023688
x 2.410179 1.450520
In [87]: df2_aligned
Out[87]:
0 1
one y 1.060074 -0.109716
x 1.060074 -0.109716
zero y 1.271532 0.713416
x 1.271532 0.713416
使用swaplevel
来交换层级
swaplevel()
open in new window函数可以用来交换两个层级
In [88]: df[:5]
Out[88]:
0 1
one y 1.519970 -0.493662
x 0.600178 0.274230
zero y 0.132885 -0.023688
x 2.410179 1.450520
In [89]: df[:5].swaplevel(0, 1, axis=0)
Out[89]:
0 1
y one 1.519970 -0.493662
x one 0.600178 0.274230
y zero 0.132885 -0.023688
x zero 2.410179 1.450520
使用reorder_levels
来进行层级重排序
reorder_levels()
open in new window是一个更一般化的 swaplevel
方法,允许您用简单的一步来重排列索引的层级:
In [90]: df[:5].reorder_levels([1, 0], axis=0)
Out[90]:
0 1
y one 1.519970 -0.493662
x one 0.600178 0.274230
y zero 0.132885 -0.023688
x zero 2.410179 1.450520
对索引
和多层索
引进行重命名
rename()
open in new window方法可以用来重命名多层索引
,并且他经常被用于DataFrame
的列名重命名。renames
的columns
参数可以接受一个字典,从而仅仅重命名你希望更改名字的列。
In [91]: df.rename(columns={0: "col0", 1: "col1"})
Out[91]:
col0 col1
one y 1.519970 -0.493662
x 0.600178 0.274230
zero y 0.132885 -0.023688
x 2.410179 1.450520
该方法也可以被用于重命名一些DataFrame
的特定主索引的名称。
In [92]: df.rename(index={"one": "two", "y": "z"})
Out[92]:
0 1
two z 1.519970 -0.493662
x 0.600178 0.274230
zero z 0.132885 -0.023688
x 2.410179 1.450520
rename_axis()
open in new window 方法可以用于对Index
或者 MultiIndex
进行重命名。尤其的,你可以明确MultiIndex
中的不同层级的名称,这可以被用于在之后使用 reset_index()
,把多层级索引
的值转换为一个列
In [93]: df.rename_axis(index=['abc', 'def'])
Out[93]:
0 1
abc def
one y 1.519970 -0.493662
x 0.600178 0.274230
zero y 0.132885 -0.023688
x 2.410179 1.450520
注意,DataFrame
的列也是一个索引,因此在rename_axis
中使用 columns
参数,将会改变那个索引的名称
In [94]: df.rename_axis(columns="Cols").columns
Out[94]: RangeIndex(start=0, stop=2, step=1, name='Cols')
rename
和rename_axis
都支持一个明确的字典,Series
或者一个映射函数,将标签,名称映射为新的值
对多索引
进行排序
对于拥有多层级索引的对象来说,你可以通过排序来是的索引或切片更为高效。就如同其他任何的索引操作一样,你可以使用 sort_index
方法来实现。
In [95]: import random
In [96]: random.shuffle(tuples)
In [97]: s = pd.Series(np.random.randn(8), index=pd.MultiIndex.from_tuples(tuples))
In [98]: s
Out[98]:
baz one 0.206053
foo two -0.251905
qux one -2.213588
foo one 1.063327
bar two 1.266143
baz two 0.299368
bar one -0.863838
qux two 0.408204
dtype: float64
In [99]: s.sort_index()
Out[99]:
bar one -0.863838
two 1.266143
baz one 0.206053
two 0.299368
foo one 1.063327
two -0.251905
qux one -2.213588
two 0.408204
dtype: float64
In [100]: s.sort_index(level=0)
Out[100]:
bar one -0.863838
two 1.266143
baz one 0.206053
two 0.299368
foo one 1.063327
two -0.251905
qux one -2.213588
two 0.408204
dtype: float64
In [101]: s.sort_index(level=1)
Out[101]:
bar one -0.863838
baz one 0.206053
foo one 1.063327
qux one -2.213588
bar two 1.266143
baz two 0.299368
foo two -0.251905
qux two 0.408204
dtype: float64
如果你的多层级索引
都被命名了的话,你也可以向 sort_index
传入一个层级名称。
In [102]: s.index.set_names(['L1', 'L2'], inplace=True)
In [103]: s.sort_index(level='L1')
Out[103]:
L1 L2
bar one -0.863838
two 1.266143
baz one 0.206053
two 0.299368
foo one 1.063327
two -0.251905
qux one -2.213588
two 0.408204
dtype: float64
In [104]: s.sort_index(level='L2')
Out[104]:
L1 L2
bar one -0.863838
baz one 0.206053
foo one 1.063327
qux one -2.213588
bar two 1.266143
baz two 0.299368
foo two -0.251905
qux two 0.408204
dtype: float64
对于多维度的对象来说,你也可以对任意的的维度来进行索引,只要他们是具有多层级索引
的:
In [105]: df.T.sort_index(level=1, axis=1)
Out[105]:
one zero one zero
x x y y
0 0.600178 2.410179 1.519970 0.132885
1 0.274230 1.450520 -0.493662 -0.023688
即便数据没有排序,你仍然可以对他们进行索引,但是索引的效率会极大降低,并且也会抛出PerformanceWarning
警告。而且,这将返回一个数据的副本而非一个数据的视图:
In [106]: dfm = pd.DataFrame({'jim': [0, 0, 1, 1],
.....: 'joe': ['x', 'x', 'z', 'y'],
.....: 'jolie': np.random.rand(4)})
.....:
In [107]: dfm = dfm.set_index(['jim', 'joe'])
In [108]: dfm
Out[108]:
jolie
jim joe
0 x 0.490671
x 0.120248
1 z 0.537020
y 0.110968
In [4]: dfm.loc[(1, 'z')]
PerformanceWarning: indexing past lexsort depth may impact performance.
Out[4]:
jolie
jim joe
1 z 0.64094
另外,如果你试图索引一个没有完全lexsorted的对象,你将会碰到如下的错误:
In [5]: dfm.loc[(0, 'y'):(1, 'z')]
UnsortedIndexError: 'Key length (2) was greater than MultiIndex lexsort depth (1)'
在MultiIndex
上使用 is_lexsorted()
open in new window 方法,你可以查看这个索引是否已经被排序。而使用lexsort_depth
属性则可以返回排序的深度
In [109]: dfm.index.is_lexsorted()
Out[109]: False
In [110]: dfm.index.lexsort_depth
Out[110]: 1
In [111]: dfm = dfm.sort_index()
In [112]: dfm
Out[112]:
jolie
jim joe
0 x 0.490671
x 0.120248
1 y 0.110968
z 0.537020
In [113]: dfm.index.is_lexsorted()
Out[113]: True
In [114]: dfm.index.lexsort_depth
Out[114]: 2
现在,你的选择就可以正常工作了。
In [115]: dfm.loc[(0, 'y'):(1, 'z')]
Out[115]:
jolie
jim joe
1 y 0.110968
z 0.537020
Take方法
与NumPy
的ndarrays
相似,pandas的 Index
, Series
,和DataFrame
也提供 take()
open in new window 方法。他可以沿着某个维度,按照给定的索引取回所有的元素。这个给定的索引必须要是一个由整数组成的列表或者ndarray,用以指明在索引中的位置。take
也可以接受负整数,作为相对于结尾的相对位置。
In [116]: index = pd.Index(np.random.randint(0, 1000, 10))
In [117]: index
Out[117]: Int64Index([214, 502, 712, 567, 786, 175, 993, 133, 758, 329], dtype='int64')
In [118]: positions = [0, 9, 3]
In [119]: index[positions]
Out[119]: Int64Index([214, 329, 567], dtype='int64')
In [120]: index.take(positions)
Out[120]: Int64Index([214, 329, 567], dtype='int64')
In [121]: ser = pd.Series(np.random.randn(10))
In [122]: ser.iloc[positions]
Out[122]:
0 -0.179666
9 1.824375
3 0.392149
dtype: float64
In [123]: ser.take(positions)
Out[123]:
0 -0.179666
9 1.824375
3 0.392149
dtype: float64
对于DataFrames
来说,这个给定的索引应当是一个一维列表或者ndarray,用于指明行或者列的位置。
In [124]: frm = pd.DataFrame(np.random.randn(5, 3))
In [125]: frm.take([1, 4, 3])
Out[125]:
0 1 2
1 -1.237881 0.106854 -1.276829
4 0.629675 -1.425966 1.857704
3 0.979542 -1.633678 0.615855
In [126]: frm.take([0, 2], axis=1)
Out[126]:
0 2
0 0.595974 0.601544
1 -1.237881 -1.276829
2 -0.767101 1.499591
3 0.979542 0.615855
4 0.629675 1.857704
需要注意的是, pandas对象的take
方法并不会正常地工作在布尔索引上,并且有可能会返回一切意外的结果。
In [127]: arr = np.random.randn(10)
In [128]: arr.take([False, False, True, True])
Out[128]: array([-1.1935, -1.1935, 0.6775, 0.6775])
In [129]: arr[[0, 1]]
Out[129]: array([-1.1935, 0.6775])
In [130]: ser = pd.Series(np.random.randn(10))
In [131]: ser.take([False, False, True, True])
Out[131]:
0 0.233141
0 0.233141
1 -0.223540
1 -0.223540
dtype: float64
In [132]: ser.iloc[[0, 1]]
Out[132]:
0 0.233141
1 -0.223540
dtype: float64
最后,关于性能方面的一个小建议,因为 take
方法处理的是一个范围更窄的输入,因此会比话实索引(fancy indexing)的速度快很多。
In [133]: arr = np.random.randn(10000, 5)
In [134]: indexer = np.arange(10000)
In [135]: random.shuffle(indexer)
In [136]: %timeit arr[indexer]
.....: %timeit arr.take(indexer, axis=0)
.....:
152 us +- 988 ns per loop (mean +- std. dev. of 7 runs, 10000 loops each)
41.7 us +- 204 ns per loop (mean +- std. dev. of 7 runs, 10000 loops each)
In [137]: ser = pd.Series(arr[:, 0])
In [138]: %timeit ser.iloc[indexer]
.....: %timeit ser.take(indexer)
.....:
120 us +- 1.05 us per loop (mean +- std. dev. of 7 runs, 10000 loops each)
110 us +- 795 ns per loop (mean +- std. dev. of 7 runs, 10000 loops each)
索引类型
我们在前面已经较为深入地探讨过了多层索引。你可以在 这里,可以找到关于 DatetimeIndex
和PeriodIndex
的说明文件。在 这里,你可以找到关于TimedeltaIndex
的说明。
In the following sub-sections we will highlight some other index types.
下面的一个子章节,我们将会着重探讨另外的一些索引的类型。
分类索引
CategoricalIndex
open in new window分类索引 这种索引类型非常适合有重复的索引。这是一个围绕 Categorical
open in new window 而创建的容器。这可以非常高效地存储和索引的具有大量重复元素的索引。
In [139]: from pandas.api.types import CategoricalDtype
In [140]: df = pd.DataFrame({'A': np.arange(6),
.....: 'B': list('aabbca')})
.....:
In [141]: df['B'] = df['B'].astype(CategoricalDtype(list('cab')))
In [142]: df
Out[142]:
A B
0 0 a
1 1 a
2 2 b
3 3 b
4 4 c
5 5 a
In [143]: df.dtypes
Out[143]:
A int64
B category
dtype: object
In [144]: df.B.cat.categories
Out[144]: Index(['c', 'a', 'b'], dtype='object')
通过设置索引将会建立一个 CategoricalIndex
分类索引.
In [145]: df2 = df.set_index('B')
In [146]: df2.index
Out[146]: CategoricalIndex(['a', 'a', 'b', 'b', 'c', 'a'], categories=['c', 'a', 'b'], ordered=False, name='B', dtype='category')
使用 __getitem__/.iloc/.loc
进行索引,在含有重复值的索引
上的工作原理相似。索引值必须在一个分类中,否者将会引发KeyError
错误。
In [147]: df2.loc['a']
Out[147]:
A
B
a 0
a 1
a 5
CategoricalIndex
在索引之后也会被保留:
In [148]: df2.loc['a'].index
Out[148]: CategoricalIndex(['a', 'a', 'a'], categories=['c', 'a', 'b'], ordered=False, name='B', dtype='category')
索引排序将会按照类别清单中的顺序进行(我们已经基于 CategoricalDtype(list('cab'))
建立了一个索引,因此排序的顺序是cab
)
In [149]: df2.sort_index()
Out[149]:
A
B
c 4
a 0
a 1
a 5
b 2
b 3
分组操作(Groupby)也会保留索引的全部信息。
In [150]: df2.groupby(level=0).sum()
Out[150]:
A
B
c 4
a 6
b 5
In [151]: df2.groupby(level=0).sum().index
Out[151]: CategoricalIndex(['c', 'a', 'b'], categories=['c', 'a', 'b'], ordered=False, name='B', dtype='category')
重设索引的操作将会根据输入的索引值返回一个索引。传入一个列表,将会返回一个最普通的Index
;如果使用类别对象Categorical
,则会返回一个分类索引CategoricalIndex
,按照其中传入的的类别值Categorical
dtype来进行索引。正如同你可以对任意pandas的索引进行重新索引一样,这将允许你随意索引任意的索引值,即便它们并不存在在你的类别对象中。
In [152]: df2.reindex(['a', 'e'])
Out[152]:
A
B
a 0.0
a 1.0
a 5.0
e NaN
In [153]: df2.reindex(['a', 'e']).index
Out[153]: Index(['a', 'a', 'a', 'e'], dtype='object', name='B')
In [154]: df2.reindex(pd.Categorical(['a', 'e'], categories=list('abcde')))
Out[154]:
A
B
a 0.0
a 1.0
a 5.0
e NaN
In [155]: df2.reindex(pd.Categorical(['a', 'e'], categories=list('abcde'))).index
Out[155]: CategoricalIndex(['a', 'a', 'a', 'e'], categories=['a', 'b', 'c', 'd', 'e'], ordered=False, name='B', dtype='category')
警告
对于一个分类索引
的对象进行变形或者比较操作,一定要确保他们的索引包含相同的列别,否则将会出发类型错误TypeError
In [9]: df3 = pd.DataFrame({'A': np.arange(6), 'B': pd.Series(list('aabbca')).astype('category')})
In [11]: df3 = df3.set_index('B')
In [11]: df3.index
Out[11]: CategoricalIndex(['a', 'a', 'b', 'b', 'c', 'a'], categories=['a', 'b', 'c'], ordered=False, name='B', dtype='category')
In [12]: pd.concat([df2, df3])
TypeError: categories must match existing categories when appending
64位整型索引和范围索引
警告
使用浮点数进行基于数值的索引已经再0.18.0的版本中进行了声明。想查看更改的汇总,请参见 这里open in new window。
Int64Index
open in new window 64位整型索引是pandas中的一种非常基本的索引操作。这是一个不可变的数组组成的一个有序的,可切片的集合。再0.18.0之前,Int64Index
是会为所有NDFrame
对象提供默认的索引。
RangeIndex
open in new window 范围索引是64位整型索引
的子集,在v0.18.0版本加入。现在由范围索引来为所有的NDFrame
对象提供默认索引。 RangeIndex
是一个对于 Int64Index
的优化版本,能够提供一个有序且严格单调的集合。这个索引与python的 range typesopen in new window是相似的
64位浮点索引
默认情况下,当传入浮点数、或者浮点整型混合数的时候,一个64位浮点索引 Float64Index
open in new window将会自动被建立。这样将能够确保一个存粹而统一的基于标签的索引切片行为,这样[],ix,loc
对于标量索引和切片的工作行为将会完全一致。
In [156]: indexf = pd.Index([1.5, 2, 3, 4.5, 5])
In [157]: indexf
Out[157]: Float64Index([1.5, 2.0, 3.0, 4.5, 5.0], dtype='float64')
In [158]: sf = pd.Series(range(5), index=indexf)
In [159]: sf
Out[159]:
1.5 0
2.0 1
3.0 2
4.5 3
5.0 4
dtype: int64
标量选择对于[],.loc
永远都是基于标签的。一个整型将会自动匹配一个浮点标签(例如,3
等于 3.0
)
In [160]: sf[3]
Out[160]: 2
In [161]: sf[3.0]
Out[161]: 2
In [162]: sf.loc[3]
Out[162]: 2
In [163]: sf.loc[3.0]
Out[163]: 2
唯一能够通过位置进行索引的方式是通过iloc
方法。
In [164]: sf.iloc[3]
Out[164]: 3
一个找不到的标量索引会触发一个KeyError
错误。当使用[],ix,loc
是,切片操作优先会选择索引的值,但是iloc
永远都会按位置索引。唯一的例外是使用布尔索引,此时将始终按位置选择。
In [165]: sf[2:4]
Out[165]:
2.0 1
3.0 2
dtype: int64
In [166]: sf.loc[2:4]
Out[166]:
2.0 1
3.0 2
dtype: int64
In [167]: sf.iloc[2:4]
Out[167]:
3.0 2
4.5 3
dtype: int64
如果你使用的是浮点数索引,那么使用浮点数切片也是可以执行的。
In [168]: sf[2.1:4.6]
Out[168]:
3.0 2
4.5 3
dtype: int64
In [169]: sf.loc[2.1:4.6]
Out[169]:
3.0 2
4.5 3
dtype: int64
在非浮点数中,如果使用浮点索引,将会触发TypeError
错误。
In [1]: pd.Series(range(5))[3.5]
TypeError: the label [3.5] is not a proper indexer for this index type (Int64Index)
In [1]: pd.Series(range(5))[3.5:4.5]
TypeError: the slice start [3.5] is not a proper indexer for this index type (Int64Index)
警告
从0.18.0开始,.iloc
将不能够使用标量浮点数进行索引,因此下列操作将触发TypeError
错误。
In [3]: pd.Series(range(5)).iloc[3.0]
TypeError: cannot do positional indexing on <class 'pandas.indexes.range.RangeIndex'> with these indexers [3.0] of <type 'float'>
这里有一个典型的场景来使用这种类型的索引方式。设想你有一个不规范的类timedelta的索引方案,但是日期是按照浮点数的方式记录的。这将会导致(例如)毫秒级的延迟。
In [170]: dfir = pd.concat([pd.DataFrame(np.random.randn(5, 2),
.....: index=np.arange(5) * 250.0,
.....: columns=list('AB')),
.....: pd.DataFrame(np.random.randn(6, 2),
.....: index=np.arange(4, 10) * 250.1,
.....: columns=list('AB'))])
.....:
In [171]: dfir
Out[171]:
A B
0.0 -0.435772 -1.188928
250.0 -0.808286 -0.284634
500.0 -1.815703 1.347213
750.0 -0.243487 0.514704
1000.0 1.162969 -0.287725
1000.4 -0.179734 0.993962
1250.5 -0.212673 0.909872
1500.6 -0.733333 -0.349893
1750.7 0.456434 -0.306735
2000.8 0.553396 0.166221
2250.9 -0.101684 -0.734907
因此选择操作将总是按照值来进行所有的选择工作,
In [172]: dfir[0:1000.4]
Out[172]:
A B
0.0 -0.435772 -1.188928
250.0 -0.808286 -0.284634
500.0 -1.815703 1.347213
750.0 -0.243487 0.514704
1000.0 1.162969 -0.287725
1000.4 -0.179734 0.993962
In [173]: dfir.loc[0:1001, 'A']
Out[173]:
0.0 -0.435772
250.0 -0.808286
500.0 -1.815703
750.0 -0.243487
1000.0 1.162969
1000.4 -0.179734
Name: A, dtype: float64
In [174]: dfir.loc[1000.4]
Out[174]:
A -0.179734
B 0.993962
Name: 1000.4, dtype: float64
你可以返回第一秒(1000毫秒)的数据:
In [175]: dfir[0:1000]
Out[175]:
A B
0.0 -0.435772 -1.188928
250.0 -0.808286 -0.284634
500.0 -1.815703 1.347213
750.0 -0.243487 0.514704
1000.0 1.162969 -0.287725
如果你想要使用基于整型的选择,你应该使用iloc
:
In [176]: dfir.iloc[0:5]
Out[176]:
A B
0.0 -0.435772 -1.188928
250.0 -0.808286 -0.284634
500.0 -1.815703 1.347213
750.0 -0.243487 0.514704
1000.0 1.162969 -0.287725
间隔索引
0.20.0中新加入IntervalIndex
open in new window和它自己特有的IntervalDtype
以及 Interval
open in new window 标量类型,在pandas中,间隔数据是获得头等支持的。
IntervalIndex
间隔索引允许一些唯一的索引,并且也是 cut()
open in new window 和qcut()
open in new window的返回类型
使用间隔索引
来进行数据索引
In [177]: df = pd.DataFrame({'A': [1, 2, 3, 4]},
.....: index=pd.IntervalIndex.from_breaks([0, 1, 2, 3, 4]))
.....:
In [178]: df
Out[178]:
A
(0, 1] 1
(1, 2] 2
(2, 3] 3
(3, 4] 4
在间隔序列上使用基于标签的索引.loc
,正如你所预料到的,将会选择那个特定的间隔
In [179]: df.loc[2]
Out[179]:
A 2
Name: (1, 2], dtype: int64
In [180]: df.loc[[2, 3]]
Out[180]:
A
(1, 2] 2
(2, 3] 3
如果你选取了一个标签,被包含在间隔当中,这个间隔也将会被选择
In [181]: df.loc[2.5]
Out[181]:
A 3
Name: (2, 3], dtype: int64
In [182]: df.loc[[2.5, 3.5]]
Out[182]:
A
(2, 3] 3
(3, 4] 4
使用 Interval
来选择,将只返回严格匹配(从pandas0.25.0开始)。
In [183]: df.loc[pd.Interval(1, 2)]
Out[183]:
A 2
Name: (1, 2], dtype: int64
试图选择一个没有被严格包含在 IntervalIndex
内的区间Interval
,将会出发KeyError
错误。
In [7]: df.loc[pd.Interval(0.5, 2.5)]
---------------------------------------------------------------------------
KeyError: Interval(0.5, 2.5, closed='right')
可以使用overlaps()
open in new window来创建一个布尔选择器,来选中所有与给定区间
(Interval
)重复的所有区间。
In [184]: idxr = df.index.overlaps(pd.Interval(0.5, 2.5))
In [185]: idxr
Out[185]: array([ True, True, True, False])
In [186]: df[idxr]
Out[186]:
A
(0, 1] 1
(1, 2] 2
(2, 3] 3
使用 cut
和 qcut
来为数据分块
cut()
open in new window 和 qcut()
open in new window 都将返回一个分类Categorical
对象,并且每个分块区域都会以 分类索引IntervalIndex
的方式被创建并保存在它的.categories
属性中。
In [187]: c = pd.cut(range(4), bins=2)
In [188]: c
Out[188]:
[(-0.003, 1.5], (-0.003, 1.5], (1.5, 3.0], (1.5, 3.0]]
Categories (2, interval[float64]): [(-0.003, 1.5] < (1.5, 3.0]]
In [189]: c.categories
Out[189]:
IntervalIndex([(-0.003, 1.5], (1.5, 3.0]],
closed='right',
dtype='interval[float64]')
cut()
open in new window 也可以接受一个 IntervalIndex
作为他的 bins
参数,这样可以使用一个非常有用的pandas的写法。 首先,我们调用 cut()
open in new window 在一些数据上面,并且将 bins
设置为某一个固定的数 ,从而生成bins。
随后,我们可以在其他的数据上调用 cut()
open in new window,并传入.categories
的值,作为 bins
参数。这样新的数据就也将会被分配到同样的bins里面
In [190]: pd.cut([0, 3, 5, 1], bins=c.categories)
Out[190]:
[(-0.003, 1.5], (1.5, 3.0], NaN, (-0.003, 1.5]]
Categories (2, interval[float64]): [(-0.003, 1.5] < (1.5, 3.0]]
任何落在bins之外的数据都将会被设为 NaN
生成一定区间内的间隔
如果我们需要经常地使用步进区间,我们可以使用 interval_range()
open in new window 函数,结合 start
, end
, 和 periods
来建立一个 IntervalIndex
对于数值型的间隔,默认的 interval_range
间隔频率是1,对于datetime类型的间隔则是日历日。
In [191]: pd.interval_range(start=0, end=5)
Out[191]:
IntervalIndex([(0, 1], (1, 2], (2, 3], (3, 4], (4, 5]],
closed='right',
dtype='interval[int64]')
In [192]: pd.interval_range(start=pd.Timestamp('2017-01-01'), periods=4)
Out[192]:
IntervalIndex([(2017-01-01, 2017-01-02], (2017-01-02, 2017-01-03], (2017-01-03, 2017-01-04], (2017-01-04, 2017-01-05]],
closed='right',
dtype='interval[datetime64[ns]]')
In [193]: pd.interval_range(end=pd.Timedelta('3 days'), periods=3)
Out[193]:
IntervalIndex([(0 days 00:00:00, 1 days 00:00:00], (1 days 00:00:00, 2 days 00:00:00], (2 days 00:00:00, 3 days 00:00:00]],
closed='right',
dtype='interval[timedelta64[ns]]')
freq
参数可以被用来明确非默认的频率,并且可以充分地利用各种各样的 frequency aliases datetime类型的时间间隔。
In [194]: pd.interval_range(start=0, periods=5, freq=1.5)
Out[194]:
IntervalIndex([(0.0, 1.5], (1.5, 3.0], (3.0, 4.5], (4.5, 6.0], (6.0, 7.5]],
closed='right',
dtype='interval[float64]')
In [195]: pd.interval_range(start=pd.Timestamp('2017-01-01'), periods=4, freq='W')
Out[195]:
IntervalIndex([(2017-01-01, 2017-01-08], (2017-01-08, 2017-01-15], (2017-01-15, 2017-01-22], (2017-01-22, 2017-01-29]],
closed='right',
dtype='interval[datetime64[ns]]')
In [196]: pd.interval_range(start=pd.Timedelta('0 days'), periods=3, freq='9H')
Out[196]:
IntervalIndex([(0 days 00:00:00, 0 days 09:00:00], (0 days 09:00:00, 0 days 18:00:00], (0 days 18:00:00, 1 days 03:00:00]],
closed='right',
dtype='interval[timedelta64[ns]]')
此外, closed
参数可以用来声明哪个边界是包含的。默认情况下,间隔的右界是包含的。
In [197]: pd.interval_range(start=0, end=4, closed='both')
Out[197]:
IntervalIndex([[0, 1], [1, 2], [2, 3], [3, 4]],
closed='both',
dtype='interval[int64]')
In [198]: pd.interval_range(start=0, end=4, closed='neither')
Out[198]:
IntervalIndex([(0, 1), (1, 2), (2, 3), (3, 4)],
closed='neither',
dtype='interval[int64]')
v0.23.0新加入
使用start
, end
, 和 periods
可以从 start
到 end
(包含)生成一个平均分配的间隔,在返回IntervalIndex
中生成periods
这么多的元素(译者:区间)。
In [199]: pd.interval_range(start=0, end=6, periods=4)
Out[199]:
IntervalIndex([(0.0, 1.5], (1.5, 3.0], (3.0, 4.5], (4.5, 6.0]],
closed='right',
dtype='interval[float64]')
In [200]: pd.interval_range(pd.Timestamp('2018-01-01'),
.....: pd.Timestamp('2018-02-28'), periods=3)
.....:
Out[200]:
IntervalIndex([(2018-01-01, 2018-01-20 08:00:00], (2018-01-20 08:00:00, 2018-02-08 16:00:00], (2018-02-08 16:00:00, 2018-02-28]],
closed='right',
dtype='interval[datetime64[ns]]')
其他索引常见问题
数值索引
使用数值作为各维度的标签,再基于标签进行索引是一个非常痛苦的话题。在Scientific Python社区的邮件列表中,进行着剧烈的争论。在Pandas中,我们一般性的观点是,标签比实际的(用数值表示的)位置更为重要。因此,对于使用数值作为标签的的对象来说,只有基于标签的索引才可以在标准工具,例如.loc
方法,中正常使用。下面的代码将引发错误:
In [201]: s = pd.Series(range(5))
In [202]: s[-1]
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
<ipython-input-202-76c3dce40054> in <module>
----> 1 s[-1]
/pandas/pandas/core/series.py in __getitem__(self, key)
1062 key = com.apply_if_callable(key, self)
1063 try:
-> 1064 result = self.index.get_value(self, key)
1065
1066 if not is_scalar(result):
/pandas/pandas/core/indexes/base.py in get_value(self, series, key)
4721 k = self._convert_scalar_indexer(k, kind="getitem")
4722 try:
-> 4723 return self._engine.get_value(s, k, tz=getattr(series.dtype, "tz", None))
4724 except KeyError as e1:
4725 if len(self) > 0 and (self.holds_integer() or self.is_boolean()):
/pandas/pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_value()
/pandas/pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_value()
/pandas/pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc()
/pandas/pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.Int64HashTable.get_item()
/pandas/pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.Int64HashTable.get_item()
KeyError: -1
In [203]: df = pd.DataFrame(np.random.randn(5, 4))
In [204]: df
Out[204]:
0 1 2 3
0 -0.130121 -0.476046 0.759104 0.213379
1 -0.082641 0.448008 0.656420 -1.051443
2 0.594956 -0.151360 -0.069303 1.221431
3 -0.182832 0.791235 0.042745 2.069775
4 1.446552 0.019814 -1.389212 -0.702312
In [205]: df.loc[-2:]
Out[205]:
0 1 2 3
0 -0.130121 -0.476046 0.759104 0.213379
1 -0.082641 0.448008 0.656420 -1.051443
2 0.594956 -0.151360 -0.069303 1.221431
3 -0.182832 0.791235 0.042745 2.069775
4 1.446552 0.019814 -1.389212 -0.702312
我们特意地做了这样的设计,是为了阻止歧义性以及一些难以避免的小bug(当我们修改了函数,从而阻止了“滚回”到基于位置的索引方式以后,许多用户报告说,他们发现了bug)。
非单调索引要求严格匹配
如果一个 序列
或者 数据表
是单调递增或递减的,那么基于标签的切片行为的边界是可以超出索引的,这与普通的python列表
的索引切片非常相似。索引的单调性可以使用 is_monotonic_increasing()
open in new window 和is_monotonic_decreasing()
open in new window 属性来检查
In [206]: df = pd.DataFrame(index=[2, 3, 3, 4, 5], columns=['data'], data=list(range(5)))
In [207]: df.index.is_monotonic_increasing
Out[207]: True
# no rows 0 or 1, but still returns rows 2, 3 (both of them), and 4:
In [208]: df.loc[0:4, :]
Out[208]:
data
2 0
3 1
3 2
4 3
# slice is are outside the index, so empty DataFrame is returned
In [209]: df.loc[13:15, :]
Out[209]:
Empty DataFrame
Columns: [data]
Index: []
另一方面,如果索引不是单调的,那么切片的两侧边界都必须是索引值中的唯一值。
In [210]: df = pd.DataFrame(index=[2, 3, 1, 4, 3, 5],
.....: columns=['data'], data=list(range(6)))
.....:
In [211]: df.index.is_monotonic_increasing
Out[211]: False
# OK because 2 and 4 are in the index
In [212]: df.loc[2:4, :]
Out[212]:
data
2 0
3 1
1 2
4 3
# 0 is not in the index
In [9]: df.loc[0:4, :]
KeyError: 0
# 3 is not a unique label
In [11]: df.loc[2:3, :]
KeyError: 'Cannot get right slice bound for non-unique label: 3'
Index.is_monotonic_increasing
和Index.is_monotonic_decreasing
方法只能进行弱单调性的检查。要进行严格的单调性检查,你可以配合 is_unique()
open in new window 方法一起使用。
In [213]: weakly_monotonic = pd.Index(['a', 'b', 'c', 'c'])
In [214]: weakly_monotonic
Out[214]: Index(['a', 'b', 'c', 'c'], dtype='object')
In [215]: weakly_monotonic.is_monotonic_increasing
Out[215]: True
In [216]: weakly_monotonic.is_monotonic_increasing & weakly_monotonic.is_unique
Out[216]: False
终止点被包含在内
与表中的python序列切片中,终止点不被包含不同,基于标签的切片在Pandas中,终止点是被包含在内的。最主要的原因是因为,我们很难准确地确定在索引中的“下一个”标签“到底是什么。例如下面这个序列
:
In [217]: s = pd.Series(np.random.randn(6), index=list('abcdef'))
In [218]: s
Out[218]:
a 0.301379
b 1.240445
c -0.846068
d -0.043312
e -1.658747
f -0.819549
dtype: float64
如果我们希望从c
选取到e
,如果我们使用基于数值的索引,那将会由如下操作:
In [219]: s[2:5]
Out[219]:
c -0.846068
d -0.043312
e -1.658747
dtype: float64
然而,如果你只有c
和e
,确定下一个索引中的元素将会是比较困难的。例如,下面的这种方法完全是行不通的:
s.loc['c':'e' + 1]
一个非常常见的用例是限制一个时间序列的起始和终止日期。为了能够便于操作,我们决定在基于标签的切片行为中包含两个端点:
In [220]: s.loc['c':'e']
Out[220]:
c -0.846068
d -0.043312
e -1.658747
dtype: float64
这是一个非常典型的“显示战胜理想”的情况,但是如果你仅仅是想当然的认为基于标签的索引应该会和标准python中的整数型索引有着相同的行为时,你也确实需要多加留意。
索引会潜在地改变序列的dtype
不同的索引操作有可能会潜在地改变一个序列
的dtypes
In [221]: series1 = pd.Series([1, 2, 3])
In [222]: series1.dtype
Out[222]: dtype('int64')
In [223]: res = series1.reindex([0, 4])
In [224]: res.dtype
Out[224]: dtype('float64')
In [225]: res
Out[225]:
0 1.0
4 NaN
dtype: float64
In [226]: series2 = pd.Series([True])
In [227]: series2.dtype
Out[227]: dtype('bool')
In [228]: res = series2.reindex_like(series1)
In [229]: res.dtype
Out[229]: dtype('O')
In [230]: res
Out[230]:
0 True
1 NaN
2 NaN
dtype: object
这是因为上述(重新)索引的操作悄悄地插入了 NaNs
,因此dtype也就随之发生改变了。如果你在使用一些numpy
的ufuncs
,如 numpy.logical_and
时,将会导致一些问题。
参见 this old issueopen in new window了解更详细的讨论过程
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论