熊猫DataFrame - 重命名多个同名列

32

我有一个数据框中存在几个同名的列。我需要对它们进行重命名,但问题是df.rename方法会将它们全部重命名为相同的方式。我该如何将下面的 blah(s) 重命名为 blah1、blah4 和 blah5?

df = pd.DataFrame(np.arange(2*5).reshape(2,5))
df.columns = ['blah','blah2','blah3','blah','blah']
df

#     blah  blah2  blah3  blah  blah
# 0   0     1      2      3     4
# 1   5     6      7      8     9

当使用df.rename方法时,会发生以下情况:

df.rename(columns={'blah':'blah1'})

#     blah1  blah2  blah3  blah1  blah1
# 0   0      1      2      3      4
# 1   5      6      7      8      9
14个回答

39

从Pandas 0.19.0开始,pd.read_csv()已经改进了对重复列名的支持

所以我们可以尝试使用内部方法:

In [137]: pd.io.parsers.ParserBase({'names':df.columns})._maybe_dedup_names(df.columns)
Out[137]: ['blah', 'blah2', 'blah3', 'blah.1', 'blah.2']

自Pandas 1.3.0版本以来:

pd.io.parsers.base_parser.ParserBase({'names':df.columns, 'usecols':None})._maybe_dedup_names(df.columns)

这就是所谓的“魔法”函数:

def _maybe_dedup_names(self, names):
    # see gh-7160 and gh-9424: this helps to provide
    # immediate alleviation of the duplicate names
    # issue and appears to be satisfactory to users,
    # but ultimately, not needing to butcher the names
    # would be nice!
    if self.mangle_dupe_cols:
        names = list(names)  # so we can index
        counts = {}

        for i, col in enumerate(names):
            cur_count = counts.get(col, 0)

            if cur_count > 0:
                names[i] = '%s.%d' % (col, cur_count)

            counts[col] = cur_count + 1

    return names

10
这是创建的,供他人使用只需执行以下操作:df.columns = pd.io.parsers.ParserBase({'names':df.columns})._maybe_dedup_names(df.columns) - miguelfg
如果您不是从CSV文件中读取数据,@miguelfg的评论是一个很好的解决方案。无需自己编写任何函数! - evilolive
这也适用于 ParserBase({}),因为 _maybe_dedup_names() 的输入不是从类中获取的,而是直接作为函数参数给出的。您只需要使构造函数的验证检查通过,并使用空字典即可。它可能并不更快,但它使代码不那么混乱,因为可以清楚地知道数据传递的位置。 - David M. Perlman
我正在使用Pandas 1.5.0,需要ParserBase({'usecols': None})。关键字names不是必需的。 - Carlos Hanson
在1.5.2版本中,需要从pandas.io.parsers.base_parser导入ParserBase。(我不知道它是否已经移至pandas.io.parsers.base_parser或不再从pandas.io.parsers导出。)仍然需要{'usecols': None} - mikm

34

我希望在Pandas中找到解决方案,而不是一般的Python解决方案。如果get_loc()函数发现重复项,则返回一个掩码数组,其中“True”值指向找到重复项的位置。 然后,我使用掩码将新值分配到这些位置。 在我的情况下,我事先知道我将要获得多少个重复项以及我将要分配给它们什么,但是似乎df.columns.get_duplicates()会返回所有重复项的列表,您可以将该列表与get_loc()结合使用,如果您需要更通用的去重操作。

'''截至2020年9月更新'''

cols=pd.Series(df.columns)
for dup in df.columns[df.columns.duplicated(keep=False)]: 
    cols[df.columns.get_loc(dup)] = ([dup + '.' + str(d_idx) 
                                     if d_idx != 0 
                                     else dup 
                                     for d_idx in range(df.columns.get_loc(dup).sum())]
                                    )
df.columns=cols

    blah    blah2   blah3   blah.1  blah.2
 0     0        1       2        3       4
 1     5        6       7        8       9

新更好的方法(更新于2019年12月3日)

以下代码比上面的代码更好。从下面另一个答案中复制(@SatishSK):

