library("rmongodb")
cat("\nSimple Teacher's Aid - A Sample Program Using rmongodb\n")
cat("The grading functions will work only if a test has at least 2 students' scores entered.\n")
# test scores of 0 are thrown out when calculating the grade curve of a test
# but are taken into consideration of a student's class grade
# these 0's are 'absent' on mandatory tests
# -1 scores (which are not stored in the database) do not effect the test curve
# nor the student's class grade
#------------------------------------------------------------------------------
# The database looks like this:
# classes
# _id : (Object ID) class id
# name : (string) name of the class
## one record exists in this collection for each class
# students
# _id : (Object ID) student id
# name : (string) name of the student
## one record exists in this collection for each student
# class_students - many-to-many relationship
# _id : (Object ID) relationship id
# class_id : (Object ID) class id
# student_id : (Object ID) student_id
## one record exists in this collection for each enrollment
## of a student in a class
# tests
# _id : (Object ID) test id
# class_id : (Object ID) the class to which this test belongs
# name : (string) the name of the test (or 'gradable')
# weight : (double) relative weight of this test to other tests in the class
# date : (date) date of the test
# mean : (double) average score on test - calculated by gradeTest()
# sd : (double) standard deviation of scores - calculated by gradeTest()
## one record exists in this collection for each test of a class
# test_scores
# _id : (Object ID) test-score id
# test_id : (Object ID) the test to which this score belongs
# student_id : (Object ID) the student to which this score belongs
# score : (double) the student's raw score on the test
# curved : (integer) curved score index into rLETS or rPOINTS
# - calculated by gradeTest()
## one record exists in this collection for each score given to a student
## on the indicated test
# admin
# _id : (Object ID) arbitrary
# indexed : (boolean) true
## one record exists in this collection if the indexes have been registered
#------------------------------------------------------------------------------
mongo <- mongo.create()
if (!mongo.is.connected(mongo))
error("No connection to MongoDB")
# define the database name and the namespaces of the collections
db <- "rmongodb_sample"
classes <- paste(db, "classes", sep=".")
students <- paste(db, "students", sep=".")
class_students <- paste(db, "class_students", sep=".")
tests <- paste(db, "tests", sep=".")
test_scores <- paste(db, "test_scores", sep=".")
admin <- paste(db, "admin", sep=".")
# register indexes with MongoDB to speed up the queries we'll be pulling
indexed = list(indexed=TRUE)
b <- mongo.find.one(mongo, admin, indexed)
if (is.null(b)) {
mongo.index.create(mongo, classes, "name")
mongo.index.create(mongo, students, "name")
mongo.index.create(mongo, class_students, "class_id")
mongo.index.create(mongo, class_students, "student_id")
mongo.index.create(mongo, tests, "class_id")
mongo.index.create(mongo, tests, "name")
mongo.index.create(mongo, test_scores, "test_id")
mongo.index.create(mongo, test_scores, "student_id")
mongo.insert(mongo, admin, indexed)
}
#define some constants
rINDEX <- 1:13
rLETS <- c("F", "D-", "D", "D+", "C-", "C", "C+", "B-", "B", "B+", "A-", "A", "A+")
rPOINTS <- c(0, 0.33, 0.67, 1, 1.33, 1.67,2, 2.33, 2.67, 3, 3.33, 3.67, 4.0)
rSCRS <- seq(from=52, by=4, length=13)
name_sort <- mongo.bson.from.list(list(name=1L))
# get a single char from the console (reads a whole line)
# used by YesNo() and for menu selections
getch <- function(emptyOk=FALSE) {
repeat {
cat("? ")
choice <- toupper(substr(readLines(n=1), 1, 1))
if (emptyOk)
break
if (choice != "")
break
}
choice
}
# get a yes/no choice from the console as a boolean (TRUE for yes)
YesNo <- function(default="Y") {
repeat {
cat(if (default == "Y") " (Y/n)" else " (y/N)")
choice <- getch(emptyOk=TRUE)
if (choice == "")
choice <- default;
if (choice == "Y")
break
if (choice == "N")
break
}
choice != "N"
}
# create a mongo.bson { _id : (Object ID) id }
# for searching a collection to find the record with given the id
bson_id <- function(id) {
buf <- mongo.bson.buffer.create()
mongo.bson.buffer.append(buf, "_id", id)
mongo.bson.from.buffer(buf)
}
# select a record from a collection by a prefix of its name field
select <- function(prompt, table) {
repeat {
cat(prompt, "Name (prefix)? ")
name <- readLines(n=1)
if (name == "") {
match <- NULL
break
} else {
buf <- mongo.bson.buffer.create()
mongo.bson.buffer.start.object(buf, "name")
mongo.bson.buffer.append(buf, "$regex", paste("^", name, sep=""))
mongo.bson.buffer.append(buf, "$options", "i")
mongo.bson.buffer.finish.object(buf)
query <- mongo.bson.from.buffer(buf)
match <- mongo.find.one(mongo, table, query)
if (is.null(match))
cat("No match\n")
else {
name <- mongo.bson.value(match, "name")
cat("Matched: ", name, ". Select", sep="")
if (YesNo())
break
}
}
}
match
}
# turn a vector of raw scores into a vector of curved indexes to rLETS or rPOINTS
grade <- function(scores) {
# borrowed from ProfessR
B <- boxplot(scores, plot=FALSE)
divs <- c(min(scores), B$stats[1:4] + diff(B$stats)/2, max(scores))
M <- length(divs)
adiff <- diff(divs)
J <- rep(0, 13)
J[1] <- 0
for (i in 2:(M-1)) {
j <- 3 * (i - 1) - 1
J[j] <- divs[i] + 0 * adiff[i] / 3
J[j+1] <- divs[i] + 1 * adiff[i] / 3
J[j+2] <- divs[i] + 2 * adiff[i] / 3
}
Jinval <- findInterval(scores, J, all.inside=TRUE)
index <- rINDEX[Jinval]
scores <- rSCRS[Jinval]+ 4*(scores-J[Jinval])/(J[Jinval+1]-J[Jinval])
scores[scores >= 100] <- 100
index[scores >= 100] <- rINDEX[13]
index
}
# grade a test given the mongo.bson record describing the test
# stores results in test_scores and test records
gradeTest <- function(test) {
test_id <- mongo.bson.value(test, "_id")
score <- c()
id <- list()
i <- 1
# find all test_scores belonging to this test
cursor <- mongo.find(mongo, test_scores, list(test_id=test_id))
while (mongo.cursor.next(cursor)) {
test_score <- mongo.cursor.value(cursor)
s = mongo.bson.value(test_score, "score")
# don't allow 0 scores to throw the curve
if (s != 0) {
score <- append(score, s)
id[[i]] <- mongo.bson.value(test_score, "_id")
i <- i + 1
}
}
count <- i - 1
if (count > 1) {
# grade curve the test
g <- grade(score)
# store the curved result in test_scores
for (i in 1:count) {
buf <- mongo.bson.buffer.create()
mongo.bson.buffer.start.object(buf, "$set")
mongo.bson.buffer.append(buf, "curved", g[i])
mongo.bson.buffer.finish.object(buf)
b <- mongo.bson.from.buffer(buf)
mongo.update(mongo, test_scores, bson_id(id[[i]]), b)
}
# calculate mean & std. dev. while we here and store them in test
buf <- mongo.bson.buffer.create()
mongo.bson.buffer.start.object(buf, "$set")
mongo.bson.buffer.append(buf, "mean", mean(score))
mongo.bson.buffer.append(buf, "sd", sd(score))
mongo.bson.buffer.finish.object(buf)
b <- mongo.bson.from.buffer(buf)
b_test_id <- bson_id(test_id)
mongo.update(mongo, tests, b_test_id, b)
}
}
# calculate a student's final grade in a class
# assumes the tests have already been run through gradeTest()
gradeClassStudent <- function(class_student, display=FALSE) {
class_id <- mongo.bson.value(class_student, "class_id")
student_id <- mongo.bson.value(class_student, "student_id")
total_weight <- 0
i <- 1
weight <- c()
curved <- c()
name <- c()
cursor <- mongo.find(mongo, tests, list(class_id=class_id))
while (mongo.cursor.next(cursor)) {
test <- mongo.cursor.value(cursor)
test_id <- mongo.bson.value(test, "_id")
test_score <- mongo.find.one(mongo, test_scores,
list(test_id=test_id, student_id=student_id))
if (!is.null(test_score)) {
score <- mongo.bson.value(test_score, "score")
if (score == 0)
c <- 1
else
c <- mongo.bson.value(test_score, "curved")
curved <- append(curved, c)
name <- append(name, mongo.bson.value(test, "name"))
weight <- append(weight, mongo.bson.value(test, "weight"))
total_weight <- total_weight + weight[i]
i <- i + 1
}
}
count <- i - 1
grade <- 0
if (count > 0) {
o <- order(name)
for (i in 1:count) {
j <- o[i]
grade <- grade + curved[j] * (weight[j] / total_weight)
if (display)
cat(sprintf("%-20s %s\n", name[j], rLETS[curved[j]]))
}
}
grade <- as.integer(grade + 0.5)
if (grade > 13)
grade <- 13
if (display)
cat(sprintf("%-20s %s\n", "Class Grade", rLETS[grade]))
grade
}
# precalculate grades for all tests in a class
gradeClass <- function(cls) {
class_id <- mongo.bson.value(cls, "_id")
cursor <- mongo.find(mongo, tests, list(class_id=class_id))
while (mongo.cursor.next(cursor))
gradeTest(mongo.cursor.value(cursor))
}
studentMenu <- function(student) {
student_id <- mongo.bson.value(student, "_id")
student_name <- mongo.bson.value(student, "name")
repeat {
cat(student_name, ": Student Menu\n", sep="")
cat("L) List classes\n")
cat("G) List Grades\n")
cat("D) Delete this student\n")
cat("Q) Quit to Students Menu\n")
choice <- getch()
if (choice == "Q")
break
else if (choice == "L") {
cursor <- mongo.find(mongo, class_students, list(student_id=student_id))
while (mongo.cursor.next(cursor)) {
class_student <- mongo.cursor.value(cursor)
class_id <- mongo.bson.value(class_student, "class_id")
cls <- mongo.find.one(mongo, classes, bson_id(class_id))
cat(mongo.bson.value(cls, "name"), "\n")
}
}
else if (choice == "D") {
cat("Delete this student? Are you sure")
if (YesNo("N")) {
mongo.remove(mongo, students, bson_id(student_id))
query <- mongo.bson.from.list(list(student_id=student_id))
mongo.remove(mongo, class_students, query)
mongo.remove(mongo, test_scores, query)
break
}
}
else if (choice == "G") {
cat("Include test results")
display <- YesNo("N")
cursor <- mongo.find(mongo, class_students, list(student_id=student_id))
name <- c()
class_student <- list()
i <- 1
while (mongo.cursor.next(cursor)) {
class_student[[i]] <- mongo.cursor.value(cursor)
class_id <- mongo.bson.value(class_student[[i]], "class_id")
cls <- mongo.find.one(mongo, classes, bson_id(class_id))
name <- append(name, mongo.bson.value(cls, "name"))
gradeClass(cls)
i <- i + 1
}
count <- i - 1
if (count > 0) {
o <- order(name)
total <- 0
for (i in 1:count) {
j <- o[i]
if (display)
cat(name, "\n")
g <- gradeClassStudent(class_student[[j]], display)
if (!display)
cat(sprintf("%-20s %s", name[j], rLETS[g]))
cat("\n")
total <- total + rPOINTS[g]
}
g <- total / count
cat(sprintf("\nGPA = %1.2f\n", g))
}
}
}
}
studentsMenu <- function(parent) {
repeat {
cat("\nStudents Menu\n")
cat("L) List students\n")
cat("A) Add a student\n")
cat("S) Select a student\n")
cat("Q) Quit to", parent, "\n")
choice <- getch()
if (choice == "Q")
break
else if (choice == "L") {
cursor <- mongo.find(mongo, students, sort=name_sort)
while (mongo.cursor.next(cursor)) {
student <- mongo.cursor.value(cursor)
cat(mongo.bson.value(student, "name"), "\n")
}
} else if (choice == "A") {
cat("Name? ")
name <- readLines(n=1)
if (name != "") {
b <- mongo.bson.from.list(list(name=name))
if (!is.null(mongo.find.one(mongo, students, b)))
cat("A student already exists with that name.\n")
else {
mongo.insert(mongo, students, b)
cat(name, "added.\n")
}
}
} else if (choice == "S") {
student <- select("Student", students)
if (!is.null(student))
studentMenu(student)
}
}
}
testMenu <- function(test) {
test_id <- mongo.bson.value(test, "_id")
class_id <- mongo.bson.value(test, "class_id")
test_name <- mongo.bson.value(test, "name")
test_date <- as.character(mongo.bson.value(test, "date"))
repeat {
cat("\n", test_name, " (", test_date, "): Test Menu\n", sep="")
cat("L) List scores\n")
cat("E) Enter scores\n")
cat("D) Delete this test\n")
cat("Q) Quit to Class Menu\n")
choice <- getch()
if (choice == "Q")
break
else if (choice == "D") {
cat("Delete this test? Are you sure")
if (YesNo("N")) {
mongo.remove(mongo, tests, bson_id(test_id))
mongo.remove(mongo, test_scores, list(test_id=test_id))
break
}
}
else if (choice == "L") {
query <- mongo.bson.from.list(list(test_id=test_id))
count <- mongo.count(mongo, test_scores, query)
if (count > 0) {
name <- rep("X", count)
score <- rep(1.0, count)
i <- 1
cursor <- mongo.find(mongo, test_scores, query)
while (mongo.cursor.next(cursor)) {
test_score <- mongo.cursor.value(cursor)
student_id <- mongo.bson.value(test_score, "student_id")
student <- mongo.find.one(mongo, students, bson_id(student_id))
name[i] <- mongo.bson.value(student, "name")
score[i] <- mongo.bson.value(test_score, "score")
i <- i + 1
}
if (count == 1)
cat(sprintf("%-20s %5.2f\n", name[1], score[1]))
else {
g <- grade(score)
o <- order(name)
for (i in 1:count) {
j <- o[i]
cat(sprintf("%-20s %5.2f %s\n", name[j], score[j], rLETS[g[j]]))
}
cat("Mean = ", mean(score), ", Standard Deviation = ", sd(score), "\n", sep="")
}
}
}
else if (choice == "E") {
query <- mongo.bson.from.list(list(class_id=class_id))
count <- mongo.count(mongo, class_students, query)
if (count > 0) {
id <- list()
name <- rep("X", count)
cursor <- mongo.find(mongo, class_students, query)
i <- 1
while (mongo.cursor.next(cursor)) {
class_student <- mongo.cursor.value(cursor)
id[[i]] <- mongo.bson.value(class_student, "student_id")
student <- mongo.find.one(mongo, students, bson_id(id[[i]]))
name[i] <- mongo.bson.value(student, "name")
i <- i + 1
}
o <- order(name)
for (i in 1:count) {
j <- o[i]
query <- mongo.bson.from.list(list(test_id=test_id, student_id=id[[j]]))
score <- -1
test_score <- mongo.find.one(mongo, test_scores, query)
if (!is.null(test_score))
score <- mongo.bson.value(test_score, "score")
newscore <- score
repeat {
cat(name[j], ": (", score, ") ")
s <- readLines(n=1)
if (s == "")
break;
inscore <- as.double(s)
if (!is.na(inscore)) {
newscore <- inscore
break
}
}
if (newscore != score) {
if (newscore == -1)
mongo.remove(mongo, test_scores, query)
else
mongo.update(mongo, test_scores, query,
list(test_id=test_id, student_id=id[[j]], score=newscore),
mongo.update.upsert)
}
}
}
}
}
}
classMenu <- function(cls) {
class_id <- mongo.bson.value(cls, "_id")
class_name <- mongo.bson.value(cls, "name")
parent <- paste(class_name, ": Class Menu", sep="")
repeat {
cat("\n", parent, "\n", sep="")
cat("L) List students\n")
cat("G) Report current Grades\n")
cat("E) Enroll a student in this class\n")
cat("R) Remove a student\n")
cat("S) Students menu\n")
cat("1) List tests\n")
cat("2) Add a test\n")
cat("3) Select a test\n")
cat("Q) Quit to Classes Menu\n")
choice <- getch()
if (choice == "Q")
break
else if (choice == "L") {
query <- mongo.bson.from.list(list(class_id=class_id))
count <- mongo.count(mongo, class_students, query)
if (count > 0) {
cursor <- mongo.find(mongo, class_students, query)
name <- rep("X", count)
i <- 1
while (mongo.cursor.next(cursor)) {
class_student <- mongo.cursor.value(cursor)
student_id <- mongo.bson.value(class_student, "student_id")
student <- mongo.find.one(mongo, students, bson_id(student_id))
name[i] <- mongo.bson.value(student, "name")
i <- i + 1
}
o <- order(name)
for (i in 1:count)
cat(name[o[i]], "\n")
}
}
else if (choice == "G") {
cat("Include test results")
display <- YesNo("N")
query <- mongo.bson.from.list(list(class_id=class_id))
count <- mongo.count(mongo, class_students, query)
if (count > 0) {
gradeClass(cls)
cursor <- mongo.find(mongo, class_students, query)
name <- rep("X", count)
i <- 1
class_student <- list()
while (mongo.cursor.next(cursor)) {
class_student[[i]] <- mongo.cursor.value(cursor)
student_id <- mongo.bson.value(class_student[[i]], "student_id")
student <- mongo.find.one(mongo, students, bson_id(student_id))
name[i] <- mongo.bson.value(student, "name")
i <- i + 1
}
o <- order(name)
for (i in 1:count) {
j <- o[i]
if (display)
cat(name[j], "\n")
g <- gradeClassStudent(class_student[[j]], display)
if (!display)
cat(sprintf("%-20s %s", name[j], rLETS[g]))
cat("\n")
}
}
}
else if (choice == "E") {
student <- select("Student name", students)
if (!is.null(student)) {
student_id <- mongo.bson.value(student, "_id")
class_student <- mongo.bson.from.list(list(class_id=class_id, student_id=student_id))
if (!is.null(mongo.find.one(mongo, class_students, class_student)))
cat("That student is already in this class")
else {
mongo.insert(mongo, class_students, class_student)
cat(mongo.bson.value(student, "name"), "enrolled.")
}
}
}
else if (choice == "S")
studentsMenu(parent)
else if (choice == "1") {
query <- mongo.bson.from.list(list(class_id=class_id))
count <- mongo.count(mongo, tests, query)
if (count > 0) {
cursor <- mongo.find(mongo, tests, query)
name <- rep("X", count)
weight <- rep(1.0, count)
date <- rep(1.0, count)
i <- 1
while (mongo.cursor.next(cursor)) {
test <- mongo.cursor.value(cursor)
name[i] <- mongo.bson.value(test, "name")
weight[i] <- mongo.bson.value(test, "weight")
date[i] <- mongo.bson.value(test, "date")
i <- i + 1
}
class(date) <- c("POSIXct", "POSIXt")
o <- order(name)
for (i in 1:count)
cat(sprintf("%-20s %5.1f %s\n", name[o[i]], weight[o[i]], as.character(date[o[i]])))
}
}
else if (choice == "2") {
cat("Name? ")
name <- readLines(n=1)
if (name != "") {
test <- mongo.find.one(mongo, tests, list(name=name, class_id=class_id))
if (!is.null(test))
cat("A test by that name already exists\n")
else {
repeat {
cat("Weight (1.0)? ")
weight <- readLines(n=1)
if (weight == "")
weight <- 1.0
else {
weight <- as.double(weight)
if (is.na(weight))
next
}
break
}
date <- as.POSIXlt(Sys.time())
date$sec <- 0
date$min <- 0
date$hour <- 0
repeat {
cat("Date YYYY-MM-DD (", as.character(date), ") ?", sep="")
sdate <- readLines(n=1)
if (sdate != "") {
d <- strptime(sdate, "%Y-%m-%d")
if (is.na(d))
next
date <- d
}
break
}
mongo.insert(mongo, tests, list(class_id=class_id, name=name, weight=weight, date=date))
}
}
}
else if (choice == "3") {
test <- select("Test", tests)
if (!is.null(test))
testMenu(test)
}
}
}
classesMenu <- function()
repeat {
cat("\nClasses Menu\n")
cat("L) List classes\n")
cat("A) Add a class\n")
cat("S) Select a class\n")
cat("Q) Quit to Main Menu\n")
choice <- getch()
if (choice == "Q")
break
else if (choice == "L") {
cursor <- mongo.find(mongo, classes, sort=name_sort)
while (mongo.cursor.next(cursor)) {
cls <- mongo.cursor.value(cursor)
cat(mongo.bson.value(cls, "name"), "\n")
}
} else if (choice == "A") {
cat("Name? ")
name <- readLines(n=1)
if (name != "") {
mongo.insert(mongo, classes, list(name=name))
cat(name, "added.\n")
}
} else if (choice == "S") {
cls <- select("Class", classes)
if (!is.null(cls))
classMenu(cls)
}
}
repeat {
cat("\nMain Menu\n")
cat("C) Classes Menu\n")
cat("S) Students Menu\n")
cat("Q) Quit\n")
choice <- getch()
if (choice == "Q") {
cat("Exiting...\n")
break
}
if (choice == "C")
classesMenu()
else if (choice == "S")
studentsMenu("Main Menu")
else
cat("Invalid choice")
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.