按名称重命名多个列

119

应该已经有人问过这个问题了,但我找不到答案。 假设我有:

x = data.frame(q=1,w=2,e=3, ...and many many columns...)  

如何以最优雅的方式将一组任意列(其位置未必已知)重命名为其他任意名称?

例如,如果我想将 "q""e" 重命名为 "A""B",最优雅的代码是什么?

显然,我可以使用循环:

oldnames = c("q","e")
newnames = c("A","B")
for(i in 1:2) names(x)[names(x) == oldnames[i]] = newnames[i]

但我想知道是否有更好的方法?也许可以使用一些包(如 plyr::rename 等)?

21个回答

137

使用dplyr,你可以这样做:

library(dplyr)

df = data.frame(q = 1, w = 2, e = 3)
    
df %>% rename(A = q, B = e)

#  A w B
#1 1 2 3

或者,如果您想使用向量,可以参考@Jelena-bioinf的建议:

library(dplyr)

df = data.frame(q = 1, w = 2, e = 3)

oldnames = c("q","e")
newnames = c("A","B")

df %>% rename_at(vars(oldnames), ~ newnames)

#  A w B
#1 1 2 3

L. D. Nicolas May建议更改,因为rename_at将被rename_with取代:

df %>% 
  rename_with(~ newnames[which(oldnames == .x)], .cols = oldnames)

#  A w B
#1 1 2 3

3
用户询问是否可以将“旧名称”和“新名称”作为向量传递,我认为。 - JelenaČuklina
4
谢谢@Jelena-bioinf。我修改了答案以包含您的建议。 - Gorka
1
请问能否解释一下在rename_with示例中的~(波浪号)和“.x”的含义是什么? - petzi
2
rename_with 可以使用函数或公式来重命名作为 .cols 参数给出的所有列。例如,rename_with(iris, toupper, starts_with("Petal")) 等同于 rename_with(iris, ~ toupper(.x), starts_with("Petal")) - Paul Rougieux
1
语法不清,这个解决方案太糟糕了。假设我要将一个名为“2012(%)”的列重命名为“2012”,基于这个例子去猜测你的解决方案在现实生活中的意义是不可能的。总的来说,rename() 太糟糕了。 - Matteo Bulgarelli

128

data.table包中的setnames函数可以用于data.framedata.table

library(data.table)
d <- data.frame(a=1:2,b=2:3,d=4:5)
setnames(d, old = c('a','d'), new = c('anew','dnew'))
d


 #   anew b dnew
 # 1    1 2    4
 # 2    2 3    5
请注意,更改是通过引用进行的,因此不进行任何复制(即使对于数据框架也是如此!)。

请注意,更改是通过引用进行的,因此不进行任何复制(即使对于数据框架也是如此!)


2
对于迟到的人 - 请查看下面的Joel's answer,其中涵盖了检查现有列的内容,以防您有一个名称更改列表,其中可能不是所有名称都存在,例如 old = c("a", "d", "e") - micstr
1
我想知道,如果你只想重命名子集/某些列而不是所有列,这是否有效?因此,如果我有一个包含十个列的数据框,并希望将_id_firstname重命名为firstname并将_id_lastname重命名为lastname,但保留其余八个列不变,我能否做到这一点,还是必须列出所有列? - Mus
@MusTheDataGuy,你提供新旧名称的子集,它就会工作。 - mnel
1
@mnel,我需要像@Mus要求的那样更改子集的变量名称。然而,上面的代码对于数据子集并没有起作用。@Gorka的答案使用rename_at()函数成功地更改了子集的变量名称。 - Mehmet Yildirim
2
@micstr skip_absent=TRUE :) - bers

45

对于不是太大的数据框,另一个解决方案是(基于@thelatemail的答案):

x <- data.frame(q=1,w=2,e=3)

> x
  q w e
1 1 2 3

colnames(x) <- c("A","w","B")

> x
  A w B
1 1 2 3

或者,你也可以使用:

names(x) <- c("C","w","D")

> x
  C w D
1 1 2 3

此外,您还可以重命名列名称的子集:

