3

I am a beginning user of R and i have written a code that I believe could be shortened with a for loop. The problem is I cannot figure out how to write the loop.

I have a dataframe with the column 'TestGrade' with values like 'Grade 1' or 'Kindergarten'. I am trying to change that column to be only a numeric value. For example 'Kindergarten' would be changed to 0 and 'Grade 1' would be changed to 1. I will provide code below of a sample dataframe and also how I solved the problem without a loop.

Any guidance will be greatly appreciated!

##Sample Data
FirstInitial <- c("A", "D", "M", "C", "J", "S", "K", "L", "M", "K", "G", "B", "F")
LastInitial <- c("S", "M", "T", "M", "A", "B", "H", "M", "S", "W", "L", "Z", "P")
TestGrade <- c('Kindergarten', 'Grade 1','Grade 2', 'Grade 3','Grade 4', 'Grade 5', 'Grade 6','Grade 7','Grade 8', 'Grade 9', 'Grade 10', 'Grade 11','Grade 12')

df <- data.frame(FirstInitial, LastInitial, TestGrade)

##The codes current function
if(any(df$TestGrade == 'Kindergarten')){
  df$TestGrade <- gsub('Kindergarten', '0', df$TestGrade)
}
if(any(df$TestGrade == 'Grade 1')){
  df$TestGrade <- gsub('Grade 1', '1', df$TestGrade)
}
if(any(df$TestGrade == 'Grade 2')){
  df$TestGrade <- gsub('Grade 2', '2', df$TestGrade)
}
if(any(df$TestGrade == 'Grade 3')){
  df$TestGrade <- gsub('Grade 3', '3', df$TestGrade)
}
if(any(df$TestGrade == 'Grade 4')){
  df$TestGrade <- gsub('Grade 4', '4', df$TestGrade)
}
if(any(df$TestGrade == 'Grade 5')){
  df$TestGrade <- gsub('Grade 5', '5', df$TestGrade)
}

if(any(df$TestGrade == 'Grade 6')){
  df$TestGrade <- gsub('Grade 6', '6', df$TestGrade)
}
if(any(df$TestGrade == 'Grade 7')){
  df$TestGrade <- gsub('Grade 7', '7', df$TestGrade)
}
if(any(df$TestGrade == 'Grade 8')){
  df$TestGrade <- gsub('Grade 8', '8', df$TestGrade)
}
if(any(df$TestGrade == 'Grade 9')){
  df$TestGrade <- gsub('Grade 9', '9', df$TestGrade)
}
if(any(df$TestGrade == 'Grade 10')){
  df$TestGrade <- gsub('Grade 10', '10', df$TestGrade)
}
if(any(df$TestGrade == 'Grade 11')){
  df$TestGrade <- gsub('Grade 11', '11', df$TestGrade)
}
if(any(df$TestGrade == 'Grade 12')){
  df$TestGrade <- gsub('Grade 12', '12', df$TestGrade)
}
1
  • 1
    The tangential advice I repeatedly offer up is to use explicit loops as a LAST choice in R. The apply functions and related packages are designed for coding efficiency. Commented Mar 29, 2019 at 15:42

6 Answers 6

7

We can use ifelse, assign 0 for "Kindergarten" and remove "Grade" from others

as.numeric(ifelse(df$TestGrade == "Kindergarten", 0, 
          sub("Grade ", "", df$TestGrade)))

#[1]  0  1  2  3  4  5  6  7  8  9 10 11 12
Sign up to request clarification or add additional context in comments.

Comments

5

We can use case_when

library(dplyr)
library(readr)
df %>%
  mutate(TestGrade = case_when(as.character(TestGrade) == "Kindergarten"~ 0,
                               TRUE ~ parse_number(TestGrade)))

#   FirstInitial LastInitial TestGrade
#1             A           S         0
#2             D           M         1
#3             M           T         2
#4             C           M         3
#5             J           A         4
#6             S           B         5
#7             K           H         6
#8             L           M         7
#9             M           S         8
#10            K           W         9
#11            G           L        10
#12            B           Z        11
#13            F           P        12

