多索引/高级索引#

这一部分包括 indexing with a MultiIndexother advanced indexing features

请参阅 Indexing and Selecting Data 以获取一般索引文档。

警告

是否为设置操作返回副本或引用可取决于上下文。这有时被称为 chained assignment 而且应该避免。看见 Returning a View versus Copy

请参阅 cookbook 一些先进的策略。

分层索引(多索引)#

分层/多级索引非常令人兴奋,因为它为一些非常复杂的数据分析和操作打开了大门,特别是对于处理更高维度的数据。本质上,它使您能够在低维数据结构中存储和操作具有任意维数的数据,如 Series (1D)及 DataFrame (2D)。

在这一节中,我们将展示我们所说的“分层”索引的确切含义,以及它如何与上述和前面几节中描述的所有Pandas索引功能集成在一起。后来在讨论的时候 group bypivoting 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'])

为了方便起见,您可以将数组列表直接传递到 SeriesDataFrame 要构建一个 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=Falsexs 以保留选定的标高。

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

高级重建索引和对齐#

使用参数 levelreindex()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

重命名的名称 IndexMultiIndex#

这个 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() 方法用于重命名 IndexMultiIndex 。特别是,一个级别的名称 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')

两者都有 renamerename_axis 支持指定字典, Series 或用于将标签/名称映射到新值的映射函数。

在使用 Index 对象,而不是通过 DataFrameIndex.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 IndexSeries ,以及 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 在前面的小节中,我做了相当广泛的介绍。文档关于 DatetimeIndexPeriodIndex 显示的是 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 将成为数值类型的默认索引类型,而不是 Int64IndexFloat64IndexUInt64Index 因此,这些索引类型已被弃用,并将在未来版本中删除。 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 将成为未来数值类型的默认索引类型,而不是 Int64IndexFloat64IndexUInt64Index 因此,这些索引类型已被弃用,并将在未来版本的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

将数据入库 cutqcut#

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 使用不同的组合 startend ,以及 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]')

指定 startend ,以及 periods 将从以下位置生成一系列均匀间隔 startend 包括,与 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更改以停止“回退”基于位置的索引时发现了错误)。

非单调索引需要完全匹配#

如果一个索引的 SeriesDataFrame 是单调增加还是减少,则基于标签的切片的范围可能在索引范围之外,这与对普通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_increasingIndex.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

假设我们想要从 ce 使用整数,这将按如下方式完成:

In [234]: s[2:5]
Out[234]: 
c   -0.846068
d   -0.043312
e   -1.658747
dtype: float64

然而,如果你有 ce ,确定索引中的下一个元素可能有些复杂。例如,以下选项不起作用:

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 以进行更详细的讨论。