Home > database >  for loop to function in R
for loop to function in R

Time:11-23

I am trying to convert for loop to a function. The expected outcome is the Summ.Stats. Any help will be appreciated to get the expected outcome (Summ.Stats) in function format which is b.

CN = colnames(mtcars);CN
var <- c("vs", "am")

Summ.Stats <- NULL
library(psych)

for (i in 1:(length(var))) {
  
  temp <- which(CN == var[i])
  
  aux.0 <- mtcars  %>% filter(mtcars[,temp]==0)
  aux.1 <- mtcars %>% filter(mtcars[,temp]==1)
  fname.0 <- paste0(paste(var[i], "0", sep = "_"))
  fname.1 <- paste0(paste(var[i], "1", sep = "_"))
  Summ.0 <- describe(aux.0)
  Summ.1 <- describe(aux.1)
  tab <- round(cbind(Summ.0$mean, Summ.1$mean), 4)
  rownames(tab) <- colnames(aux.0)
  colnames(tab) <- c(fname.0, fname.1)
  Summ.Stats [[i]] <- tab
}
Summ.Stats #EXPECTED OUTCOME

What I tried is the following;

Summ.Stats <- NULL
my.function <- function(var, df){
  
  df <- df[, !sapply(df, is.character)]#REMOVE THE CHARACTER COLUMNS
  CN = colnames(df)  
  
  for (i in 1:length(var)) {
    temp <- which(CN == var[i])
    #
    res <- split(df, df[,temp])
    names(res) <- paste(var[i], names(res), sep = ".") 
    return(res) }
    
    for (j in 1:length(res)){
      tab <- describe(res[[j]]) #here the mean of res[[1]] and res[[2]] should be saved
      Summ.Stats [[j]] <- tab
      return(Summ.Stats)
    }
  }

b <- my.function(var, mtcars);b #only shows vs.0 and vs.1

CodePudding user response:

I'm being lazy here but on first look it could be because of where you've written return, try:

my.function <- function(var, df){
  
  df <- df[, !sapply(df, is.character)]#REMOVE THE CHARACTER COLUMNS
  CN = colnames(df)  
  
  for (i in 1:length(var)) {
    temp <- which(CN == var[i])
    #
    res <- split(df, df[,temp])
    names(res) <- paste(var[i], names(res), sep = ".") 
    }
    
    for (j in 1:length(res)){
      tab <- describe(res[[j]]) #here I need to save mean res[[1]] and res[[2]]
      Summ.Stats [[j]] <- tab
    } 
   return(Summ.Stats)
  }

CodePudding user response:

Some advices :

  • Be careful to return at the right places, returning at the end of each for loop is never right
  • Try to understand which objects you're growing, res is erased by the new value each time in your loop, don't program the next step before testing the previous one
  • Don't convert numeric, character, logical indices between each other when not needed
  • Don't loop on a numeric index when you can loop on a name, don't loop on a name if you can loop directly on items.
  • Learn to use lapply rather than for loops when possible
  • Use browser() in your function to understand what you're doing.
  • put the data argument first if possible (I don't do this below to reproduce your requested output)
  • Have fun :)

I think you want the following

my.function <- function(var, df){
  df <- Filter(is.numeric, df) 
  lapply(var, function(nm) {
    # browser() # uncomment, run and print objects to understand what these steps do
    split_data <- split(df, df[[nm]])
    cols <- lapply(split_data, function(x) psych::describe(x)["mean"])
    df <- do.call(cbind, cols)
    names(df) <- paste(nm, names(split_data), sep = ".")
    df
  })
}
my.function(c("vs", "am"), mtcars)
#> [[1]]
#>             vs.0       vs.1
#> mpg   16.6166667  24.557143
#> cyl    7.4444444   4.571429
#> disp 307.1500000 132.457143
#> hp   189.7222222  91.357143
#> drat   3.3922222   3.859286
#> wt     3.6885556   2.611286
#> qsec  16.6938889  19.333571
#> vs     0.0000000   1.000000
#> am     0.3333333   0.500000
#> gear   3.5555556   3.857143
#> carb   3.6111111   1.785714
#> 
#> [[2]]
#>             am.0        am.1
#> mpg   17.1473684  24.3923077
#> cyl    6.9473684   5.0769231
#> disp 290.3789474 143.5307692
#> hp   160.2631579 126.8461538
#> drat   3.2863158   4.0500000
#> wt     3.7688947   2.4110000
#> qsec  18.1831579  17.3600000
#> vs     0.3684211   0.5384615
#> am     0.0000000   1.0000000
#> gear   3.2105263   4.3846154
#> carb   2.7368421   2.9230769

Created on 2021-11-22 by the reprex package (v2.0.1)

CodePudding user response:

Yet another approach - using some dots and avoiding the psych package altogether.

data <- mtcars

b <- function(df, ...){
  m <- function(y, z) df[df[[y]] == z,] |> colMeans()
  args <- as.list(match.call()[-c(1L, 2L)])
  lapply(args, \(.){
    cbind(m(., 0), m(., 1)) |> 
      (\(x) {colnames(x) <- paste0(c(., .), c("_1", "_2")) ; x})()
  }) |>
    lapply(round, 4)
}
b(data, vs, am)

#> [[1]]
#>          vs_1     vs_2
#> mpg   16.6167  24.5571
#> cyl    7.4444   4.5714
#> disp 307.1500 132.4571
#> hp   189.7222  91.3571
#> drat   3.3922   3.8593
#> wt     3.6886   2.6113
#> qsec  16.6939  19.3336
#> vs     0.0000   1.0000
#> am     0.3333   0.5000
#> gear   3.5556   3.8571
#> carb   3.6111   1.7857
#> 
#> [[2]]
#>          am_1     am_2
#> mpg   17.1474  24.3923
#> cyl    6.9474   5.0769
#> disp 290.3789 143.5308
#> hp   160.2632 126.8462
#> drat   3.2863   4.0500
#> wt     3.7689   2.4110
#> qsec  18.1832  17.3600
#> vs     0.3684   0.5385
#> am     0.0000   1.0000
#> gear   3.2105   4.3846
#> carb   2.7368   2.9231

Explanation - On inspecting psych::describe() it runs a bunch of functions that we don't need in the output. Instead, what we can do is make an auxiliary function m that subsets the data and computes the column means directly, preserving names. To work with arbitrary number of variables its usually a good idea to work with lists, i.e using lapply or purrr::map style approaches, which makes for concise syntax.

  • Related