3

I'm trying to create a custom points table for exams. Currently, the instructors in our department write these exams in MS Word, so to ease the growing pains, I'd like to emulate the old design as closely as possible.

Sample exam grade table

I am aware that the exam package has a built in grade table, but it is a different format. Furthermore, for questions with subparts (e.g. 1a-1h), there's no way to directly access the point values of each (the exam package has \pointsofquestion{#}, but it only refers to the top level).

Ideally, I'd like to be able to automatically compute the Multiple Choice and Free Response point totals and populate the "Points Possible" columns of the table. Automatically generating the table seems a little too far fetched (number of rows may vary), but I'm not opposed to a solution for that too.

Edit 1:

As mentioned in the comments, I've been hacking up the code posted in the referenced answer. At the moment, I'm able to grab all the labels for the question parts along with the point values to create the code for the table. Below is a MWE of getting the labels and point values. I've cut out the specific code that builds the table as I can figure that out that syntax fairly easily now.

\documentclass[addpoints]{exam}

\usepackage{xparse,xpatch}

% redefine \question command to be \myquest
\appto\questions{\let\examquest\question\let\question\myquest}
% redefine \part command to be \mypart
\appto\parts{\let\exampart\part\let\part\mypart}


\ExplSyntaxOn
\tl_new:N \g_grade_list_tl % this is a grading list

