在 Pandas 中重命名 MultiIndex 列

95
df = pd.DataFrame([[1,2,3], [10,20,30], [100,200,300]])
df.columns = pd.MultiIndex.from_tuples((("a", "b"), ("a", "c"), ("d", "f")))
df

返回

     a         d
     b    c    f
0    1    2    3
1   10   20   30
2  100  200  300

df.columns.levels[1]

返回值

Index([u'b', u'c', u'f'], dtype='object')

我想将"f"重命名为"e"。根据pandas.MultiIndex.rename,我运行以下代码:

df.columns.rename(["b1", "c1", "f1"], level=1)

但它引发了一些问题。

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-110-b171a2b5706c> in <module>()
----> 1 df.columns.rename(["b1", "c1", "f1"], level=1)

C:\Users\USERNAME\AppData\Local\Continuum\Miniconda2\lib\site-packages\pandas\indexes\base.pyc in set_names(self, names, level, inplace)
    994         if level is not None and not is_list_like(level) and is_list_like(
    995                 names):
--> 996             raise TypeError("Names must be a string")
    997 
    998         if not is_list_like(names) and level is None and self.nlevels > 1:

TypeError: Names must be a string

我使用的是Python 2.7.12 | Continuum Analytics,Inc. |(默认,2016年6月29日,11:07:13)[MSC v.1500 64位(AMD64)]pandas 0.19.1


8
另一件你不能做的事是 df.rename(columns={('d', 'f'): ('e', 'g')}),尽管它看起来是正确的。换句话说:.rename() 不会像人们期望的那样工作,因为即使每列的键都是一个元组,但 pandas 中的实现是由两个列表 df.keys().levelsdf.keys().labels 组成的。如果你不想改变该名称的所有出现次数,更改一列的键可能需要将元素附加到 levels - Lukas
7个回答

85

使用 set_levels 方法:

In [22]:
df.columns.set_levels(['b1','c1','f1'],level=1,inplace=True)
df

Out[22]:
     a         d
    b1   c1   f1
0    1    2    3
1   10   20   30
2  100  200  300

rename用于设置索引的名称,不会重命名列名:

In [26]:
df.columns = df.columns.rename("b1", level=1)
df

Out[26]:
      a         d
b1    b    c    f
0     1    2    3
1    10   20   30
2   100  200  300
这就是你出现错误的原因。

6
在Python3中,可以使用df.index.set_levels(['b1','c1','f1'],level=1,inplace=True)命令。该命令可用于设置数据框的索引级别,并将索引级别1设置为'b1'、'c1'和'f1'。注意要加上inplace=True参数才能直接在原始数据框上进行修改。 - gies0r
能否在不打印数据框的情况下访问列名? - Antonio Sesto
@AntonioSesto 是的。df.columns 用于标签,df.columns.names 用于级别名称。 - fantabolous
我无法让这个工作。我得到 TypeError: set_levels() got an unexpected keyword argument 'inplace' 错误。当前文档 上没有显示 inplace 参数。是否有所更改? - Bill
这个操作有效:df.columns = df.columns.set_levels(['b1', 'c1', 'f1'], level=1) - Bill

66

在 pandas 0.21.0+ 版本中,请使用参数 level=1

d = dict(zip(df.columns.levels[1], ["b1", "c1", "f1"]))
print (d)
{'c': 'c1', 'b': 'b1', 'f': 'f1'}

df = df.rename(columns=d, level=1)
print (df)
     a         d
    b1   c1   f1
0    1    2    3
1   10   20   30
2  100  200  300

44
你可以直接使用pandas.DataFrame.rename()。假设你有以下数据框:
print(df)

     a         d
     b    c    f
0    1    2    3
1   10   20   30
2  100  200  300
df = df.rename(columns={'f': 'f1', 'd': 'd1'})
print(df)

     a        d1
     b    c   f1
0    1    2    3
1   10   20   30
2  100  200  300

你看,列名映射器与级别无关。

假设你有以下数据框:

     a         d
     b    f    f
0    1    2    3
1   10   20   30
2  100  200  300

如果您想重命名 a 下的 f,您可以执行

df.columns = df.columns.values
df.columns = pd.MultiIndex.from_tuples(df.rename(columns={('a', 'f'): ('a', 'af')}))
# or in one line
df.columns = pd.MultiIndex.from_tuples(df.set_axis(df.columns.values, axis=1)
                                       .rename(columns={('a', 'f'): ('a', 'af')}))
print(df)

     a         d
     b   af    f
0    1    2    3
1   10   20   30
2  100  200  300

请问您能否解释一下为什么在最后一个案例中需要使用 df.columns = df.columns.values - Thomas Hilger
2
@ThomasHilger 将MultiIndex转换为元组列表,因为元组可以在“rename”中匹配。另一个选项是使用pandas.MultiIndex.to_flat_index - Ynjxsjmh
我原本期望这个可以用来将 a 中的 f 改名为 af: df.rename(columns={('a', 'f'): ('a', 'af')});为什么会失败? - Attila the Fun
1
@AttilatheFun,“MultiIndex”与元组不同。 - Ynjxsjmh

