Home > Software engineering >  Creating a function to generate a matrix using several lists
Creating a function to generate a matrix using several lists

Time:07-07

Suppose I have the following lists:

brice_grades <- list(math = c(90, 64, 100), reading = c(55, 71, 50),
                 science = c(100, 100, 98))
talia_grades <- list(math = c(70, 64, 70), reading = c(100, 80, 50),
                     science = c(100, 92, 98))
annie_grades <- list(math = c(14, 64, 50), reading = c(90, 71, 99),
                     science = c(88, 70, 98))

I am trying to create a function, report_card, that I can run through the sapply function such that I return a matrix with average grades for each class, unless the grade is below a 70, in which case it says "fail" rather than the grade.

Here is what I mean:

          brice_grades  talia_grades  annie_grades
math      84.7          fail          fail
reading   fail          76.7          86.7
science   99.3          96.7          85.3

I'm new to creating functions in R so I'm a little tripped up. I'm not sure if I should be using sapply given that all of my output is not the same class ("fail" is a string and the means are numeric obviously). Additionally, it gets a little weird for me writing this function using vectors contained within lists.

CodePudding user response:

If you're happy with a character matrix output, a double loop with sapply should take care of it.

failfun <- function(x) {m <-  mean(x); if(m < 70) "fail" else round(m,1)}
sapply(mget(c("brice_grades", "talia_grades", "annie_grades")), \(x) sapply(x, failfun))
#        brice_grades talia_grades annie_grades
#math    "84.7"       "fail"       "fail"      
#reading "fail"       "76.7"       "86.7"      
#science "99.3"       "96.7"       "85.3" 

CodePudding user response:

Another option is to turn your lists into a data frame and use tidyverse for the rest:

library(tidyverse)

# All into a data frame
df <-
  mget(ls(pattern = "_grades")) |>
  imap(~ .x |> as_tibble() |> mutate(person = .y)) |>
  bind_rows()

# Inspired by thelatemail
failfun <- function(x) {
  m <- mean(x)
  if (m < 70) "fail" else as.character(round(m, 1))
}

# Do the calculations
df <-
  df |>
  group_by(person) |>
  summarise(across(everything(), ~ failfun(.))) |>
  pivot_longer(-person) |>
  pivot_wider(person)
  
df

Output:

# A tibble: 3 × 4
  person       math  reading science
  <chr>        <chr> <chr>   <chr>  
1 annie_grades fail  86.7    85.3   
2 brice_grades 84.7  fail    99.3   
3 talia_grades fail  76.7    96.7   

CodePudding user response:

You can use transpose from purrr package

purrr::transpose(list(brice_grades , talia_grades , annie_grades)) 
|> sapply(\(x) sapply(x ,
\(x) if(mean(x) < 70) "fail" else round(mean(x) , 1))) -> avg

row.names(avg) <- c("brice_grades" , "talia_grades" , "annie_grades")

avg <- t(avg)
  • output
        brice_grades talia_grades annie_grades
math    "84.7"       "fail"       "fail"      
reading "fail"       "76.7"       "86.7"      
science "99.3"       "96.7"       "85.3"      
  • Related