按名称重命名多个列

Posted

技术标签:

【中文标题】按名称重命名多个列【英文标题】:Rename multiple columns by names 【发布时间】:2014-01-26 01:26:52 【问题描述】:

应该已经有人问过了,但我找不到答案。说我有:

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 等)

【问题讨论】:

【参考方案1】:

data.tablepackage 中的setnames 将适用于data.frames 或data.tables

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

请注意,更改是通过引用进行的,因此不能复制(即使是 data.frames!)

【讨论】:

对于迟到的人 - 还请查看下面的Joel's answer,其中包括检查现有列,以防您有可能不存在的名称更改列表,例如old = c("a", "d", "e") 我想知道,如果您只想重命名一个子集/一些列而不是所有列,这是否有效?因此,如果我有一个包含十列的数据框,并且希望将 _id_firstname 重命名为 firstname,并将 _id_lastname 重命名为 lastname,但保留其余八列不变,我可以这样做还是必须列出所有列? @MusTheDataGuy 您提供新旧名称的子集,它将起作用。 @mnel 我需要按照@Mus 的要求更改子集的变量名称。但是,上面的代码不适用于数据子集。 @Gorka 对rename_at() 的回答用于更改子集的变量名称。 @micstr skip_absent=TRUE :)【参考方案2】:

使用 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

【讨论】:

用户询问是否将 oldnew 名称作为向量传递,我认为 感谢@Jelena-bioinf。我修改了答案以包含您的建议。 能否解释一下~(波浪号)的含义以及rename_with示例中“.x”的来源? rename_with 可以使用函数或公式来重命名作为.cols 参数给出的所有列。例如rename_with(iris, toupper, starts_with("Petal")) 等价于rename_with(iris, ~ toupper(.x), starts_with("Petal"))【参考方案3】:

