美文网首页
Python 数据处理(二十九)—— MultiIndex 高级

Python 数据处理(二十九)—— MultiIndex 高级

作者: 名本无名 | 来源:发表于2021-03-06 10:54 被阅读0次

    MultiIndex 的高级索引操作

    从语法上将 MultiIndex.loc 集成在高级索引中是有些挑战性。通常,MultiIndex 的键采用元组的形式。例如

    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
    

    想要获取第一个级别的某一标签的所有元素,并不需要传递元组,例如

    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
    

    传递一个标签或元组列表的工作原理类似于 reindex

    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 中,当涉及到索引时,元组和列表的处理方式并不相同。

    元组被解释为一个多级键,而列表则用来指定多个键。或者换句话说,元组是横向的,列表是纵向的

    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
    

    1 使用切片

    您可以通过提供多个索引器来对 MultiIndex 进行切片

    可以使用 slice(None) 选择该级别的所有内容,没有指定索引的级别默认为 slice(None)

    通常,切片的两段都会包括在内,因为这是标签索引

    在使用 .loc 的时候,最好同时指定索引(行)和标签(列),因为在某些情况下,传递的索引可能会被解析为两个轴而不是 MultiIndex

    你应该使用

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

    而不是

    df.loc[(slice("A1", "A3"), ...)]
    

    例如

    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]
    

    使用切片,列表和标签的基本 MultiIndex 切片操作

    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
    

    您还可以为 .loc 指定 axis 参数,以在某一个轴上传递切片

    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]
    

    此外,您可以使用以下方法设置这些值

    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]
    

    2 横切数据

    DataFramexs() 方法还接受一个 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
    

    使用切片

    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 提供 axis 参数来选择列

    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
    

    使用切片

    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
    

    使用切片

    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
    

    3 高级重建索引和对齐

    pandas 对象的 reindex()align() 方法中使用 level 参数,可以在一个级别上广播值

    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.mean(level=0)
    
    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
    

    4 用 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
    

    5 使用 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
    

    6 重命名索引或多级索引的名称

    rename() 方法可用于重命名 MultiIndex 的标签,通常用于重命名 DataFrame 的列

    renamecolumns 参数允许指定需要重命名的列的字典

    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_axiscolumns 参数可以改变该索引的名称

    In [95]: df.rename_axis(columns="Cols").columns
    Out[95]: RangeIndex(start=0, stop=2, step=1, name='Cols')
    

    renamerename_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'])
    

    您不能通过 level 设置 MultiIndex 的名称

    In [100]: mi.levels[0].name = "name via level"
    ---------------------------------------------------------------------------
    RuntimeError                              Traceback (most recent call last)
    <ipython-input-100-35d32a9a5218> in <module>
    ----> 1 mi.levels[0].name = "name via level"
    
    /pandas/pandas/core/indexes/base.py in name(self, value)
       1241         if self._no_setting_name:
       1242             # Used in MultiIndex.levels to avoid silently ignoring name updates.
    -> 1243             raise RuntimeError(
       1244                 "Cannot set name on a level of a MultiIndex. Use "
       1245                 "'MultiIndex.set_names' instead."
    
    RuntimeError: Cannot set name on a level of a MultiIndex. Use 'MultiIndex.set_names' instead.
    

    使用 Index.set_names() 替代

    相关文章

      网友评论

          本文标题:Python 数据处理(二十九)—— MultiIndex 高级

          本文链接:https://www.haomeiwen.com/subject/slvjqltx.html