### Code Review One change I would make to your solution is to directly modify the row names as below (instead of modifying `gene` and then setting the row names equal to `gene`). ``` for (i in 1:nrow(df)){ if (rownames(df)[i] %in% annotation$gene) { rownames(df)[i] = annotation$name[which(annotation$gene == rownames(df)[i])] } } df$gene <- NULL ``` This version is quite similar to yours, but iterates over row numbers (1-3 in this case) instead of row names. This allows direct modification of the row names and makes the code slightly more concise. This version doesn't use `gene` at all. This is how I would modify your existing solution, but overall I'd caution against using a `for` loop at all. `for` loops can be slow since they iterate over every element (and your data set has 10^5 rows). Below is another approach using the `dplyr` library. ### Another Approach ``` library(dplyr) library(tibble) df <- left_join(df, annotation, by = "gene") %>% # Join "annotation" and "df" mutate(gene = if_else(is.na(name), gene, name)) %>% # Convert "gene" to "name" when "name" is valid column_to_rownames(var = "gene") %>% # Set row names to "gene" select(-name) # Remove "name" ``` This syntax may look unfamiliar but it's doing 3 main things: - left-joining `df` and `annotation` by the `gene` field. It has to be a left-join so all rows of `df` are preserved! - changing the value of `gene` to `name` only when `name` is non-null - setting the row names equal to `gene` The pipe operator `%>%` passes the data through these operations without needing to repeatedly type `df <-`. These are all vectorized operations and so are faster than a `for` loop, especially for large data sets. `dplyr` syntax can be intimidating at first, but it's an extremely popular (and I believe intuitive) paradigm to manipulate data. [Here](https://cran.r-project.org/web/packages/dplyr/vignettes/dplyr.html) is a handy intro on `dplyr` syntax.