如何更新多级索引的pandas DataFrame 的子集

12

我正在使用一个多级索引的pandas DataFrame,并想将DataFrame的子集乘以某个数字。

这与此问题相同,只是有了MultiIndex。

>>> d = pd.DataFrame({'year':[2008,2008,2008,2008,2009,2009,2009,2009], 
                      'flavour':['strawberry','strawberry','banana','banana',
                      'strawberry','strawberry','banana','banana'],
                      'day':['sat','sun','sat','sun','sat','sun','sat','sun'],
                      'sales':[10,12,22,23,11,13,23,24]})

>>> d = d.set_index(['year','flavour','day'])                  

>>> d
                     sales
year flavour    day       
2008 strawberry sat     10
                sun     12
     banana     sat     22
                sun     23
2009 strawberry sat     11
                sun     13
     banana     sat     23
                sun     24

到目前为止,一切都很好。但假设我发现所有星期六的数字只有应该有的一半!我想把所有 sat 的销售额乘以2。

我第一次尝试是:

sat = d.xs('sat', level='day')
sat = sat * 2
d.update(sat)

但这不起作用,因为变量sat已经失去了索引的day级别:

>>> sat
                 sales
year flavour          
2008 strawberry     20
     banana         44
2009 strawberry     22
     banana         46

所以Pandas不知道如何将新的销售数据与旧的数据框联接。

我简单尝试了一下:

>>> sat = d.xs('sat', level='day', copy=False)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Python27\lib\site-packages\pandas\core\frame.py", line 2248, in xs
    raise ValueError('Cannot retrieve view (copy=False)')
ValueError: Cannot retrieve view (copy=False)

我不知道那个错误是什么意思,但感觉自己小题大做了。有人知道正确的做法吗?

先行致谢, 罗布


1
对于那些寻求更详细的MultiIndexing解决方案的用户,请查看下面的答案。链接 - Ted Petrou
如果您对切片和过滤多级索引的数据框更感兴趣,请查看我的文章:如何切片或过滤MultiIndex DataFrame levels? - cs95
2个回答

12

注意:在即将发布的0.13版本中xs方法添加了一个drop_level参数(感谢这个问题!):

In [42]: df.xs('sat', level='day', drop_level=False)
Out[42]:
                     sales
year flavour    day
2008 strawberry sat     10

另一个选项是使用select函数(它提取原始数据的子数据帧(副本),即具有相同的索引,因此可以正确更新):

In [11]: d.select(lambda x: x[2] == 'sat') * 2
Out[11]:
                     sales
year flavour    day
2008 strawberry sat     20
     banana     sat     44
2009 strawberry sat     22
     banana     sat     46

In [12]: d.update(d.select(lambda x: x[2] == 'sat') * 2)

另外一种选项是使用apply函数:

In [21]: d.apply(lambda x: x*2 if x.name[2] == 'sat' else x, axis=1)

另一个选择是使用get_level_values(这可能是其中最有效的方法)

In [22]: d[d.index.get_level_values('day') == 'sat'] *= 2

另一种选项是将“日”级别提升为列,然后使用apply函数。


1
作为一个旁注,使用MultiIndex似乎使一切变得更加困难。我真的无法弄清它究竟使什么变得更容易了!! - LondonRob
我认为它加速的是索引,但你说得对,它会使计算变得更困难(也许在计算中使用索引不是最佳实践?) - Andy Hayden
1
@LondonRob 我认为 d[d.index.get_level_values('day') == 'sat'] *= 2 更快... - Andy Hayden
不是有意打扰,但是 d.apply(lambda x: x*2 if x.name[2] == 'sat' else x, axis=1) 是如何工作的?参数 x 是 d.index.names 吗? - ileadall42
@Tangfeifan 有点类似,它是一行/序列,而名称属性(对于MultiIndex)是一个元组。 - Andy Hayden
显示剩余4条评论

9

多级索引详解

您可以使用.loc索引器从具有多级索引的DataFrame中选择数据子集。假设我们有原始问题中的数据:

                     sales
