R dplyr:使用字符串函数重命名变量

65

(与之有关的问题:在dplyr的rename函数中输入新的列名

dplyr 链式操作(%>%) 中,我希望能够用旧列名的函数(如 tolowergsub 等)来替换多个列名。

library(tidyr); library(dplyr)
data(iris)
# This is what I want to do, but I'd like to use dplyr syntax
names(iris) <- tolower( gsub("\\.", "_", names(iris) ) )
glimpse(iris, 60)
# Observations: 150
# Variables:
#   $ sepal_length (dbl) 5.1, 4.9, 4.7, 4.6, 5.0, 5.4, 4.6,...
#   $ sepal_width  (dbl) 3.5, 3.0, 3.2, 3.1, 3.6, 3.9, 3.4,...
#   $ petal_length (dbl) 1.4, 1.4, 1.3, 1.5, 1.4, 1.7, 1.4,...
#   $ petal_width  (dbl) 0.2, 0.2, 0.2, 0.2, 0.2, 0.4, 0.3,...
#   $ species      (fctr) setosa, setosa, setosa, setosa, s...

# the rest of the chain:
iris %>% gather(measurement, value, -species) %>%
  group_by(species,measurement) %>%
  summarise(avg_value = mean(value)) 

我看到?rename接受replace作为一个以旧名称为名、新名称为值的命名字符向量的参数。

于是我尝试了:

iris %>% rename(replace=c(names(iris)=tolower( gsub("\\.", "_", names(iris) ) )  ))

但是这个(a)返回Error: unexpected '=' in iris %>% ...,而且(b)需要通过名称引用之前操作链中的数据框,而在我的实际使用案例中,我无法做到这一点。

iris %>% 
  rename(replace=c(    )) %>% # ideally the fix would go here
  gather(measurement, value, -species) %>%
  group_by(species,measurement) %>%
  summarise(avg_value = mean(value)) # I realize I could mutate down here 
                                     #  instead, once the column names turn into values, 
                                     #  but that's not the point
# ---- Desired output looks like: -------
# Source: local data frame [12 x 3]
# Groups: species
# 
#       species  measurement avg_value
# 1      setosa sepal_length     5.006
# 2      setosa  sepal_width     3.428
# 3      setosa petal_length     1.462
# 4      setosa  petal_width     0.246
# 5  versicolor sepal_length     5.936
# 6  versicolor  sepal_width     2.770
# ... etc ....  

10
优雅的写法是:iris %>% \names<-`(.,tolower( gsub("\.", "_", names(.) ) ))`(我只是开玩笑而已)。 - Frank
以下答案中使用的一些函数已被弃用。rename_with是最新的dplyr动词,可使用函数对变量进行编程重命名。请参见下面的答案。 - Paul Rougieux
8个回答

58

这是一个非常晚的答案,关于2017年5月

截至dplyr 0.5.0.9004版本,即将发布的0.6.0版本,该软件包中已添加了许多符合magrittr管道操作符%>% 的新列重命名方法。

这些函数包括:

  • rename_all
  • rename_if
  • rename_at

有很多不同的使用方式,但与您的问题相关的其中一种方法是,使用stringr软件包中的以下内容:

df <- df %>%
  rename_all(
      funs(
        stringr::str_to_lower(.) %>%
        stringr::str_replace_all(., '\\.', '_')
      )
  )

那么,请继续进行管道工作 :)(无恶意的双关语)。


16
谢谢,知道了。另外值得注意的是,你可以使用 df %<>% foo() 这种简写形式来代替 df <- df %>% foo() - C8H10N4O2
2
由于dplyr的新更新改变了funs()的工作方式(真希望他们没有这样做),您需要将funs替换为list,并在函数前加上波浪号,例如`list(str_replace(., to_replace, replacement))`。 - MokeEire

38

我认为你正在查看plyr::rename的文档,而不是dplyr::rename的文档。你可以使用dplyr::rename进行如下操作:

iris %>% rename_(.dots=setNames(names(.), tolower(gsub("\\.", "_", names(.)))))