另一个不太大的数据框的解决方案是(基于@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

【讨论】:

【参考方案4】:

这是我发现使用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

【讨论】:

这应该是答案,但您是否也应该扩展 set_names() 管道中的 ~. 参数的作用。 在某些情况下,您需要显式输入purrr::set_names() @DaveRGP 在使用 purrr 函数时,波浪号 ~ 表示“对于每一列”。 . 是 LHS = 管道左侧的 dplyr 语法,即对管道对象的引用,在本例中为 data 波浪号~ 是一个公式。您还可以使用函数调用并将参数传递给set_names... 参数,例如rlang::set_names(head(iris), paste0, "_hi") 等效于rlang::set_names(head(iris), ~ paste0(.x, "_hi"))【参考方案5】:

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

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

【讨论】:

【参考方案6】:

更新 dplyr 1.0.0

最新的 dplyr 版本通过添加 rename_with() 变得更加灵活,其中 _with 指的是作为输入的函数。 技巧是将字符向量 newnames 重新表述为一个公式(通过~),所以它相当于function(x) return (newnames)

在我的主观看来,这是最优雅的 dplyr 表达方式。

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

A w B
1 1 2 3

旁注:

如果您颠倒顺序,则必须指定任一参数 .fn,因为 .fn 应在 .cols 参数之前:

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

【讨论】:

目前看来这个答案会返回一个警告,并且将来会返回一个错误,因为在 select tidyselect.r-lib.org/reference/faq-external-vector.html 中使用外部向量时会产生歧义。这应该可以解决它df %&gt;% rename_with(~ newnames, all_of(oldnames))【参考方案7】:

基于@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 数据集中列的特定顺序。

【讨论】:

我赞成你的回答,但我仍然想知道是否有更优雅的方法来做到这一点,特别是按名称重命名而不是按位置重命名的方法 @qoheleth - 它正在重命名!这里没有输入是位置向量,因为match 负责处理。你要做的最好的可能是@mnel 的setnames 答案。 它仍然是按位置重命名,因为正如您所说,即使我不必明确指定位置向量,match 仍然是一个面向位置的命令。本着这种精神,我认为@user3114046 的回答位置也是基于的(甚至认为%in% 命令会处理(或尝试处理)事情)。当然,我想您可以争辩说,当我们深入研究低级机制时,所有命令都是面向位置的……但这不是我的意思…… data.table 答案很好,因为没有多次调用name 命令。【参考方案8】:
names(x)[names(x) %in% c("q","e")]<-c("A","B")

【讨论】:

不完全是,因为正如我所说,我不一定知道列的位置,您的解决方案仅在 oldnames 被排序以便 oldnames[i] 出现在 oldnames[j] 之前为我工作【参考方案9】:

这将改变所有名字中所有这些字母的出现:

 names(x) <- gsub("q", "A", gsub("e", "B", names(x) ) )

【讨论】:

我认为一旦你通过了几个重命名实例,这并不是特别优雅。 我只是不够好,无法提出gsubfn 的答案。也许 G.Grothendieck 会来。他是正则表达式大师。【参考方案10】:

已经有几个答案提到了函数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 作为重命名函数。 pasteO 接受 2 个参数,因此根据我们使用函数还是公式,传递第二个参数的方式也不同。

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"))

【讨论】:

【参考方案11】:

您可以使用命名向量。以下两个选项(使用 base 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' 运算符 !!! 正在强制评估列表或向量。

?`!!`

!!!强制拼接对象列表。列表的元素是 拼接到位,这意味着它们每个都成为一个单独的参数。

【讨论】:

不明白这是如何工作的 - !!!oldnames 返回 c("A", "B") 但哪个逻辑将其转换为 c("A", "w", "B")?? @AgileBean 我不知道你在哪里发现 !!!oldnames 会返回一个向量。它用于强制对 dplyr 中的多个参数进行非标准评估。见?`!!`Use `!!!` to add multiple arguments to a function. Its argument should evaluate to a list or vector: args &lt;- list(1:3, na.rm = TRUE) ; quo(mean(!!!args))。我想我会在答案中添加这个解释。为提出来干杯【参考方案12】:

您可以设置名称,将其保存为列表,然后对字符串进行批量重命名。一个很好的例子是当您对数据集进行从长到宽的转换时:

names(labWide)
      Lab1    Lab10    Lab11    Lab12    Lab13    Lab14    Lab15    Lab16
1 35.75366 22.79493 30.32075 34.25637 30.66477 32.04059 24.46663 22.53063

nameVec <- names(labWide)
nameVec <- gsub("Lab","LabLat",nameVec)

names(labWide) <- nameVec
"LabLat1"  "LabLat10" "LabLat11" "LabLat12" "LabLat13" "LabLat14""LabLat15"    "LabLat16" " 

【讨论】:

【参考方案13】:

旁注,如果你想将一个字符串连接到所有列名,你可以使用这个简单的代码。

colnames(df) <- paste("renamed_",colnames(df),sep="")

【讨论】:

【参考方案14】:

如果表包含两个同名的列,那么代码如下所示,

rename(df,newname=oldname.x,newname=oldname.y)

【讨论】:

【参考方案15】:

很多答案,所以我只是编写了函数,以便您可以复制/粘贴。

rename <- function(x, old_names, new_names) 
    stopifnot(length(old_names) == length(new_names))
    # pull out the names that are actually in x
    old_nms <- old_names[old_names %in% names(x)]
    new_nms <- new_names[old_names %in% names(x)]

    # call out the column names that don't exist
    not_nms <- setdiff(old_names, old_nms)
    if(length(not_nms) > 0) 
        msg <- paste(paste(not_nms, collapse = ", "), 
            "are not columns in the dataframe, so won't be renamed.")
        warning(msg)
    

    # rename
    names(x)[names(x) %in% old_nms] <- new_nms
    x


 x = data.frame(q = 1, w = 2, e = 3)
 rename(x, c("q", "e"), c("Q", "E"))

   Q w E
 1 1 2 3

【讨论】:

rename(x, c("q", "e"), c("Q", "E")) 似乎不再适用于 dplyr 重命名?【参考方案16】:

如果一行数据包含您想要将所有列更改为的名称,您可以这样做

names(data) <- data[row,]

假设 data 是您的数据框,row 是包含新值的行号。

然后您可以删除包含名称的行

data <- data[-row,]

【讨论】:

【参考方案17】:

这是您需要的功能: 然后只需在 rename(X) 中传递 x ,它就会重命名所有出现的值,如果它不在那里它不会出错

rename <-function(x)
  oldNames = c("a","b","c")
  newNames = c("d","e","f")
  existing <- match(oldNames,names(x))
  names(x)[na.omit(existing)] <- newNames[which(!is.na(existing))]
  return(x)

【讨论】:

这个好像和JoelKuiper's answer一样,不过后来改成了函数.....【参考方案18】:

上面使用专门的软件包有很多很好的答案。这是一种仅使用基础 R 的简单方法。

df.rename.cols <- function(df, col2.list) 
  tlist <- transpose(col2.list)
    
  names(df)[which(names(df) %in% tlist[[1]])] <- tlist[[2]]

  df
 

这是一个例子:

df1 <- data.frame(A = c(1, 2), B = c(3, 4), C = c(5, 6), D = c(7, 8))
col.list <- list(c("A", "NewA"), c("C", "NewC"))
df.rename.cols(df1, col.list)

  NewA B NewC D
1    1 3    5 7
2    2 4    6 8

【讨论】:

【参考方案19】:

我最近根据@agile bean 的答案(使用rename_with,以前的rename_at)构建了一个函数,如果它们存在于数据框中,它会更改列名,这样就可以使列名异构数据帧在适用时相互匹配。

循环肯定可以改进,但我想我会分享给后代。

创建示例数据框:
x= structure(list(observation_date = structure(c(18526L, 18784L, 
17601L), class = c("IDate", "Date")), year = c(2020L, 2021L, 
2018L)), sf_column = "geometry", agr = structure(c(id = NA_integer_, 
common_name = NA_integer_, scientific_name = NA_integer_, observation_count = NA_integer_, 
country = NA_integer_, country_code = NA_integer_, state = NA_integer_, 
state_code = NA_integer_, county = NA_integer_, county_code = NA_integer_, 
observation_date = NA_integer_, time_observations_started = NA_integer_, 
observer_id = NA_integer_, sampling_event_identifier = NA_integer_, 
protocol_type = NA_integer_, protocol_code = NA_integer_, duration_minutes = NA_integer_, 
effort_distance_km = NA_integer_, effort_area_ha = NA_integer_, 
number_observers = NA_integer_, all_species_reported = NA_integer_, 
group_identifier = NA_integer_, year = NA_integer_, checklist_id = NA_integer_, 
yday = NA_integer_), class = "factor", .Label = c("constant", 
"aggregate", "identity")), row.names = c("3", "3.1", "3.2"), class = "data.frame")
功能
match_col_names <- function(x)

  col_names <- list(date = c("observation_date", "date"),
                    C =    c("observation_count", "count","routetotal"),
                    yday  = c("dayofyear"),
                    latitude  = c("lat"),
                    longitude = c("lon","long")
                    )

  for(i in seq_along(col_names))
    newname=names(col_names)[i]
    oldnames=col_names[[i]]

  toreplace = names(x)[which(names(x) %in% oldnames)]
  x <- x %>%
    rename_with(~newname, toreplace)


return(x)



应用函数
x <- match_col_names(x)

【讨论】:

【参考方案20】:

出于执行时间的目的,我建议使用数据表结构:

> df = data.table(x = 1:10, y = 3:12, z = 4:13)
> oldnames = c("x","y","z")
> newnames = c("X","Y","Z")
> library(microbenchmark)
> library(data.table)
> library(dplyr)
> microbenchmark(dplyr_1 = df %>% rename_at(vars(oldnames), ~ newnames) ,
+                dplyr_2 = df %>% rename(X=x,Y=y,Z=z) ,
+                data_tabl1= setnames(copy(df), old = c("x","y","z") , new = c("X","Y","Z")),
+                times = 100) 
Unit: microseconds
       expr    min      lq     mean  median      uq     max neval
    dplyr_1 5760.3 6523.00 7092.538 6864.35 7210.45 17935.9   100
    dplyr_2 2536.4 2788.40 3078.609 3010.65 3282.05  4689.8   100
 data_tabl1  170.0  218.45  368.261  243.85  274.40 12351.7   100

【讨论】:

以上是关于按名称重命名多个列的主要内容,如果未能解决你的问题,请参考以下文章

用名称中的句点重命名clickhouse中的列

在Ubuntu中按字母顺序重命名多个文件

重命名 node.js 续集时间戳列

重命名由当前名称引用的多个数据框列

如何合并多个工作表并用工作表名称重命名列名?

按特定条件复制和重命名 - 批量