Comments

4

First shortening: you don't need any if(any(...)). gsub is smart, it's like a find/replace. The command gsub('Grade 9', '9', df$TestGrade) will replace 'Grade 9' with '9', and won't touch anything else. So deleting all your if statements, we get:

df$TestGrade <- gsub('Kindergarten', '0', df$TestGrade)
df$TestGrade <- gsub('Grade 1', '1', df$TestGrade)
df$TestGrade <- gsub('Grade 2', '2', df$TestGrade)
df$TestGrade <- gsub('Grade 3', '3', df$TestGrade)
df$TestGrade <- gsub('Grade 4', '4', df$TestGrade)
df$TestGrade <- gsub('Grade 5', '5', df$TestGrade)
df$TestGrade <- gsub('Grade 6', '6', df$TestGrade)
df$TestGrade <- gsub('Grade 7', '7', df$TestGrade)
df$TestGrade <- gsub('Grade 8', '8', df$TestGrade)
df$TestGrade <- gsub('Grade 9', '9', df$TestGrade)
df$TestGrade <- gsub('Grade 10', '10', df$TestGrade)
df$TestGrade <- gsub('Grade 11', '11', df$TestGrade)
df$TestGrade <- gsub('Grade 12', '12', df$TestGrade)

Next improvement, we could do a loop. This is exactly equivalent to the code above, just less typing.

pattern = c("Kindergarten", paste("Grade", 1:12))
replacement = as.character(0:12)

for (i in seq_along(pattern)) {
  df$TestGrade <- gsub(pattern[i], replacement[i], df$TestGrade)
}

Even better, we could be cleverer, make kindergarten a special case and just delete "Grade " from everything else, as in Juian's and Ronak's answers. Another variation of that is this:

df$TestGrade = as.character(df$TestGrade) # needed only if it is a factor
df$TestGrade[df$TestGrade == "Kindergarten"] = 0
df$TestGrade = sub("Grade ", "", df$TestGrade)
df$TestGrade = as.numeric(df$TestGrade) # if needed

If we really want to be fancy, we could set fixed = TRUE inside sub(). This tells sub we want exact matches only, we're not trying to use regular expressions. This will make the code run faster, but unless you've got a lot of data, you won't notice a difference. If you have 100,000+ rows, this method will be quite fast:

# optimized
df$TestGrade = as.character(df$TestGrade) # needed only if it is a factor
df$TestGrade[df$TestGrade == "Kindergarten"] = 0
df$TestGrade = as.integer(sub("Grade ", "", df$TestGrade, fixed = TRUE))

Comments

3

This can be done without the need for a for loop using two line code. I also suggest you add stringsAsFactors = F in your data.frame command before running these lines

df$TestGrade[df$TestGrade == "Kindergarten"] = 0
df$TestGrade <- gsub("Grade ", "", df$TestGrade)

> df
   FirstInitial LastInitial TestGrade
1             A           S         0
2             D           M         1
3             M           T         2
4             C           M         3
5             J           A         4
6             S           B         5
7             K           H         6
8             L           M         7
9             M           S         8
10            K           W         9
11            G           L        10
12            B           Z        11
13            F           P        12

Comments

3

You can write a key and set the grades as a factor. This will work even if the format of the grades changes.

key <- c('Kindergarten',
         'Grade 1',
         'Grade 2',
         'Grade 3',
         'Grade 4',
         'Grade 5',
         'Grade 6',
         'Grade 7',
         'Grade 8',
         'Grade 9',
         'Grade 10',
         'Grade 11',
         'Grade 12')
dat <- c('Grade 3', 'Grade 5', 'Grade 2')
dat <- factor(dat, levels = key)
dat <- as.numeric(dat) - 1
dat

We subtract 1 at the end because the factors start at 1 and you wanted kindergarten set to 0.

Comments

2

This solves your Problem here:

df$TestGrade <- sapply(df$TestGrade,function(el)
  {
  if(el == "Kindergarten") return(0)
  else return(as.numeric(sub("Grade ","",el)))
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.