多索引/高级索引#
这一部分包括 indexing with a MultiIndex 和 other advanced indexing features 。
请参阅 Indexing and Selecting Data 以获取一般索引文档。
警告
是否为设置操作返回副本或引用可取决于上下文。这有时被称为 chained assignment
而且应该避免。看见 Returning a View versus Copy 。
请参阅 cookbook 一些先进的策略。
分层索引(多索引)#
分层/多级索引非常令人兴奋,因为它为一些非常复杂的数据分析和操作打开了大门,特别是对于处理更高维度的数据。本质上,它使您能够在低维数据结构中存储和操作具有任意维数的数据,如 Series
(1D)及 DataFrame
(2D)。
在这一节中,我们将展示我们所说的“分层”索引的确切含义,以及它如何与上述和前面几节中描述的所有Pandas索引功能集成在一起。后来在讨论的时候 group by 和 pivoting and reshaping data ,我们将展示一些重要的应用程序,以说明它如何帮助组织数据以进行分析。
请参阅 cookbook 一些先进的策略。
创建多索引(分层索引)对象#
这个 MultiIndex
对象是标准的分层类比 Index
对象,该对象通常存储Pandas对象中的轴标签。你可以想到 MultiIndex
作为元组数组,其中每个元组是唯一的。一个 MultiIndex
可以从阵列列表中创建(使用 MultiIndex.from_arrays()
),一个元组数组(使用 MultiIndex.from_tuples()
),一组交叉的迭代程序(使用 MultiIndex.from_product()
),或一个 DataFrame
(使用 MultiIndex.from_frame()
)。这个 Index
构造函数将尝试返回一个 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()
方法:
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
从一个 DataFrame
直接使用该方法 MultiIndex.from_frame()
。这是一种补充方法 MultiIndex.to_frame()
。
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
参数,该参数存储级别本身的字符串名称。如果没有提供姓名, None
将被分配:
In [17]: df.index.names
Out[17]: FrozenList([None, None])
此索引可以支持Pandas对象的任意轴,以及 级别 指数的多少由你决定:
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
我们对较高级别的索引进行了“稀疏”处理,以使控制台的输出更美观一些。注意,索引的显示方式可以使用 multi_sparse
选项输入 pandas.set_options()
:
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()
将返回特定级别上每个位置的标签向量:
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')
利用多重索引实现轴上的基本索引#
分层索引的一个重要特征是,您可以通过标识数据中的子组的“部分”标签来选择数据。 部分 选择将删除结果中层次索引的级别,其方式与选择常规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
看见 Cross-section with hierarchical index 关于如何在更深的层面上进行选择。
定义的级别#
这个 MultiIndex
保留索引的所有已定义级别,即使它们实际上并未使用。在对索引进行切片时,您可能会注意到这一点。例如:
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']])
这样做是为了避免重新计算级别,从而使切片具有很高的性能。如果只想查看已使用的级别,可以使用 get_level_values()
方法。
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()
方法可以使用。
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
这个 reindex()
一种方法 Series
/DataFrames
可以与另一个 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
支持分层索引的高级索引#
句法整合 MultiIndex
在高级索引中使用 .loc
有点挑战,但我们已经尽了一切努力。通常,多索引键采用元组的形式。例如,以下内容的工作方式与您预期的一样:
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
不必指定所有级别的 MultiIndex
只传递元组的第一个元素。例如,您可以使用“部分”索引来获取具有 bar
在第一级中如下:
In [43]: df.loc["bar"]
Out[43]:
A B C
second
one 0.895717 0.410835 -1.413681
two 0.805244 0.813850 1.607920
这是略显冗长的表示法的快捷方式 df.loc[('bar',),]
(相当于 df.loc['bar',]
在本例中)。
“部分”切片也非常有效。
In [44]: df.loc["baz":"foo"]
Out[44]:
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
通过提供一个元组切片,您可以使用一个“范围”的值进行切片。
In [45]: df.loc[("baz", "two"):("qux", "one")]
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
qux one -1.170299 1.130127 0.974466
In [46]: df.loc[("baz", "two"):"foo"]
Out[46]:
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
传递标签或元组列表的工作原理类似于重新索引:
In [47]: df.loc[[("bar", "two"), ("qux", "one")]]
Out[47]:
A B C
first second
bar two 0.805244 0.813850 1.607920
qux one -1.170299 1.130127 0.974466
备注
值得注意的是,当涉及到索引时,Pandas对元组和列表的处理并不相同。元组被解释为一个多级关键字,而列表用于指定多个关键字。或者换句话说,元组水平移动(遍历级别),列表垂直移动(扫描级别)。
重要的是,元组列表索引了几个完整的元组 MultiIndex
键,而列表的元组引用一个级别中的多个值:
In [48]: s = pd.Series(
....: [1, 2, 3, 4, 5, 6],
....: index=pd.MultiIndex.from_product([["A", "B"], ["c", "d", "e"]]),
....: )
....:
In [49]: s.loc[[("A", "c"), ("B", "d")]] # list of tuples
Out[49]:
A c 1
B d 5
dtype: int64
In [50]: s.loc[(["A", "B"], ["c", "d"])] # tuple of lists
Out[50]:
A c 1
d 2
B c 4
d 5
dtype: int64
使用切片器#
你可以把一片 MultiIndex
通过提供多个索引器。
您可以提供任何选择器,就像按标签编制索引一样,请参见 Selection by Label ,包括切片、标签列表、标签和布尔索引器。
您可以使用 slice(None)
选择所有内容的步骤 that 水平。您不需要指定所有 更深一层 级别,它们将被隐含为 slice(None)
。
像往常一样, 两边都有 的切片机被包括在内,因为这是标签索引。
警告
属性中的所有轴。 .loc
说明符,表示 索引 而对于 列 。在某些不明确的情况下,传递的索引器可能会被错误地解释为索引 both 斧头,而不是进入 MultiIndex
排成一排。
您应该这样做:
df.loc[(slice("A1", "A3"), ...), :] # noqa: E999
你应该 not 执行以下操作:
df.loc[(slice("A1", "A3"), ...)] # noqa: E999
In [51]: def mklbl(prefix, n):
....: return ["%s%s" % (prefix, i) for i in range(n)]
....:
In [52]: miindex = pd.MultiIndex.from_product(
....: [mklbl("A", 4), mklbl("B", 2), mklbl("C", 4), mklbl("D", 2)]
....: )
....:
In [53]: micolumns = pd.MultiIndex.from_tuples(
....: [("a", "foo"), ("a", "bar"), ("b", "foo"), ("b", "bah")], names=["lvl0", "lvl1"]
....: )
....:
In [54]: 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 [55]: dfmi
Out[55]:
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 [56]: dfmi.loc[(slice("A1", "A3"), slice(None), ["C1", "C3"]), :]
Out[56]:
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
为了促进更自然的语法,请使用 :
,而不是使用 slice(None)
。
In [57]: idx = pd.IndexSlice
In [58]: dfmi.loc[idx[:, :, ["C1", "C3"]], idx[:, "foo"]]
Out[58]:
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 [59]: dfmi.loc["A1", (slice(None), "foo")]
Out[59]:
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 [60]: dfmi.loc[idx[:, :, ["C1", "C3"]], idx[:, "foo"]]
Out[60]:
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 [61]: mask = dfmi[("a", "foo")] > 200
In [62]: dfmi.loc[idx[mask, :, ["C1", "C3"]], idx[:, "foo"]]
Out[62]:
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
您还可以指定 axis
参数为 .loc
在单个轴上解释传递的切片器。
In [63]: dfmi.loc(axis=0)[:, :, ["C1", "C3"]]
Out[63]:
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]
此外,您还可以 set 这些值使用以下方法。
In [64]: df2 = dfmi.copy()
In [65]: df2.loc(axis=0)[:, :, ["C1", "C3"]] = -10
In [66]: df2
Out[66]:
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 [67]: df2 = dfmi.copy()
In [68]: df2.loc[idx[:, :, ["C1", "C3"]], :] = df2 * 1000
In [69]: df2
Out[69]:
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]
横截面#
这个 xs()
一种方法 DataFrame
此外还采用Level参数,以使在 MultiIndex
更容易些。
In [70]: df
Out[70]:
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 [71]: df.xs("one", level="second")
Out[71]:
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 [72]: df.loc[(slice(None), "one"), :]
Out[72]:
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 [73]: df = df.T
In [74]: df.xs("one", level="second", axis=1)
Out[74]:
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 [75]: df.loc[:, (slice(None), "one")]
Out[75]:
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
还允许使用多个关键点进行选择。
In [76]: df.xs(("one", "bar"), level=("second", "first"), axis=1)
Out[76]:
first bar
second one
A 0.895717
B 0.410835
C -1.413681
# using the slicers
In [77]: df.loc[:, ("bar", "one")]
Out[77]:
A 0.895717
B 0.410835
C -1.413681
Name: (bar, one), dtype: float64
你可以过去了 drop_level=False
至 xs
以保留选定的标高。
In [78]: df.xs("one", level="second", axis=1, drop_level=False)
Out[78]:
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 [79]: df.xs("one", level="second", axis=1, drop_level=True)
Out[79]:
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
在 reindex()
和 align()
Pandas对象的方法对于跨级别广播值很有用。例如:
In [80]: midx = pd.MultiIndex(
....: levels=[["zero", "one"], ["x", "y"]], codes=[[1, 1, 0, 0], [1, 0, 1, 0]]
....: )
....:
In [81]: df = pd.DataFrame(np.random.randn(4, 2), index=midx)
In [82]: df
Out[82]:
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 [83]: df2 = df.groupby(level=0).mean()
In [84]: df2
Out[84]:
0 1
one 1.060074 -0.109716
zero 1.271532 0.713416
In [85]: df2.reindex(df.index, level=0)
Out[85]:
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 [86]: df_aligned, df2_aligned = df.align(df2, level=0)
In [87]: df_aligned
Out[87]:
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 [88]: df2_aligned
Out[88]:
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()
方法可以切换两个级别的顺序:
In [89]: df[:5]
Out[89]:
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 [90]: df[:5].swaplevel(0, 1, 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
对级别进行重新排序 reorder_levels
#
这个 reorder_levels()
方法泛化了 swaplevel
方法,允许您在一个步骤中排列层次索引级别:
In [91]: df[:5].reorder_levels([1, 0], axis=0)
Out[91]:
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
重命名的名称 Index
或 MultiIndex
#
这个 rename()
方法用于重命名 MultiIndex
,通常用于重命名 DataFrame
。这个 columns
论证 rename
允许指定只包含要重命名的列的字典。
In [92]: df.rename(columns={0: "col0", 1: "col1"})
Out[92]:
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 [93]: df.rename(index={"one": "two", "y": "z"})
Out[93]:
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()
方法用于重命名 Index
或 MultiIndex
。特别是,一个级别的名称 MultiIndex
可以指定,这在以下情况下很有用 reset_index()
稍后用于将值从 MultiIndex
排成一列。
In [94]: df.rename_axis(index=["abc", "def"])
Out[94]:
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 [95]: df.rename_axis(columns="Cols").columns
Out[95]: RangeIndex(start=0, stop=2, step=1, name='Cols')
两者都有 rename
和 rename_axis
支持指定字典, Series
或用于将标签/名称映射到新值的映射函数。
在使用 Index
对象,而不是通过 DataFrame
, Index.set_names()
可用于更改名称。
In [96]: mi = pd.MultiIndex.from_product([[1, 2], ["a", "b"]], names=["x", "y"])
In [97]: mi.names
Out[97]: FrozenList(['x', 'y'])
In [98]: mi2 = mi.rename("new name", level=0)
In [99]: mi2
Out[99]:
MultiIndex([(1, 'a'),
(1, 'b'),
(2, 'a'),
(2, 'b')],
names=['new name', 'y'])
不能通过级别设置多重索引的名称。
In [100]: mi.levels[0].name = "name via level"
---------------------------------------------------------------------------
RuntimeError Traceback (most recent call last)
Input In [100], in <cell line: 1>()
----> 1 mi.levels[0].name = "name via level"
File /usr/local/lib/python3.10/dist-packages/pandas-1.5.0.dev0+697.gf9762d8f52-py3.10-linux-x86_64.egg/pandas/core/indexes/base.py:1734, in Index.name(self, value)
1730 @name.setter
1731 def name(self, value: Hashable):
1732 if self._no_setting_name:
1733 # Used in MultiIndex.levels to avoid silently ignoring name updates.
-> 1734 raise RuntimeError(
1735 "Cannot set name on a level of a MultiIndex. Use "
1736 "'MultiIndex.set_names' instead."
1737 )
1738 maybe_extract_name(value, None, type(self))
1739 self._name = value
RuntimeError: Cannot set name on a level of a MultiIndex. Use 'MultiIndex.set_names' instead.
使用 Index.set_names()
取而代之的是。
排序 MultiIndex
#
为 MultiIndex
-要对对象进行有效的索引和切片,需要对它们进行排序。与任何索引一样,您可以使用 sort_index()
。
In [101]: import random
In [102]: random.shuffle(tuples)
In [103]: s = pd.Series(np.random.randn(8), index=pd.MultiIndex.from_tuples(tuples))
In [104]: s
Out[104]:
baz two 0.206053
bar two -0.251905
baz one -2.213588
bar one 1.063327
qux one 1.266143
foo one 0.299368
qux two -0.863838
foo two 0.408204
dtype: float64
In [105]: s.sort_index()
Out[105]:
bar one 1.063327
two -0.251905
baz one -2.213588
two 0.206053
foo one 0.299368
two 0.408204
qux one 1.266143
two -0.863838
dtype: float64
In [106]: s.sort_index(level=0)
Out[106]:
bar one 1.063327
two -0.251905
baz one -2.213588
two 0.206053
foo one 0.299368
two 0.408204
qux one 1.266143
two -0.863838
dtype: float64
In [107]: s.sort_index(level=1)
Out[107]:
bar one 1.063327
baz one -2.213588
foo one 0.299368
qux one 1.266143
bar two -0.251905
baz two 0.206053
foo two 0.408204
qux two -0.863838
dtype: float64
您还可以将级别名称传递给 sort_index
如果 MultiIndex
标高是命名的。
In [108]: s.index.set_names(["L1", "L2"], inplace=True)
In [109]: s.sort_index(level="L1")
Out[109]:
L1 L2
bar one 1.063327
two -0.251905
baz one -2.213588
two 0.206053
foo one 0.299368
two 0.408204
qux one 1.266143
two -0.863838
dtype: float64
In [110]: s.sort_index(level="L2")
Out[110]:
L1 L2
bar one 1.063327
baz one -2.213588
foo one 0.299368
qux one 1.266143
bar two -0.251905
baz two 0.206053
foo two 0.408204
qux two -0.863838
dtype: float64
在更高维度的对象上,如果其他轴具有 MultiIndex
:
In [111]: df.T.sort_index(level=1, axis=1)
Out[111]:
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 [112]: dfm = pd.DataFrame(
.....: {"jim": [0, 0, 1, 1], "joe": ["x", "x", "z", "y"], "jolie": np.random.rand(4)}
.....: )
.....:
In [113]: dfm = dfm.set_index(["jim", "joe"])
In [114]: dfm
Out[114]:
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
此外,如果您尝试对未完全词法排序的内容进行索引,可能会引发以下问题:
In [5]: dfm.loc[(0, 'y'):(1, 'z')]
UnsortedIndexError: 'Key length (2) was greater than MultiIndex lexsort depth (1)'
这个 is_monotonic_increasing()
对象上的方法 MultiIndex
显示索引是否已排序:
In [115]: dfm.index.is_monotonic_increasing
Out[115]: False
In [116]: dfm = dfm.sort_index()
In [117]: dfm
Out[117]:
jolie
jim joe
0 x 0.490671
x 0.120248
1 y 0.110968
z 0.537020
In [118]: dfm.index.is_monotonic_increasing
Out[118]: True
现在,选择的工作与预期的一样。
In [119]: dfm.loc[(0, "y"):(1, "z")]
Out[119]:
jolie
jim joe
1 y 0.110968
z 0.537020
采取方法#
与NumPy ndarray类似,Pandas Index
, Series
,以及 DataFrame
还提供了 take()
方法,该方法在给定索引处沿给定轴检索元素。给定的索引必须是整数索引位置的列表或ndarray。 take
还将接受负整数作为对象末尾的相对位置。
In [120]: index = pd.Index(np.random.randint(0, 1000, 10))
In [121]: index
Out[121]: Int64Index([214, 502, 712, 567, 786, 175, 993, 133, 758, 329], dtype='int64')
In [122]: positions = [0, 9, 3]
In [123]: index[positions]
Out[123]: Int64Index([214, 329, 567], dtype='int64')
In [124]: index.take(positions)
Out[124]: Int64Index([214, 329, 567], dtype='int64')
In [125]: ser = pd.Series(np.random.randn(10))
In [126]: ser.iloc[positions]
Out[126]:
0 -0.179666
9 1.824375
3 0.392149
dtype: float64
In [127]: ser.take(positions)
Out[127]:
0 -0.179666
9 1.824375
3 0.392149
dtype: float64
对于DataFrame,给定的索引应该是指定行或列位置的一维列表或ndarray。
In [128]: frm = pd.DataFrame(np.random.randn(5, 3))
In [129]: frm.take([1, 4, 3])
Out[129]:
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 [130]: frm.take([0, 2], axis=1)
Out[130]:
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
需要注意的是, take
方法不适用于布尔索引,可能会返回意外结果。
In [131]: arr = np.random.randn(10)
In [132]: arr.take([False, False, True, True])
Out[132]: array([-1.1935, -1.1935, 0.6775, 0.6775])
In [133]: arr[[0, 1]]
Out[133]: array([-1.1935, 0.6775])
In [134]: ser = pd.Series(np.random.randn(10))
In [135]: ser.take([False, False, True, True])
Out[135]:
0 0.233141
0 0.233141
1 -0.223540
1 -0.223540
dtype: float64
In [136]: ser.iloc[[0, 1]]
Out[136]:
0 0.233141
1 -0.223540
dtype: float64
最后,作为一个关于性能的小说明,因为 take
方法处理的输入范围较窄,因此它可以提供比花哨的索引快得多的性能。
In [137]: arr = np.random.randn(10000, 5)
In [138]: indexer = np.arange(10000)
In [139]: random.shuffle(indexer)
In [140]: %timeit arr[indexer]
.....: %timeit arr.take(indexer, axis=0)
.....:
107 us +- 89.2 ns per loop (mean +- std. dev. of 7 runs, 10,000 loops each)
36.2 us +- 15.7 ns per loop (mean +- std. dev. of 7 runs, 10,000 loops each)
In [141]: ser = pd.Series(arr[:, 0])
In [142]: %timeit ser.iloc[indexer]
.....: %timeit ser.take(indexer)
.....:
42.7 us +- 58.9 ns per loop (mean +- std. dev. of 7 runs, 10,000 loops each)
34.8 us +- 93.8 ns per loop (mean +- std. dev. of 7 runs, 10,000 loops each)
索引类型#
我们已经讨论过 MultiIndex
在前面的小节中,我做了相当广泛的介绍。文档关于 DatetimeIndex
和 PeriodIndex
显示的是 here ,以及有关的文档 TimedeltaIndex
找到了 here 。
在以下小节中,我们将重点介绍其他一些索引类型。
CategoricalIndex#
CategoricalIndex
是一种索引类型,对于支持具有重复项的索引非常有用。这是一个容器,围绕着一个 Categorical
并且允许高效地索引和存储具有大量重复元素的索引。
In [143]: from pandas.api.types import CategoricalDtype
In [144]: df = pd.DataFrame({"A": np.arange(6), "B": list("aabbca")})
In [145]: df["B"] = df["B"].astype(CategoricalDtype(list("cab")))
In [146]: df
Out[146]:
A B
0 0 a
1 1 a
2 2 b
3 3 b
4 4 c
5 5 a
In [147]: df.dtypes
Out[147]:
A int64
B category
dtype: object
In [148]: df["B"].cat.categories
Out[148]: Index(['c', 'a', 'b'], dtype='object')
设置索引将创建 CategoricalIndex
。
In [149]: df2 = df.set_index("B")
In [150]: df2.index
Out[150]: CategoricalIndex(['a', 'a', 'b', 'b', 'c', 'a'], categories=['c', 'a', 'b'], ordered=False, dtype='category', name='B')
索引使用 __getitem__/.iloc/.loc
其工作方式类似于 Index
带着复制品。索引者 must 属于该类别,否则操作将引发 KeyError
。
In [151]: df2.loc["a"]
Out[151]:
A
B
a 0
a 1
a 5
这个 CategoricalIndex
是 保存下来的 编制索引后:
In [152]: df2.loc["a"].index
Out[152]: CategoricalIndex(['a', 'a', 'a'], categories=['c', 'a', 'b'], ordered=False, dtype='category', name='B')
对索引进行排序将按类别的顺序进行排序(回想一下,我们使用 CategoricalDtype(list('cab'))
,所以排序的顺序是 cab
)。
In [153]: df2.sort_index()
Out[153]:
A
B
c 4
a 0
a 1
a 5
b 2
b 3
对索引的Groupby操作也将保留索引的性质。
In [154]: df2.groupby(level=0).sum()
Out[154]:
A
B
c 4
a 6
b 5
In [155]: df2.groupby(level=0).sum().index
Out[155]: CategoricalIndex(['c', 'a', 'b'], categories=['c', 'a', 'b'], ordered=False, dtype='category', name='B')
重建索引操作将根据传递的索引器的类型返回结果索引。传递一个列表将返回一个普通的 Index
;使用 Categorical
将返回一个 CategoricalIndex
,并根据 通过 Categorical
数据类型。这使得用户即使使用值也可以任意地对这些内容进行索引 not 在类别中,类似于如何重新编制索引 any Pandas指数。
In [156]: df3 = pd.DataFrame(
.....: {"A": np.arange(3), "B": pd.Series(list("abc")).astype("category")}
.....: )
.....:
In [157]: df3 = df3.set_index("B")
In [158]: df3
Out[158]:
A
B
a 0
b 1
c 2
In [159]: df3.reindex(["a", "e"])
Out[159]:
A
B
a 0.0
e NaN
In [160]: df3.reindex(["a", "e"]).index
Out[160]: Index(['a', 'e'], dtype='object', name='B')
In [161]: df3.reindex(pd.Categorical(["a", "e"], categories=list("abe")))
Out[161]:
A
B
a 0.0
e NaN
In [162]: df3.reindex(pd.Categorical(["a", "e"], categories=list("abe"))).index
Out[162]: CategoricalIndex(['a', 'e'], categories=['a', 'b', 'e'], ordered=False, dtype='category', name='B')
警告
对象上的重塑和比较操作 CategoricalIndex
必须具有相同的类别或 TypeError
都会被举起。
In [163]: df4 = pd.DataFrame({"A": np.arange(2), "B": list("ba")})
In [164]: df4["B"] = df4["B"].astype(CategoricalDtype(list("ab")))
In [165]: df4 = df4.set_index("B")
In [166]: df4.index
Out[166]: CategoricalIndex(['b', 'a'], categories=['a', 'b'], ordered=False, dtype='category', name='B')
In [167]: df5 = pd.DataFrame({"A": np.arange(2), "B": list("bc")})
In [168]: df5["B"] = df5["B"].astype(CategoricalDtype(list("bc")))
In [169]: df5 = df5.set_index("B")
In [170]: df5.index
Out[170]: CategoricalIndex(['b', 'c'], categories=['b', 'c'], ordered=False, dtype='category', name='B')
In [1]: pd.concat([df4, df5])
TypeError: categories must match existing categories when appending
Int64Index和RangeIndex#
1.4.0 版后已移除: 在Pandas2.0中, Index
将成为数值类型的默认索引类型,而不是 Int64Index
, Float64Index
和 UInt64Index
因此,这些索引类型已被弃用,并将在未来版本中删除。 RangeIndex
将不会被移除,因为它表示整数索引的优化版本。
Int64Index
是Pandas的一项基本基本指标。这是一个不可变的数组,实现了一个有序的、可分割的集合。
RangeIndex
是的子类 Int64Index
that provides the default index for all NDFrame
objects. RangeIndex
is an optimized version of Int64Index
that can represent a monotonic ordered set. These are analogous to Python range types 。
Float64Index#
1.4.0 版后已移除: Index
将成为未来数值类型的默认索引类型,而不是 Int64Index
, Float64Index
和 UInt64Index
因此,这些索引类型已被弃用,并将在未来版本的Pandas中删除。 RangeIndex
将不会被移除,因为它表示整数索引的优化版本。
默认情况下, Float64Index
在索引创建过程中传递浮点值或混合整数浮点值时,将自动创建。这实现了纯基于标签的切片范例,从而使 [],ix,loc
标量索引和切片的工作原理完全相同。
In [171]: indexf = pd.Index([1.5, 2, 3, 4.5, 5])
In [172]: indexf
Out[172]: Float64Index([1.5, 2.0, 3.0, 4.5, 5.0], dtype='float64')
In [173]: sf = pd.Series(range(5), index=indexf)
In [174]: sf
Out[174]:
1.5 0
2.0 1
3.0 2
4.5 3
5.0 4
dtype: int64
标量选择 [],.loc
将始终基于标签。整数将匹配相等的浮点索引(例如 3
相当于 3.0
)。
In [175]: sf[3]
Out[175]: 2
In [176]: sf[3.0]
Out[176]: 2
In [177]: sf.loc[3]
Out[177]: 2
In [178]: sf.loc[3.0]
Out[178]: 2
唯一的位置索引是VIA iloc
。
In [179]: sf.iloc[3]
Out[179]: 3
未找到的标量索引将引发 KeyError
。使用时,切片主要基于索引值 [],ix,loc
,以及 始终 使用时按位置 iloc
。例外情况是切片是布尔型的,在这种情况下,它将始终是位置的。
In [180]: sf[2:4]
Out[180]:
2.0 1
3.0 2
dtype: int64
In [181]: sf.loc[2:4]
Out[181]:
2.0 1
3.0 2
dtype: int64
In [182]: sf.iloc[2:4]
Out[182]:
3.0 2
4.5 3
dtype: int64
在浮点型索引中,允许使用浮点型进行切片。
In [183]: sf[2.1:4.6]
Out[183]:
3.0 2
4.5 3
dtype: int64
In [184]: sf.loc[2.1:4.6]
Out[184]:
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)
下面是使用这种索引类型的一个典型用例。假设您有一个有点不规则的类似时间增量的索引方案,但是数据被记录为浮点数。例如,这可能是毫秒级的偏移量。
In [185]: 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 [186]: dfir
Out[186]:
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 [187]: dfir[0:1000.4]
Out[187]:
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 [188]: dfir.loc[0:1001, "A"]
Out[188]:
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 [189]: dfir.loc[1000.4]
Out[189]:
A -0.179734
B 0.993962
Name: 1000.4, dtype: float64
您可以检索前1秒(1000毫秒)的数据,如下所示:
In [190]: dfir[0:1000]
Out[190]:
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 [191]: dfir.iloc[0:5]
Out[191]:
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
IntervalIndex#
IntervalIndex
连同它自己的数据类型, IntervalDtype
以及 Interval
标量类型,允许在Pandas中对间隔记数法提供一流的支持。
这个 IntervalIndex
允许建立一些唯一索引,并且还用作 cut()
和 qcut()
。
使用 IntervalIndex
#
一个 IntervalIndex
可用于 Series
以及在 DataFrame
作为索引。
In [192]: df = pd.DataFrame(
.....: {"A": [1, 2, 3, 4]}, index=pd.IntervalIndex.from_breaks([0, 1, 2, 3, 4])
.....: )
.....:
In [193]: df
Out[193]:
A
(0, 1] 1
(1, 2] 2
(2, 3] 3
(3, 4] 4
基于标签的索引通过 .loc
沿间隔边缘的工作方式与您预期的一样,即选择该特定间隔。
In [194]: df.loc[2]
Out[194]:
A 2
Name: (1, 2], dtype: int64
In [195]: df.loc[[2, 3]]
Out[195]:
A
(1, 2] 2
(2, 3] 3
如果您选择一个标签 包含 在一个间隔内,这也将选择该间隔。
In [196]: df.loc[2.5]
Out[196]:
A 3
Name: (2, 3], dtype: int64
In [197]: df.loc[[2.5, 3.5]]
Out[197]:
A
(2, 3] 3
(3, 4] 4
使用选项进行选择 Interval
将只返回完全匹配的(从Pandas0.25.0开始)。
In [198]: df.loc[pd.Interval(1, 2)]
Out[198]:
A 2
Name: (1, 2], dtype: int64
正在尝试选择一个 Interval
这并不完全包含在 IntervalIndex
将引发一个 KeyError
。
In [7]: df.loc[pd.Interval(0.5, 2.5)]
---------------------------------------------------------------------------
KeyError: Interval(0.5, 2.5, closed='right')
全选 Intervals
与给定的数据重叠 Interval
可以使用 overlaps()
方法来创建布尔索引器。
In [199]: idxr = df.index.overlaps(pd.Interval(0.5, 2.5))
In [200]: idxr
Out[200]: array([ True, True, True, False])
In [201]: df[idxr]
Out[201]:
A
(0, 1] 1
(1, 2] 2
(2, 3] 3
将数据入库 cut
和 qcut
#
cut()
和 qcut()
两者都返回一个 Categorical
对象,并且它们创建的回收站存储为 IntervalIndex
在ITS中 .categories
属性。
In [202]: c = pd.cut(range(4), bins=2)
In [203]: c
Out[203]:
[(-0.003, 1.5], (-0.003, 1.5], (1.5, 3.0], (1.5, 3.0]]
Categories (2, interval[float64, right]): [(-0.003, 1.5] < (1.5, 3.0]]
In [204]: c.categories
Out[204]: IntervalIndex([(-0.003, 1.5], (1.5, 3.0]], dtype='interval[float64, right]')
cut()
还接受一个 IntervalIndex
因为它的 bins
参数,这使得一个有用的Pandas习语成为可能。首先,我们打电话给 cut()
带着一些数据和 bins
设置为固定数量,以生成回收箱。然后,我们将 .categories
作为 bins
参数的后续调用中 cut()
,提供新数据,这些数据将被放入相同的箱中。
In [205]: pd.cut([0, 3, 5, 1], bins=c.categories)
Out[205]:
[(-0.003, 1.5], (1.5, 3.0], NaN, (-0.003, 1.5]]
Categories (2, interval[float64, right]): [(-0.003, 1.5] < (1.5, 3.0]]
位于所有存储箱之外的任何值都将被分配一个 NaN
价值。
区间的生成区间#
如果我们需要固定频率的时间间隔,我们可以使用 interval_range()
函数来创建 IntervalIndex
使用不同的组合 start
, end
,以及 periods
。的默认频率 interval_range
1表示数字间隔,日历日表示类似日期的间隔:
In [206]: pd.interval_range(start=0, end=5)
Out[206]: IntervalIndex([[0, 1], [1, 2], [2, 3], [3, 4], [4, 5]], dtype='interval[int64, both]')
In [207]: pd.interval_range(start=pd.Timestamp("2017-01-01"), periods=4)
Out[207]: 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]], dtype='interval[datetime64[ns], both]')
In [208]: pd.interval_range(end=pd.Timedelta("3 days"), periods=3)
Out[208]: 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]], dtype='interval[timedelta64[ns], both]')
这个 freq
参数可用于指定非默认频率,并可利用各种 frequency aliases 使用类似日期时间的间隔:
In [209]: pd.interval_range(start=0, periods=5, freq=1.5)
Out[209]: IntervalIndex([[0.0, 1.5], [1.5, 3.0], [3.0, 4.5], [4.5, 6.0], [6.0, 7.5]], dtype='interval[float64, both]')
In [210]: pd.interval_range(start=pd.Timestamp("2017-01-01"), periods=4, freq="W")
Out[210]: 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]], dtype='interval[datetime64[ns], both]')
In [211]: pd.interval_range(start=pd.Timedelta("0 days"), periods=3, freq="9H")
Out[211]: 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]], dtype='interval[timedelta64[ns], both]')
此外, inclusive
参数可用于指定在哪一侧闭合间隔。默认情况下,两边的间隔都是关闭的。
In [212]: pd.interval_range(start=0, end=4, inclusive="both")
Out[212]: IntervalIndex([[0, 1], [1, 2], [2, 3], [3, 4]], dtype='interval[int64, both]')
In [213]: pd.interval_range(start=0, end=4, inclusive="neither")
Out[213]: IntervalIndex([(0, 1), (1, 2), (2, 3), (3, 4)], dtype='interval[int64, neither]')
指定 start
, end
,以及 periods
将从以下位置生成一系列均匀间隔 start
至 end
包括,与 periods
结果中的元素数 IntervalIndex
:
In [214]: pd.interval_range(start=0, end=6, periods=4)
Out[214]: IntervalIndex([[0.0, 1.5], [1.5, 3.0], [3.0, 4.5], [4.5, 6.0]], dtype='interval[float64, both]')
In [215]: pd.interval_range(pd.Timestamp("2018-01-01"), pd.Timestamp("2018-02-28"), periods=3)
Out[215]: 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]], dtype='interval[datetime64[ns], both]')
其他索引常见问题解答#
整数索引#
使用整数轴标签进行基于标签的索引是一个棘手的主题。它已经在邮件列表上和科学Python社区的不同成员中得到了广泛的讨论。在大Pandas身上,我们的普遍观点是,标签比整数位置更重要。因此,使用整数轴索引 only 基于标签的索引可以使用标准工具,如 .loc
。以下代码将生成异常:
In [216]: s = pd.Series(range(5))
In [217]: s[-1]
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
File /usr/local/lib/python3.10/dist-packages/pandas-1.5.0.dev0+697.gf9762d8f52-py3.10-linux-x86_64.egg/pandas/core/indexes/range.py:385, in RangeIndex.get_loc(self, key, method, tolerance)
384 try:
--> 385 return self._range.index(new_key)
386 except ValueError as err:
ValueError: -1 is not in range
The above exception was the direct cause of the following exception:
KeyError Traceback (most recent call last)
Input In [217], in <cell line: 1>()
----> 1 s[-1]
File /usr/local/lib/python3.10/dist-packages/pandas-1.5.0.dev0+697.gf9762d8f52-py3.10-linux-x86_64.egg/pandas/core/series.py:967, in Series.__getitem__(self, key)
964 return self._values[key]
966 elif key_is_scalar:
--> 967 return self._get_value(key)
969 if is_hashable(key):
970 # Otherwise index.get_value will raise InvalidIndexError
971 try:
972 # For labels that don't resolve as scalars like tuples and frozensets
File /usr/local/lib/python3.10/dist-packages/pandas-1.5.0.dev0+697.gf9762d8f52-py3.10-linux-x86_64.egg/pandas/core/series.py:1075, in Series._get_value(self, label, takeable)
1072 return self._values[label]
1074 # Similar to Index.get_value, but we do not fall back to positional
-> 1075 loc = self.index.get_loc(label)
1076 return self.index._get_values_for_loc(self, loc, label)
File /usr/local/lib/python3.10/dist-packages/pandas-1.5.0.dev0+697.gf9762d8f52-py3.10-linux-x86_64.egg/pandas/core/indexes/range.py:387, in RangeIndex.get_loc(self, key, method, tolerance)
385 return self._range.index(new_key)
386 except ValueError as err:
--> 387 raise KeyError(key) from err
388 self._check_indexing_error(key)
389 raise KeyError(key)
KeyError: -1
In [218]: df = pd.DataFrame(np.random.randn(5, 4))
In [219]: df
Out[219]:
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 [220]: df.loc[-2:]
Out[220]:
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
这个深思熟虑的决定是为了防止模棱两可和细微的错误(许多用户报告说,在进行API更改以停止“回退”基于位置的索引时发现了错误)。
非单调索引需要完全匹配#
如果一个索引的 Series
或 DataFrame
是单调增加还是减少,则基于标签的切片的范围可能在索引范围之外,这与对普通Python进行切片索引非常相似 list
。索引的单调性可以用 is_monotonic_increasing()
和 is_monotonic_decreasing()
属性。
In [221]: df = pd.DataFrame(index=[2, 3, 3, 4, 5], columns=["data"], data=list(range(5)))
In [222]: df.index.is_monotonic_increasing
Out[222]: True
# no rows 0 or 1, but still returns rows 2, 3 (both of them), and 4:
In [223]: df.loc[0:4, :]
Out[223]:
data
2 0
3 1
3 2
4 3
# slice is are outside the index, so empty DataFrame is returned
In [224]: df.loc[13:15, :]
Out[224]:
Empty DataFrame
Columns: [data]
Index: []
另一方面,如果索引不是单调的,则两个切片边界必须是 独一无二的 索引的成员。
In [225]: df = pd.DataFrame(index=[2, 3, 1, 4, 3, 5], columns=["data"], data=list(range(6)))
In [226]: df.index.is_monotonic_increasing
Out[226]: False
# OK because 2 and 4 are in the index
In [227]: df.loc[2:4, :]
Out[227]:
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()
属性。
In [228]: weakly_monotonic = pd.Index(["a", "b", "c", "c"])
In [229]: weakly_monotonic
Out[229]: Index(['a', 'b', 'c', 'c'], dtype='object')
In [230]: weakly_monotonic.is_monotonic_increasing
Out[230]: True
In [231]: weakly_monotonic.is_monotonic_increasing & weakly_monotonic.is_unique
Out[231]: False
端点包罗万象#
与切片终点不包含的标准Python序列切片相比,在Pandas中基于标签的切片 是包容的 。这样做的主要原因是,通常不可能容易地确定索引中特定标签之后的“继任者”或下一个元素。例如,请考虑以下内容 Series
:
In [232]: s = pd.Series(np.random.randn(6), index=list("abcdef"))
In [233]: s
Out[233]:
a 0.301379
b 1.240445
c -0.846068
d -0.043312
e -1.658747
f -0.819549
dtype: float64
假设我们想要从 c
至 e
使用整数,这将按如下方式完成:
In [234]: s[2:5]
Out[234]:
c -0.846068
d -0.043312
e -1.658747
dtype: float64
然而,如果你有 c
和 e
,确定索引中的下一个元素可能有些复杂。例如,以下选项不起作用:
s.loc['c':'e' + 1]
一种非常常见的用例是将时间序列限制为在两个特定日期开始和结束。为了实现这一点,我们做出了设计选择,使基于标签的切片包括两个端点:
In [235]: s.loc["c":"e"]
Out[235]:
c -0.846068
d -0.043312
e -1.658747
dtype: float64
这绝对是一种“实用性胜过纯洁性”的事情,但如果您期望基于标签的切片的行为与标准的Python整数切片的工作方式完全相同,则需要注意这一点。
索引可能会更改基础系列数据类型#
不同的索引操作可能会更改 Series
。
In [236]: series1 = pd.Series([1, 2, 3])
In [237]: series1.dtype
Out[237]: dtype('int64')
In [238]: res = series1.reindex([0, 4])
In [239]: res.dtype
Out[239]: dtype('float64')
In [240]: res
Out[240]:
0 1.0
4 NaN
dtype: float64
In [241]: series2 = pd.Series([True])
In [242]: series2.dtype
Out[242]: dtype('bool')
In [243]: res = series2.reindex_like(series1)
In [244]: res.dtype
Out[244]: dtype('O')
In [245]: res
Out[245]:
0 True
1 NaN
2 NaN
dtype: object
这是因为上面的(重新)索引操作以静默方式插入 NaNs
以及 dtype
相应地发生变化。这可能会在使用时导致一些问题 numpy
ufuncs
比如 numpy.logical_and
。
请参阅 GH2388 以进行更详细的讨论。