Search code examples
rloopsvectorreturnrscript

Creating a loop for segments within a vector that give return values for each corresponding segment


I've created a vector "numGrades" of 100 random numbers to represent values within a grading system. I need to write a "for" loop that takes segments of numerical grades and returns a vector of corresponding letter grades i.e.: 90+ = "A", 80-89 = "B", 70-79 = "C", 60-69 = "D", 0-59 = "F". I want to be able to run numGrades to return the corresponding letter grade for example: numGrades = [72, 65, 93] returning = ["C", "D", "A"] with the loop handling vectors of any length. This is what I have tried so far individually. All of these loops have returned warnings:

set.seed(43)
numGrades <- sample(0:100, 100, replace=FALSE)

for (i in numGrades )
  if(91 <= numGrades[i]) {
    numGrades[i] <- "A"
  } else if (80 <= numGrades[i] & numGrades[i] <= 90) {
    numGrades[i] =="B"
  } else if (70 <= numGrades[i] & numGrades[i] <= 79) {
    numGrades[i] =="C"
  } else if (60 <= numGrades[i] & numGrades[i] <= 69) {
    numGrades[i] =="D"
  } else if (0 <= numGrades[i] & numGrades[i] <= 59) {
    numGrades[i] =="F"
  }

It's saying:

Error in if (91 <= numGrades[i]) { : 
  missing value where TRUE/FALSE needed

New Edit (Returns for grades >= 91 only):

numGrades <- (0:100)
for (i in 1:length(numGrades ))
  if(91 <= numGrades[i]) {
    numGrades[i] <- "A"
  } else if (80 <= numGrades[i] & numGrades[i] <= 90) {
    numGrades[i] =="B"
  } else if (70 <= numGrades[i] & numGrades[i] <= 79) {
    numGrades[i] =="C"
  } else if (60 <= numGrades[i] & numGrades[i] <= 69) {
    numGrades[i] =="D"
  } else if (0 <= numGrades[i] & numGrades[i] <= 59) {
    numGrades[i] =="F"
  }

Working Rough Draft

ltrGrades <- (0:100)
numGrades <- character(length(ltrGrades))

for (i in 1:length(ltrGrades ))
  if(any(ltrGrades[i] == 91:100)) {
    numGrades[i] <- "A"
  } else if (any(ltrGrades[i] == 80:90)) {
    numGrades[i] <- "B"
  } else if (any(ltrGrades[i] == 70:79)) {
    numGrades[i] <- "C"
  } else if (any(ltrGrades[i] == 60:69)) {
    numGrades[i] <- "D"
  } else if (any(ltrGrades[i] == 0:59)) {
    numGrades[i] <- "F"
  }

Solution

  • There are some fundamental R syntax problems with your code.

    (numGrades[i]) c(80:90))
    (numGrades[i]) >=80 && <=89)
    (numGrades[i]) ==80:89 )
    

    Several alternatives, most inefficient:

    (80 <= numGrades[i] & numGrades[i] < 90)    # the most basic
    (dplyr::between(numGrades[i], 80, 90))      # if dplyr is loaded
    (data.table::between(numGrades[i], 80, 90)) # if data.table is available
    (numGrades[i] %in% 80:89)                   # works only if all grades are perfectly integral
    (any(numGrades[i] == 80:89))                # ditto
    

    Why are they wrong?

    • (numGrades[i]) c(80:90)), because there is not operator
    • (numGrades[i]) >=80 && <=89), R does not infer them as you suggest, every time you do an (in)equality test you need to specify both the LHS and RHS for each one; similarly, unlikely many languages, R does not "chain" them, so (80 <= numGrades[i] <= 89) will not work
    • (numGrades[i]) ==80:89 ) is getting closer, but if/else statements require a single comparison; in this case, you are comparing one number with a sequence (range) of 10, so the reply from this is length 10. It must be length 1.

    Bottom line, though, is that you do not need a loop.

    # set.seed(43)
    numGrades <- sample(0:100, 100, replace=FALSE)
    cut(numGrades, c(-1, 60, 70, 80, 90, 101), labels=c("F","D","C","B","A"))
    #   [1] F A F D F F D F F C F F F F F B F A F F C A F F F F A F A F C C C B F
    #  [36] F F F B A F F F A B B C D F F F B D F F B D D B F F F F F F D F A F F
    #  [71] F F F F B F D F A F F F F F A B F F F C F F F D D C C F F F
    # Levels: F D C B A
    

    or if you don't like or understand what a factor is, then

    as.character(cut(numGrades, c(-1, 60, 70, 80, 90, 101), labels=c("F","D","C","B","A")))
    #   [1] "F" "A" "F" "D" "F" "F" "D" "F" "F" "C" "F" "F" "F" "F" "F" "B" "F"
    #  [18] "A" "F" "F" "C" "A" "F" "F" "F" "F" "A" "F" "A" "F" "C" "C" "C" "B"
    #  [35] "F" "F" "F" "F" "B" "A" "F" "F" "F" "A" "B" "B" "C" "D" "F" "F" "F"
    #  [52] "B" "D" "F" "F" "B" "D" "D" "B" "F" "F" "F" "F" "F" "F" "D" "F" "A"
    #  [69] "F" "F" "F" "F" "F" "F" "B" "F" "D" "F" "A" "F" "F" "F" "F" "F" "A"
    #  [86] "B" "F" "F" "F" "C" "F" "F" "F" "D" "D" "C" "C" "F" "F" "F"
    

    EDIT

    I just noticed something else about your code.

    When you start, numGrades is either numeric or integer. However, since it is a vector, the first time you assign a letter to one of its elements, the entire vector is converted to a character vector. The second pass through the for loop will try to compare a number with a string, which will not do a numeric comparison, try 8 < "75" for why this will fail.

    As a workaround for this:

    ltrGrades <- character(length(numGrades))
    for (i in 1:length(numGrades ))
      if(91 <= numGrades[i]) {
        ltrGrades[i] <- "A"
      } else if (80 <= numGrades[i] & numGrades[i] <= 90) {
        ltrGrades[i] <- "B"
      } else if (70 <= numGrades[i] & numGrades[i] <= 79) {
        ltrGrades[i] <- "C"
      } else if (60 <= numGrades[i] & numGrades[i] <= 69) {
        ltrGrades[i] <- "D"
      } else if (0 <= numGrades[i] & numGrades[i] <= 59) {
        ltrGrades[i] <- "F"
      }