简体   繁体   中英

R: update rownames in data.frame using conversion table

I have a data.frame of the following form:

> set.seed(1)
> myp <- paste0('P', sort(sample(1:15, 10)))
> mydf <- data.frame(param=c(paste0(myp, 'B'), paste0(myp, 'R'), paste0(myp, 'max'), paste0(myp, 'min')),
+ value1=NA, value2=NA, value3=NA)
> rownames(mydf) <- mydf$param
> mydf$param <- NULL
> mydf
       value1 value2 value3
P1B        NA     NA     NA
P3B        NA     NA     NA
P4B        NA     NA     NA
P5B        NA     NA     NA
P6B        NA     NA     NA
P8B        NA     NA     NA
P9B        NA     NA     NA
P10B       NA     NA     NA
P11B       NA     NA     NA
P14B       NA     NA     NA
P1R        NA     NA     NA
P3R        NA     NA     NA
P4R        NA     NA     NA
P5R        NA     NA     NA
P6R        NA     NA     NA
P8R        NA     NA     NA
P9R        NA     NA     NA
P10R       NA     NA     NA
P11R       NA     NA     NA
P14R       NA     NA     NA
P1max      NA     NA     NA
P3max      NA     NA     NA
P4max      NA     NA     NA
P5max      NA     NA     NA
P6max      NA     NA     NA
P8max      NA     NA     NA
P9max      NA     NA     NA
P10max     NA     NA     NA
P11max     NA     NA     NA
P14max     NA     NA     NA
P1min      NA     NA     NA
P3min      NA     NA     NA
P4min      NA     NA     NA
P5min      NA     NA     NA
P6min      NA     NA     NA
P8min      NA     NA     NA
P9min      NA     NA     NA
P10min     NA     NA     NA
P11min     NA     NA     NA
P14min     NA     NA     NA

I want to update the names of the rows, and for that I have a conversion table that looks like the following one:

> conv.df <- data.frame(name=myp, new.name=paste0('P', 1:10))
> conv.df
   name new.name
1    P1       P1
2    P3       P2
3    P4       P3
4    P5       P4
5    P6       P5
6    P8       P6
7    P9       P7
8   P10       P8
9   P11       P9
10  P14      P10

The only thing I want to do is to update the rownames in mydf so it reflects the new.names in the conversion table conv.df .

It seems very easy but I cannot wrap my head around it... I would try a regular expression approach, my only problem is that I am not 100% sure that I would ONLY encounter rownames of the form PnB , PnR , Pnmax , Pnmin ... I would like a solution that applies for any PnX instance (it would always be Pn followed by [:alpha:] )

You can make the rowname a column, split the root "Pn" and letter "[:alpha:]" and then rename them as follows,

set.seed(1)
myp <- paste0('P', sort(sample(1:15, 10)))
mydf <- data.frame(param=c(paste0(myp, 'B'), paste0(myp, 'R'), paste0(myp, 'max'), paste0(myp, 'min')),
                     value1=NA, value2=NA, value3=NA)
rownames(mydf) <- mydf$param
mydf$param <- NULL
mydf


library(tidyverse)

mydf%>%
  rownames_to_column()%>%
  mutate(root = gsub("^(P\\d\\d?).*$","\\1",rowname),
         letter = gsub("^P\\d\\d?(.*)$","\\1",rowname))%>%
  mutate(root = recode(root,
    P1 = "P1",
    P3 = "P2",
    P4 = "P3",
    P5 = "P4",
    P6 = "P5",
    P8 = "P6",
    P9 = "P7",
    P10 = "P8",
    P11 = "P9",
    P14 = "P10"
  ))%>%
  mutate(rowname = paste0(root,letter))%>%
  column_to_rownames()%>%
  select(-root,-letter)

The answer by @teofil is good and it works. Here is another way to do it by still using recode and some meta programming,

library(tidyverse)

rename_col_df <- function(data,colname,df_rename){
  # data is the input data frame
  # colname is the column to be modified
  # df_rename must have columns name and new.name

  colname = enexpr(colname) # Capture the user input col name as a symbol
  old_name = df_rename$name
  new_name = df_rename$new.name

  # Start construcing an expression
  # The following line creates a recode function
  # recode_expr[[1]] is "recode"
  # recode_expr[[2]] is the first argument
  recode_expr = expr(recode(!!colname))

  # All subsequent arguments to recode are added here

  for(i in seq_along(old_name)){
    recode_expr[[old_name[i]]] = new_name[i]
  }


  data = data %>% mutate(!!colname := !!recode_expr)

  return(data)

}

