【问题标题】:Completely remove one index label from a multiindex, in a dataframe在数据框中从多索引中完全删除一个索引标签
【发布时间】:2015-09-03 01:45:43
【问题描述】:

鉴于我有这个多索引数据框:

>>> import pandas as p 
>>> import numpy as np
... 
>>> arrays = [np.array(['bar', 'bar', 'baz', 'baz', 'foo', 'foo']),
...          np.array(['one', 'two', 'one', 'two', 'one', 'two'])]
... 
>>> s = p.Series(np.random.randn(6), index=arrays)
>>> s
bar  one   -1.046752
     two    2.035839
baz  one    1.192775
     two    1.774266
foo  one   -1.716643
     two    1.158605
dtype: float64

我应该怎么做才能消除索引栏?
我试过drop

>>> s1 = s.drop('bar')
>>> s1
baz  one    1.192775
     two    1.774266
foo  one   -1.716643
     two    1.158605
dtype: float64

看起来不错,但 bar 仍然以某种奇怪的方式存在:

>>> s1.index
MultiIndex(levels=[[u'bar', u'baz', u'foo'], [u'one', u'two']],
           labels=[[1, 1, 2, 2], [0, 1, 0, 1]])
>>> s1['bar']
Series([], dtype: float64)
>>> 

我怎样才能摆脱这个索引标签上的任何残留物?

【问题讨论】:

  • 这听起来像一个错误,不是吗?
  • @AmiTavory 也许吧。无论如何,问题仍然存在。 :-)
  • 绝对!重点是,也许——除了找到解决方法——你可能会将它作为一个错误提交?找到它的功劳是你的。

标签: python pandas multi-index


【解决方案1】:

见长讨论here

归根结底,何时重新计算级别并不明显,因为用户正在进行的操作是未知的(从索引的角度考虑)。例如,假设您正在删除,然后将一个值添加到一个级别(例如通过索引)。这将是非常浪费的并且有点计算密集型。

In [11]: s1.index
Out[11]: 
MultiIndex(levels=[[u'bar', u'baz', u'foo'], [u'one', u'two']],
           labels=[[1, 1, 2, 2], [0, 1, 0, 1]])

这是实际的索引本身。

In [12]: s1.index.values
Out[12]: array([('baz', 'one'), ('baz', 'two'), ('foo', 'one'), ('foo', 'two')], dtype=object)

In [13]: s1.index.get_level_values(0)
Out[13]: Index([u'baz', u'baz', u'foo', u'foo'], dtype='object')

In [14]: s1.index.get_level_values(1)
Out[14]: Index([u'one', u'two', u'one', u'two'], dtype='object')

如果您真的觉得有必要“摆脱”已删除的级别,则只需重新创建索引。但是,它根本没有害处。这些因式分解(例如标签)对用户是隐藏的(是的,它们是显示出来的,但老实说,这更像是一个令人困惑的痛点,因此提出了这个问题)。

In [15]: pd.MultiIndex.from_tuples(s1.index.values)
Out[15]: 
MultiIndex(levels=[[u'baz', u'foo'], [u'one', u'two']],
           labels=[[0, 0, 1, 1], [0, 1, 0, 1]])

