如何简单地向Pandas数据框添加一列?

94

假设我有一个数据框长这样:

df = pd.DataFrame(index=list('abcde'), data={'A': range(5), 'B': range(5)})
 df
Out[92]: 
   A  B
a  0  0
b  1  1
c  2  2
d  3  3
e  4  4
假设这个数据框已经存在,我该如何简单地在列索引中添加一个级别'C',使得我可以得到以下结果:
 df
Out[92]: 
   A  B
   C  C
a  0  0
b  1  1
c  2  2
d  3  3
e  4  4

我看到了这样一个SO的答案:python/pandas: how to combine two dataframes into one with hierarchical column index?,但它是将不同的数据帧连接起来,而不是给已经存在的数据帧添加列级别。

10个回答

144

正如@StevenG本人所建议的,更好的答案:

df.columns = pd.MultiIndex.from_product([df.columns, ['C']])

print(df)
#    A  B
#    C  C
# a  0  0
# b  1  1
# c  2  2
# d  3  3
# e  4  4

2
这很好,我喜欢pd.MultiIndex.from_product([df.columns, ['C']]),这样就不必跟踪df.columns的长度了,更加简便。您介意将其添加到答案中,以便我接受吗? - Steven G
1
@StevenG 太棒了,我不知道这个技巧。谢谢,我学到了新东西 :-) - Romain
24
你有什么建议,如何在原始数据框已经具有多级列名的情况下添加另一个级别?我尝试使用from_product()方法添加新级别,但是我收到了这个错误消息:'NotImplementedError:isnull is not defined for MultiIndex'。 - Lenka Vraná
6
pd.MultiIndex.from_product(df.columns.levels + [['C']]) 可以创建一个多级索引,其中包含 df 数据框的列级别和额外的 'C' 级别。 - user3556757
4
对于任何人。在将其用于MultiIndex.from_product之前,我发现将现有的列索引转换为列表可解决“未实现isna”的问题。 pd.MultiIndex.from_product([list(df.columns), ['C']]) - Max
显示剩余5条评论

28

选项 1
set_indexT

df.T.set_index(np.repeat('C', df.shape[1]), append=True).T

选项2
pd.concatkeysswaplevel

pd.concat([df], axis=1, keys=['C']).swaplevel(0, 1, 1)

enter image description here


谢谢,我之前不知道有swap这个函数,它很方便。我测试了一下一个大的数据框,发现使用swap比设置pd.MultiIndex.from_product([df.columns, ['C']])慢了约25%。 - Steven G
没有意外!Romain的答案更快。我加上这个是因为我认为它很有价值。 - piRSquared
13
pd.concat([df], axis=1, keys=['C']) 对于多层次列非常有效。意思是将DataFrame df 作为一个元素,沿着轴1拼接起来,并将其列命名为'C'。 - Justislav Bogevolnov
1
df.columns可以是pd.MultiIndex时,选项2应该是通用情况下的接受答案。 - Josh
pd.concat 的答案很好,因为它不会修改原始的 df。 - BallpointBen
一定要小心使用.T,因为它可能会对已经类型化的列造成一些干扰。通常情况下,.T-.T转换是有损的。 使用seaborn,取df = sns.load_dataset("diamonds")并比较df.info()df.T.T.info();所有列都变成了对象,并且内存使用量增加了五倍! - creanion

18

一个解决方案,为新级别添加名称,且比现有的其他答案更易于阅读:

df['newlevel'] = 'C'
df = df.set_index('newlevel', append=True).unstack('newlevel')

print(df)
#           A  B
# newlevel  C  C
# a         0  0
# b         1  1
# c         2  2
# d         3  3
# e         4  4

7
这段话简短明了,适用于已经具有多级别的列!可以一行代码实现:df.assign(newlevel='C').set_index('newlevel', append=True).unstack('newlevel') - Michele Piccolini
3
如果数据框中有很多行,则会产生每行成本,这是不必要的。 - creanion

11
您可以简单地按以下方式分配列:
>>> df.columns = [df.columns, ['C', 'C']]
>>> df
   A  B
   C  C
a  0  0
b  1  1
c  2  2
d  3  3
e  4  4
>>> 

或者对于未知列长度:

>>> df.columns = [df.columns.get_level_values(0), np.repeat('C', df.shape[1])]
>>> df
   A  B
   C  C
a  0  0
b  1  1
c  2  2
d  3  3
e  4  4
>>> 

1
这是一种灵活的方式,当您想要将任何列表分配为新级别时使用。 - spettekaka

9

MultiIndex 的另一种方式(添加 'E'):

df.columns = pd.MultiIndex.from_tuples(map(lambda x: (x[0], 'E', x[1]), df.columns))

   A  B
   E  E
   C  D
a  0  0
b  1  1
c  2  2
d  3  3
e  4  4

6
简化版:df.columns = pd.MultiIndex.from_tuples([(c[0], 'E', c[1]) for c in df.columns])翻译:将DataFrame的列名转换为多级索引,新的列名由原列名第一个元素、固定字符'E'和原列名第二个元素组成。 - Itamar Mushkin

5

我喜欢使用MultiIndex和链式调用友好的方式来明确表达:

df.set_axis(pd.MultiIndex.from_product([df.columns, ['C']]), axis=1)

当合并具有不同列级别数量的数据框时,这将特别方便,其中Pandas(1.4.2)会引发FutureWarning (FutureWarning: merging between different levels is deprecated and will be removed ... ):

