How can I use a for loop for each specific subject in the same data frame in R?

Viewed 172

I have a dataframe that has 100 subjects' data in it. I want to perform some analysis on each subjects data, individually. Because of the large size, its not feasible to manually select certain rows. Is there a way i can perform an analysis for each subject which outputs the data of interest to an array?

Example df:

subject <- c(1,1,1,2,2,2,3,3,3)
score <- c(c(10, 20, 30, 40, 50, 60, 70, 80, 90))
other_data <- c('x', 'y', 'z', 'x', 'y', 'z', 'x', 'y', 'z')
df <- data.frame(subject, score, other_data)
df

which prints:

  subject score other_data
1       1    10          x
2       1    20          y
3       1    30          z
4       2    40          x
5       2    50          y
6       2    60          z
7       3    70          x
8       3    80          y
9       3    90          z

I have tried the following: I simply used 'sum' for an example. I want the sum of each of the 3 subjects scores' output to an array.

for (i in unique(subject)){
        subject_score <- sum(score)
        print(subject_score)
}

Which prints the total sum for all subjects 3 times, not the sum for each subject:

[1] 450
[1] 450
[1] 450

I would like to know which subject had which (sum) score and output to an array. I appreciate any help! Thanks.

4 Answers

We need to subset the data based on the unique value looped i.e. create a logical expression (subject == i) use that as index for extracting ([) the corresponding 'score'

for (i in unique(subject)){
        subject_score <- sum(score[subject == i])
        print(subject_score)
}
[1] 60
[1] 150
[1] 240

In the above case, we are doing the subset on the vector objects created. Also, print doesn't have return value. If we need to store the output, create an object for storing.

subject_score_out <- numeric(0)
for(i in unique(df$subject)) {
    subject_score <- with(df, sum(score[subject == i]))
    subject_score_out <- c(subject_score_out, subject_score)
    print(subject_score)
}
[1] 60
[1] 150
[1] 240
subject_score_out
[1]  60 150 240

Another type of for loop with split

for (i in with(df, split(score, subject)[as.character(unique(subject))])) {
  print(sum(i))
}

gives

[1] 60
[1] 150
[1] 240

An alternative is using group_by() and summarise().

The benefit of this is you can easily add different analyses, e.g. calculate the mean score, or group by multiple levels, if your data has something like age cohorts or gender as well and it's an easy to read array.

  df |> 
    dplyr::group_by(subject) |>
    dplyr::summarise(sum = sum(score), mean = mean(score))

  subject   sum  mean
    <dbl> <dbl> <dbl>
1       1    60    20
2       2   150    50
3       3   240    80

The following might be useful to consider. This sort of operation, a groupwise aggregation, can be handled using a Split-Apply-Combine approach. That being said you can't beat aggregate() for simple aggregations such as this (groupwise summation) -- see @RonakShah's comment below your post.

#Apply (and combine although the data-structure is defined below):
vapply(
#Split:
  with(df, split(df, subject)),
  function(x){
    sum(x$score, na.rm = TRUE)     
  },
  # Combine:
  numeric(1)
)
Related