【讨论】:

    【解决方案2】:

    绝对看起来像一个错误。

    s1.index.tolist() 返回预期值,不带“bar”。

    >>> s1.index.tolist()
    [('baz', 'one'), ('baz', 'two'), ('foo', 'one'), ('foo', 'two')]
    

    s1["bar"] 返回一个空系列。

    >>> s1["bar"]
    Series([], dtype: float64)
    

    覆盖它的标准方法似乎也不起作用:

    >>> del s1["bar"] 
    >>> s1["bar"]
    Series([], dtype: float64)
    >>> s1.__delitem__("bar")
    >>> s1["bar"]
    Series([], dtype: float64)
    

    但是,正如预期的那样,尝试获取新密钥会调用 KeyError:

    >>> s1["booz"]
    ... KeyError: 'booz'
    

    主要区别在于当您实际查看 pandas.core.index.py 中两者之间的源代码时

    class MultiIndex(Index):
        ...
    
        def _get_levels(self):
            return self._levels
    
        ...
    
        def _get_labels(self):
            return self._labels
    
        # ops compat
        def tolist(self):
            """
            return a list of the Index values
            """
            return list(self.values)
    

    因此,index.tolist() 和 _labels 访问的不是同一条共享信息,事实上,它们甚至不接近。

    因此,我们可以使用它来手动更新生成的索引器。

    >>> s1.index.labels
    FrozenList([[1, 1, 2, 2], [0, 1, 0, 1]])
    >>> s1.index._levels
    FrozenList([[u'bar', u'baz', u'foo'], [u'one', u'two']])
    >>> s1.index.values
    array([('baz', 'one'), ('baz', 'two'), ('foo', 'one'), ('foo', 'two')], dtype=object)
    

    如果我们将其与初始多索引索引进行比较,我们得到

    >>> s.index.labels
    FrozenList([[0, 0, 1, 1, 2, 2], [0, 1, 0, 1, 0, 1]])
    >>> s.index._levels
    FrozenList([[u'bar', u'baz', u'foo'], [u'one', u'two']])
    

    所以 _levels 属性不会更新,而值会更新。

    编辑:覆盖它并不像我想象的那么容易。

    编辑:编写了一个自定义函数来修复此行为

    from pandas.core.base import FrozenList, FrozenNDArray
    
    def drop(series, level, index_name):
        # make new tmp series
        new_series = series.drop(index_name)
        # grab all indexing labels, levels, attributes
        levels = new_series.index.levels
        labels = new_series.index.labels
        index_pos = levels[level].tolist().index(index_name)
        # now need to reset the actual levels
        level_names = levels[level]
        # has no __delitem__, so... need to remake
        tmp_names = FrozenList([i for i in level_names if i != index_name])
        levels = FrozenList([j if i != level else tmp_names
                             for i, j in enumerate(levels)])
        # need to turn off validation
        new_series.index.set_levels(levels, verify_integrity=False, inplace=True)
        # reset the labels
        level_labels = labels[level].tolist()
        tmp_labels = FrozenNDArray([i-1 if i > index_pos else i
                                    for i in level_labels])
        labels = FrozenList([j if i != level else tmp_labels
                             for i, j in enumerate(labels)])
        new_series.index.set_labels(labels, verify_integrity=False, inplace=True)
        return new_series
    

    示例用户:

    >>> s1 = drop(s, 0, "bar")
    >>> s1.index
    MultiIndex(levels=[[u'baz', u'foo'], [u'one', u'two']],
               labels=[[0, 0, 1, 1], [0, 1, 0, 1]])
    >>> s1.index.tolist()
    [('baz', 'one'), ('baz', 'two'), ('foo', 'one'), ('foo', 'two')]
    >>> s1["bar"]
    ...
    KeyError: 'bar'
    

    编辑:这似乎特定于具有多索引的数据帧/系列,因为标准 pandas.core.index.Index 类没有相同的限制。我建议提交错误报告。

    考虑具有标准索引的同一系列:

    >>> s = p.Series(np.random.randn(6))
    >>> s.index
    Int64Index([0, 1, 2, 3, 4, 5], dtype='int64')
    >>> s.drop(0, inplace=True)
    >>> s.index
    Int64Index([1, 2, 3, 4, 5], dtype='int64')
    

    数据框也是如此

    >>> df = p.DataFrame([np.random.randn(6), np.random.randn(6)])
    >>> df.index
    Int64Index([0, 1], dtype='int64')
    >>> df.drop(0, inplace=True)
    >>> df.index
    Int64Index([1], dtype='int64')
    

    【讨论】:

    • +1 好点。即使我无法解决发布的问题,我认为您在这里提供了一些线索,可以帮助解决我的实际编码问题。
    • @joaquin,我添加了一个自定义函数,可以为您完成所有工作。它删除索引,并使用标准 Pandas 索引来防止重新制作数据框,并且只重置索引。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-09-25
    • 1970-01-01
    • 2020-06-09
    • 2017-08-27
    • 2013-05-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多