\int_new:N \g_mcscore_int% this will be the multiple choice score
\int_new:N \g_frscore_int% this will be the free response score
\int_new:N \g_exscore_int% this will be the total exam score
%% Add question parts to grading list
\NewDocumentCommand\mypart{o}{
  \IfNoValueTF{#1}{\exampart}{
    % don't do anything special inside solutions
    \if@insolution\exampart[#1]
    \else\exampart[#1]
      \int_gadd:Nn \g_frscore_int {#1}
      \tl_gput_right:Nx \g_grade_list_tl {\arabic{question}\alph{partno},}
      \tl_gput_right:No \g_grade_list_tl {#1,}
    \fi
  }
}

\NewDocumentCommand\prtGradeList{}{
\tl_use:N \g_grade_list_tl}
\ExplSyntaxOff

\begin{document}

\begin{questions}
  \question
    \begin{parts}
      \part[1]
      \part[2]
    \end{parts}
  \question
    \begin{parts}
        \part[4]
        \part[2]
    \end{parts}
  \question
    \begin{parts}
        \part[2]
        \part[4]
        \part[4]
        \part[1]
      \end{parts}
  \question[5] 
\end{questions}

\prtGradeList{}

\end{document}

What I have been able to make is a document with several questions followed by a grading table and then display the values and points under the table. The values under the table are just for testing purposes and won't be in the final product. Currently the vertical line on the RHS of the table is missing, but that's only because I haven't finished each row. Sample exam grade table v2

Thus, what I think my next step is now is to find the length of the list of labels and values, divide it in "half" (if the number of questions/parts is odd, I'd like to make the RHS longer).

Finally, the icing on the cake would be if I could place this table on the first page of the document. I haven't figured out how to do that yet as I'm not sure if I should save the table to an external file or something else.

Edit 2:

After reading the expl3 documentation and experimenting, I'm trying to build the table using sequences:

\documentclass[addpoints]{exam}

\usepackage{xparse,xpatch,multirow}
\usepackage[table,xcdraw]{xcolor}
\definecolor{rowGray}{HTML}{EFEFEF}
\def\scantronPt{1} %% Scantron point
\def\numGradeCols{2} 

% redefine \question command to be \myquest
\appto\questions{\let\examquest\question\let\question\myquest}
% redefine \part command to be \mypart
\appto\parts{\let\exampart\part\let\part\mypart}

\makeatletter
\ExplSyntaxOn
\tl_new:N \g_grade_table_tl% this will; become the new grade table
\seq_new:N \g_grade_seq % this is a grading sequence

\int_new:N \g_mcscore_int% this will be the multiple choice score
\int_new:N \g_frscore_int% this will be the free response score
\int_new:N \g_exscore_int% this will be the total exam score

%% Add question parts to grading sequence
\NewDocumentCommand\mypart{o}{
  \IfNoValueTF{#1}{\exampart}{
    \if@insolution\exampart[#1]
    \else\exampart[#1]
      \int_gadd:Nn \g_frscore_int {#1}
      \seq_gput_right:Nx \g_grade_seq {\arabic{question}\alph{partno}}
      \seq_gput_right:No \g_grade_seq {#1}
    \fi
  }
}

%% Add question to grading sequence
\NewDocumentCommand\myquest{o}{
  \IfNoValueTF{#1}{\examquest}{
    \if@insolution\examquest[#1]
    \else\examquest[#1]
      \int_gadd:Nn \g_frscore_int {#1}
      \seq_gput_right:Nx \g_grade_seq {\arabic{question}}
      \seq_gput_right:No \g_grade_seq {#1}
    \fi
  }
}

\NewDocumentCommand\GradeTable{}{% the new grade table
  %\BuildGradeTable{}
  \seq_gput_right:Nn \g_grade_seq {Scantron}
  \seq_gput_right:Nx \g_grade_seq {\scantronPt}
  \seq_new:N \g_gradeLeft_seq

  %% Macro
  \def\seqLen{\seq_count:N \g_grade_seq} 
  \def\seqLeftLen{\seq_count:N \g_gradeLeft_seq}

  %% Grab sequence original length
  \int_const:Nn \seqOrigLen \seqLen

  %% Create two integer variables
  \int_new:N \leftSideLen \int_new:N \rightSideLen
  %% Compute length of left and right columns
  \int_gset:Nn \leftSideLen {\int_eval:n {2*\int_div_truncate:nn \seqOrigLen {4}}} 
  \int_gset:Nn \rightSideLen {\int_eval:n {\seqOrigLen-\int_use:N \leftSideLen}} 

  %% Split sequence in two
  \int_do_until:nNnn {\seqLen} = {\int_use:N \rightSideLen} {
    \seq_gpop:NN \g_grade_seq \l_tmpa_tl
    \seq_gpush:Nx \g_gradeLeft_seq \l_tmpa_tl
  }
  \seq_reverse:N \g_gradeLeft_seq
  %% Displays sequences in terminal (debugging purposes)
  %\seq_show:N \g_gradeLeft_seq
  %\seq_show:N \g_grade_seq

  %% Build Table
  \int_do_until:nNnn {\seqLeftLen} = {0} {
    \tl_gput_right:Nn \g_grade_table_tl {\hline}
    \seq_gpop:NN \g_gradeLeft_seq \l_tmpa_tl
    \tl_gput_right:No \g_grade_table_tl {\l_tmpa_tl & }
    \seq_gpop:NN \g_gradeLeft_seq \l_tmpa_tl
    \tl_gput_right:No \g_grade_table_tl {\l_tmpa_tl &&}
    \seq_gpop:NN \g_grade_seq \l_tmpb_tl
    \tl_gput_right:No \g_grade_table_tl {\l_tmpb_tl &}
    \seq_gpop:NN \g_grade_seq \l_tmpb_tl
    \tl_gput_right:No \g_grade_table_tl {\l_tmpb_tl & \\}
  }
  \seq_if_empty:NF \g_grade_seq {
    \tl_gput_right:Nn \g_grade_table_tl {\hline \multicolumn{2}{r}{}& }
    \seq_gpop:NN \g_grade_seq \l_tmpa_tl
    \tl_gput_right:No \g_grade_table_tl {\l_tmpa_tl &}
    \seq_gpop:NN \g_grade_seq \l_tmpa_tl
    \tl_gput_right:No \g_grade_table_tl {\l_tmpa_tl & \\}
  }
  %\seq_show:N \g_gradeLeft_seq
  %\seq_show:N \g_grade_seq
  \tl_show:N \g_grade_table_tl
  \renewcommand{\arraystretch}{1.7}

  %\tl_gput_right:Nn \g_grade_table_tl {\hline Scantron&\scantronPt &&\\}
  \int_gadd:Nn \g_frscore_int {\scantronPt }
  \int_gadd:Nn \g_mcscore_int {\g_frscore_int}
  \tl_gclear:N \g_grade_table_tl
  \begin{center}
    \begin{tabular}{|*{6}{c|}} %% This syntax repeats column types
\multicolumn{6}{c}{\textit{\textbf{For~instructor~or~teaching~assistant~use~only.}}}\\[5pt]\hline
    \rowcolor{rowGray}
    \multicolumn{1}{|r|}{\textbf{Question}} & \multicolumn{1}{r|}{\textbf{Points~Possible}} & \multicolumn{1}{r|}{\textbf{Points~Earned}} & \textbf{Question} & \textbf{Points~Possible} & \textbf{Points~Earned}\\ \hline
    %\tl_use:N \g_grade_table_tl \hline
    \multicolumn{2}{r}{} & \multicolumn{2}{|r|}{\textbf{Multiple~Choice}} & \int_use:N \g_mcscore_int & \\ \cline{3-6} 
    \multicolumn{2}{l}{} & \multicolumn{2}{|r|}{\textbf{Free~Response}} & \int_use:N \g_frscore_int &\\ \cline{3-6} 
    \multicolumn{2}{l}{} & \multicolumn{2}{|r|}{\textit{\textbf{Exam~Total}}} & \int_use:N \g_exscore_int &\\ \cline{3-6} 
    \end{tabular}
  \end{center}
}
\ExplSyntaxOff
\makeatother

\begin{document}

\begin{questions}
\question
What if there were no air?
\begin{parts}
\part[1]
Describe the effect on the balloon industry.
\part[2]
Describe the effect on the aircraft industry.
\end{parts}
\question
\begin{parts}
  \part[4]
    Define the universe.
    Give three examples.
  \part[2]
    If the universe were to end, how would you know?
\end{parts}
\question
\begin{parts}
  \part[2]
  \part[4]
  \part[4]
  \part[1]
  \part[1]
\end{parts}
\question[5] 
%\question[1]
\end{questions}

\GradeTable{}

\end{document}

At this point, I can see that my \g_grade_table_tl gives me what I want in the .log file, but when I run this code through pdflatex, it gets stuck at the \GradeTable{} function.

  • Welcome to TeX.SX! According to this answer by the exam class author, showing grade tables by part is not possible. – erik Jun 12 at 22:02
  • It would be great if you could show us what you have tried. Posting a minimal working example that indicates what you are trying to do makes it easier for people to understand what you want. It also makes it easier for people to help you, since they have some code to start from, and hence much more likely that some one will try to help you. If you are using the exams package then at the least you should give a MWE for generating the points. It would be nice to know how variable the tables are too? – Andrew Jun 12 at 22:02
  • @erik It would be more correct to say that the exam package does not support this functionality. I have just added an answer to the question you referred to to show one way of doing this. – Andrew Jun 12 at 23:43
  • @Andrew, your answer helps a lot! I'm working with your code and trying to split the grading table into 6 columns instead of 3. Is there anyway for me to put the first half of the questions on the left and the second half on the right? – pwesterbaan Jun 13 at 4:04
  • @Andrew Indeed, that's a very good point. I should not have stuck so closely to the original wording when paraphrasing the answer. – erik Jun 13 at 5:02
1

As you want to put the scores for the question parts in two columns, and as the number of scores is variable, you will need to store the scores somewhere and then generate the whole table at the end. Building on my previous idea, I would put the scores into a LaTeX3 sequence, whilst keeping track of the total score and the number of scores as you go. As in my other post, I would then define a \GradeBook command to produce your custom table:

enter image description here

I am not sure how "multiple choice" and "free response" questions are coded when using the exam class, which is why I asked for a minimal working example :), so in the code below I have cheated and hard-code these marks using:

\def\multiplechoice{54}
\def\freeresponse{46}

Apart from this, everything is automatic. Here is the full code:

\documentclass[addpoints]{exam}

\usepackage[table]{xcolor}
\usepackage{xparse,xpatch}
% redefine \part command to be \mypart
\appto\parts{\let\exampart\part\let\part\mypart}

\def\multiplechoice{54}
\def\freeresponse{46}
\makeatletter
\ExplSyntaxOn
% this will become a sequence of the part numbers and scores
% like: 1a&10&, 1b&8&, 1c&9&, 2a&6, 2b&8&, 3&12&, 4&14&, ...
\seq_new:N \g_part_scores_seq
\tl_new:N \g_grade_table_tl

\int_new:N \g_total_score_int% this will be the exam score
\int_new:N \g_number_of_scores_int
\NewDocumentCommand\mypart{o}{
  \IfNoValueTF{#1}{\exampart}{
    % don't do anything special inside solutions
    \if@insolution\exampart[#1]
    \else\exampart[#1]
      % store both the part number and score in \g_part_scores_seq
      % together with their column separators for the tabular env
      \tl_set:Nx \l_tmpa_tl { \arabic{question}\alph{partno} }
      \tl_put_right:Nn \l_tmpa_tl {&}
      \tl_put_right:No \l_tmpa_tl {#1}
      \tl_put_right:Nn \l_tmpa_tl {&}
      \seq_gput_right:No \g_part_scores_seq \l_tmpa_tl
      % increment the running total and number of scores
      \int_gadd:Nn \g_total_score_int {#1}
      \int_gincr:N \g_number_of_scores_int
    \fi
  }
}
% print row #1 of the part scores in the grade table
\cs_new:Nn \__add_row_to_grade_table:n {
   \tl_gput_right:Nx \g_grade_table_tl {\seq_item:Nn \g_part_scores_seq {#1}}
   \tl_gput_right:Nn \g_grade_table_tl { & }
   \tl_gput_right:Nx \g_grade_table_tl {\seq_item:Nn \g_part_scores_seq {#1+\g_number_of_scores_int/2}}
   \tl_gput_right:Nn \g_grade_table_tl {\\\hline}
}
\NewDocumentCommand\GradeTable{}{% the new grade table
  % we need an exam number of scores so add two
  % empty cells if we have an odd number
  \int_if_odd:nT {\g_number_of_scores_int} {
      \seq_gput_right:Nn \g_part_scores_seq {&}
      \int_ginc:N \g_number_of_scores_int
  }
  \int_gset:Nn \g_number_of_scores_int {\g_number_of_scores_int}
  \int_gadd:Nn \g_total_score_int { \multiplechoice }
  \int_gadd:Nn \g_total_score_int { \freeresponse }
  % create the grade table
  \tl_gclear:N \g_grade_table_tl
  \int_step_function:nnN {1} {\g_number_of_scores_int/2} \__add_row_to_grade_table:n
  \begin{tabular}{|c|c|c|c|c|c|}\hline\rowcolor{gray!20}
    Question & Points~Possible & Points~Earned & Question & Points~Possible & Points~Earned \\\hline
    \tl_use:N \g_grade_table_tl
    \multicolumn2{c|}{}&\multicolumn{2}{r|}{Multiple~Choice}
        & \multiplechoice & \\\cline{3-6}
    \multicolumn2{c|}{}&\multicolumn{2}{r|}{Free~response}
        & \freeresponse   & \\\cline{3-6}
    \multicolumn2{c|}{}&\multicolumn{2}{r|}{\textit{Exam~total}}
        & \int_use:N \g_total_score_int & \\\cline{3-6}
  \end{tabular}
}
\ExplSyntaxOff
\makeatother

\begin{document}

  \begin{questions}
    \question
      What if there were no air?
      \begin{parts}
        \part[4]
        Describe the effect on the balloon industry.
        \part[6]
        Describe the effect on the aircraft industry.
      \end{parts}

    \question
      \begin{parts}
        \part[12]
        Define the universe.
        Give three examples.
        \part[8]
        If the universe were to end, how would you know?
      \end{parts}
  \end{questions}

  \GradeTable

\end{document}

EDIT

Here is updated (and streamlined) version of the code above that saves the data to the aux file and reads it back in to construct the grade table. This allows you to put the table anywhere you like, including at the start of the document but also means that you will have to LaTeX the file twice before you see any scores.

After compiling the document two or more times, the updated MWE gives the following output:

enter image description here

This is much the same as before except that the table is now at the top of the document. If you compile the document only once then the grades table will have no scores for individuals questions, or their parts, and the total will be 0. Here is the updated code:

\documentclass[addpoints]{exam}

\usepackage[table]{xcolor}
\usepackage{xparse,xpatch,etoolbox}

% redefine \question command to be \myquest
\appto\questions{\let\examquestion\question\let\question\myquestion}
% redefine \part command to be \mypart
\appto\parts{\let\exampart\part\let\part\mypart}

\def\multiplechoice{5}
\def\freeresponse{6}
\makeatletter
\ExplSyntaxOn
% this will become a sequence of the part numbers and scores
% like: 1a,10,1b,8,1c,9,2a,6,2b,8,3,12,4,14, ...
\clist_new:N \g_grades_clist
\clist_new:N \g_grades_aux_clist

\int_new:N \g_row_int
\int_new:N \g_multiple_choice_int
\int_new:N \g_free_response_int
\int_new:N \g_grade_total_int
\int_new:N \g_number_of_scores_int

% add a question/part number and score to \g_grades_clist
\cs_new:Nn \__add_to_grades_list:nn {
  \clist_gput_right:Nx \g_grades_clist { #1 }
  \clist_gput_right:Nx \g_grades_clist { #2 }
}

\NewDocumentCommand\myquestion{o}{
  \IfNoValueTF{#1}{\examquestion}{
    % don't do anything special inside solutions
    \if@insolution\examquestion[#1]
    \else\examquestion[#1]
      % store both the part number and score in \g_grades_clist
      \__add_to_grades_list:nn { \arabic{question} } { #1 }
    \fi
  }
}

\NewDocumentCommand\mypart{o}{
  \IfNoValueTF{#1}{\exampart}{
    % don't do anything special inside solutions
    \if@insolution\exampnrt[#1]
    \else\exampart[#1]
      % store both the part number and score in \g_grades_clist
      \__add_to_grades_list:nn { \arabic{question}\alph{partno} } { #1 }
    \fi
  }
}

\AtEndDocument{
  \iow_now:cx { @auxout } {
    \token_to_str:N \SetGradeList { \g_grades_clist  } ^^J
    \token_to_str:N \SetMultipleChoice {\multiplechoice} ^^J
    \token_to_str:N \SetFreeResponse   {\freeresponse} ^^J
  }
}
% set grade list, multiple choice and free responses from the aux file
\NewDocumentCommand\SetGradeList{m}{\clist_gset:Nn \g_grades_aux_clist {#1}}
\NewDocumentCommand\SetMultipleChoice{m}{\int_gset:Nn \g_multiple_choice_int {#1}}
\NewDocumentCommand\SetFreeResponse{m}{\int_gset:Nn \g_free_response_int {#1}}
% print row #1 of the part scores in the grade table
\cs_new:Nn \__add_row_to_grade_table: {
     \int_gincr:N \g_row_int
       \clist_item:Nn \g_grades_aux_clist {2*\g_row_int-1}
      &\clist_item:Nn \g_grades_aux_clist {2*\g_row_int}
       \int_gadd:Nn \g_grade_total_int {\clist_item:Nn \g_grades_aux_clist {2*\g_row_int}}
     &&
     \int_compare:nTF {2*\g_row_int+\g_number_of_scores_int <= \clist_count:N \g_grades_aux_clist }{
       \clist_item:Nn \g_grades_aux_clist {2*\g_row_int+\g_number_of_scores_int-1}
      &\clist_item:Nn \g_grades_aux_clist {2*\g_row_int+\g_number_of_scores_int}
        \int_gadd:Nn \g_grade_total_int {\clist_item:Nn \g_grades_aux_clist {2*\g_row_int+\g_number_of_scores_int}}
     }{&}
     &\\\hline
     \int_compare:nT {\g_row_int < \g_number_of_scores_int/2} { \__add_row_to_grade_table: }
}
\NewDocumentCommand\PrintGradeTable{}{% the new grade table
  % we need an exam number of scores so add two
  % empty cells if we have an odd number
  \int_set:Nn \g_number_of_scores_int {(\clist_count:N \g_grades_aux_clist)/2}
  \int_if_odd:nT {\g_number_of_scores_int} {
      \int_add:Nn \g_number_of_scores_int {1}
  }
  \int_gzero:N \g_row_int % a counter to step through the rows
  \int_add:Nn \g_grade_total_int { \g_multiple_choice_int }
  \int_add:Nn \g_grade_total_int { \g_free_response_int }
  % create the grade table
  \begin{tabular}{|c|c|c|c|c|c|}\hline\rowcolor{gray!20}
    Question & Points~Possible & Points~Earned & Question & Points~Possible & Points~Earned \\\hline
    % the number of rows that we need is \g_number_of_scores_int/2
    \int_compare:nT {\g_number_of_scores_int>0} { \__add_row_to_grade_table: }
    \multicolumn2{c|}{}&\multicolumn{2}{r|}{Multiple~Choice}
        & \int_use:N \g_multiple_choice_int & \\\cline{3-6}
    \multicolumn2{c|}{}&\multicolumn{2}{r|}{Free~response}
        & \int_use:N \g_free_response_int   & \\\cline{3-6}
    \multicolumn2{c|}{}&\multicolumn{2}{r|}{\textit{Exam~total}}
        & \int_use:N \g_grade_total_int & \\\cline{3-6}
  \end{tabular}
}
\ExplSyntaxOff
\makeatother

\begin{document}

  \PrintGradeTable

  \begin{questions}
    \question
      \begin{parts}
        \part[1]
        \part[2]
      \end{parts}
    \question
      \begin{parts}
          \part[4]
          \part[2]
      \end{parts}
    \question
      \begin{parts}
          \part[2]
          \part[4]
          \part[4]
          \part[1]
        \end{parts}
    \question[5]
  \end{questions}

\end{document}

The code is probably simpler than before. The main changes are:

  • As in the new MWE, I added \myquestion and a new command \__add_to_grades_list:nn to add the question/part label and the score to tyhe grade list table
    • Instead of using sequences I am now using clists (=comma separated lists) as this works better with the aux file
    • the grades data is saved to the auxfile and then read back into \g_grades_aux_clist
    • The grade table is now printed in place
    • I am still hard coding the marks for the multiple choice and free response questions but I have added code to save these values to the aux file
    • new version that does a bare-hands loop so as to avoid potential issues with overleaf and TeXLive 2019 not yet being ported to ubuntu

After compiling the document once you will find the following lines in the aux file:

\SetGradeList{1a,1,1b,2,2a,4,2b,2,3a,2,3b,4,3c,4,3d,1,4,5}
\SetMultipleChoice{5}
\SetFreeResponse{6}

This the data that is used to construct the grade table.

\SetFreeResponse{6}
  • I ended up going a similar route, but my code is much messier (this is my first time using expl3). I didn't include much information about the MC and FR totals because I think I can compute them elsewhere. I'll edit my post again to include what I have now. Edit: Also, I also redefined \question since there may be a question without a subpart – pwesterbaan Jun 14 at 2:36
  • @pwesterbaan Sorry, didn't see your update. If you want to be able to print the grade table at the front then you will need to save the data to the aux file and then read it back in. Paradoxically, this seems to make it easier. I'll edit. Btw, it takes a while to get used to LaTeX3 so that you've thrown together code so quickly, pretty or not, is an achievement! – Andrew Jun 14 at 3:41
  • This looks great! But I get a compile error in the \PrintGradeTable macro. Also, I'm confused on some of the other syntax: ^^J, {o}, {m}. What do these do and where can I read about them? – pwesterbaan Jun 14 at 4:06
  • The code compiles for me. I suspect that you need to either update to TeXLive 2019 or, if you are already using TeXLive 2019, then you need to run tlmgr update --all to update all packages. In my log file I see expl3 2019-04-06. If you have a different version of the expl3 then it is almost certain that you need to update. Secondly, ^^J puts a newline in the aux file -- I am not sure where this is documented. Finally, the {o} specifies an optional argument and {m} a mandatory argument for \NewDocumentCommand; see the xparse manual for details. – Andrew Jun 14 at 4:38
  • It looks like I need to update TeXLive, then I'll give it another shot. Now I need to make sure that Overleaf will support this (as this is likely where many other people will try to use this). – pwesterbaan Jun 14 at 4:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.