2
在后续出现的地方,您可以将“iris”替换为“.”。 - Frank
这非常有用,为什么你不使用rename而是使用rename_ - Konrad
习惯了,因为我大多数情况下都是以编程方式使用dplyr。 - Matthew Plourde
1
@Konrad 实际上,我手头没有文档,但我认为非安全版本没有 .dots 参数。 - Matthew Plourde
@MatthewPlourde 非常感谢您的有用评论。 - Konrad
2
FYI:rename_正在逐渐弃用。虽然我还没有找到明显的替代方法,但@Frank使用的setNames似乎是最直接的(如果dplyr未提供)。 - r2evans

31

以下是一个避免使用有些笨拙的rename语法的方法:

myris <- iris %>% setNames(tolower(gsub("\\.","_",names(.))))

又需要一个解决方法的依赖?这变得越来越深奥了。 - Anton
你可以将 setnames 替换为 setNames 并且删除对 data.table 的调用。 - Matthew Plourde
@MatthewPlourde你是否知道为什么会更喜欢较长的rename而不是更简单的方法?你的答案看起来像是 rename_(.dots=this_answer),是吗?与data.table中的setnames不同,rename函数的帮助页面并没有宣传通过引用进行修改。 - Frank
@Frank 不是这样的,这是我会做的。希望我的回答能为 OP 解释如何正确使用 rename。我认为你可以通过删除第一个点来进一步简化这个过程。 - Matthew Plourde
2
@Frank,我最终采用了你的回答(+1)因为它是实现我想要的功能的更简单的方法 - 并且教会了我如何使用setNames。但是,@MatthewPlourde更直接地回答了提出的问题(即使用'rename')。感谢你们的时间! - C8H10N4O2
显示剩余4条评论

26

截至2020年,rename_ifrename_atrename_all已被标记为过时。使用最新的dplyr方式处理这个问题应该是使用rename_with()函数:

iris %>% rename_with(tolower)

或者更复杂的版本:

iris %>% 
  rename_with(stringr::str_replace, 
              pattern = "Length", replacement = "len", 
              matches("Length"))

(编辑2021-09-08)
如@a_leemo所评论的那样,这种记号在手册中没有直接提到。相反,可以从手册中推导出以下内容:

iris %>% 
  rename_with(~ stringr::str_replace(.x, 
                                     pattern = "Length", 
                                     replacement = "len"), 
              matches("Length")) 

两种方法都可以实现同样的功能,但我认为第一种方法更易读。在第一个实例中,pattern = ...replacement = ...作为...点的一部分被转发到函数中。有关更多详细信息,请参见?rename_with?dots


1
谢谢!我一直在苦苦思索如何使用rename_with进行编码,但是您的方法解决了我的困惑。 - Erik Ruzek
2
刚刚发现:只需不向函数提供任何参数,但将其指定为函数 mydataframe %>% rename_with(myawesomefunction) - TobiO
@a_leemo,更接近于手册的版本是:iris %>% rename_with(~ stringr::str_replace(.x, pattern = "Length", replacement = "len"), matches("Length")),这里用到了 ~.x 符号。但是,我觉得这种方式比较复杂。不过,正如您所指出的那样,我提出的解决方案与手册有所偏差。感谢您的批评。我会相应地编辑我的答案。 - loki
嗯,谢谢@loki。我还在努力理解Tidyverse中的NSE。我想我尝试过几种方法,包括~或.,但我不认为我两者都使用了!感谢您的帮助。 - a_leemo
1
@LarissaCury 你可能想要使用 mutaterename!!。看一下 ?rlang::\topic-inject`` 的示例。 - loki
显示剩余4条评论

9

对于这种特定但相当普遍的情况,该函数已经在janitor包中编写好了:

library(janitor)

iris %>% clean_names()

##   sepal_length sepal_width petal_length petal_width species
## 1          5.1         3.5          1.4         0.2  setosa
## 2          4.9         3.0          1.4         0.2  setosa
## 3          4.7         3.2          1.3         0.2  setosa
## 4          4.6         3.1          1.5         0.2  setosa
## 5          5.0         3.6          1.4         0.2  setosa
## 6          5.4         3.9          1.7         0.4  setosa
## .          ...         ...          ...         ...     ...