names(x)[2:3] <- c("E","F")

> x
  C E F
1 1 2 3

33

这是我发现的使用 purrr::set_names() 和一些 stringr 操作来重命名多个列最有效的方法。

library(tidyverse)

# Make a tibble with bad names
data <- tibble(
    `Bad NameS 1` = letters[1:10],
    `bAd NameS 2` = rnorm(10)
)

data 
# A tibble: 10 x 2
   `Bad NameS 1` `bAd NameS 2`
   <chr>                 <dbl>
 1 a                    -0.840
 2 b                    -1.56 
 3 c                    -0.625
 4 d                     0.506
 5 e                    -1.52 
 6 f                    -0.212
 7 g                    -1.50 
 8 h                    -1.53 
 9 i                     0.420
 10 j                     0.957

# Use purrr::set_names() with annonymous function of stringr operations
data %>%
    set_names(~ str_to_lower(.) %>%
                  str_replace_all(" ", "_") %>%
                  str_replace_all("bad", "good"))

# A tibble: 10 x 2
   good_names_1 good_names_2
   <chr>               <dbl>
 1 a                  -0.840
 2 b                  -1.56 
 3 c                  -0.625
 4 d                   0.506
 5 e                  -1.52 
 6 f                  -0.212
 7 g                  -1.50 
 8 h                  -1.53 
 9 i                   0.420
10 j                   0.957

6
这应该是答案,但是你可能还需要解释一下 set_names() 管道中的 ~. 参数具体作用。 - DaveRGP
1
在某些情况下,您需要明确地键入 purrr::set_names() - Levi Baguley
2
@DaveRGP 当使用 purrr 函数时,波浪号 ~ 表示“对于每一列”。点号 . 是 dplyr 语法中管道左侧的 LHS(左手边),即被管道引用的对象,此处为 data - Agile Bean
波浪号 ~ 是一个公式。你也可以使用函数调用,并将参数传递给 set_names... 参数,例如 rlang::set_names(head(iris), paste0, "_hi") 等同于 rlang::set_names(head(iris), ~ paste0(.x, "_hi")) - Paul Rougieux
今天我被 purrr::set_names() 给搞糊涂了,谢谢 Levi! - taiyodayo

31

更新dplyr 1.0.0

最新的dplyr版本通过添加rename_with()更加灵活,其中_with指的是一个函数作为输入。 诀窍在于通过~将字符向量newnames重新制定为公式,这样它就相当于function(x) return (newnames)

据我主观看法,这是最优雅的dplyr表达式。 更新:感谢@desval,必须使用all_ofoldnames向量进行包装以包含其所有元素:

# shortest & most elegant expression
df %>% rename_with(~ newnames, all_of(oldnames))

A w B
1 1 2 3

小贴士:

如果您反转顺序,任何一个参数都必须指定为.fn,因为在.cols参数之前期望.fn出现:

df %>% rename_with(oldnames, .fn = ~ newnames)

A w B
1 1 2 3

或者指定参数 .col:

 df %>% rename_with(.col = oldnames, ~ newnames)

A w B
1 1 2 3

3
目前看起来这个答案会返回一个警告,并且由于在select函数中使用外部向量时的歧义,将来会返回一个错误 https://tidyselect.r-lib.org/reference/faq-external-vector.html。可以通过以下方式解决:```df %>% rename_with(~ newnames, all_of(oldnames))``` - desval
你能提供一个具体的例子吗?我无法让任何newnamesoldnames的替换工作。 - FLonLon

14

我最近也遇到了这个问题,如果您不确定列是否存在,并且只想重命名那些存在的列:

existing <- match(oldNames,names(x))
names(x)[na.omit(existing)] <- newNames[which(!is.na(existing))]

10

基于 @user3114046 的回答:

x <- data.frame(q=1,w=2,e=3)
x
#  q w e
#1 1 2 3

names(x)[match(oldnames, names(x))] <- newnames

x
#  A w B
#1 1 2 3

这不会依赖于x数据集中特定列的顺序。


