从DataFrame的多级索引中完全删除一个索引标签

5

假设我有这个多索引数据框:

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

看起来还不错,但是一些奇怪的方式仍然有条形码

>>> 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)
>>> 

我应该如何清除这个索引标签上的任何残留物呢?

那听起来像是个bug,不是吗? - Ami Tavory
@AmiTavory 可能吧。无论如何,问题仍然存在。 :-) - joaquin
当然可以!重点是,除了找到解决方法之外,你可能会将其报告为一个 bug?发现它的功劳归于你。 - Ami Tavory
2个回答

4

这明显是一个bug。

s1.index.tolist()返回的值不包含"bar"。

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

s1["bar"] 返回一个空的Series。

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

同样适用于数据框(dataframe)。
>>> 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
+1个好点。即使我不能解决发布的问题,我认为你在这里提供了一些线索,可以帮助解决我的实际编码问题。 - joaquin
@joaquin,我添加了一个自定义函数,可以为您完成所有操作。它会删除索引,并使用标准的 Pandas 索引来防止重新创建数据帧,仅重置索引。 - Alex Huszagh

3

请参考这里的长篇讨论。

总的来说,什么时候重新计算层级并不明显,因为用户所执行的操作是未知的(从索引的角度考虑)。例如,假设您正在删除,然后通过索引将一个值添加到层级中。这将非常浪费且计算密集。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接