替换熊猫数据框中部分匹配字符串的列名

Posted

技术标签:

【中文标题】替换熊猫数据框中部分匹配字符串的列名【英文标题】:Replace column names in a pandas data frame that partially match a string 【发布时间】:2017-11-17 23:11:35 【问题描述】:

背景

我想识别数据框中与字符串部分匹配的列名,并将它们替换为原始名称以及添加到其中的一些新元素。新元素是由列表定义的整数。这是similar question,但恐怕建议的解决方案在我的特定情况下不够灵活。 here 是另一个帖子,其中包含一些与我面临的问题非常接近的优秀答案。

一些研究

我知道我可以组合两个字符串列表,将它们成对映射 into a dictionary 和 rename the columns,使用字典作为函数 df.rename 中的输入。但是考虑到现有列的数量会有所不同,这似乎有点太复杂了,而且不是很灵活。重命名的列数也是如此。

下面的 sn-p 将产生一个输入示例:

# Libraries
import numpy as np
import pandas as pd
import itertools

# A dataframe
Observations = 5
Columns = 5
np.random.seed(123)
df = pd.DataFrame(np.random.randint(90,110,size=(Observations, Columns)),
              columns = ['Price','obs_1','obs_2','obs_3','obs_4'])

datelist = pd.date_range(pd.datetime.today().strftime('%Y-%m-%d'),
                     periods=Observations).tolist()
df['Dates'] = datelist
df = df.set_index(['Dates'])
print(df)

输入

我想识别以obs_ 开头的列名,并在= 符号后添加newElements = [5, 10, 15, 20] 列表中的元素(整数)。名为 Price 的列保持不变。 obs_ 列之后出现的其他列也应该保持不变。

以下 sn-p 将演示所需的输出:

# Desired output
Observations = 5
Columns = 5
np.random.seed(123)
df2 = pd.DataFrame(np.random.randint(90,110,size=(Observations, Columns)),
              columns = ['Price','Obs_1 = 5','Obs_2 = 10','Obs_3 = 15','Obs_4 = 20'])

df2['Dates'] = datelist
df2 = df2.set_index(['Dates'])
print(df2)

输出

我的尝试

# Define the partial string I'm lookin for
stringMatch = 'Obs_'

# Put existing column names in a list
oldnames = list(df)

# Put elements that should be added to the column names
# where the three first letters match 'obs_'
newElements = [5, 10, 15, 20]
oldElements = [1, 2, 3, 4]

# Change types of the elements in the list
str_newElements = [str(x) for x in newElements]
str_oldElements = [str(y) for y in oldElements]
str_newNames = str_newElements.copy()

# Since I know the first column should not be renamed,
# I start with 'Price' in a list
newnames = ['Price']

# Then I add the renamed parts to the same list
i = 0
for oldElement in str_oldElements:   
    #print(repr(oldElement) + repr(str_newElements[i]))
    newnames.append(stringMatch + oldElement + ' = ' + str_newElements[i])
    i = i + 1

# Rename columns using the dict as input in df.rename
df.rename(columns = dict(zip(oldnames, newnames)), inplace = True)

print('My attempt: ', df)

已经制作了新列名的完整列表 我也可以使用df.columns = newnames, 但希望你们中的一个人有使用的建议 df.rename 以更 Python 的方式。

感谢您的任何建议!

这是一个简单的复制粘贴的完整代码:

# Libraries
import numpy as np
import pandas as pd
import itertools

# A dataframe
Observations = 5
Columns = 5
np.random.seed(123)
df = pd.DataFrame(np.random.randint(90,110,size=(Observations, Columns)),
                  columns = ['Price','obs_1','obs_2','obs_3','obs_4'])

datelist = pd.date_range(pd.datetime.today().strftime('%Y-%m-%d'),
                         periods=Observations).tolist()
df['Dates'] = datelist
df = df.set_index(['Dates'])
print('Input: ', df)

# Desired output
Observations = 5
Columns = 5
np.random.seed(123)
df2 = pd.DataFrame(np.random.randint(90,110,size=(Observations, Columns)),
                  columns = ['Price','Obs_1 = 5','Obs_2 = 10','Obs_3 = 15','Obs_4 = 20'])

df2['Dates'] = datelist
df2 = df2.set_index(['Dates'])
print('Desired output: ', df2)

# My attempts
# Define the partial string I'm lookin for
stringMatch = 'Obs_'

# Put existing column names in a list
oldnames = list(df)

# Put elements that should be added to the column names
# where the three first letters match 'obs_'
newElements = [5, 10, 15, 20]
oldElements = [1, 2, 3, 4]

# Change types of the elements in the list
str_newElements = [str(x) for x in newElements]
str_oldElements = [str(y) for y in oldElements]
str_newNames = str_newElements.copy()


# Since I know the first column should not be renamed,
# I start with 'Price' in a list
newnames = ['Price']

# Then I add the renamed parts to the same list
i = 0
for oldElement in str_oldElements:

    #print(repr(oldElement) + repr(str_newElements[i]))
    newnames.append(stringMatch + oldElement + ' = ' + str_newElements[i])
    i = i + 1

# Rename columns using the dict as input in df.rename
df.rename(columns = dict(zip(oldnames, newnames)), inplace = True)