14

我认为你的答案缺少参数 level=1 - normanius

10

另一件你不能做的事情是 df.rename(columns={('d', 'f'): ('e', 'g')}),尽管它看起来是正确的。换句话说:.rename() 不会按照人们所期望的方式进行操作,<...>

-- 来自 Lukas 的评论

“hacky”方法大致如下(适用于 pandas 1.0.5)

def rename_columns(df, columns, inplace=False):
    """Rename dataframe columns.

    Parameters
    ----------
    df : pandas.DataFrame
        Dataframe.
    columns : dict-like
        Alternative to specifying axis. If `df.columns` is
        :obj: `pandas.MultiIndex`-object and has a few levels, pass equal-size tuples.

    Returns
    -------
    pandas.DataFrame or None
        Returns dataframe with modifed columns or ``None`` (depends on `inplace` parameter value).
    
    Examples
    --------
    >>> columns = pd.Index([1, 2, 3])
    >>> df = pd.DataFrame([[1, 2, 3], [10, 20, 30]], columns=columns)
    ...     1   2   3
    ... 0   1   2   3
    ... 1  10  20  30
    >>> rename_columns(df, columns={1 : 10})
    ...    10   2   3
    ... 0   1   2   3
    ... 1  10  20  30
    
    MultiIndex
    
    >>> columns = pd.MultiIndex.from_tuples([("A0", "B0", "C0"), ("A1", "B1", "C1"), ("A2", "B2", "")])
    >>> df = pd.DataFrame([[1, 2, 3], [10, 20, 30]], columns=columns)
    >>> df
    ...    A0  A1  A2
    ...    B0  B1  B2
    ...    C0  C1
    ... 0   1   2   3
    ... 1  10  20  30
    >>> rename_columns(df, columns={("A2", "B2", "") : ("A3", "B3", "")})
    ...    A0  A1  A3
    ...    B0  B1  B3
    ...    C0  C1
    ... 0   1   2   3
    ... 1  10  20  30
    """
    columns_new = []
    for col in df.columns.values:
        if col in columns:
            columns_new.append(columns[col])
        else:
            columns_new.append(col)
    columns_new = pd.Index(columns_new, tupleize_cols=True)

    if inplace:
        df.columns = columns_new
    else:
        df_new = df.copy()
        df_new.columns = columns_new
        return df_new

所以只需要
>>> df = pd.DataFrame([[1,2,3], [10,20,30], [100,200,300]])
>>> df.columns = pd.MultiIndex.from_tuples((("a", "b"), ("a", "c"), ("d", "f")))
>>> rename_columns(df, columns={('d', 'f'): ('e', 'g')})
...      a         e
...      b    c    g
... 0    1    2    3
... 1   10   20   30
... 2  100  200  300

请问Pandas团队对此有何看法?为什么这种行为不是默认设置?


这似乎只允许您更改第二级,因此如果您想将 ("a", "c") 更改为 ("b", "c"),则无法实现。我不确定为什么会这样,但我有一个特定的用例需要这种处理方式。有任何线索吗? - double0darbo
我不得不走一条迂回的路:pd.MultiIndex.from_tuples( [("b", "c") if t == ("a", "c") else t for t in pd.MultiIndex.from_tuples(df.columns)]) - double0darbo

5

另一种方法是使用pandas.Series.map和下列lambda函数来完成

df.columns = df.columns.map(lambda x: (x[0], "e") if x[1] == "f" else x)

[Out]:
     a         d
     b    c    e
0    1    2    3
1   10   20   30
2  100  200  300

2
这个答案被低估了。 - Shadi

3

使用字典重命名元组

由于多级索引将值存储为元组,而Python字典接受元组作为键和值,因此我们可以使用字典来替换它们。

mapping_dict = {("d","f"):("d","e")}

# Dictionary allows using tuples as keys and values
def rename_tuple(tuple_, dict_):
    """Replaces tuple if present in tuple dict"""
    if tuple_ in dict_.keys():
        return dict_[tuple_]
    return tuple_

# Rename chosen elements from list of tuples from df.columns
altered_index_list = [rename_tuple(tuple_,mapping_dict) for tuple_ in df.columns.to_list()]

# Update columns with new renamed columns
df.columns = pd.Index(altered_index_list)

返回预期的数据框

     a         d
     b    c    e
0    1    2    3
1   10   20   30
2  100  200  300

在函数中进行聚合

这可以在一个函数中进行聚合,以简化操作。

def rename_multi_index(index,mapper):
    """Renames pandas multi_index"""
    return pd.Index([rename_tuple(tuple_,mapper) for tuple_ in index])

# And now simply do
df.columns = rename_multi_index(df.columns,mapping_dict)

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