conv.df <- data.frame(name=myp, new.name=paste0('P', 1:10),stringsAsFactors = FALSE)

mydf%>%
  rownames_to_column()%>%
  mutate(root = gsub("^(P\\d\\d?).*$","\\1",rowname),
         letter = gsub("^P\\d\\d?(.*)$","\\1",rowname))%>%
  rename_col_df(root,conv.df) %>%
  mutate(rowname = paste0(root,letter))%>%
  column_to_rownames()%>%
  select(-root,-letter)

If we wanted to apply the renaming to a vector instead of a dataframe,

rename_vec_df <- function(vec,df_rename){
  # vec is the vector to be modified
  # df_rename must have columns name and new.name

  old_name = df_rename$name
  new_name = df_rename$new.name

  # Start construcing an expression
  # The following line creates a recode function
  # recode_expr[[1]] is "recode"
  # recode_expr[[2]] is the first argument
  recode_expr = expr(recode(!!vec))

  # All subsequent arguments to recode are added here

  for(i in seq_along(old_name)){
    recode_expr[[old_name[i]]] = new_name[i]
  }


  vec = eval(recode_expr)

  return(vec)

}

myp <- paste0('P', sort(sample(1:15, 10)))
conv.df <- data.frame(name=myp, new.name=paste0('P', 1:10),stringsAsFactors = FALSE)

vec <- c("P1","P14","P10")

rename_vec_df(vec,conv.df)

To learn more about the techniques used here,

https://rlang.r-lib.org/reference/quotation.html https://adv-r.hadley.nz/metaprogramming.html

Following up on @Sada93 code. To avoid the recode step, use join :

library(tidyverse)
set.seed(1)
myp <- paste0('P', sort(sample(1:15, 10)))
mydf <-
  data.frame(
    param = c(
      paste0(myp, 'B'),
      paste0(myp, 'R'),
      paste0(myp, 'max'),
      paste0(myp, 'min')
    ),
    value1 = NA,
    value2 = NA,
    value3 = NA
  )
rownames(mydf) <- mydf$param
mydf$param <- NULL
mydf

conv.df <- data.frame(name=myp, new.name=paste0('P', 1:10))

mydf %>% rownames_to_column() %>%
  mutate(name = gsub("^(P\\d\\d?).*$","\\1",rowname),
         letter = gsub("^P\\d\\d?(.*)$","\\1",rowname)) %>% 
  left_join(., conv.df, by="name") %>% 
  mutate(rowname=paste(new.name, letter, sep="")) %>% 
  column_to_rownames() %>% 
  select(-name, -letter)

       value1 value2 value3 new.name
P1B        NA     NA     NA       P1
P2B        NA     NA     NA       P2
P3B        NA     NA     NA       P3
P4B        NA     NA     NA       P4
P5B        NA     NA     NA       P5
P6B        NA     NA     NA       P6
P7B        NA     NA     NA       P7
P8B        NA     NA     NA       P8
P9B        NA     NA     NA       P9
P10B       NA     NA     NA      P10
P1R        NA     NA     NA       P1

You cannot have duplicate row names in a dataframe. Here is one way in base R to add it as a column name. Here we extract the common part in the original rownames which is "P" followed by a number, match it with conv.df$name and get the corresponding conv.df$new.name .

mydf$new_name <- conv.df$new.name[
                match(sub("(P\\d+).*", "\\1", rownames(mydf)), conv.df$name)]


mydf
#       value1 value2 value3 new_name
#P1B        NA     NA     NA       P1
#P2B        NA     NA     NA       P2
#P3B        NA     NA     NA       P3
#P4B        NA     NA     NA       P4
#P7B        NA     NA     NA       P5
#P8B        NA     NA     NA       P6
#P9B        NA     NA     NA       P7
#P11B       NA     NA     NA       P8
#P12B       NA     NA     NA       P9
#P13B       NA     NA     NA      P10
#P1R        NA     NA     NA       P1
#P2R        NA     NA     NA       P2
#...

where

sub("(P\\d+).*", "\\1", rownames(mydf)) #returns

#[1] "P1"  "P2"  "P3"  "P4"  "P7"  "P8"  "P9"  "P11" "P12" "P13" "P1"  "P2"  "P3"  
#    "P4"  "P7"  "P8"  "P9"  "P11" "P12" "P13" "P1"  "P2"  "P3"  "P4"  "P7"  "P8"  
#    "P9"  "P11" "P12" "P13" "P1"  "P2"  "P3"  "P4"  "P7"  "P8"  "P9"  "P11" "P12" 
#    "P13"

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM