Skip to contents

Introduction

Moodle, a learning management system, store data related to quiz taking in Moodle Quiz Report which has 4 types: Grades report, Responses report, Statistic report, and Manual grading.

moodleQuiz package have a functions that operate specifically on 2 types of report: Grades report and Responses report. The notable difference between the 2 are:

  • Grades report has columns started with Q. x /x, these columns store score of each questions.

  • Responses report has columns started with Response x, these columns store raw text responses of each questions.

The rest of the columns are the same for these 2 reports:

  • Surname: student’s last name, First name: student’s first name

  • Institution: student’s institution, Department: student’s department

  • Email address: student’s Email (often, an educational account)

  • State: the state of the attempt usually have 2 values, one of: “Finished” for submitted attempt, and “In progress” for ongoing not-yet-submitted attempt.

  • Started on and Completed are the time point at which student started and submitted the quiz. Time taken is a duration from started to submitted the quiz.

  • Grade/xx: total student’s score (if present) of the quiz.

Getting Data

Grades report and Responses report can be downloaded from Moodle server in various formats (e.g., .csv, .xlsx, .json). You have to find a way to import that report in to R as a data frame or list of data frames, which is the data structure in which functions in moodleQuiz package will operate on.

Once you imported the report into R, do not format or clean anything prior to passing into the functions that I will introduce to you next.

Example Data

moodleQuiz package comes with example data of Grade reports and Responses reports.

Grades Reports

grades_ls is a list containing 3 data frames of Moodle Grades Report.

class(grades_ls)
#> [1] "list"

Each elements are data frames of Grade reports from 3 different quiz that the same group of students have completed.

names(grades_ls)
#> [1] "Quiz_1" "Quiz_2" "Quiz_3"

For example, this is the Grade report of Quiz_1

head(grades_ls$Quiz_1)
#> # A tibble: 6 × 18
#>   Surname  First…¹ Insti…² Depar…³ Email…⁴ State Start…⁵ Time …⁶ Compl…⁷ Grade…⁸
#>   <chr>    <chr>   <lgl>   <lgl>   <chr>   <chr> <chr>   <chr>   <chr>     <dbl>
#> 1 Roquemo… Jada    NA      NA      u017@e… Fini… 2 June… 16 min… 2 June…    9.47
#> 2 Ali      Ronin   NA      NA      u001@e… Fini… 2 June… 17 min… 2 June…    9.74
#> 3 Hoffpau… Jerry   NA      NA      u002@e… Fini… 2 June… 11 min… 2 June…    9.47
#> 4 Babbitt  Nathan  NA      NA      u026@e… Fini… 2 June… 11 min… 2 June…   10   
#> 5 Huynh    Rohith  NA      NA      u024@e… Fini… 2 June… 14 min… 2 June…   10   
#> 6 Ellis    Joy     NA      NA      u013@e… Fini… 2 June… 10 min… 2 June…   10   
#> # … with 8 more variables: `Q. 1 /0.79` <dbl>, `Q. 2 /0.26` <dbl>,
#> #   `Q. 3 /2.37` <dbl>, `Q. 4 /2.11` <dbl>, `Q. 5 /2.11` <dbl>,
#> #   `Q. 6 /0.26` <dbl>, `Q. 7 /1.05` <chr>, `Q. 8 /1.05` <dbl>, and abbreviated
#> #   variable names ¹​`First name`, ²​Institution, ³​Department, ⁴​`Email address`,
#> #   ⁵​`Started on`, ⁶​`Time taken`, ⁷​Completed, ⁸​`Grade/10.00`

Notice the last row here which is an “Overall average” that Moodle calculated for us.

grades_ls$Quiz_1 %>% 
  select(Surname, starts_with("G"), starts_with("Q")) %>% 
  tail(1)
#> # A tibble: 1 × 10
#>   Surname        Grade…¹ Q. 1 …² Q. 2 …³ Q. 3 …⁴ Q. 4 …⁵ Q. 5 …⁶ Q. 6 …⁷ Q. 7 …⁸
#>   <chr>            <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl> <chr>  
#> 1 Overall avera…    9.55    0.64    0.26    2.28    2.02     2.1    0.25 0.95   
#> # … with 1 more variable: `Q. 8 /1.05` <dbl>, and abbreviated variable names
#> #   ¹​`Grade/10.00`, ²​`Q. 1 /0.79`, ³​`Q. 2 /0.26`, ⁴​`Q. 3 /2.37`, ⁵​`Q. 4 /2.11`,
#> #   ⁶​`Q. 5 /2.11`, ⁷​`Q. 6 /0.26`, ⁸​`Q. 7 /1.05`

