4

This question is an extension of Changing Column Names in a List of Data Frames in R.

That post addresses changing names of all columns of a data.frame.

But how do you change the names of only a selected number of columns?

Example:

I want to change the name of the first column only in each data.frame in my list:

dat <- data.frame(Foo = 1:5,Bar = 1:5)
lst <- list(dat,dat)

print(lst)

[[1]]
  Foo Bar
1   1   1
2   2   2
3   3   3
4   4   4
5   5   5

[[2]]
  Foo Bar
1   1   1
2   2   2
3   3   3
4   4   4
5   5   5

(Failed) Attempts:

lapply(1:2, function(x) names(lst[[x]])[names(lst[[x]]) == 'Foo'] <- 'New')
lapply(1:2, function(x) names(lst[[x]])[names(lst[[x]]) == 'Foo'])  <- rep('New',2)
lapply(1:2, function(x) setNames(lst[[x]][names(lst[[x]]) == 'Foo'],'New'))
theforestecologist
  • 4,667
  • 5
  • 54
  • 91
  • Note: the column number that needs to be changed might vary between data.frames. In the above exmaple, `Foo` might be in column 1 in one data.frame but in column 2 of a second data.frame. etc. – theforestecologist Jun 08 '18 at 16:41

6 Answers6

7

Here is one possibility using setNames and gsub:

# Sample data
dat <- data.frame(Foo = 1:5,Bar = 1:5)
lst <- list(dat,dat[, 2:1])

# Replace Foo with FooFoo
lst <- lapply(lst, function(x) setNames(x, gsub("^Foo$", "FooFoo", names(x))) )
#[[1]]
#  FooFoo Bar
#1      1   1
#2      2   2
#3      3   3
#4      4   4
#5      5   5
#
#[[2]]
#  Bar FooFoo
#1   1      1
#2   2      2
#3   3      3
#4   4      4
#5   5      5
Maurits Evers
  • 49,617
  • 4
  • 47
  • 68
  • 1
    Excellent answer and equally as useful as @Gregor's [solution](https://stackoverflow.com/a/50765133/4581200). Thanks +1 – theforestecologist Jun 08 '18 at 16:54
  • 3
    To be pedantic and conservative, you might want to change the regex pattern to `"^Foo$"` or use `replace` instead of `gsub`. I've definitely been bitten before by having a pattern match part of a string when I only intended it to match whole strings. – Gregor Thomas Jun 08 '18 at 16:56
  • 1
    @Gregor, it's funny you say that, b/c I just realized this was a problem in my actual list of data.frames that inspired me to post this question! :p Thanks! (and thanks for the update, Maurits) – theforestecologist Jun 08 '18 at 16:58
5

Two problems with your attempts:

  1. It's weird to use lapply(1:2, ...) instead of lapply(lst, ...). This makes your anonymous function more awkward.

  2. Your anonymous function doesn't return the data frame. The last line of a function is returned (in absence of a return() statement). In your first attempt, the value of the last line is just the value assigned, "new" - we need to return the whole data frame with the modified name.

Solution:

lapply(lst, function(x) {names(x)[names(x) == 'Foo'] <- 'New'; x})
# [[1]]
#   New Bar
# 1   1   1
# 2   2   2
# 3   3   3
# 4   4   4
# 5   5   5
# 
# [[2]]
#   New Bar
# 1   1   1
# 2   2   2
# 3   3   3
# 4   4   4
# 5   5   5
Gregor Thomas
  • 136,190
  • 20
  • 167
  • 294
  • This works great (and thanks for the suggestions). However, what if I don't want to print the list? I just want to modify the name of the already saved list. Using your method, do I just replace my previous list object with this one via `lst <- lapply(lst, function(x) {names(x)[names(x) == 'Foo'] <- 'New'; x})` ? – theforestecologist Jun 08 '18 at 16:49
  • 1
    Exactly. Just like any other R function, you have the option to overwrite the original object, save it as a new object, or just print the results without changing anything. – Gregor Thomas Jun 08 '18 at 16:51
  • 1
    The exception being the `data.table` package - if you are using `data.table` you can use `data.table::setnames` to modify the names of the original data tables by reference. – Gregor Thomas Jun 08 '18 at 16:52
1

Here is a way to change the name of the column by column index.

lapply(lst, function(x, pos = 1, newname = "New"){
  # x: data frame, pos: column index, newname: new name of the column
  column <- names(x)
  column[pos] <- newname
  names(x) <- column
  return(x)
})
# [[1]]
#   New Bar
# 1   1   1
# 2   2   2
# 3   3   3
# 4   4   4
# 5   5   5
# 
# [[2]]
#   New Bar
# 1   1   1
# 2   2   2
# 3   3   3
# 4   4   4
# 5   5   5

I posted this answer before I saw an updated comment from the OP saying that the index of the target column from each data frame could be different. This is not mentioned in the original post. Please see others' post as my answer only works if the column index is consistent.

www
  • 38,575
  • 12
  • 48
  • 84
1

My solution is more complicated than the others but here it goes.

The main difference is that instead of == it uses grep (with argument ignore.case = TRUE).

lapply(lst, function(DF) {
  inx <- grep("^foo$", names(DF), ignore.case = TRUE)
  names(DF)[inx] <- "New"
  DF
})
#[[1]]
#  New Bar
#1   1   1
#2   2   2
#3   3   3
#4   4   4
#5   5   5
#
#[[2]]
#  New Bar
#1   1   1
#2   2   2
#3   3   3
#4   4   4
#5   5   5
Rui Barradas
  • 70,273
  • 8
  • 34
  • 66
  • See my comment on on Maurits's answer - I'd recommend being more careful with with your regex pattern, or just using `==`, or making it clear that this will rename any column that *contains* the string "foo". – Gregor Thomas Jun 08 '18 at 17:00
1

Using tidyverse:

library(tidyverse)
map(lst,rename_at,"Foo",~"New")
# [[1]]
# New Bar
# 1   1   1
# 2   2   2
# 3   3   3
# 4   4   4
# 5   5   5
# 
# [[2]]
# New Bar
# 1   1   1
# 2   2   2
# 3   3   3
# 4   4   4
# 5   5   5

Using data.table:

library(data.table)
lst2 <- copy(lst)
lapply(lst2,setnames,"Foo","New")

# [[1]]
# New Bar
# 1   1   1
# 2   2   2
# 3   3   3
# 4   4   4
# 5   5   5
# 
# [[2]]
# New Bar
# 1   1   1
# 2   2   2
# 3   3   3
# 4   4   4
# 5   5   5

Here changes are made by reference so we make a copy first.

moodymudskipper
  • 46,417
  • 11
  • 121
  • 167
0

Note without the assignment, it doesn't change the original object.

lst <- purrr::map(lst, ~setNames(.x, c('new', names(.x)[-1])))

zack
  • 5,205
  • 1
  • 19
  • 25