MultiIndex / Advanced Indexing

原文:http://pandas.pydata.org/pandas-docs/stable/advanced.html

译者:飞龙 UsyiyiCN

校对:(虚位以待)

本节介绍使用MultiIndex和更高级的索引功能建立索引。

有关一般索引文档,请参见Indexing and Selecting Data

警告

是否为设置操作返回副本或引用可以取决于上下文。这有时被称为链接 分配,应该避免。请参见Returning a View versus Copy

警告

在0.15.0 Index内部被重构为不再子类ndarray,而是子类化PandasObject,类似于其余的pandas对象。这应该是一个透明的变化,只有非常有限的API影响(参见Internal Refactoring

有关某些高级策略,请参阅cookbook

Hierarchical indexing (MultiIndex)

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

在本节中,我们将展示“层次化”索引的确切含义,以及它如何与上述和前面章节中描述的所有Pandas索引功能集成。稍后,当讨论group bypivoting and reshaping data时,我们将展示非平凡的应用程序,以说明它如何帮助构建分析数据。

有关某些高级策略,请参阅cookbook

Creating a MultiIndex (hierarchical index) object

MultiIndex对象是标准Index对象的分层模拟,通常将对象标签存储在pandas对象中。您可以将MultiIndex视为一个元组数组,其中每个元组都是唯一的。可以从数组列表(使用MultiIndex.from_arrays),元组数组(使用MultiIndex.from_tuples)创建MultiIndex一组交叉的迭代(使用MultiIndex.from_product)。当传递元组列表时,Index构造函数将尝试返回MultiIndex以下示例演示了初始化MultiIndexes的不同方法。

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(levels=[[u'bar', u'baz', u'foo', u'qux'], [u'one', u'two']],
           labels=[[0, 0, 1, 1, 2, 2, 3, 3], [0, 1, 0, 1, 0, 1, 0, 1]],
           names=[u'first', u'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(levels=[[u'bar', u'baz', u'foo', u'qux'], [u'one', u'two']],
           labels=[[0, 0, 1, 1, 2, 2, 3, 3], [0, 1, 0, 1, 0, 1, 0, 1]],
           names=[u'first', u'second'])

为了方便,可以将数组列表直接传递到Series或DataFrame,以自动构建MultiIndex:

In [10]: arrays = [np.array(['bar', 'bar', 'baz', 'baz', 'foo', 'foo', 'qux', 'qux']),
   ....:           np.array(['one', 'two', 'one', 'two', 'one', 'two', 'one', 'two'])]
   ....: 

In [11]: s = pd.Series(np.random.randn(8), index=arrays)

In [12]: s
Out[12]: 
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 [13]: df = pd.DataFrame(np.random.randn(8, 4), index=arrays)

In [14]: df
Out[14]: 
                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 [15]: df.index.names
Out[15]: FrozenList([None, None])

此索引可以返回一个pandas对象的任何轴,并且索引的级别数量取决于您:

In [16]: df = pd.DataFrame(np.random.randn(3, 8), index=['A', 'B', 'C'], columns=index)

In [17]: df
Out[17]: 
first        bar                 baz                 foo                 qux  \
second       one       two       one       two       one       two       one   
A       0.895717  0.805244 -1.206412  2.565646  1.431256  1.340309 -1.170299   
B       0.410835  0.813850  0.132003 -0.827317 -0.076467 -1.187678  1.130127   
C      -1.413681  1.607920  1.024180  0.569605  0.875906 -2.211372  0.974466   

first             
second       two  
A      -0.226169  
B      -1.436737  
C      -2.006747  

In [18]: pd.DataFrame(np.random.randn(6, 6), index=index[:6], columns=index[:6])
Out[18]: 
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

我们已经“稀化”了高阶索引,使控制台输出更易检视。

值得注意的是,没有什么阻止你使用元组作为轴上的原子标签:

In [19]: pd.Series(np.random.randn(8), index=tuples)
Out[19]: 
(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

请注意,通过使用pandas.set_printoptions中的multi_sparse选项来控制索引的显示方式:

In [20]: pd.set_option('display.multi_sparse', False)

In [21]: df
Out[21]: 
first        bar       bar       baz       baz       foo       foo       qux  \
second       one       two       one       two       one       two       one   
A       0.895717  0.805244 -1.206412  2.565646  1.431256  1.340309 -1.170299   
B       0.410835  0.813850  0.132003 -0.827317 -0.076467 -1.187678  1.130127   
C      -1.413681  1.607920  1.024180  0.569605  0.875906 -2.211372  0.974466   

first        qux  
second       two  
A      -0.226169  
B      -1.436737  
C      -2.006747  

In [22]: pd.set_option('display.multi_sparse', True)

Reconstructing the level labels

方法get_level_values将返回特定级别每个位置的标签向量:

In [23]: index.get_level_values(0)
Out[23]: Index([u'bar', u'bar', u'baz', u'baz', u'foo', u'foo', u'qux', u'qux'], dtype='object', name=u'first')

In [24]: index.get_level_values('second')
Out[24]: Index([u'one', u'two', u'one', u'two', u'one', u'two', u'one', u'two'], dtype='object', name=u'second')

Basic indexing on axis with MultiIndex

分层索引的一个重要特征是,您可以通过标识数据中的子组的“部分”标签来选择数据。部分选择以类似于在常规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的repr显示所有定义的索引级别,即使它们没有被实际使用。当切片索引时,您可能会注意到这一点。例如:

# original multi-index
In [29]: df.columns
Out[29]: 
MultiIndex(levels=[[u'bar', u'baz', u'foo', u'qux'], [u'one', u'two']],
           labels=[[0, 0, 1, 1, 2, 2, 3, 3], [0, 1, 0, 1, 0, 1, 0, 1]],
           names=[u'first', u'second'])

# sliced
In [30]: df[['foo','qux']].columns
Out[30]: 
MultiIndex(levels=[[u'bar', u'baz', u'foo', u'qux'], [u'one', u'two']],
           labels=[[2, 2, 3, 3], [0, 1, 0, 1]],
           names=[u'first', u'second'])

这样做是为了避免重新计算水平以便使切片具有高性能。如果你想看到实际使用的水平。

In [31]: df[['foo','qux']].columns.values
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([u'foo', u'foo', u'qux', u'qux'], dtype='object', name=u'first')

仅使用已使用的级别重建多索引

In [33]: pd.MultiIndex.from_tuples(df[['foo','qux']].columns.values)
Out[33]: 
MultiIndex(levels=[[u'foo', u'qux'], [u'one', u'two']],
           labels=[[0, 0, 1, 1], [0, 1, 0, 1]])

Data alignment and using reindex

在轴上具有MultiIndex的不同索引对象之间的操作将如您所期望的那样工作;数据对齐将与元组的索引相同:

In [34]: s + s[:-2]
Out[34]: 
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 [35]: s + s[::2]
Out[35]: 
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

可以使用另一个MultiIndex或甚至一个元组的列表或数组调用reindex

In [36]: s.reindex(index[:3])
Out[36]: 
first  second
bar    one      -0.861849
       two      -2.104569
baz    one      -0.494929
dtype: float64

In [37]: s.reindex([('foo', 'two'), ('bar', 'one'), ('qux', 'one'), ('baz', 'one')])
Out[37]: 
foo  two   -0.706771
bar  one   -0.861849
qux  one   -1.039575
baz  one   -0.494929
dtype: float64

Advanced indexing with hierarchical index

.loc/.ix中将MultiIndex与高级索引语法结合起来有点具有挑战性,但我们已竭尽全力。例如下面的工作,你会期望:

In [38]: df = df.T

In [39]: df
Out[39]: 
                     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 [40]: df.loc['bar']
Out[40]: 
               A         B         C
second                              
one     0.895717  0.410835 -1.413681
two     0.805244  0.813850  1.607920

In [41]: df.loc['bar', 'two']
Out[41]: 
A    0.805244
B    0.813850
C    1.607920
Name: (bar, two), dtype: float64

“部分”切片也可以很好地工作。

In [42]: df.loc['baz':'foo']
Out[42]: 
                     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 [43]: df.loc[('baz', 'two'):('qux', 'one')]
Out[43]: 
                     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 [44]: df.loc[('baz', 'two'):'foo']
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

传递标签或元组的列表与重建索引类似:

In [45]: df.ix[[('bar', 'two'), ('qux', 'one')]]
Out[45]: 
                     A         B         C
first second                              
bar   two     0.805244  0.813850  1.607920
qux   one    -1.170299  1.130127  0.974466

Using slicers

版本0.14.0中的新功能。

在0.14.0中,我们添加了一种新的方法来切割多索引对象。您可以通过提供多个索引器来分割多索引。

您可以提供任何选择器,就像按标签建立索引一样,请参阅Selection by Label,包括切片,标签列表,标签和布尔索引器。

You can use slice(None) to select all the contents of that level. 您不需要指定所有更深的级别,它们将被暗示为slice(None)

通常,包括切片器的两侧,因为这是标签索引。

警告

您应该在.loc说明符中指定所有轴,这意味着索引的索引器。有一些不明确的情况,传递的索引器可能被误解释为两个轴的索引,而不是行的MuliIndex。

你应该做这个:

df.loc[(slice('A1','A3'),.....),:]

而不是这样:

df.loc[(slice('A1','A3'),.....)]
In [46]: def mklbl(prefix,n):
   ....:     return ["%s%s" % (prefix,i)  for i in range(n)]
   ....: 

In [47]: miindex = pd.MultiIndex.from_product([mklbl('A',4),
   ....:                                       mklbl('B',2),
   ....:                                       mklbl('C',4),
   ....:                                       mklbl('D',2)])
   ....: 

In [48]: micolumns = pd.MultiIndex.from_tuples([('a','foo'),('a','bar'),
   ....:                                        ('b','foo'),('b','bah')],
   ....:                                       names=['lvl0', 'lvl1'])
   ....: 

In [49]: 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 [50]: dfmi
Out[50]: 
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
         D1   21   20   23   22
      C3 D0   25   24   27   26
...          ...  ...  ...  ...
A3 B1 C0 D1  229  228  231  230
      C1 D0  233  232  235  234
         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 [51]: dfmi.loc[(slice('A1','A3'),slice(None), ['C1','C3']),:]
Out[51]: 
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
         D1  109  108  111  110
      C3 D0  121  120  123  122
...          ...  ...  ...  ...
A3 B0 C1 D1  205  204  207  206
      C3 D0  217  216  219  218
         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]

您可以使用:而不是使用slice(None)使用pd.IndexSlice

In [52]: idx = pd.IndexSlice

In [53]: dfmi.loc[idx[:,:,['C1','C3']],idx[:,'foo']]
Out[53]: 
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
         D1   44   46
      C3 D0   56   58
...          ...  ...
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

[32 rows x 2 columns]

可以在多个轴上同时使用此方法执行相当复杂的选择。

In [54]: dfmi.loc['A1',(slice(None),'foo')]
Out[54]: 
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
      D1   84   86
   C3 D0   88   90
...       ...  ...
B1 C0 D1  100  102
   C1 D0  104  106
      D1  108  110
   C2 D0  112  114
      D1  116  118
   C3 D0  120  122
      D1  124  126

[16 rows x 2 columns]

In [55]: dfmi.loc[idx[:,:,['C1','C3']],idx[:,'foo']]
Out[55]: 
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
         D1   44   46
      C3 D0   56   58
...          ...  ...
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

[32 rows x 2 columns]

使用布尔索引器,您可以提供与相关的选择。

In [56]: mask = dfmi[('a','foo')]>200

In [57]: dfmi.loc[idx[mask,:,['C1','C3']],idx[:,'foo']]
Out[57]: 
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 [58]: dfmi.loc(axis=0)[:,:,['C1','C3']]
Out[58]: 
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
         D1   45   44   47   46
      C3 D0   57   56   59   58
...          ...  ...  ...  ...
A3 B0 C1 D1  205  204  207  206
      C3 D0  217  216  219  218
         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 [59]: df2 = dfmi.copy()

In [60]: df2.loc(axis=0)[:,:,['C1','C3']] = -10

In [61]: df2
Out[61]: 
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
         D1   21   20   23   22
      C3 D0  -10  -10  -10  -10
...          ...  ...  ...  ...
A3 B1 C0 D1  229  228  231  230
      C1 D0  -10  -10  -10  -10
         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 [62]: df2 = dfmi.copy()

In [63]: df2.loc[idx[:,:,['C1','C3']],:] = df2*1000

In [64]: df2
Out[64]: 
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
         D1      21      20      23      22
      C3 D0   25000   24000   27000   26000
...             ...     ...     ...     ...
A3 B1 C0 D1     229     228     231     230
      C1 D0  233000  232000  235000  234000
         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]

Cross-section

DataFramexs方法还需要一个级别参数,以便更容易地选择MultiIndex的特定级别上的数据。

In [65]: df
Out[65]: 
                     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 [66]: df.xs('one', level='second')
Out[66]: 
              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 (new in 0.14.0)
In [67]: df.loc[(slice(None),'one'),:]
Out[67]: 
                     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 [68]: df = df.T

In [69]: df.xs('one', level='second', axis=1)
Out[69]: 
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 (new in 0.14.0)
In [70]: df.loc[:,(slice(None),'one')]
Out[70]: 
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 [71]: df.xs(('one', 'bar'), level=('second', 'first'), axis=1)
Out[71]: 
first        bar
second       one
A       0.895717
B       0.410835
C      -1.413681
# using the slicers (new in 0.14.0)
In [72]: df.loc[:,('bar','one')]
Out[72]: 
A    0.895717
B    0.410835
C   -1.413681
Name: (bar, one), dtype: float64

版本0.13.0中的新功能。

您可以将drop_level=False传递给xs(),以保留所选的级别

In [73]: df.xs('one', level='second', axis=1, drop_level=False)
Out[73]: 
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 [74]: df.xs('one', level='second', axis=1, drop_level=True)
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

Advanced reindexing and alignment

参数level已添加到pandas对象的reindexalign方法中。这对于跨级别广播值很有用。例如:

In [75]: midx = pd.MultiIndex(levels=[['zero', 'one'], ['x','y']],
   ....:                      labels=[[1,1,0,0],[1,0,1,0]])
   ....: 

In [76]: df = pd.DataFrame(np.random.randn(4,2), index=midx)

In [77]: df
Out[77]: 
               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 [78]: df2 = df.mean(level=0)

In [79]: df2
Out[79]: 
             0         1
zero  1.271532  0.713416
one   1.060074 -0.109716

In [80]: df2.reindex(df.index, level=0)
Out[80]: 
               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 [81]: df_aligned, df2_aligned = df.align(df2, level=0)

In [82]: df_aligned
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_aligned
Out[83]: 
               0         1
one  y  1.060074 -0.109716
     x  1.060074 -0.109716
zero y  1.271532  0.713416
     x  1.271532  0.713416

Swapping levels with swaplevel()

swaplevel函数可以切换两个级别的顺序:

In [84]: df[:5]
Out[84]: 
               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 [85]: df[:5].swaplevel(0, 1, axis=0)
Out[85]: 
               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

Reordering levels with reorder_levels()

reorder_levels函数概括了swaplevel函数,允许您在一个步骤中置换层次索引级别:

In [86]: df[:5].reorder_levels([1,0], axis=0)
Out[86]: 
               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

Sorting a MultiIndex

要有效地对多索引对象进行索引和分片,需要对它们进行排序。与任何索引一样,您可以使用sort_index

In [87]: import random; random.shuffle(tuples)

In [88]: s = pd.Series(np.random.randn(8), index=pd.MultiIndex.from_tuples(tuples))

In [89]: s
Out[89]: 
baz  two    0.206053
qux  one   -0.251905
bar  two   -2.213588
     one    1.063327
baz  one    1.266143
qux  two    0.299368
foo  one   -0.863838
     two    0.408204
dtype: float64

In [90]: s.sort_index()
Out[90]: 
bar  one    1.063327
     two   -2.213588
baz  one    1.266143
     two    0.206053
foo  one   -0.863838
     two    0.408204
qux  one   -0.251905
     two    0.299368
dtype: float64

In [91]: s.sort_index(level=0)
Out[91]: 
bar  one    1.063327
     two   -2.213588
baz  one    1.266143
     two    0.206053
foo  one   -0.863838
     two    0.408204
qux  one   -0.251905
     two    0.299368
dtype: float64

In [92]: s.sort_index(level=1)
Out[92]: 
bar  one    1.063327
baz  one    1.266143
foo  one   -0.863838
qux  one   -0.251905
bar  two   -2.213588
baz  two    0.206053
foo  two    0.408204
qux  two    0.299368
dtype: float64

如果MultiIndex级别命名,您也可以将级别名称传递给sort_index

In [93]: s.index.set_names(['L1', 'L2'], inplace=True)

In [94]: s.sort_index(level='L1')
Out[94]: 
L1   L2 
bar  one    1.063327
     two   -2.213588
baz  one    1.266143
     two    0.206053
foo  one   -0.863838
     two    0.408204
qux  one   -0.251905
     two    0.299368
dtype: float64

In [95]: s.sort_index(level='L2')
Out[95]: 
L1   L2 
bar  one    1.063327
baz  one    1.266143
foo  one   -0.863838
qux  one   -0.251905
bar  two   -2.213588
baz  two    0.206053
foo  two    0.408204
qux  two    0.299368
dtype: float64

对于较高维度的对象,如果具有MultiIndex,您可以按级别对任何其他轴进行排序:

In [96]: df.T.sort_index(level=1, axis=1)
Out[96]: 
       zero       one      zero       one
          x         x         y         y
0  2.410179  0.600178  0.132885  1.519970
1  1.450520  0.274230 -0.023688 -0.493662

即使数据没有排序,索引也会起作用,但是效率相当低(并显示PerformanceWarning)。它还将返回数据的副本,而不是视图:

In [97]: dfm = pd.DataFrame({'jim': [0, 0, 1, 1],
   ....:                     'joe': ['x', 'x', 'z', 'y'],
   ....:                     'jolie': np.random.rand(4)})
   ....: 

In [98]: dfm = dfm.set_index(['jim', 'joe'])

In [99]: dfm
Out[99]: 
            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')]
KeyError: 'Key length (2) was greater than MultiIndex lexsort depth (1)'

Index上的is_lexsorted()方法显示索引是否已排序,并且lexsort_depth属性返回排序深度:

In [100]: dfm.index.is_lexsorted()
Out[100]: False

In [101]: dfm.index.lexsort_depth
Out[101]: 1
In [102]: dfm = dfm.sort_index()

In [103]: dfm
Out[103]: 
            jolie
jim joe          
0   x    0.490671
    x    0.120248
1   y    0.110968
    z    0.537020

In [104]: dfm.index.is_lexsorted()
Out[104]: True

In [105]: dfm.index.lexsort_depth
Out[105]: 2

现在选择工作正如预期。

In [106]: dfm.loc[(0,'y'):(1, 'z')]
Out[106]: 
            jolie
jim joe          
1   y    0.110968
    z    0.537020

Take Methods

与numpy ndarrays类似,pandas Index,Series和DataFrame还提供了take方法,用于在给定的索引处检索沿给定轴的元素。给定的索引必须是整数索引位置的列表或者ndarray。take也会接受负整数作为对象结尾的相对位置。

In [107]: index = pd.Index(np.random.randint(0, 1000, 10))

In [108]: index
Out[108]: Int64Index([214, 502, 712, 567, 786, 175, 993, 133, 758, 329], dtype='int64')

In [109]: positions = [0, 9, 3]

In [110]: index[positions]
Out[110]: Int64Index([214, 329, 567], dtype='int64')

In [111]: index.take(positions)
Out[111]: Int64Index([214, 329, 567], dtype='int64')

In [112]: ser = pd.Series(np.random.randn(10))

In [113]: ser.iloc[positions]
Out[113]: 
0   -0.179666
9    1.824375
3    0.392149
dtype: float64

In [114]: ser.take(positions)
Out[114]: 
0   -0.179666
9    1.824375
3    0.392149
dtype: float64

对于DataFrames,给定的索引应该是指定行或列位置的1d列表或ndarray。

In [115]: frm = pd.DataFrame(np.random.randn(5, 3))

In [116]: frm.take([1, 4, 3])
Out[116]: 
          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 [117]: frm.take([0, 2], axis=1)
Out[117]: 
          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 [118]: arr = np.random.randn(10)

In [119]: arr.take([False, False, True, True])
Out[119]: array([-1.1935, -1.1935,  0.6775,  0.6775])

In [120]: arr[[0, 1]]
Out[120]: array([-1.1935,  0.6775])

In [121]: ser = pd.Series(np.random.randn(10))

In [122]: ser.take([False, False, True, True])
Out[122]: 
0    0.233141
0    0.233141
1   -0.223540
1   -0.223540
dtype: float64

In [123]: ser.ix[[0, 1]]
Out[123]: 
0    0.233141
1   -0.223540
dtype: float64

最后,作为对性能的一个小注释,因为take方法处理较窄的输入范围,它可以提供比花式索引更快的性能。

Index Types

我们已经在前面的章节讨论了MultiIndexDatetimeIndexPeriodIndex在此显示为hereTimedeltaIndex在此处为here

在下面的小节中,我们将高亮一些其他索引类型。

CategoricalIndex

版本0.16.1中的新功能。

我们引入了一个CategoricalIndex,一种新类型的索引对象,用于支持使用重复索引。这是围绕Categorical(在v0.15.0中引入)的容器,允许对具有大量重复元素的索引进行有效的索引和存储。在0.16.1之前,使用category dtype设置DataFrame/Series的索引会将其转换为常规基于对象的Index

In [124]: df = pd.DataFrame({'A': np.arange(6),
   .....:                    'B': list('aabbca')})
   .....: 

In [125]: df['B'] = df['B'].astype('category', categories=list('cab'))

In [126]: df
Out[126]: 
   A  B
0  0  a
1  1  a
2  2  b
3  3  b
4  4  c
5  5  a

In [127]: df.dtypes
Out[127]: 
A       int64
B    category
dtype: object

In [128]: df.B.cat.categories
Out[128]: Index([u'c', u'a', u'b'], dtype='object')

设置索引,将创建CategoricalIndex

In [129]: df2 = df.set_index('B')

In [130]: df2.index
Out[130]: CategoricalIndex([u'a', u'a', u'b', u'b', u'c', u'a'], categories=[u'c', u'a', u'b'], ordered=False, name=u'B', dtype='category')

使用__getitem__/.iloc/.loc/.ix索引与Index类似,具有重复。索引器必须在类别中或操作将增加。

In [131]: df2.loc['a']
Out[131]: 
   A
B   
a  0
a  1
a  5

这些保留CategoricalIndex

In [132]: df2.loc['a'].index
Out[132]: CategoricalIndex([u'a', u'a', u'a'], categories=[u'c', u'a', u'b'], ordered=False, name=u'B', dtype='category')

排序将按类别的顺序排序

In [133]: df2.sort_index()
Out[133]: 
   A
B   
c  4
a  0
a  1
a  5
b  2
b  3

索引上的Groupby操作也将保留索引本质

In [134]: df2.groupby(level=0).sum()
Out[134]: 
   A
B   
c  4
a  6
b  5

In [135]: df2.groupby(level=0).sum().index
Out[135]: CategoricalIndex([u'c', u'a', u'b'], categories=[u'c', u'a', u'b'], ordered=False, name=u'B', dtype='category')

重索引操作将根据传递的索引器的类型返回一个结果索引,这意味着传递一个列表将返回一个普通的Index;用Categorical索引将返回CategoricalIndex,根据PASSED Categorical dtype的类别索引。这允许任意索引这些甚至与不在类别中的值,类似于如何重新索引任何熊猫索引。

In [136]: df2.reindex(['a','e'])
Out[136]: 
     A
B     
a  0.0
a  1.0
a  5.0
e  NaN

In [137]: df2.reindex(['a','e']).index
Out[137]: Index([u'a', u'a', u'a', u'e'], dtype='object', name=u'B')

In [138]: df2.reindex(pd.Categorical(['a','e'],categories=list('abcde')))
Out[138]: 
     A
B     
a  0.0
a  1.0
a  5.0
e  NaN

In [139]: df2.reindex(pd.Categorical(['a','e'],categories=list('abcde'))).index
Out[139]: CategoricalIndex([u'a', u'a', u'a', u'e'], categories=[u'a', u'b', u'c', u'd', u'e'], ordered=False, name=u'B', dtype='category')

警告

CategoricalIndex的重塑和比较操作必须具有相同的类别,否则会引发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([u'a', u'a', u'b', u'b', u'c', u'a'], categories=[u'a', u'b', u'c'], ordered=False, name=u'B', dtype='category')

In [12]: pd.concat([df2, df3]
TypeError: categories must match existing categories when appending

Int64Index and RangeIndex

警告

对于具有浮点数的整数索引的索引已经在0.18.0中阐明,对于变化的总结,参见here

Int64Indexpandas中的基本基本索引。这是一个不可变数组实现有序,可切分集。在0.18.0之前,Int64Index将为所有NDFrame对象提供默认索引。

RangeIndex是版本0.18.0中添加的Int64Index的子类,现在为所有NDFrame对象提供默认索引。RangeIndex是可以表示单调有序集的Int64Index的优化版本。这些类似于python 范围类型

Float64Index

注意

从0.14.0开始,Float64Index由原生float64 dtype数组支持。在0.14.0之前,Float64Indexobject dtype数组支持。在后端中使用float64 dtype将算术运算速度提高大约30倍,并且在Float64Index本身上的布尔索引操作大约快两倍。

版本0.13.0中的新功能。

默认情况下,在索引创建中传递浮动或混合整数浮点值时,将自动创建Float64Index这使得纯标签的切片范例使[],ix,loc用于标量索引和切片工作完全相同。

In [140]: indexf = pd.Index([1.5, 2, 3, 4.5, 5])

In [141]: indexf
Out[141]: Float64Index([1.5, 2.0, 3.0, 4.5, 5.0], dtype='float64')

In [142]: sf = pd.Series(range(5), index=indexf)

In [143]: sf
Out[143]: 
1.5    0
2.0    1
3.0    2
4.5    3
5.0    4
dtype: int64

对于[],.ix,.loc的标量选择将始终以标签为基础。整数将匹配相等的浮点索引(例如,3等效于3.0

In [144]: sf[3]
Out[144]: 2

In [145]: sf[3.0]
Out[145]: 2

In [146]: sf.ix[3]
Out[146]: 2

In [147]: sf.ix[3.0]
Out[147]: 2

In [148]: sf.loc[3]
Out[148]: 2

In [149]: sf.loc[3.0]
Out[149]: 2

唯一的位置索引是通过iloc

In [150]: sf.iloc[3]
Out[150]: 3

未找到的标量索引将引发KeyError

对于[],ix,loc和ALWAYS位置,iloc的值始终对索引的值进行切片。

In [151]: sf[2:4]
Out[151]: 
2.0    1
3.0    2
dtype: int64

In [152]: sf.ix[2:4]
Out[152]: 
2.0    1
3.0    2
dtype: int64

In [153]: sf.loc[2:4]
Out[153]: 
2.0    1
3.0    2
dtype: int64

In [154]: sf.iloc[2:4]
Out[154]: 
3.0    2
4.5    3
dtype: int64

在浮点索引中,允许使用浮点数进行切片

In [155]: sf[2.1:4.6]
Out[155]: 
3.0    2
4.5    3
dtype: int64

In [156]: sf.loc[2.1:4.6]
Out[156]: 
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'>

此外,在非浮点索引上使用浮点索引器处理.ix将基于标签,从而强制索引。

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

In [158]: s2
Out[158]: 
a    1
b    2
c    3
dtype: int64

In [159]: s2.ix[1.0] = 10

In [160]: s2
Out[160]: 
a       1
b       2
c       3
1.0    10
dtype: int64

以下是使用此类型索引的典型用例。想象一下,你有一个有点不规则的timedelta样索引方案,但数据记录为浮点数。这可以是例如毫秒偏移。

In [161]: 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 [162]: dfir
Out[162]: 
               A         B
0.0     0.997289 -1.693316
250.0  -0.179129 -1.598062
500.0   0.936914  0.912560
750.0  -1.003401  1.632781
1000.0 -0.724626  0.178219
1000.4  0.310610 -0.108002
1250.5 -0.974226 -1.147708
1500.6 -2.281374  0.760010
1750.7 -0.742532  1.533318
2000.8  2.495362 -0.432771
2250.9 -0.068954  0.043520

对于所有选择运算符,选择操作将始终以值为基础工作。

In [163]: dfir[0:1000.4]
Out[163]: 
               A         B
0.0     0.997289 -1.693316
250.0  -0.179129 -1.598062
500.0   0.936914  0.912560
750.0  -1.003401  1.632781
1000.0 -0.724626  0.178219
1000.4  0.310610 -0.108002

In [164]: dfir.loc[0:1001,'A']
Out[164]: 
0.0       0.997289
250.0    -0.179129
500.0     0.936914
750.0    -1.003401
1000.0   -0.724626
1000.4    0.310610
Name: A, dtype: float64

In [165]: dfir.loc[1000.4]
Out[165]: 
A    0.310610
B   -0.108002
Name: 1000.4, dtype: float64

然后,您可以轻松挑出前1秒(1000毫秒)的数据。

In [166]: dfir[0:1000]
Out[166]: 
               A         B
0.0     0.997289 -1.693316
250.0  -0.179129 -1.598062
500.0   0.936914  0.912560
750.0  -1.003401  1.632781
1000.0 -0.724626  0.178219

当然,如果你需要基于整数的选择,然后使用iloc

In [167]: dfir.iloc[0:5]
Out[167]: 
               A         B
0.0     0.997289 -1.693316
250.0  -0.179129 -1.598062
500.0   0.936914  0.912560
750.0  -1.003401  1.632781
1000.0 -0.724626  0.178219