Responses Report

responses_ls is a list containing 2 data frames of Moodle Responses Report. Each data frame is a quiz that the same group of students have done.

names(responses_ls)
#> [1] "Quiz_1" "Quiz_2"
head(responses_ls$Quiz_1)
#> # A tibble: 6 × 12
#>   Surname  First…¹ Insti…² Depar…³ Email…⁴ State Start…⁵ Time …⁶ Compl…⁷ Grade…⁸
#>   <chr>    <chr>   <lgl>   <lgl>   <chr>   <chr> <chr>   <chr>   <chr>   <chr>  
#> 1 Xiong    Lance   NA      NA      u004@e… Fini… 19 Apr… 11 min… 19 Apr… Not ye…
#> 2 Halamic… Sydney  NA      NA      u002@e… Fini… 19 Apr… 15 min… 19 Apr… Not ye…
#> 3 Palomino Itzel   NA      NA      u016@e… Fini… 19 Apr… 19 min… 19 Apr… Not ye…
#> 4 el-Faro… Hakam   NA      NA      u001@e… Fini… 19 Apr… 12 min… 19 Apr… Not ye…
#> 5 al-Farha Hamood  NA      NA      u012@e… Fini… 19 Apr… 12 min… 19 Apr… Not ye…
#> 6 Solis    Domini… NA      NA      u007@e… Fini… 19 Apr… 14 min… 19 Apr… Not ye…
#> # … with 2 more variables: `Response 1` <chr>, `Response 2` <chr>, and
#> #   abbreviated variable names ¹​`First name`, ²​Institution, ³​Department,
#> #   ⁴​`Email address`, ⁵​`Started on`, ⁶​`Time taken`, ⁷​Completed, ⁸​`Grade/2.00`

Check Submission

check_sub() check student submission from state column of Grades or Responses report.

Calling this function would

  1. Clean and format column names

  2. Encode State column resulting in Encode column, “Finished” attempt encoded as 1, and “In progress” attempt encoded as 0.

  3. Filter rows by time and encoding. By default, It choose the first maximum score of each student.

Check Submission of Quiz 1

grades_ls$Quiz_1 %>%
  check_sub(id_regex = "[:digit:]+") %>%
  select(Name, ID, State, Encode)
#> # A tibble: 26 × 4
#>    Name               ID    State    Encode
#>    <chr>              <chr> <chr>     <dbl>
#>  1 Jada Roquemore     017   Finished      1
#>  2 Ronin Ali          001   Finished      1
#>  3 Jerry Hoffpauir    002   Finished      1
#>  4 Nathan Babbitt     026   Finished      1
#>  5 Rohith Huynh       024   Finished      1
#>  6 Joy Ellis          013   Finished      1
#>  7 Jeremy Nelson      023   Finished      1
#>  8 Christopher Dillon 003   Finished      1
#>  9 Israel Munoz       018   Finished      1
#> 10 Zubaida al-Attar   022   Finished      1
#> # … with 16 more rows

Check Submission of All Quizzes

You can check whether student submit multiple quizzes by passing a list of data frames as show here as grades_ls.

grades_ls %>%
  check_sub(id_regex = "[:digit:]+") %>%
  select(Name, ID, ends_with("Encode"), Total)
#> # A tibble: 26 × 6
#>    Name               ID    Quiz_1_Encode Quiz_2_Encode Quiz_3_Encode Total
#>    <chr>              <chr>         <dbl>         <dbl>         <dbl> <dbl>
#>  1 Jada Roquemore     017               1             1             1     3
#>  2 Ronin Ali          001               1             1             1     3
#>  3 Jerry Hoffpauir    002               1             1             1     3
#>  4 Nathan Babbitt     026               1             1             1     3
#>  5 Rohith Huynh       024               1             1             1     3
#>  6 Joy Ellis          013               1             1             1     3
#>  7 Jeremy Nelson      023               1             1             1     3
#>  8 Christopher Dillon 003               1             1             1     3
#>  9 Israel Munoz       018               1             1             1     3
#> 10 Zubaida al-Attar   022               1             1             1     3
#> # … with 16 more rows

Notice that the name of each data frame (e.g., “Quiz_1”) are prefix in the column names, and Total column is calculated by sum of encoding by each student in each quiz.

Now you can check whether students submit each quizzes in a course and how many they missed by just calling one function!

Combine Responses

combine_resp() combine student’s responses (Response x) from the Responses report.

Calling this function would

  1. Clean and format column names

  2. Filter rows by time and encoding. By default, It choose the first maximum score of each student.

  3. Return data frame with Response_x columns