#sample df with duplicate blah column
df=pd.DataFrame(np.arange(2*5).reshape(2,5))
df.columns=['blah','blah2','blah3','blah','blah']
df

# you just need the following 4 lines to rename duplicates
# df is the dataframe that you want to rename duplicated columns

cols=pd.Series(df.columns)

for dup in cols[cols.duplicated()].unique(): 
    cols[cols[cols == dup].index.values.tolist()] = [dup + '.' + str(i) if i != 0 else dup for i in range(sum(cols == dup))]

# rename the columns with the cols list.
df.columns=cols

df

输出:

    blah    blah2   blah3   blah.1  blah.2
0   0   1   2   3   4
1   5   6   7   8   9

2
你应该通过编辑来为你的答案添加一些描述。 - Artjom B.
太棒了!它对我有用...因为我有重复的列并需要自动重命名...当pandas读取Excel文件到数据框时,它可以自动重命名重复的名称...只有在我们手动操作列名时才需要这段代码。我希望这段代码可以被缩进...并由某人清晰地解释。 - ihightower
3
为什么会出现 AttributeError: 'slice' object has no attribute 'sum' 错误?嗯 - Hack-R
@Lamakaha,我也遇到了同样的问题...请帮忙解决一下。 - Learnings
1
对我来说没有用,所有重复的列必须同时重命名。 - rosefun
这对我来说不起作用,因为重复名称的前两个出现被重命名为相等,或者作为原点,或者使用.1,或者使用.2,具体取决于我使用str(d_idx)进行的不同试验。所以我认为这是一个指针问题。@MaxU的解决方案在一行中对我起了作用。 - miguelfg

13

你可以使用这个:

def df_column_uniquify(df):
    df_columns = df.columns
    new_columns = []
    for item in df_columns:
        counter = 0
        newitem = item
        while newitem in new_columns:
            counter += 1
            newitem = "{}_{}".format(item, counter)
        new_columns.append(newitem)
    df.columns = new_columns
    return df

然后

import numpy as np
import pandas as pd

df=pd.DataFrame(np.arange(2*5).reshape(2,5))
df.columns=['blah','blah2','blah3','blah','blah']

为了使 df

   blah  blah2  blah3   blah   blah
0     0      1      2      3      4
1     5      6      7      8      9

然后
df = df_column_uniquify(df)

为了让 df:

   blah  blah2  blah3  blah_1  blah_2
0     0      1      2       3       4
1     5      6      7       8       9

4
您可以直接分配给列:
In [12]:

df.columns = ['blah','blah2','blah3','blah4','blah5']
df
Out[12]:
   blah  blah2  blah3  blah4  blah5
0     0      1      2      3      4
1     5      6      7      8      9

[2 rows x 5 columns]

如果您想动态地重命名重复的列,可以像下面这样做(代码取自答案2:Python列表中重复项的索引):
In [25]:

import collections
dups = collections.defaultdict(list)
dup_indices=[]
col_list=list(df.columns)
for i, e in enumerate(list(df.columns)):
  dups[e].append(i)
for k, v in sorted(dups.items()):
  if len(v) >= 2:
    dup_indices = v

for i in dup_indices:
    col_list[i] = col_list[i] + ' ' + str(i)
col_list
Out[25]:
['blah 0', 'blah2', 'blah3', 'blah 3', 'blah 4']

您可以使用此方法进行重新命名,您还可以编写一个函数来生成一个在重命名之前不存在于列中的唯一名称。

2
或者类似于 df.columns = ['blah{}'.format(i) for i in range(1,len(df.columns)+1)],或者 "blah" + pd.Series(range(1,6)).astype(str) 等等。 - DSM
@DSM 是的,那会起作用。我假设 OP 的例子不是真实的例子。 - EdChum
直接分配列名对我不起作用 - 我真的不想知道这些重复列与其他列的位置关系在哪里。我只是真正需要将它们重命名。为了使我的示例更清晰,假设我读入了3个名为“price”的列,我知道第一个价格是开盘价,第二个是收盘价,第三个是当天结束价格,因此我需要按照这些方式将它们重命名。可能还有很多其他列,我不想知道它们是什么以及所有这些列相对于彼此的位置。 - Lamakaha
这里有一个相关的帖子:https://dev59.com/7m035IYBdhLWcg3wcviS,你可以使用它来获取重复项的索引,然后只需枚举一个新的后缀或其他内容,修改列表并重新赋值。 - EdChum
@EdChum - 是的,它可以工作,但我正在寻找更像Pandas的东西。 - Lamakaha
显示剩余2条评论