所以总体来说,

iris %>% 
    clean_names() %>%
    gather(measurement, value, -species) %>%
    group_by(species,measurement) %>%
    summarise(avg_value = mean(value))

## Source: local data frame [12 x 3]
## Groups: species [?]
## 
##       species  measurement avg_value
##        <fctr>        <chr>     <dbl>
## 1      setosa petal_length     1.462
## 2      setosa  petal_width     0.246
## 3      setosa sepal_length     5.006
## 4      setosa  sepal_width     3.428
## 5  versicolor petal_length     4.260
## 6  versicolor  petal_width     1.326
## 7  versicolor sepal_length     5.936
## 8  versicolor  sepal_width     2.770
## 9   virginica petal_length     5.552
## 10  virginica  petal_width     2.026
## 11  virginica sepal_length     6.588
## 12  virginica  sepal_width     2.974

9

我使用base、stringr和dplyr尝试了一下:

编辑: 现在library(tidyverse)已经包括了这三个库。

library(tidyverse)
library(maggritr) # Though in tidyverse to use %>% pipe you need to call it 
# library(dplyr)
# library(stringr)
# library(maggritr)

names(iris) %<>% # pipes so that changes are apply the changes back
    tolower() %>%
    str_replace_all(".", "_")

我这么做是为了使用管道构建功能。

my_read_fun <- function(x) {
    df <- read.csv(x) %>%
    names(df) %<>%
        tolower() %>%
        str_replace_all("_", ".")
    tempdf %<>%
        select(a, b, c, g)
}

1
str_replace_all 不包含在这两个软件包中。顺便说一下,在你的回答文本中不需要包含“编辑”注释;只需让它成为可能的最佳答案即可。如果需要,人们可以通过点击下面的链接查看编辑历史记录。 - Frank
1
第一个 str_replace_all 函数中的句点应该被转义为 \\. - 否则所有内容都会被替换为下划线。 - sbha

2
如果您不想自己编写正则表达式,您可以使用以下工具:
  • 非常灵活的snakecase-pkg
  • janitor::make_clean_names() 具有一些不错的默认值
  • janitor::clean_names()make_clean_names()相同,但直接在数据框上运行。
在管道中调用它们应该很简单。
library(magrittr)
library(snakecase)

iris %>% setNames(to_snake_case(names(.)))
iris %>% tibble::as_tibble(.name_repair = to_snake_case)
iris %>% purrr::set_names(to_snake_case)
iris %>% dplyr::rename_all(to_snake_case)
iris %>% janitor::clean_names()


2

无论是使用select()还是select_all()都可以用来重命名列。

如果你只想重命名特定的列,你可以使用select

iris %>% 
  select(sepal_length = Sepal.Length, sepal_width = Sepal.Width, everything()) %>% 
  head(2)

  sepal_length sepal_width Petal.Length Petal.Width Species
1          5.1         3.5          1.4         0.2  setosa
2          4.9         3.0          1.4         0.2  setosa

rename 做的事情与 everything() 相同,只是不需要包含 everything() 函数:

iris %>% 
  rename(sepal_length = Sepal.Length, sepal_width = Sepal.Width) %>% 
  head(2)

  sepal_length sepal_width Petal.Length Petal.Width Species
1          5.1         3.5          1.4         0.2  setosa
2          4.9         3.0          1.4         0.2  setosa

select_all() 可以作用于所有列,并且可以接受一个函数作为参数:

iris %>% 
  select_all(tolower)

iris %>% 
  select_all(~gsub("\\.", "_", .)) 

或者将两者结合起来:

iris %>% 
  select_all(~gsub("\\.", "_", tolower(.))) %>% 
  head(2)

  sepal_length sepal_width petal_length petal_width species
1          5.1         3.5          1.4         0.2  setosa
2          4.9         3.0          1.4         0.2  setosa

这比rename系列中的任何东西都要好,而且更加简单明了...奇怪的是,使用~gsubselect_all比使用带有谓词或变量声明的rename_atrename_if更容易...看起来这就是rename_*的用途。 - dre

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