Combine Response of Quiz 1

This would get similar result to check_sub(), except it also return Response_* column and has an ability to split Cloze columns.

responses_ls$Quiz_1 %>%
  combine_resp(id_regex = "[:digit:]+") %>%
  select(Name, ID, starts_with("R"))
#> # A tibble: 26 × 4
#>    Name             ID    Response_1                                     Respo…¹
#>    <chr>            <chr> <chr>                                          <chr>  
#>  1 Lance Xiong      004   intuitively singled out as the instigator of … she re…
#>  2 Sydney Halamicek 002   Tarzan’s face was a pathetic expression of di… the me…
#>  3 Itzel Palomino   016   not to harm you if you will return our ivory … -      
#>  4 Hakam el-Farooq  001   “A derelict,” was the terse explanation of th… “I fea…
#>  5 Hamood al-Farha  012   At last they passed through two great walls a… Findin…
#>  6 Dominique Solis  007   the powers of the ape-man and overestimated t… though…
#>  7 Zhjade Alarid    009   upon the unsuspecting woman. A faint rustling… and fo…
#>  8 Jordan Tan       013   “In time to save you from death upon the alta… warrio…
#>  9 Kevin Vasquez    025   A FRIEND.                                      Thus, …
#> 10 Angela Carrillo  018   very faithful and loyal wife, but as she had … “they …
#> # … with 16 more rows, and abbreviated variable name ¹​Response_2

Combine Response of All Quizzes

Calling combine_resp() on a list of data frame would get all responses of each students combined in 1 data frame.

responses_ls %>%
  combine_resp(id_regex = "[:digit:]+") %>%
  select(Name, ID, contains("R")) %>%
  glimpse()
#> Rows: 26
#> Columns: 7
#> $ Name              <chr> "Lance Xiong", "Sydney Halamicek", "Itzel Palomino",…
#> $ ID                <chr> "004", "002", "016", "001", "012", "007", "009", "01…
#> $ Quiz_1_Response_1 <chr> "intuitively singled out as the instigator of the ou…
#> $ Quiz_1_Response_2 <chr> "she replied. “The thing that has just happened has …
#> $ Quiz_2_Response_1 <chr> "The next day Thuran was worse. Almost constantly he…
#> $ Quiz_2_Response_2 <chr> "you insisted it would have been too late to have re…
#> $ Quiz_2_Response_3 <chr> "While Monsieur Flaubert spoke Tarzan selected a cig…

Notice that the name of each data frame (e.g., “Quiz_1”) are prefix in the column names.

Count Responses

count_resp() counts how many responses each student answered from the Responses report.

Calling this function would

  1. Clean and format column names

  2. Filter rows by time and encoding. By default, It choose the first maximum score of each student.

  3. Return data frame with Count_Resp_x columns

In contrast to combine_resp(), count_resp() returns counts of how many responses do each students answered.

Beware that count_resp() counts any non-blank answers that is not - (dash) as an answers, even though it is not appropriate to the question.

Count Responses of Quiz 1

responses_ls$Quiz_1 %>%
  count_resp(id_regex = "[:digit:]+") %>%
  select(Name, ID, starts_with("Count"))
#> # A tibble: 26 × 3
#>    Name             ID    Count_Resp_2
#>    <chr>            <chr>        <int>
#>  1 Lance Xiong      004              2
#>  2 Sydney Halamicek 002              2
#>  3 Itzel Palomino   016              1
#>  4 Hakam el-Farooq  001              2
#>  5 Hamood al-Farha  012              2
#>  6 Dominique Solis  007              2
#>  7 Zhjade Alarid    009              2
#>  8 Jordan Tan       013              2
#>  9 Kevin Vasquez    025              2
#> 10 Angela Carrillo  018              2
#> # … with 16 more rows

Count Responses from All Quizzes

Calling count_resp() on a list of data frame would yields counts of responses for all quizzes and the total counts is calculated.

responses_ls %>%
  count_resp(id_regex = "[:digit:]+") %>%
  select(Name, ID, contains("Count"), starts_with("Total"))
#> # A tibble: 26 × 5
#>    Name             ID    Quiz_1_Count_Resp_2 Quiz_2_Count_Resp_3 Total_5
#>    <chr>            <chr>               <int>               <int>   <int>
#>  1 Lance Xiong      004                     2                   3       5
#>  2 Sydney Halamicek 002                     2                   3       5
#>  3 Itzel Palomino   016                     1                   3       4
#>  4 Hakam el-Farooq  001                     2                   3       5
#>  5 Hamood al-Farha  012                     2                   3       5
#>  6 Dominique Solis  007                     2                   3       5
#>  7 Zhjade Alarid    009                     2                   2       4
#>  8 Jordan Tan       013                     2                   2       4
#>  9 Kevin Vasquez    025                     2                   3       5
#> 10 Angela Carrillo  018                     2                   2       4
#> # … with 16 more rows

