Introduction page for exams2nops (r-exams) - r-exams

I would like to use your excellent r-exams package to create a paper and pencil exam with automatic grading. I have used exams2nops in the past for a series of schoice and mchoice questions.
However, I now need to have an exam with an introduction page where I give a table with data and some outputs from statistical software (say normality tests, Levene, etc... I can generate that with Rmd)and tell a small history about the data and the experiments involved in gathering the data.
So My Exam structure would be:
Page 1. Box for student's name and number and Answer sheet
Page 2. Introductory page with dataset and selected figures/outputs for testing assumptions (and no questions)
Page 3. Question 1.1
Page 4. Question 1.2.
...
Page k: Question n.
Would this be possible. I guess the novelty is the "intro" page ... after that is just an exams2nops file....
Thanks in advance for any ideas or thoughts...
João

Our solution for - let's say - 5 different versions:
Prepare your own intro with randomly generated data (i.e. Intro.Rmd). Our Intro.Rmd also saves the generated data frames in a folder named Databases. Which is then called by each exercise of the correspondent loop (i).
When rendering rmd files to pdf you must call the right LaTeX packages in your rmd's yaml header. Our case:
- \usepackage{booktabs}
- \usepackage{longtable}
- \usepackage{array}
- \usepackage{multirow}
- \usepackage{wrapfig}
- \usepackage{float}
- \usepackage{colortbl}
- \usepackage{pdflscape}
- \usepackage{tabu}
- \usepackage{threeparttable}
- \usepackage{threeparttablex}
- \usepackage[normalem]{ulem}
- \usepackage{makecell}
- \usepackage{xcolor}
Several folders were created:
one for the generated Intros (i.e., Intros);
one for the exams2nops generated PDFs (i.e., nops_pdf);
one for the spitted files (i.e., subsets);
one for the final merged versions (i.e., exams).
The loop:
for (i in 1:5) {
rmarkdown::render(input = "Intro.Rmd",output_file = paste0("Intros/Intro_v",i,".pdf"))
exams2nops(questions, n = 1, nsamp = 1, intro = "Leia as questões com atenção e MARQUE TODAS AS SUAS RESPOSTAS NA FOLHA DE RESPOSTAS! Este exame tem a duração de 60 minutos. Boa sorte!", language = "pt-PT", institution = "Análise Estatística II", title = "Época Normal: Métodos Tipo I - ",dir = "nops_pdf", name = paste0("Ex_AEII_MTI_v",i,"_"), date = "2020-12-01",encoding = "UTF-8", blank = 0, nchoice = 5, duplex = T, reglength = 7L, points = 4, replacement = T,schoice = list(eval = ee))
pdf_subset(input = paste0("nops_pdf/Ex_AEII_MTI_v",i,"_1.pdf"),pages = c(1,3),
output = paste0("subsets/subset_",i,"_part1.pdf"))
pdf_subset(input = paste0("nops_pdf/Ex_AEII_MTI_v",i,"_1.pdf"),pages = c(5:pdf_length(paste0("nops_pdf/Ex_AEII_MTI_v",i,"_1.pdf"))),
output = paste0("subsets/subset_",i,"_part2.pdf"))
pdf_combine(input = c(paste0("subsets/subset_",i,"_part1.pdf"),
paste0("Intros/Intro_v",i,".pdf"),
paste0("subsets/subset_",i,"_part2.pdf")),
output = paste0("exams/exams_v",i,".pdf"))
}
Achim, you say that the pdf generated by the Intro.Rmd can be merged using exams2nops, can you exemplify how?

How to implement this depends on whether the introductory page is the same for all participants or whether it should contain different data/graphics/information for every exam.
Same information for everyone
You can use exams2pdf(..., intro = ...).
intro: character. Either a single string with the path to a .tex
file or a vector with with LaTeX code for optional
introduction text on the first page of the exam.
Note that if this LaTeX code includes graphics (or other files) these need to be included with the full path because the LaTeX code is compiled in a different (temporary) directory.
Randomized information
If different data/graphics/information should be randomly generated for every exam, then the best way to implement this is to put it into the first question. You can emphasize the different roles of the materials by structuring the content of the "Question" environment in the first exercise, say:
Starting with "General information" in bold.
Then data/graphics/information.
Then including "First question" in bold and/or a pagebreak, e.g., via \newpage.
Then the actual first question.
If you do so, then the main deviation from your ideal structure is that the first itemized point "1." is at the beginning of the general information and not the actual first question. But I don't think it would be worth going through setting up a completely new type of "random intro text" for exams2nops().
If you want to emphasize this to the participants so that no one overlooks the first question, you can couple it with a general intro such as:
intro <- paste(c(
"\\textbf{\\large Important information}",
"",
"Please note that the first question a data set is introduced that is also used in subsequent questions. The actual first question is included below the general introducation.",
"\\newpage"),
collapse = "\n")
exams2nops(..., intro = intro)