3

duplicated_idx = dataset.columns.duplicated()

duplicated = dataset.columns[duplicated_idx].unique()



rename_cols = []

i = 1
for col in dataset.columns:
    if col in duplicated:
        rename_cols.extend([col + '_' + str(i)])
    else:
        rename_cols.extend([col])

dataset.columns = rename_cols


2
虽然这段代码可能解决了问题,但包括解释真的有助于提高您的帖子质量。请记住,您正在回答未来读者的问题,而这些人可能不知道您提出代码建议的原因。 - Muhammad Dyas Yaskur
请在for循环中的if/else之后添加i+=1。 - pnv

3
我刚刚编写了这段代码,它使用列表推导式来更新所有重复的名称。
df.columns = [x[1] if x[1] not in df.columns[:x[0]] else f"{x[1]}_{list(df.columns[:x[0]]).count(x[1])}" for x in enumerate(df.columns)]

2
感谢 @Lamakaha 提供的解决方案。你的想法让我有机会修改它并使其在所有情况下都可行。
我正在使用Python 3.7.3版本。
我在我的数据集上尝试了你的代码片段,其中只有一个重复的列,即两个具有相同名称的列。不幸的是,列名保持不变,没有被重命名。最重要的是,我收到了一个警告,提示"get_duplicates()已弃用,将在未来版本中删除"。我使用duplicated()unique()代替get_duplicates(),但没有产生预期的结果。
我稍微修改了你的代码,现在它适用于我的数据集以及其他一般情况。
以下是在问题中提到的示例数据集上运行代码的结果,包括有无代码修改的情况:
df=pd.DataFrame(np.arange(2*5).reshape(2,5))

df.columns=['blah','blah2','blah3','blah','blah']
df

cols=pd.Series(df.columns)

for dup in df.columns.get_duplicates(): 
    cols[df.columns.get_loc(dup)]=[dup+'.'+str(d_idx) if d_idx!=0 else dup for d_idx in range(df.columns.get_loc(dup).sum())]
df.columns=cols

df

f:\Anaconda3\lib\site-packages\ipykernel_launcher.py:2: FutureWarning: 'get_duplicates'已经被弃用,将在将来的版本中删除, 您可以使用idx[idx.duplicated()].unique()代替

输出:

    blah    blah2   blah3   blah    blah.1
0   0   1   2   3   4
1   5   6   7   8   9

三个“blah”中的两个没有被正确重命名。


修改后的代码

df=pd.DataFrame(np.arange(2*5).reshape(2,5))
df.columns=['blah','blah2','blah3','blah','blah']
df

cols=pd.Series(df.columns)

for dup in cols[cols.duplicated()].unique(): 
    cols[cols[cols == dup].index.values.tolist()] = [dup + '.' + str(i) if i != 0 else dup for i in range(sum(cols == dup))]
df.columns=cols

df

输出:

    blah    blah2   blah3   blah.1  blah.2
0   0   1   2   3   4
1   5   6   7   8   9

最初的回答:
这里是对另一个示例代码进行修改后的运行结果:
cols = pd.Series(['X', 'Y', 'Z', 'A', 'B', 'C', 'A', 'A', 'L', 'M', 'A', 'Y', 'M'])

for dup in cols[cols.duplicated()].unique():
    cols[cols[cols == dup].index.values.tolist()] = [dup + '_' + str(i) if i != 0 else dup for i in range(sum(cols == dup))]

cols

Output:
0       X
1       Y
2       Z
3       A
4       B
5       C
6     A_1
7     A_2
8       L
9       M
10    A_3
11    Y_1
12    M_1
dtype: object