import pandas as pd

df1 = pd.DataFrame(index=list('abcde'), data={'A': range(5), 'B': range(5)})
df2 = pd.DataFrame(index=list('abcde'), data=range(10, 15), columns=pd.MultiIndex.from_tuples([("C", "x")]))

# df1:
   A  B
a  0  0
b  1  1

# df2:
    C
    x
a  10
b  11

# merge while giving df1 another column level:
pd.merge(df1.set_axis(pd.MultiIndex.from_product([df1.columns, ['']]), axis=1),
         df2, 
         left_index=True, right_index=True)

# result:
   A  B   C
          x
a  0  0  10
b  1  1  11



0

另一种方法是使用元组列表推导作为参数传递给pandas.MultiIndex.from_tuples():

df.columns = pd.MultiIndex.from_tuples([(col, 'C') for col in df.columns])

df
   A  B
   C  C
a  0  0
b  1  1
c  2  2
d  3  3
e  4  4

0
我还没有找到一种全面的方法来做到这一点,所以在这里给出了一个解决方案:

def add_multindex_level(
        data: pd.DataFrame,
        keys: Union[Any, List[Any]],
        level: int=0,
        axis: int=0,
        name: str=None,
        inplace: bool=False,
    ) -> pd.DataFrame:

    to_promote = data.columns if axis==1 else data.index
    keys = [keys]*len(to_promote) if isinstance(keys, str) else keys
    if len(keys)!=len(to_promote):
        raise ValueError(
            "Keys must be a value or array-like matching the length of the index to extend"
        )

    new_keys = []
    for existing_key,insert_key in zip(to_promote, keys):
        if isinstance(existing_key, tuple):
            new_key = (*existing_key[:level], insert_key, *existing_key[level:])
        else:
            new_key = (existing_key, insert_key) if level else (insert_key, existing_key)
        new_keys.append(new_key)

    data_ = data if inplace else data.copy(deep=True)
    new_index = pd.MultiIndex.from_tuples(new_keys)

    new_names = []
    for l in range(new_index.nlevels):
        if l==level:
            n = name
        else:
            n = to_promote.names[l - (1 if l>=level else 0)]
        new_names.append(n)

    new_index.names = new_names

    if axis:
        data_.columns = new_index
    else:
        data_.index = new_index

    return None if inplace else data_

>>> source
   a  b  c
0  0  5  0
1  1  6  1
2  0  9  4

>>> add_multindex_level(source, ['x','y','z'], level=1, axis=1)
   a  b  c
   x  y  z
0  0  5  0
1  1  6  1
2  0  9  4

>>> add_multindex_level(source, ['x','y','z'], level=0, axis=1)
   x  y  z
   a  b  c
0  0  5  0
1  1  6  1
2  0  9  4

>>> add_multindex_level(source, 'A', level=0, axis=1)
   x  y  z
   A  A  A
0  0  5  0
1  1  6  1
2  0  9  4

>>> add_multindex_level(source, 'A', level=0, axis=0)
      x  y  z
A  0  0  5  0
A  1  1  6  1
A  2  0  9  4

0
我有一个专门的功能来处理这个。它不太优雅,但更加灵活。它的优点包括:
- 自动处理索引和多级索引 - 可以指定名称 - 可以一次添加多个级别 - 可以选择位置(顶部或底部)
祝好。
def addLevel(index, value='', name=None, n=1, onTop=False):
    """Add extra dummy levels to index"""
    assert isinstance(index, (pd.MultiIndex, pd.Index))
    xar = np.array(index.tolist()).transpose()
    names = index.names if isinstance(index, pd.MultiIndex) else [index.name]
    addValues = np.full(shape=(n, xar.shape[-1]), fill_value=value)
    addName = [name] * n

    if onTop:
        names = addName + names
        xar = np.vstack([addValues, xar])
    else:
        names = names + addName
        xar = np.vstack([xar, addValues])

    return pd.MultiIndex.from_arrays(xar, names=names)
    
df = pd.DataFrame(index=list('abc'), data={'A': range(3), 'B': range(3)})
df.columns = addLevel(df.columns, value='C')
df.columns = addLevel(df.columns, value='D', name='D-name')
df.columns = addLevel(df.columns, value='E2', n=2)
df.columns = addLevel(df.columns, value='Top', name='OnTop', onTop=True)
df.columns = addLevel(df.columns, value=1, name='Number')
print(df)
## OnTop  Top   
##          A  B
##          C  C
## D-name   D  D
##         E2 E2
##         E2 E2
## Number   1  1
## a        0  0
## b        1  1
## c        2  2

0
接受的答案和其他高分答案无法处理多索引的数据框。 我编写了这个函数,用于向特定级别添加具有自定义名称的值或列表:
from typing import Iterable

def add_level(df, vals, name='', level=0):
    cols = df.columns
    if not isinstance(vals, Iterable):
        vals = np.repeat(vals, cols.shape[0])
    else:
        assert cols.shape[0]%len(vals) == 0, 'cols.shape[0] must be divisible by len(vals)'
        vals = np.repeat(vals.to_list(), cols.shape[0]//len(vals))

    new_names = list(cols.names)
    new_names.insert(level, name)

    new_cols_df = cols.to_frame().assign(**{name:vals})
    new_cols = pd.MultiIndex.from_frame(new_cols_df[new_names])
    
    df1 = df.copy()
    df1.columns = new_cols
    return df1

预览: 在此输入图像描述

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