Related

Looping variables in the parameters of the YAML header of an R Markdown file and automatically outputting a PDF for each variable

I am applying for junior data analyst positions and have come to the realization that I will be sending out a lot of cover letters.
To (somewhat) ease the pain and suffering that this will entail, I want to automate the parts of the cover letter that is suited for automation and will be using R Markdown to (hopefully) achieve this.
For the purposes of this question, let's say that the parts I am looking to automate is the position applied for and the company looking to hire someone for that position, to be used in the header of the cover letter.
These are the steps I envision in my mind's eye:
Gather the positions of interest and corresponding company in an Excel spreadsheet. This gives and Excel sheet with two columns with the variables position and company, respectively.
Read the Excel file into the R Markdown as a data frame/tibble (let's call this jobs).
Define two parameters in the YAML header of the .Rmd file to look something like this:
---
output: pdf_document
params:
position: jobs$position[i]
company: jobs$company[i]
---
The heading of the cover letter would then look something like this:
"Application for the position as r params$position at r params$company"
To summarize: In order to not have to change the values of the parameters manually for each cover letter, I would like to read an Excel file with the position titles and company names, loop these through the parameters in the YAML header, and then have R Markdown output a PDF for each pair of position and company (and ideally have the name of each PDF include the position title and company name for easier identification when sending the letters out). Is that possible? (Note: the title of the position and the company name does not necessarily have to be stored in an Excel file, that's just how I've collected them.)
Hopefully, the above makes clear what I am trying to achieve.
Any nudges in the right direction is greatly appreciated!
EDIT (11 July 2021):
I have partly arrived at an answer to this.
The trick is to define a function that includes the rmarkdown::render function. This function can then be included in a nested for-loop to produce the desired PDF files.
Again, assuming that I want to automate the position and the company, I defined the rendering function as follows (in a script separate from the "main" .Rmd file containing the text [named "loop_test.Rmd" here]):
render_function <- function(position, company){
rmarkdown::render(
# Name of the 'main' .Rmd file
'loop_test.Rmd',
# What should the output PDF files be called?
output_file = paste0(position, '-', company, '.pdf'),
# Define the parameters that are used in the 'main' .Rmd file
params = list(position = position, company = company),
evir = parent.frame()
)
}
Then, use the function in a for-loop:
for (position in positions$position) {
for (company in positions$company) {
render_function(position, company)
}
}
Where the Excel file containing the relevant positions is called positions with two variables called position and company.
I tested this method using 3 "observations" for a position and a company, respectively ("Company 1", "Company 2" and "Company 3" and "Position 1", "Position 2" and "Position 3"). One problem with the above method is that it produces 3^2 = 9 reports. For example, Position 1 is used in letters for Company 1, Company 2 and Company 3. I obviously only want to match outputs for Company 1 and Position 1. Does anyone have any idea on how to achieve this? This is quite unproblematic for two variables with only three observations, but my intent is to use several additional parameters. The number of companies (i.e. "observations") is, unfortunately, also highly likely to be quite numerous before I can end my search... With, say, 5-6 parameters and 20 companies, the number of reports output will obviously become ridiculous.
As said, I am almost there, but any nudges in the right direction for how to restrict the output to only "match" the company with the position would be highly appreciated.
You can iterate over by row like below.
for(i in 1:nrow(positions)) {
render_function(positions$position[i], positions$company[i])
}

Ho to hide the exam ID on the title page of a NOPS exam?

I'm using exams2nops() from R/exams to prepare multiple variants of an exam (as part of an open-book-exam). I'd like to obscure the variant/group of exam a test-taker is assigned to (in order to prevent "team work" during the open book exam: "Hey guys, who else is in group 1 ?!").
By default, the exams2nops() function will print the exam ID automatically on the title page (in my case: 20112600001):
It appears there's no argument in the exams2nops() function to prevent the exam ID to be printed on the title page of the PDF exam. I am unsure where to adapt the underlying TeX template.
So my question is: How to suppress the exam ID on the title page of the PDF for NOPS exams?
You are correct that this is not possible in exams2nops(), the simple reason being that the ID is essential for automatically evaluating NOPS exams after scanning them. Thus, if you want to scan the NOPS PDF file you must not remove the ID. The standard strategy for making team work on the same ID impossible would be to simply generate a different random PDF with a different ID for every participant.
If you are not actually scanning the exam, then I would recommend using exams2pdf() rather than exams2nops() and simply "roll your own" LaTeX template. If you want to take inspiration from the NOPS template, then you can create one on the fly (here with 2 exercises) via:
make_nops_template(2, file = "mynops.tex")
Note, however, that this has quite a few options that can be controlled through appropriate header commands in exams2pdf(), e.g.,
exams2pdf(c("anova", "boxplots"),
template = "mynops.tex",
header = list(
nopsinstitution = "Sauer School of Statistics",
nopstitle = "Exam",
nopscourse = " (AWM)",
"newcommand{\\mylogo}" = ""
)
)
In addition to the elements above, one would usually specify Date, ID and the NOPS language comments (see ?nops_language). But rather than using the header argument for this I would recommend to edit mynops.tex "by hand" and hard-code all the relevant aspects, including omitting the ID.

Is there a knitr strat for passing R content to LaTeX commands?

I'm creating a small R package that will allow a user to create exams with R code for tables and figures, multiple question types, randomly ordered questions, and randomly ordered responses on multiple choice items. Inserting R code into LaTeX is not problematic, but one issue I've run into is the need to "slap" together text ingested via R with LaTeX commands. Consider this example:
I have this in a LaTeX file:
\newcommand{\question}[1]{
\begin{minipage}{\linewidth}
\item
{#1}
\end{minipage}
}
I read the content with readr::read_file and store it in a variable. I then have the contents of the questions in a .json file:
...
{
"type" : "mc",
"section_no" : 1,
"points" : 2,
"question" : "What is the best beer?",
"correct_answer" : "Hamm's",
"lure_1" : "Miller Lite",
"lure_2" : "PBR",
"lure_3" : "Naturdays",
"lure_4" : "Leine's"
},
...
which I read with jsonlite::fromJSON (which converts to a dataframe), do some massaging, and store in a variable. Let's call the questions and their available options questions. What I've been doing is putting the necessary LaTeX content together with the character string manually with
question.tex <- paste0("\\question{", question[i], "\\\\")
to achieve this in the knitted .tex file:
\question{What is the best beer?\\A. PBR\\B. Naturdays\\C. Miller Lite\\D. Leine's\\E. Hamm's\\}
but I'm thinking there has to be a better way to do this. I'm looking for a function that will allow for a more seamless passing of arguments to my LaTeX command, something like knitr::magic_func(latex.command, question[i]) to achieve the result above. Does this exist?
Maybe I am asking for an extra level of abstraction that knitr doesn't have (or wasn't designed to have)? Or perhaps there's a better way? I guess at this point I'm not far away from being able to create a function that reads the LaTeX command name, number of arguments, and inserts text appropriately, but better to not reinvent the wheel! Also, I think this question could be generalized to simpler commands like \title, \documentclass, etc.
Small MWE:
## (more backslashes since we need to escape in R)
tex.command <- "\\newcommand{\\question}[1]{
\\begin{minipage}{\\linewidth}
\\item
{#1}
\\end{minipage}
}"
q <- "What is the best beer?\\\\A. PBR\\\\B. Naturdays\\\\C. Miller Lite\\\\D. Leine's\\\\E. Hamm's\\\\"
## some magic function here?
magic_func(tex.command, q)
## desired result
"\\question{What is the best beer?\\\\A. PBR\\\\B. Naturdays\\\\C. Miller Lite\\\\D. Leine's\\\\E. Hamm's\\\\\\\\"

`bookdown`/`rmarkdown`/`knitr`: Non-code sequential processing howto?

Programatically my bookdown project proceeds as follows:
Reading in raw data - produces all kind of stats.
Data preprocessing (logarithmization, normalization, imputation) - produces various plots for monitoring the population-level defects incurred.
PCA for analysis QC - produces plots for PCA and loadings-dominating data points.
Differential expression analysis - produces volcano plots and plots characterizing prominent differentially expressed features.
Overrepresentation analysis of the differentially expressed features from 4. in various biological ontology systems - produces example bar plots for enriched categories.
I have analysis and narrative nicely integrated using bookdown, enabling efficient on fly discarding of temporary (sizable) data sets/ggplot2 objects (pre/post transformation data etc.).
HOWEVER: The target audience is mostly/only interested in 4. & 5., leading me to the aspired to following structure:
4., 5., Appendix(1., 2., 3.)
Is there any other way but precomputing 1.-5. and then revisiting in the targeted order - I would prefer to avoid accumulating all those ggplot2 objects in memory if at all possible.
You could do the following:
Split steps 1-3 and 4-5 into two speparate *.Rmd files, say 123.Rmd and 45.Rmd.
Add a code chunk to the beginning of 45.md that knits 123.Rmd to 123.md:
```{r knit123, include = FALSE}
knitr::knit("123.Rmd", output = "123.md")
```
This will generate the output of steps 1-3 in Markdown and make all the objects created thereby available to steps 4-5.
Add a code chunk to the end of 45.Rmd that reads 123.md prints its content:
```{r include123, results = "asis"}
cat(readLines("123.md"), sep = "\n")
```
The results = "asis" will prevent any further processing as it is already valid Markdown.
Knit 45.Rmd to whatever target format you want.
edit (1):
TL;DR: Instead of storing the object from steps 1-3 in memory throughout steps 4-5 in order to print them afterwards, print them first and store the results on disk.
edit (2):
Since you explicitely mentioned bookdown: I would not be surprised if there was a YAML option to include a Markdown file at the end of the knitting process (something like include-after: 123.md); but I don't know for sure from the top of my head and I'm too lazy to look it up myself. ;-)

