如何从 DataFrame 中的命名列级别选择值的子集?

Posted

技术标签:

【中文标题】如何从 DataFrame 中的命名列级别选择值的子集?【英文标题】:How to select a subset of values from a named column level in a DataFrame? 【发布时间】:2018-01-14 03:33:02 【问题描述】:

假设我们有一个带有多级列标题的DataFrame

level_0         A                   B                   C          
level_1         P                   P                   P          
level_2         x         y         x         y         x         y
0       -1.027155  0.667489  0.314387 -0.428607  1.277167 -1.328771
1        0.223407 -1.713410  0.480903 -3.517518 -1.412756  0.718804

我想从指定级别中选择列列表。

required_columns = ['A', 'B']
required_level = 'level_0'

方法一:(弃用,取而代之 df.loc)

print df.select(lambda x: x[0] in required_columns, axis=1)

这样做的问题是我必须用0指定级别。如果我使用级别的名称,它会失败。

方法二:

print df.xs('A', level=required_level, axis=1)

问题在于我只能指定一个值。如果我使用 ['A', 'B'] 会失败。

方法三:

print df.ix[:, df.columns.get_level_values(required_level).isin(required_columns)]

这可行,但不如前两种方法简洁! :)

问题:

如何让方法 1 或 2 起作用?或者,有没有更pythonic的方式?

MWE:

import pandas as pd
import numpy as np

header = pd.MultiIndex.from_product([['A', 'B', 'C'],
                                     ['P'],
                                     ['x', 'y']],
                                    names=['level_0',
                                           'level_1',
                                           'level_2'])
df = pd.DataFrame(
    np.random.randn(2, 6),
    columns=header
)

required_columns = ['A', 'B']
required_level = 'level_0'

print df
print df.select(lambda x: x[0] in required_columns, axis=1)
print df.xs('A', level=required_level, axis=1)
print df.ix[:, df.columns.get_level_values(required_level).isin(required_columns)]

相关问题

    pandas dataframe select columns in multiindex Giving a column multiple indexes/headers

【问题讨论】:

虽然没有更好的方法令人惊讶,但我认为您的方法 3 是目前​​最好的方法。可读性很强。 fyi,ix 在 pandas 0.20.0 pandas.pydata.org/pandas-docs/stable/whatsnew.html#deprecate-ix 中已弃用 df.select() 已弃用,取而代之的是 df.loc(),因为 pandas 0.21 【参考方案1】:

你可以使用reindex:

df.reindex(columns=required_columns, level=required_level)

结果输出:

level_0         A                   B          
level_1         P                   P          
level_2         x         y         x         y
0       -1.265558  0.681565 -0.553084 -1.340652
1        1.705043 -0.512333 -0.785326  0.968391 

【讨论】:

reindex 很好,但当然它只会返回数据框的视图。任何后续修改都不会反映在原件中。这可能很好,但实际上取决于用例和 OP 实际试图实现的目标。【参考方案2】:

您是否考虑过使用IndexSlice?它通常需要首先对列进行排序(在原始数据框中,它们已经排序)。

df.sort_index(axis=1, inplace=True)
>>> df.loc[:, pd.IndexSlice[required_columns, :, :]]
# Output:
# level_0         A                   B          
# level_1         P                   P          
# level_2         x         y         x         y
# 0        0.079368 -1.083421  0.129979 -0.558004
# 1       -0.157843 -1.176632 -0.219833  0.935364

更新

您选择的方法实际上取决于您首先选择数据的原因以及是否需要通过您的选择来修改原始数据。

首先,为了使示例更具挑战性,让我们使用一个 MultiIndex 数据框,该数据框在不同级别具有相同的值且未排序。

required_columns = ['A', 'B']  # Per original question.
required_level = 'level_0'  # Per original question.

np.random.seed(0)
idx = pd.MultiIndex.from_product([list('BAC'), list('AB')], names=['level_0', 'level_1'])
df = pd.DataFrame(np.random.randn(2, len(idx)), columns=idx)
>>> df
# Output:
# level_0         B                   A                   C          
# level_1         A         B         A         B         A         B
# 0        1.764052  0.400157  0.978738  2.240893  1.867558 -0.977278
# 1        0.950088 -0.151357 -0.103219  0.410599  0.144044  1.454274

返回数据的副本

如果您只需要直接查看数据或用于管道中的后续计算,那么@root 提到并在文档中讨论here 的reindex 方法是一个不错的选择。

df2 = df.reindex(columns=required_columns, level=required_level)
>>> df2
# Output:
# level_0         A                   B          
# level_1         A         B         A         B
# 0        0.978738  2.240893  1.764052  0.400157
# 1       -0.103219  0.410599  0.950088 -0.151357

但是,如果您尝试修改此数据框,则更改不会反映在您的原始数据中。

df2.iloc[0, 0] = np.nan
>>> df  # Check values in original dataframe.  None are `NaN`.
# Output:
# level_0         B                   A                   C          
# level_1         A         B         A         B         A         B
# 0        1.764052  0.400157  0.978738  2.240893  1.867558 -0.977278
# 1        0.950088 -0.151357 -0.103219  0.410599  0.144044  1.454274

修改数据

另一种方法是使用带有loc 的布尔索引。您可以使用条件列表推导和get_level_values 一起选择所需的列:

cols = [col in required_columns for col in df.columns.get_level_values(required_level)]
>>> df.loc[:, cols]
# Output:
# level_0         B                   A          
# level_1         A         B         A         B
# 0        1.764052  0.400157  0.978738  2.240893
# 1        0.950088 -0.151357 -0.103219  0.410599

如果您要切片索引而不是列,那么显然需要在上面的代码 sn-p 中将 df.columns.get_level_values 更改为 df.index.get_level_values

您也可以使用loc修改原始数据:

df2 = df.copy()
df2.loc[:, cols] = 1
>>> df2
# Output:
# level_0  B     A            C          
# level_1  A  B  A  B         A         B
# 0        1  1  1  1  1.867558 -0.977278
# 1        1  1  1  1  0.144044  1.454274

结论

虽然select 是返回多索引数据视图的不错选择,但使用loc 的布尔索引允许您查看或修改数据。

我将使用上述loc 方法,而不是方法1方法2

从 pandas 0.20.0 开始,ix 方法已被弃用。我不推荐方法3

【讨论】:

既然是第一个,我觉得df.loc[:, pd.IndexSlice[required_columns]]也可以 是的,但是使用:, : 可以更清楚地看到还有两个额外的级别没有被切片(在我看来)。 它可以完成这项工作,但我希望能够按名称识别级别,正如我在问题中所说的那样。原因是我可能并不总是知道有多少关卡,或者我在追求哪一个关卡。我只知道关卡的名称。 如果您知道required_columns,那么@ayhan 的评论为什么不起作用?事实上,即使df.loc[:, slice('A','B')] 也是可行的(不需要IndexSlice)。 @andrew_reece 因为您可以在不同的索引级别上拥有相同的required_columns(例如,列A 可以同时位于“level_1”和“level_2”中)。

以上是关于如何从 DataFrame 中的命名列级别选择值的子集?的主要内容,如果未能解决你的问题,请参考以下文章

SQL-当连接不涵盖它们时,如何添加与ID直接对应的命名列

如何在pandas dataframe中为新列添加值?

使用 Eloquent 在 mysql 中左连接后分别获取相同的命名列

重命名 Pyspark Dataframe 中的未命名列

Pandas:从 dict 在 DataFrame 中创建命名列

在python中怎么取dataframe索引值的方法是啥?