year flavour    day       
2008 strawberry sat     10
                sun     12
     banana     sat     22
                sun     23
2009 strawberry sat     11
                sun     13
     banana     sat     23
                sun     24

这个DataFrame在其索引中有3个级别,每个级别都有一个名称(`year`、`flavour`和`day`)。这些级别也隐含地给出了从外部开始的整数位置。因此,可以用`0`引用`year`级别,用`1`引用`flavour`,用`2`引用`day`。

从第0级(最外层)选择

第0级是最容易进行选择的级别。例如,如果我们想要选择只有2008年的数据,我们可以执行以下操作:
df.loc[2008]

                sales
flavour    day       
strawberry sat     10
           sun     12
banana     sat     22
           sun     23

这会删除最外层的索引级别。如果您想保留最外层,请将您的选择作为列表(或切片)传递:
df.loc[[2008]]  # df.loc[2008:2008] gets the same result

                     sales
year flavour    day       
2008 strawberry sat     10
                sun     12
     banana     sat     22
                sun     23

从其他层级进行选择

从除了第0层之外的任何层级进行选择更加复杂。让我们以选择特定的组合为例,比如年份 2008香蕉sat。要做到这一点,您需要将该组合作为元组传递给 .loc

df.loc[(2008, 'banana', 'sat')]

sales    22
Name: (2008, banana, sat), dtype: int64

我通常使用上述括号,但Python会自动将任何逗号分隔的值集解释为元组,因此以下代码将得到相同的结果:
df.loc[2008, 'banana', 'sat']

所有的级别都被删除了,而且返回了一个Series。我们可以通过将元组放在列表中来保留这些级别:
df.loc[[(2008, 'banana', 'sat')]]

                  sales
year flavour day       
2008 banana  sat     22

从特定层次选择多个值

前面的示例从每个层次中进行了单一选择。可以使用列表来包含所需层次的所有值。例如,如果我们想选择所有具有2008年和2009年、香蕉味以及在周六和周日发生的行,则可以执行以下操作:

df.loc[([2008, 2009], 'banana', ('sat','sun'))]

                  sales
year flavour day       
2008 banana  sat     22
             sun     23
2009 banana  sat     23
             sun     24

再次强调,您不必使用括号将整个选择包裹起来表示元组,只需执行以下操作:

df.loc[[2008, 2009], 'banana', ('sat','sun')]

选择特定层级中的所有值。

您可能想要选择特定层级中的所有值。例如,让我们尝试选择所有年份,所有口味和仅限星期六。您可能认为以下内容可以实现:

df.loc[:, :, 'sat']

但是,这会遇到一个“太多索引器的索引错误”。有三种不同的方法可以从特定级别中选择所有值。

  • df.loc[(slice(None), slice(None), 'sat'), :]
  • df.loc(axis=0)[:, :, 'sat']
  • df.loc[pd.IndexSlice[:, :, 'sat'], :]

所有三种方法都产生以下结果:

                     sales
year flavour    day       
2008 strawberry sat     10
     banana     sat     22
2009 strawberry sat     11
     banana     sat     23

df.loc(axis=0)[:, :, 'sat'] 哇...好的。你能解释一下或者指导我如何使用 loc 的轴参数吗?+1 - Scott Boston
@ScottBoston 是的,我之前也不知道,直到我再次阅读高级索引文档。你需要从那里往下翻一页左右。看起来这个功能是在2014年5月的v0.14版本中添加的。 - Ted Petrou
@TedPetrou,请问您能否添加选择器,以允许在特定级别设置所有值? - Steve Lorimer
我正在尝试运行 df.loc[('2008', 'banana', 'sat'), 'sales'],但是出现了以下错误: KeyError: "不再支持将缺失标签的列表传递给.loc或[]。以下标签丢失:Index(['00'], dtype='object', name='master_part_no')。请参见https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#deprecate-loc-reindex-listlike" - Jonathan Biemond

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