Use loop to generate section of text in rmarkdown

I need to produce a report that is composed of several sections, all sections look similar, with only some differences in data. The number of sections is also dependent on the data. What I ultimately want to have is something like this:
```{r}
section_names = c("A","B","C")
section_data = c(13,14,16)
```
# some looping mechanism here with variable i
This is section `r section_names[i]`
This section's data is `r section_data[i]`
#more things go here for the section
#end of loop should go here
The result should be a single html/document with all the sections one after the other.
Can you point me to a way for producing such an Rmd file with the loop?
Ideally I would have hoped to see something like in PHP:
<$php for(i=0;i<10;i++) { ?>
## some html template + code chunks here
<$php } ?>
This question is similar to that one, although it is LateX/RNW based. Besides, this answer demonstrates how to generate a rmarkdown document dynamically. However, neither of the questions is a exact duplicate of this one.
Basically, there are two mental steps to take:
Figure out the markdown markup needed per section. This could be something along the lines of
## This is section <section_name>
Section data is `<section_data>`.
Additional section text is: <section_text>.
Write R code that generates this markup, replacing the placeholders with the appropriate values.
For step 2, using sprintf is a natural candidate to combine static and dynamic text. Don't forget to use the chunk options results = "asis" to prevent knitr from adding formatting to your output and use cat (instead of print) to prevent R from adding additional stuff like quotes and element numbers.
I changed the input data structure a little bit for the sake of clarity (using a data.frame instead of independent vectors section_names and section_data).
```{r echo = FALSE, results = "asis"}
input <- data.frame(
name = LETTERS[1:4],
data = runif(n = 4),
text = replicate(4, paste(sample(x = LETTERS, size = 100, replace = TRUE), collapse = "")),
stringsAsFactors = FALSE)
template <- "## This is section %s
Section data is `%0.2f`.
Additional section text is: %s.
" # dont't forget the newline
for (i in seq(nrow(input))) {
current <- input[i, ]
cat(sprintf(template, current$name, current$data, current$text))
}
```
Output:
This is section A
Section data is 0.83.
Additional section text is: PUFTZQFCYJFNENMAAUDPTWIKLBSVKWMJWODFHSPRJRROTVDGNEROBVQPLLMVNPOUUHGVGRPMKAOAOMVYXKMGMUHNYWZGPRAWPYLU.
This is section B
Section data is 0.49.
Additional section text is: PFTYCGFSGSMAYSSCZXWLNLDOQEBJYEVSJIYDJPEPSWQBNWJVRUKBTYIUSTOICFKJFEJCWCAYBCQSRTXUDEQLLXCZNPUKNLJIQJXE.
This is section C
Section data is 0.58.
Additional section text is: FCJDDDMNLBUSJMCZVSBPYWCKSFJEARBXXFPAGBTKCWKHPEDGYWYTNGLVGQGJAFZRUMNSDCHKTTMGRFNSUZKFLOUGNWHUBNLVMGDB.
This is section D
Section data is 0.52.
Additional section text is: YQIXHABFVQUAAYZNWTZXJDISSLTZJJAZOLJMJSXEENFTUOFOTYKDNNUMFDXLJSWZEVDLCLSYCTSMEXFLBVQYRTBEVZLCTEBPUGTT.
Just sharing the approach I've used eventually.
I wrote a markdown file for the section. prepared the data for each section in the master document, and looped over all the sections I needed, each time calling to knit_child() with the section Rmd.
I know this is late, but I used this in my code to make numbered sections and it works a treat.
for (k in 1:length(listcsv)){ #Begin Loop at pdf file one and continue until all have been completed
subsection <- paste("5", k, sep = ".")}
this uses the loop number (k) to create the subsection number and then paste it against the section number. This happens to be in section 5, but you could use the same principle to make sections and subsections ad infinitum.

Resources