Using count_resp() to checking response counts can be a great way to check whether student did the quiz as expected, since the checking is done per questions, while check_sub() only check for student’s submission of the quiz.

Interestingly, If the student did every questions from the quiz and forgot to press the “Submit all and finished” button, the result from check_sub() would shows that student didn’t do the quiz (“In progress”), while count_resp() can reveal that student do the quiz with that many questions answered.

Combine Grades

combine_grades() filter, adjust, and combine student’s grade from Grade/xx column.

Calling this function would

  1. Clean and format column names

  2. Filter rows by time and grade. By default, It choose the first maximum score of each student.

  3. Return data frame with Grade_x columns (and may be Qx)

Combine & Adjust Grades of Quiz 1

You can filter grade by choose_grade parameter and readjust maximum grade to any score by new_max_grade parameter.

grades_ls$Quiz_1 %>%
  combine_grades(
    id_regex = "[:digit:]+",
    choose_grade = "max", # Choose only maximum grade of each student
    new_max_grade = 100 # Adjust maximum grade to 100
  ) %>%
  select(Name, ID, starts_with("G"), starts_with("Q"))
#> # A tibble: 26 × 11
#>    Name            ID    Grade…¹    Q1    Q2    Q3    Q4    Q5    Q6    Q7    Q8
#>    <chr>           <chr>   <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#>  1 Jada Roquemore  017      94.7   2.6   2.6  23.7  21.1  21.1   2.6  10.5  10.5
#>  2 Ronin Ali       001      97.4   7.9   2.6  23.7  21.1  21.1   2.6   7.9  10.5
#>  3 Jerry Hoffpauir 002      94.7   2.6   2.6  23.7  21.1  21.1   2.6  10.5  10.5
#>  4 Nathan Babbitt  026     100     7.9   2.6  23.7  21.1  21.1   2.6  10.5  10.5
#>  5 Rohith Huynh    024     100     7.9   2.6  23.7  21.1  21.1   2.6  10.5  10.5
#>  6 Joy Ellis       013     100     7.9   2.6  23.7  21.1  21.1   2.6  10.5  10.5
#>  7 Jeremy Nelson   023      92.1   7.9   2.6  21.1  15.8  21.1   2.6  10.5  10.5
#>  8 Christopher Di… 003      94.7   2.6   2.6  23.7  21.1  21.1   2.6  10.5  10.5
#>  9 Israel Munoz    018     100     7.9   2.6  23.7  21.1  21.1   2.6  10.5  10.5
#> 10 Zubaida al-Att… 022     100     7.9   2.6  23.7  21.1  21.1   2.6  10.5  10.5
#> # … with 16 more rows, and abbreviated variable name ¹​Grade_100

Combine Grades for All Quizzes

Calling combine_grades() on list of data frame would combine the score of each quizzes from each student in to 1 data frame, and Total_x column will be added as a sum of student’s score.

You can adjust the maximum score of each individual quizzes by input new_max_grade as a numeric vector corresponding to each quiz. In this example, I will adjust the maximum score of “Quiz_1” and “Quiz_2” to 25, and “Quiz_3” to 50.

grades_ls %>%
  combine_grades(
    id_regex = "[:digit:]+",
    choose_grade = "max", # Choose only maximum grade of each student
    new_max_grade = c(25, 25, 50) # Maximum score of each quizzes
  ) %>%
  select(Name, ID, contains("Grade"), starts_with("Total")) %>%
  glimpse()
#> Rows: 26
#> Columns: 6
#> $ Name            <chr> "Jada Roquemore", "Ronin Ali", "Jerry Hoffpauir", "Nat…
#> $ ID              <chr> "017", "001", "002", "026", "024", "013", "023", "003"…
#> $ Quiz_1_Grade_25 <dbl> 23.675, 24.350, 23.675, 25.000, 25.000, 25.000, 23.025…
#> $ Quiz_2_Grade_25 <dbl> 19.425, 20.350, 16.650, 18.500, 25.000, 13.875, 25.000…
#> $ Quiz_3_Grade_50 <dbl> 49.95, 46.40, 49.95, 49.95, 49.95, 49.95, 46.40, 49.95…
#> $ Total_100       <dbl> 93.050, 91.100, 90.275, 93.450, 99.950, 88.825, 94.425…

Last updated: 2022-09-23