|
| 1 | +# Input solution for the challenge and get ranking |
| 2 | +# ================================================ |
| 3 | +# Note: the shared folder is created like this using SSH onto the server |
| 4 | +# cd /data1 |
| 5 | +# sudo mkdir C03_challenge |
| 6 | +# sudo chown rstudio-connect: C03_challenge |
| 7 | +# Then, put wine2quality.rds (from sdd_preparation ) into that directory |
| 8 | +# scp wine2quality.rds econum@sdd.umons.ac.be:. |
| 9 | +# sudo mv /home/econum/wine2quality.rds /data1/C03_challenge |
| 10 | +# sudo chown rstudio-connect:/data1/C03_challenge/wine2quality.rds |
| 11 | + |
| 12 | +# We also need flipdown from: |
| 13 | +#remotes::install_github("feddelegrand7/flipdownr") |
| 14 | + |
| 15 | +library(data.io) |
| 16 | +library(mlearning) |
| 17 | +library(RSQLite) |
| 18 | +library(flipdownr) |
| 19 | + |
| 20 | +# Indicate title and deadline here |
| 21 | +title <- "Challenge vins" |
| 22 | +deadline <- "2020-11-16 20:00:00" |
| 23 | + |
| 24 | +# Read data from the SQLite database |
| 25 | +dir <- "/data1/C03_challenge" |
| 26 | +if (!file.exists(dir)) |
| 27 | + dir <- "~/C03_challenge" # Alternate dir for local tests |
| 28 | +database <- file.path(dir, "wine.sqlite") |
| 29 | +table <- "wines" |
| 30 | +wine2quality <- read$rds(file.path(dir, "wine2quality.rds"))$value |
| 31 | + |
| 32 | +# Is the countdown over? |
| 33 | +is_done <- function() |
| 34 | + as.POSIXct(deadline) < Sys.time() |
| 35 | + |
| 36 | +# The function that calculates score and returns also a message |
| 37 | +score_model <- function(x, reference = wine2quality) { |
| 38 | + if (!is.factor(x)) |
| 39 | + return(structure(NA, |
| 40 | + message = "Le fichier doit contenir un objet de classe 'factor'. Corrigez et resoumettez !")) |
| 41 | + if (length(x) != length(reference)) |
| 42 | + return(structure(NA, |
| 43 | + message = paste("Le r\u00e9sultat doit contenir", length(reference), |
| 44 | + "items, or vous en fournissez", length(x), ". Corrigez et resoumettez !"))) |
| 45 | + if (!"excellent" %in% levels(x)) |
| 46 | + return(structure(NA, |
| 47 | + message = "Il faut un niveau de la variable 'factor' qui soit nomm\u00e9 'excellent'. Corrigez et resoumettez !")) |
| 48 | + # In case of a recoding , we contrast "excellent" with the rest |
| 49 | + wine_pred2 <- |
| 50 | + c("autre", "excellent")[(as.character(x) == "excellent") + 1] |
| 51 | + wine_true2 <- |
| 52 | + c("autre", "excellent")[(as.character(reference) == "excellent") + 1] |
| 53 | + res <- summary(confusion(as.factor(wine_pred2), as.factor(wine_true2))) |
| 54 | + #res |
| 55 | + # Precision for 'excellent' must by higher than 25% |
| 56 | + prec <- res["excellent", "Precision"] |
| 57 | + if (prec < 0.25) |
| 58 | + return(structure(NA, |
| 59 | + message = paste0("La pr\u00e9cision pour la classe 'excellent' ne peut pas \u00eatre en dessous de 25% et vous avez ", |
| 60 | + round(prec * 100, 1), "%. Votre proposition n'est pas retenue !"))) |
| 61 | + # Le classement du modèle se fait sur base du rappel pour la classe "excellent" |
| 62 | + recall <- res["excellent", "Recall"] |
| 63 | + score <- recall * 100 # In percents |
| 64 | + structure(score, |
| 65 | + message = paste0("Votre proposition est accept\u00e9e. Son score est de ", |
| 66 | + round(score, 3), ".")) |
| 67 | +} |
| 68 | + |
| 69 | +# Manage results into the SQLite database |
| 70 | +empty_data <- function() |
| 71 | + data.frame(project = character(0), model = character(0), |
| 72 | + date = as.POSIXct(character(0)), score = numeric(0)) |
| 73 | + |
| 74 | +save_data <- function(data) { |
| 75 | + # Connect to the database |
| 76 | + db <- dbConnect(SQLite(), database) |
| 77 | + # Make sure table exists in the database |
| 78 | + try(dbWriteTable(db, table, empty_data()), silent = TRUE) |
| 79 | + # Construct the update query by looping over the data fields |
| 80 | + query <- sprintf( |
| 81 | + "INSERT INTO %s (%s) VALUES ('%s')", |
| 82 | + table, |
| 83 | + paste(names(data), collapse = ", "), |
| 84 | + paste(data, collapse = "', '") |
| 85 | + ) |
| 86 | + # Submit the update query and disconnect |
| 87 | + dbGetQuery(db, query) |
| 88 | + dbDisconnect(db) |
| 89 | +} |
| 90 | + |
| 91 | +load_data <- function() { |
| 92 | + # Connect to the database |
| 93 | + db <- dbConnect(SQLite(), database) |
| 94 | + # Construct the fetching query |
| 95 | + query <- sprintf("SELECT * FROM %s", table) |
| 96 | + # Submit the fetch query and disconnect |
| 97 | + data <- try(dbGetQuery(db, query), silent = TRUE) |
| 98 | + dbDisconnect(db) |
| 99 | + if (inherits(data, "try-error")) { |
| 100 | + empty_data() |
| 101 | + } else { |
| 102 | + data |
| 103 | + } |
| 104 | +} |
| 105 | + |
| 106 | +ui <- fluidPage( |
| 107 | + titlePanel(title), |
| 108 | + |
| 109 | + sidebarLayout( |
| 110 | + sidebarPanel( |
| 111 | + fileInput("file", "Votre proposition (fichier RDS)", accept = ".rds"), |
| 112 | + textOutput("message") |
| 113 | + ), |
| 114 | + mainPanel( |
| 115 | + h3("Temps restant pour le challenge :"), |
| 116 | + flipdown(downto = deadline, id = "csfrench", theme = "dark", |
| 117 | + headings = c("jours", "heures", "minutes", "secondes")), |
| 118 | + hr(), |
| 119 | + h3("Classement :"), |
| 120 | + tableOutput("ranking") |
| 121 | + ) |
| 122 | + ) |
| 123 | +) |
| 124 | + |
| 125 | +server <- function(input, output) { |
| 126 | + output$message <- renderText({ |
| 127 | + file <- input$file |
| 128 | + ext <- tools::file_ext(file$datapath) |
| 129 | + req(file) |
| 130 | + validate(need(ext == "rds", "Vous devez indiquer un fichier RDS")) |
| 131 | + # Check that there is still time remaining |
| 132 | + if (is_done()) { |
| 133 | + "Ce challenge est fini, vous ne pouvez plus soumettre de proposition !" |
| 134 | + } else { |
| 135 | + # Check that filename is correct (repos__model.rds) |
| 136 | + if (!grepl("^.+__.+\\.rds", file$name)) { |
| 137 | + "Le nom de votre fichier est incorrect : il faut <repos>__<model>.rds. Corrigez et resoumettez." |
| 138 | + } else { |
| 139 | + solution <- data.io::read$rds(file$datapath)$value |
| 140 | + # Check if a model of the same name already exists |
| 141 | + name <- file$name |
| 142 | + project <- sub("(^.+)__.+$", "\\1", name) |
| 143 | + model <- sub(("^.+__(.+)\\.rds$"), "\\1", name) |
| 144 | + ranking <- load_data() |
| 145 | + if (NROW(ranking[ranking$project == project & ranking$model == model, ])) { |
| 146 | + "Un mod\u00e8le de m\u00eame nom existe dans le classement, changez le nom avant de soumettre une nouvelle variante." |
| 147 | + } else { |
| 148 | + attr(score_model(solution), "message") |
| 149 | + } |
| 150 | + } |
| 151 | + } |
| 152 | + }) |
| 153 | + |
| 154 | + output$ranking <- renderTable({ |
| 155 | + file <- input$file |
| 156 | + if (!is.null(file$datapath) && grepl("^.+__.+\\.rds", file$name) && |
| 157 | + !is_done()) { |
| 158 | + solution <- data.io::read$rds(file$datapath)$value |
| 159 | + score <- score_model(solution) |
| 160 | + name <- file$name |
| 161 | + project <- sub("(^.+)__.+$", "\\1", name) |
| 162 | + model <- sub(("^.+__(.+)\\.rds$"), "\\1", name) |
| 163 | + } else { |
| 164 | + score <- NA |
| 165 | + } |
| 166 | + ranking <- load_data() |
| 167 | + # Record an entry in the mongoDB database |
| 168 | + # But we need the login of *all* members of the team, and we don't have them |
| 169 | + # right now => leave this to a post-process task instead! |
| 170 | + if (!is.na(score)) { |
| 171 | + # Check if it is not submitted yet |
| 172 | + if (!NROW(ranking[ranking$project == project & ranking$model == model, ])) { |
| 173 | + save_data(list( |
| 174 | + project = project, model = model, date = Sys.time(), |
| 175 | + score = as.numeric(score) |
| 176 | + )) |
| 177 | + # Reload the full dataset |
| 178 | + ranking <- load_data() |
| 179 | + } |
| 180 | + } |
| 181 | + # Rework the ranking table |
| 182 | + if (NROW(ranking)) { |
| 183 | + ranking <- ranking[order(-ranking$score, as.numeric(ranking$date)), ] |
| 184 | + ranking$date <- as.POSIXct(ranking$date, origin = "1960-01-01") |
| 185 | + ranking$date <- format(ranking$date, "%Y-%m-%d %H:%M:%S") |
| 186 | + } |
| 187 | + # Add a column with medals for the three first results |
| 188 | + n <- NROW(ranking) |
| 189 | + if (n == 0) { |
| 190 | + medals <- character(0) |
| 191 | + } else { |
| 192 | + medals <- c("\U1F947", "\U1F948", "\U1F949") |
| 193 | + if (n < 4) { |
| 194 | + medals <- medals[1:n] |
| 195 | + } else { |
| 196 | + medals <- c(medals, rep("", 1:(n - 3))) |
| 197 | + } |
| 198 | + } |
| 199 | + ranking <- data.frame(rank = medals, ranking) |
| 200 | + names(ranking) <- c("", "Projet", "Mod\u00e8le", "Date", "Score") |
| 201 | + ranking |
| 202 | + }) |
| 203 | +} |
| 204 | + |
| 205 | +shinyApp(ui, server) |
0 commit comments