希望这能帮助到寻找上述问题答案的任何人。最初的回答:

1

由于Lamakaha提供的答案在最近版本的pandas中无法使用,而其他建议看起来有点笨拙,因此我自己想出了解决方案:

def dedupIndex(idx, fmt=None, ignoreFirst=True):
    # fmt:          A string format that receives two arguments: 
    #               name and a counter. By default: fmt='%s.%03d'
    # ignoreFirst:  Disable/enable postfixing of first element.
    idx = pd.Series(idx)
    duplicates = idx[idx.duplicated()].unique()
    fmt = '%s.%03d' if fmt is None else fmt
    for name in duplicates:
        dups = idx==name
        ret = [ fmt%(name,i) if (i!=0 or not ignoreFirst) else name
                      for i in range(dups.sum()) ]
        idx.loc[dups] = ret
    return pd.Index(idx)

使用函数的方法如下:
df.columns = dedupIndex(df.columns)
# Result: ['blah', 'blah2', 'blah3', 'blah.001', 'blah.002']
df.columns = dedupIndex(df.columns, fmt='%s #%d', ignoreFirst=False)
# Result: ['blah #0', 'blah2', 'blah3', 'blah #1', 'blah #2']

1

创建了一个带有一些测试的函数,因此它应该可以直接使用;这与Lamakaha的出色解决方案略有不同,因为它会重命名重复列的第一个出现:

from collections import defaultdict
from typing import Dict, List, Set

import pandas as pd

def rename_duplicate_columns(df: pd.DataFrame) -> pd.DataFrame:
    """Rename column headers to ensure no header names are duplicated.

    Args:
        df (pd.DataFrame): A dataframe with a single index of columns

    Returns:
        pd.DataFrame: The dataframe with headers renamed; inplace
    """
    if not df.columns.has_duplicates:
        return df
    duplicates: Set[str] = set(df.columns[df.columns.duplicated()].tolist())
    indexes: Dict[str, int] = defaultdict(lambda: 0)
    new_cols: List[str] = []
    for col in df.columns:
        if col in duplicates:
            indexes[col] += 1
            new_cols.append(f"{col}.{indexes[col]}")
        else:
            new_cols.append(col)
    df.columns = new_cols
    return df

def test_rename_duplicate_columns():
    df = pd.DataFrame(data=[[1, 2]], columns=["a", "b"])
    assert rename_duplicate_columns(df).columns.tolist() == ["a", "b"]

    df = pd.DataFrame(data=[[1, 2]], columns=["a", "a"])
    assert rename_duplicate_columns(df).columns.tolist() == ["a.1", "a.2"]

    df = pd.DataFrame(data=[[1, 2, 3]], columns=["a", "b", "a"])
    assert rename_duplicate_columns(df).columns.tolist() == ["a.1", "b", "a.2"]


1
这里有一个适用于多级索引的解决方案。
# Take a df and rename duplicate columns by appending number suffixes
def rename_duplicates(df):
    import copy
    new_columns = df.columns.values
    suffix = {key: 2 for key in set(new_columns)}
    dup = pd.Series(new_columns).duplicated()

    if type(df.columns) == pd.core.indexes.multi.MultiIndex:
        # Need to be mutable, make it list instead of tuples
        for i in range(len(new_columns)):
            new_columns[i] = list(new_columns[i])
        for ix, item in enumerate(new_columns):
            item_orig = copy.copy(item)
            if dup[ix]:
                for level in range(len(new_columns[ix])):
                    new_columns[ix][level] = new_columns[ix][level] + f"_{suffix[tuple(item_orig)]}"
                suffix[tuple(item_orig)] += 1

        for i in range(len(new_columns)):
            new_columns[i] = tuple(new_columns[i])

        df.columns = pd.MultiIndex.from_tuples(new_columns)
    # Not a MultiIndex
    else:
        for ix, item in enumerate(new_columns):
            if dup[ix]:
                new_columns[ix] = item + f"_{suffix[item]}"
                suffix[item] += 1
        df.columns = new_columns

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