如何更新 MultiIndex pandas DataFrame 的子集

Posted

技术标签:

【中文标题】如何更新 MultiIndex pandas DataFrame 的子集【英文标题】:How to update a subset of a MultiIndexed pandas DataFrame 【发布时间】:2013-07-07 08:11:33 【问题描述】:

我正在使用 MultiIndexed pandas DataFrame,并希望将 DataFrame 的子集乘以某个数字。

它与 this 相同,但具有 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)

我不知道这个错误是什么意思,但我觉得我是在鼹鼠山上造山。有谁知道这样做的正确方法?

提前致谢, 抢

【问题讨论】:

对于那些在 MultiIndexing 上寻找更详细解决方案的用户,check this answer below 如果您有兴趣了解更多关于切片和过滤多索引数据帧的信息,请查看我的帖子:How do I slice or filter MultiIndex DataFrame levels?。 【参考方案1】:

注意:即将发布 0.13 a drop_level argument has been added to xs(感谢这个问题!):

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

另一种选择是使用 select(它提取相同数据的子 DataFrame(副本),即它具有相同的索引,因此可以正确更新):

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)

另一个选项是使用应用:

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

另一个选项是将“天”级别提升为列,然后使用应用。

【讨论】:

是的,这是一个可行的解决方案,但使用lambdas 处理这样一个(看似)简单的任务感觉不对。 SQL 等效项是UPDATE table SET col = col * 2 WHERE day = 'sat'。我想知道xs 是否应该包含一个选项来保持选中的索引级别。 @LondonRob 是的(更新是我不太喜欢的东西,lambdas 也不错),我想知道同样的事情,也许值得作为一个问题添加(如果我们'再快一点,下周可能会在 0.12 出)。绝对有一些改进的余地。 作为旁注,似乎使用 MultiIndex 会使一切变得更加困难。我真的无法弄清楚它使什么变得更容易! 哈哈!是的。好吧,那太棒了,但我想开发人员会争先恐后地完成工作,而不是想要添加新功能。 @LondonRob d[d.index.get_level_values('day') == 'sat'] *= 2 我认为更快...【参考方案2】:

多索引详解

您可以使用.loc 索引器从具有MultiIndex 的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 个级别,每个级别都有一个名称(yearflavourday)。级别也隐式地给出了从外部从 0 开始的整数位置。因此,year 级别可以引用为0flavour1,以及day2

从 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 级以外的任何级别进行选择都比较复杂。让我们首先选择一个特定的组合,例如年份2008bananasat。为此,您将组合作为元组传递给.loc

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

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

我总是像上面那样使用括号,但 Python 会自动将任何以逗号分隔的值集解释为元组,因此以下将得到相同的结果:

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

所有级别都被删除并返回一个系列。我们可以通过在列表中传递元组来保持级别:

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

但是,这会遇到“索引器过多的 IndexError”。可以通过三种不同的方式从特定级别选择所有值。

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 @ScottBoston 是的,在我 read the advanced indexing docs again 之前我也不知道。你必须从那里往下走一页左右。看起来它是从 2014 年 5 月开始添加的 way back in 0.14 @TedPetrou 你能添加选择器来允许在特定级别设置所有值吗? 我正在尝试df.loc[('2008', 'banana', 'sat'), 'sales'] 并收到此错误:KeyError: "Passing list-likes to .loc or [] with any missing labels。缺少以下标签:索引( ['00'], dtype='object', name='master_part_no')。见pandas.pydata.org/pandas-docs/stable/user_guide/…"

以上是关于如何更新 MultiIndex pandas DataFrame 的子集的主要内容,如果未能解决你的问题,请参考以下文章

Pandas DataFrame - 如何检索 MultiIndex 级别的特定组合

如何检查pandas MultiIndex中是不是存在列

如何使用 pandas multiIndex 查询多列

合并pandas DataFrames时如何保留列MultiIndex值

使用 MultiIndex 时如何在 Pandas 中使用转换器

pandas:将两个 DataFrame 与已排序的 MultiIndex 连接起来,使得结果具有已排序的 MultiIndex