calculate z-score across multiple dataframes in R

Viewed 366

I have ten dataframes with equal number of rows and columns. They look like this:

df1 <- data.frame(geneID=c("AKT1","AKT2","AKT3","ALK",
                           "APC"),
                  CDKN2A=c(3490,9447,4368,908,204),
                  INPP4B=c(NA,9459,4395,1030,NA),
                  BCL2=c(NA,9480,4441,1209,NA),
                  IRS2=c(NA,NA,4639,1807,NA),
                  HRAS=c(3887,9600,4691,1936,1723))

df2 <- data.frame(geneID=c("AKT1","AKT2","AKT3","ALK",
                           "APC"),
                  CDKN2A=c(10892,17829,7156,1325,387),
                  INPP4B=c(NA,17840,7185,1474,NA),
                  BCL2=c(NA,17845,7196,1526,NA),
                  IRS2=c(NA,NA,12426,10244,NA),
                  HRAS=c(11152,17988,7545,2734,2423))

df3 <- data.frame(geneID=c("AKT1","AKT2","AKT3","ALK",
                           "APC"),
                  CDKN2A=c(11376,17103,8580,780,178),
                  INPP4B=c(NA,17318,9001,2829,NA),
                  BCL2=c(NA,17124,8621,1141,NA),
                  IRS2=c(NA,NA,8658,1397,NA),
                  HRAS=c(11454,17155,8683,1545,1345))

I would like to calculate z-score for each data frame, based on mean and variance across multiple dataframes. The z-score should be calculated as follows: z-score=(x-mean(x))/sd(x))).

I found that ddply function of plyr can do this job, but the solution was for single dataframe, while I have multiple dataframes as separate files with 18214 rows and 269 columns.

I would appreciate any suggestions.

Thank you very much for your help! Olha

2 Answers

Here is one option where we bind the datasets together with bind_rows (from dplyr), then group by the grouping column and return the zscore transformed numeric columns

library(dplyr)
bind_rows(df1, df2, df3, .id = 'grp') %>% 
     group_by(geneID) %>% 
     mutate(across(where(is.numeric), 
      ~(.- mean(., na.rm = TRUE))/sd(., na.rm = TRUE), .names = '{col}_zscore'))

NOTE: if we dont need new columns, then remove the .names part


If we need to do this in a loop, without binding into a single data.frame, can loop over the list

library(purrr)
list(df1, df2, df3) %>% # // automatically => mget(ls('^df\\d+$'))
        map(~ .x %>%
          mutate(across(where(is.numeric), 
      ~(.- mean(., na.rm = TRUE))/sd(., na.rm = TRUE), .names = '{col}_zscore')))

Here is a base R solution with function scale.

df_list <- list(df1, df2, df3)

df_list2 <- lapply(df_list, function(DF){
  i <- sapply(DF, is.numeric)
  DF[i] <- lapply(DF[i], scale)
  DF
})

S3 methods

Considering that scale is generic and that methods can be written for it, here is a data.frame method, then applied to the same list df_list.

scale.data.frame <- function(x, center = TRUE, scale = TRUE){
  i <- sapply(x, is.numeric)
  x[i] <- lapply(x[i], scale, center = center, scale = scale)
  x
}

df_list3 <- lapply(df_list, scale)

identical(df_list2, df_list3)
#[1] TRUE
Related