print('My attempt: ', df)

编辑:后果

仅仅一天之后就有这么多好的答案真是太棒了!这使得很难决定接受哪个答案。我不知道以下内容是否会为整个帖子增加很多价值,但我继续将所有建议包装到函数中并使用 %timeit 对其进行测试。

结果如下:

建议框架 HH1 是第一个发布的,也是执行时间最快的框架之一。如果有人感兴趣,我稍后会包含代码。

编辑 2

当我尝试时,来自 suvy 的建议呈现了这些结果:

sn-p 工作正常,直到最后一行。运行df = df.rename(columns=dict(zip(names,renames))) 行后,数据框如下所示:

【问题讨论】:

【参考方案1】:

这行得通吗?

df.columns = [col + ' = ' + str(newElements.pop(0)) if col.startswith(stringMatch) else col for col in df.columns]

【讨论】:

差不多。您的函数以相反的顺序返回 newElements:Price obs_1 = 20 obs_2 = 15 obs_3 = 10 obs_4 = 5 编辑后工作正常。它也非常快,当我扩大规模时,这实际上很重要。【参考方案2】:

您可以使用列表推导:

df.columns = [ i if "_" not in i else i + "=" + str(newElements[int(i[-1])-1]) for i in df.columns]

输出

    Price   obs_1=5 obs_2=10    obs_3=15    obs_4=20
0   103     92       92         96          107
1   109     100      91         90          107
2   105     99       90         104         90
3   105     109      104        94          90
4   106     94       107        93          92

【讨论】:

不错的解决方案! +1。你甚至可以在 '=' 中使用空格,让它更吸引眼球:)【参考方案3】:

从您在此处调用的输入数据框开始df

            Price  obs_1  obs_2  obs_3  obs_4
Dates                                        
2017-06-15    103     92     92     96    107
2017-06-16    109    100     91     90    107
2017-06-17    105     99     90    104     90
2017-06-18    105    109    104     94     90
2017-06-19    106     94    107     93     92


newElements = [5, 10, 15, 20]
names = list(filter(lambda x: x.startswith('obs'), df.columns.values))
renames = list(map(lambda x,y: ' = '.join([x,str(y)]), names, newElements))
df = df.rename(columns=dict(zip(names,renames)))

返回

            Price   obs_1 = 5   obs_2 = 10  obs_3 = 15  obs_4 = 20
Dates                   
2017-06-19  103     92          92          96          107
2017-06-20  109     100         91          90          107
2017-06-21  105     99          90          104         90
2017-06-22  105     109         104         94          90
2017-06-23  106     94          107         93          92

【讨论】:

我对您的建议有一些疑问。 df[renames] = df[renames]+newElements 行返回错误。在df = df.rename(columns=dict(zip(names,renames)))这一行之后,列的名称是Price obs_2 = 5 obs_2 obs_4 = 10 obs_4 我在问题的“后果”部分添加了截图。 好的,我明白了问题所在,python 2 过滤器和 map 之间的区别在于返回列表,而在 python 3 中,它们分别返回 map 和 filter 对象。上述解决方案假定 python 2,对 python 3 进行了快速修复。 谢谢!我应该在问题中提到我使用的是 Python 3。【参考方案4】:

选择所需的列,进行所需的更改并加入原始 df

obs_cols = df.columns[df.columns.str.startswith('obs')]

obs_cols = [col + ' = ' + str(val) for col, val in zip(obs_cols, newElements)]

df.columns = list(df.columns[~df.columns.str.startswith('obs')]) + obs_cols


    Price   obs_1 = 5   obs_2 = 10  obs_3 = 15  obs_4 = 20
0   103     92          92          96          107
1   109     100         91          90          107
2   105     99          90          104         90
3   105     109         104         94          90
4   106     94          107         93          92

【讨论】:

【参考方案5】:

为了完整起见,既然您提到了df.rename,您可以使用字典推导式为其创建输入,其方式与其他答案中的列表推导式类似。

# Where Observations = len(df.index) as in the example
>>>newcols = col: col+' = '+str(int(col[col.rfind('_')+1:])*Observations)
              for col in df.columns if col.find('obs_') != -1
>>>df.rename(columns=newcols)
            Price  obs_1 = 5  obs_2 = 10  obs_3 = 15  obs_4 = 20
Dates                                                           
2017-06-15    103         92          92          96         107
2017-06-16    109        100          91          90         107
2017-06-17    105         99          90         104          90
2017-06-18    105        109         104          94          90
2017-06-19    106         94         107          93          92

在这里,我还对您添加特定新元素的原因做了一些假设。如果这些假设是错误的,df.rename 和字典理解仍然可以与其他答案之一中的方法一起使用。

【讨论】:

以上是关于替换熊猫数据框中部分匹配字符串的列名的主要内容,如果未能解决你的问题,请参考以下文章

在将带有空字符串的新列添加到熊猫数据框时替换现有列名

使用部分字符串匹配将数据框中的列替换为另一个数据框列

根据列名中的匹配字符串对熊猫单元格(字符串)进行排序

我该如何处理这种情况:“n/a”在 pandas 数据框中显示为“nan”,但无法对其进行字符串匹配和替换

在熊猫数据框中替换特殊字符

在熊猫数据框中使用正则表达式替换列值