1
我已经为你的答案点赞了,但我仍然想知道是否有更加优雅的方法来完成这个任务,特别是通过名称而不是位置进行重命名的方法。 - qoheleth
@qoheleth - 这是按名称重命名!这里没有输入是位置向量,因为match已经处理了。你最好的选择可能是@mnel的setnames答案。 - thelatemail
1
它仍然是一种按位置重命名的方式,因为正如你所说,即使我不必显式指定位置向量,match仍然是一个面向位置的命令。在这个精神下,我认为@user3114046的答案也是基于位置的(即使%in%命令会处理(或尝试处理)事情)。当然,我想你可以争论所有命令在我们深入到低级机制时都是面向位置的....但这不是我的意思...data.table的答案很棒,因为没有多次调用name命令。 - qoheleth

8
你可以使用命名向量。下面是两个选项(使用基础R和dplyr)。
基础R,通过子集操作:
x = data.frame(q = 1, w = 2, e = 3) 

rename_vec <- c(q = "A", e = "B")
## vector of same length as names(x) which returns NA if there is no match to names(x)
which_rename <- rename_vec[names(x)]
## simple ifelse where names(x) will be renamed for every non-NA 
names(x) <- ifelse(is.na(which_rename), names(x), which_rename)

x
#>   A w B
#> 1 1 2 3

或者使用 !!!dplyr 选项:

library(dplyr)

rename_vec <- c(A = "q", B = "e") # the names are just the other way round than in the base R way!

x %>% rename(!!!rename_vec)
#>   A w B
#> 1 1 2 3

后者的工作原理是因为 'big-bang' 运算符 !!! 强制评估列表或向量。 ?`!!`

!!! 强制展开对象列表。列表的元素被展开到原位置,这意味着它们各自成为一个单独的参数。


1
不理解这是如何工作的 - !!!oldnames 返回 c("A", "B"),但是哪种逻辑将其转换为 c("A", "w", "B") - Agile Bean
1
@AgileBean, 我不知道你从哪里找到的 !!!oldnames 会返回一个向量。它用于强制在 dplyr 中多个参数进行非标准评估。请参见 ?\!!`。使用 \!!!` 向函数添加多个参数。它的参数应评估为列表或向量:args <- list(1:3, na.rm = TRUE) ; quo(mean(!!!args))。我想我会将这个解释添加到答案中。感谢你提出。 - tjebo

5
names(x)[names(x) %in% c("q","e")]<-c("A","B")

4
并不完全正确,因为如我所说,我不一定知道列的位置,你的解决方案仅在 oldnames 经过排序并且 oldnames[i] 出现在 oldnames[j] 之前时才有效。 - qoheleth

5

有一些答案提到了dplyr::rename_withrlang::set_names这两个函数,但它们是不同的。本答案将说明两者之间的差异以及使用函数和公式来重命名列。

dplyr包中的rename_with函数可以使用函数或公式来重命名传递给.cols参数的一组列。例如,可以传递函数名toupper

library(dplyr)
rename_with(head(iris), toupper, starts_with("Petal"))

等同于传递公式~ toupper(.x)

rename_with(head(iris), ~ toupper(.x), starts_with("Petal"))

当需要重命名所有列时,您也可以使用rlang包中的set_names。为了举例说明,让我们使用paste0作为重命名函数。paste0需要两个参数,因此根据是否使用函数或公式,有不同的方法来传递第二个参数。

rlang::set_names(head(iris), paste0, "_hi")
rlang::set_names(head(iris), ~ paste0(.x, "_hi"))

使用rename_with也能实现同样的功能,只需将数据框作为第一个参数.data,函数作为第二个参数.fn,所有列作为第三个参数.cols=everything(),函数参数作为第四个参数...。或者您可以将第二、第三和第四个参数放在公式中,作为第二个参数给出。

rename_with(head(iris), paste0, everything(), "_hi")
rename_with(head(iris), ~ paste0(.x, "_hi"))

rename_with 只能用于数据框。 set_names 更通用,可以对向量进行重命名。

rlang::set_names(1:4, c("a", "b", "c", "d"))

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