row.names(x) versus row.names<- in lapply on a list of data frames

When I’m attempting to answer this question, I noticed that row.names(x) <- vector is not working when using lapply on a list of data frames, while row.names<- worked.

Here’s some sample data, I’m attempting to append the names of the list to the rownames of the dataframes:

l <- list(a=data.frame(col = c(1,2,3),row.names = c("k","l","m")), 
          b=data.frame(col = c(4,5,6), row.names = c("o","p","r")))

$a
  col
k   1
l   2
m   3

$b
  col
o   4
p   5
r   6

For example when looping over the names of the list and setting row.names in lapply, this is the result:

lapply(names(l), \(x) row.names(l[[x]]) <- paste0(x, ".", rownames(l[[x]])))

[[1]]
[1] "a.k" "a.l" "a.m"

[[2]]
[1] "b.o" "b.p" "b.r"

When we do the same code outside lapply, it worked:

> row.names(l[["a"]]) <- paste0("a.", rownames(l[["a"]]))
> l

$a
    col
a.k   1
a.l   2
a.m   3

The solution I have for this problem is to use row.names<- (as suggested by the original OP):

setNames(lapply(names(l), 
                \(x) l[[x]] %>% `row.names<-` (paste(x, rownames(l[[x]]), sep = "."))), 
         names(l))

$a
    col
a.k   1
a.l   2
a.m   3

$b
    col
b.o   4
b.p   5
b.r   6

But I’m not sure why did the row.names(x) <- vector syntax failed and why row.names<- worked.

>Solution :

  • The assignment is local, i.e. it creates a modified object l with assigned row names inside the anonymous function.

  • The result of the <- assignment is returned from that anonymous function. However, the result of <- is always the RHS, even for replacement functions. You can make this visible by putting parentheses around the assignment in the global scope:

    (row.names(l$a) <- paste0('a.', row.names(l$a)))
    # [1] "a.k" "a.l" "a.m"
    

So in your second piece of code, the the transformation used by lapply is just the result of the assignment, which is the RHS of the assignment, which is the transformed row names. To fix the code, you need to return the modified object, l[[x]], itself:

lapply(
    names(l),
    \(x) {
        row.names(l[[x]]) <- paste0(x, ".", row.names(l[[x]]))
        l[[x]]
    }
)

Or you use row.names<- as a function call:

lapply(names(l), \(x) `row.names<-`(l[[x]], paste0(x, ".", row.names(l[[x]]))))

And to make this a bit more readable use Map instead of lapply:

Map(\(x, n) `row.names<-`(x, paste0(n, ".", row.names(x))), l, names(l))

Or, without anonymous function (although I’m not convinced this is more readable):

Map(`row.names<-`, l, Map(paste0, names(l), ".", Map(row.names, l)))

Leave a Reply