3

We want to categorize the questions in our exams according to the learning outcomes. Each question will have one or more category numbers. And these numbers should appear in the grade table. For instance, I can create a custom combinedgradetable group by modifying the bonus point environment like this (ref:similar question but not same):

\begingroup
  \chbpword{Category}
  \settabletotalbonuspoints{}
  \renewcommand{\bonuspointsofquestion}[1]{}
  \combinedgradetable[h]
\endgroup

and got this: Custom gradetable with category row

However I don't know how to assign categories to questions and show them in the grade table. Or any other method to achieve this will be appreciated.

I would like it to appear like this: Desired grade table

Thanks in advance.

3

1 Answer 1

2
+50

I defined here some commands which allow to assign categories to questions in an exam class document, based on the code from the exam class itself. I thought it would be clearer to put most of the code in a category.sty file, which is at the end of the answer. I ran some tests and I think it should work in most situations. I noticed however that it will probably not work with the hyperref package.

The new commands defined are the following.

  • \newcategory{<name>}{<label>}. This command allows to define a new category in the exam. <name> is the name of the category as it will be displayed in text, and <label> is the label with which the category will be accessed in the code. This command should be placed in the preamble.
  • \category{<label>}. Returns the name of the category associated to the label <label>, all in lowercase characters.
  • \Category{<label>}. Same as \category, but capitalizes the first letter of the category name.
  • \categorynumber{<label>}. Returns the number of the category associated to the label <label>. Categories are numbered in the order in which they are defined from the \newcategory command.
  • \categorize{<label>}. To be used in a \question item. Associates the corresponding question to the category labelled <label>. The \categorize command can take only a single label as an argument, but multiple \categorize commands can be used with the same question to associate a question to multiple categories.
  • \categorytable. Creates a table displaying the category names with the corresponding numbers. The category table does not need to be called for the categories to be numbered.

If categories are defined and questions are categorized, the categories will be displayed in horizontal complete grading tables (grade, point, bonus, or combined) if they are indexed by questions. In a partial or a vertical grading table, or in a table indexed by pages, categories will simply be ignored.

Here is a small example to show the use of most commands.

\documentclass[addpoints]{exam}
\usepackage{wrapfig}
\usepackage{category}

\newcategory{Analysis}{cat:an}
\newcategory{Topology}{cat:topo}
\newcategory{Algebra}{cat:alg}

\begin{document}

\begin{wraptable}{r}{0.2\textwidth}
\centering
\categorytable
\caption{Example of \textbackslash\texttt{categorytable}.}
\end{wraptable}

The categories used in this exam are \category{cat:an} (\categorynumber{cat:an}), \category{cat:topo} (\categorynumber{cat:topo}) and \category{cat:alg} (\categorynumber{cat:alg}).

\medskip\combinedgradetable[h]

\begin{questions}
  \question[15] \categorize{cat:an} First question.
  \question[25] \categorize{cat:an}\categorize{cat:topo} Second question.
  \question[30] \categorize{cat:topo} Third question.
  \question[30] \categorize{cat:alg} Fourth question.
  \bonusquestion[5] \categorize{cat:topo}\categorize{cat:alg} Bonus question.
  \bonusquestion[5] Bonus question, not categorized.
\end{questions}

\end{document}

Example of use of categories.

By default, the word "Category:" will be displayed at the beginning of the corresponding row in a grade table, but the commands \hcword{newname}, \bhcword{newname} and \chcword{newname} could be used to replace "Category:" by "newname" in the associated table (grade, bonus and combined, respectively). The similar command \cwordctbl allows to change the name displayed in a \categorytable, which is "Categories" by default. If a question is left uncategorized, what is displayed in the category row for this question is defined by the \nocword command. The default is \nocword{--}, but a similar call will replace -- by the argument.

The following is the category.sty file. If you prefer to put that code in your preamble, just load the refcount package, and put everything but the first three lines of the following code between \makeatletter and \makeatother.

\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{category}

\RequirePackage{refcount}

\def\hcword#1{\def\@hcword{#1}}
\def\bhcword#1{\def\@bhcword{#1}}
\def\chcword#1{\def\@chcword{#1}}
\def\cwordctbl#1{\def\@cwordctbl{#1}}
\def\nocword#1{\def\@nocword{#1}}
\hcword{Category:}
\bhcword{Category:}
\chcword{Category:}
\cwordctbl{Categories}
\nocword{--}
\newcounter{num@cats}
\newcounter{current@category}
\newcounter{current@catofq}
\newcounter{qfor@lastc}
\def\relaxwitharg#1{\relax}
\def\newcategory#1#2{%
  \stepcounter{num@cats}
  \expandafter\gdef\csname category\romannumeral\thenum@cats \endcsname{#1}%
  \expandafter\edef\csname numberof#2 \endcsname{\thenum@cats}%
  \protected@write\@auxout{}%
    {\string\newlabel{#2}{{\thenum@cats}{\thepage}}}%
}% newcategory
\def\categorynumber#1{%
  \csname numberof#1 \endcsname%
}% categorynumber
\def\category#1{%
  \expandafter\MakeLowercase \csname category\romannumeral\categorynumber{#1} \endcsname%
}% category
\def\Category#1{%
  \edef\temp{\csname category\romannumeral\categorynumber{#1}\endcsname}%
  \expandafter\MakeUppercase{\expandafter\@car\temp\@nil}%
  \expandafter\@cdr\temp\@nil%
}% Category
\def\categorize#1{%
  \@ifundefined{c@num@catsofq@\romannumeral\thequestion}{%
    \newcounter{num@catsofq@\romannumeral\thequestion}%
    }{}%
  \stepcounter{num@catsofq@\romannumeral\thequestion}%
  \immediate\write\@mainaux%
  {\string\gdef\string\category\romannumeral\the\value{num@catsofq@\romannumeral\thequestion}ofq@\romannumeral\thequestion{\string\ref{#1}}}%
}% categorize
\def\do@catloop@ctbl{%
  \stepcounter{current@category}%
  \thecurrent@category%
  \hidden@ampersand%
  \csname category\romannumeral\thecurrent@category \endcsname%
  \ifnum \value{current@category} = \value{num@cats}\relax%
    \\
    \hline
    \end{tabular}
    \let\nextdo@catloop@ctbl\relax
  \else
    \\
    \let\nextdo@catloop@ctbl\do@catloop@ctbl
  \fi
  \nextdo@catloop@ctbl
}% do@catloop@ctbl
\def\categorytable{%
  \begin{tabular}{ll}
  \multicolumn{2}{c}{\@cwordctbl} \\
  \hline
  \do@catloop@ctbl
}% categorytable

\def\do@lastcloop{%
  \stepcounter{qfor@lastc}%
  \@ifundefined{exam@numquestions}{%
    \let\nextdo@lastcloop=\relax
  }{%
    \ifnum \value{qfor@lastc} > \exam@numquestions\relax
      \let\nextdo@lastcloop=\relax%
    \else
      \@ifundefined{thenum@catsofq@\romannumeral\theqfor@lastc}%
        {%
          \immediate\write\@mainaux%
          {\string\xdef\string\exam@catsofquestion\romannumeral\theqfor@lastc{0}}%
          \let\nextdo@lastcloop=\do@lastcloop%
        }{%
          \immediate\write\@mainaux%
          {\string\xdef\string\exam@catsofquestion\romannumeral\theqfor@lastc{\csname thenum@catsofq@\romannumeral\theqfor@lastc \endcsname}}%
          \let\nextdo@lastcloop=\do@lastcloop%
        }%
    \fi
  }%
  \nextdo@lastcloop
}% do@lastcloop
\AtEndDocument{%
  \do@lastcloop%
}
\def\do@catsofqloop#1{%
  \stepcounter{current@catofq}%
  \@ifundefined{exam@catsofquestion\romannumeral #1}{%
    \let\nextdo@catsofqloop=\relaxwitharg
  }{%
    \ifnum \value{current@catofq} = \csname exam@catsofquestion\romannumeral #1 \endcsname\relax
      \csname category\romannumeral\thecurrent@catofq ofq@\romannumeral #1 \endcsname%
      \let\nextdo@catsofqloop=\relaxwitharg
    \else
      \csname category\romannumeral\thecurrent@catofq ofq@\romannumeral #1 \endcsname,\,%
      \let\nextdo@catsofqloop=\do@catsofqloop
    \fi
  }%
  \nextdo@catsofqloop{#1}%
}% do@catsofqloop
\def\categoriesofquestion#1{\@ifundefined{categoryiofq@\romannumeral #1}%
  {\mbox{\@nocword}}%
  {%
    \setcounter{current@catofq}{0}%
    \do@catsofqloop{#1}
  }%
}% categoriesofquestion
\def\categoriesof@index#1{%
  \categoriesofquestion{\arabic{#1}}%
}% categoryof@index
\def\do@catloop@h{%
  \increment@index{pq@index}%
  \ifnum \value{pq@index} > \last@pq@index\relax
    % Do nothing!
  \else
    \hidden@ampersand
    \addtocounter{cols@done}{1}%
    \categoriesof@index{pq@index}%
  \fi
  \ifnum \value{pq@index} < \last@pq@index\relax
    \ifnum \value{cols@done} < \value{num@cols}\relax
      \let\nextdo@catloop@h=\do@catloop@h
    \else
      \let\nextdo@catloop@h=\relax
    \fi
  \else
    \let\nextdo@catloop@h=\relax
  \fi
  \nextdo@catloop@h
}% do@catloop@h

\def\do@lines@h{%
  \addtocounter{current@row}{1}%
  \iftbl@pgs
    \if@bonus
      \@bhpgword
    \else
      \@hpgword
    \fi
  \else
    \if@bonus
      \@bhqword
    \else
      \@hqword
    \fi
  \fi
  \setcounter{cols@done}{0}%
  \do@pq@indexloop@h
  \ifnum \value{cols@done} < \value{num@cols}\relax
    \ifnum \value{current@row} = \value{num@rows}\relax
      \do@htword@h
    \else
      \setcounter{@iterator}{\value{num@cols}}%
      \addtocounter{@iterator}{-\value{cols@done}}%
      \do@emptycols@h
    \fi
  \fi
  \\
  \hline
  \iftbl@pgs
    %Do nothing
  \else
    \if@partial
      %Do nothing
    \else
      \setcounter{pq@index}{0}%
      \setcounter{cols@done}{0}%
      \@hcword
      \do@catloop@h
      \hidden@ampersand
      \\
      \hline
    \fi
  \fi
  % Point values go here!
  \setcounter{cols@done}{0}%
  \if@bonus
    \@bhpword
    \do@bptloop@h
  \else
    \@hpword
    \do@ptloop@h
  \fi
  \ifnum \value{cols@done} < \value{num@cols}\relax
    \ifnum \value{current@row} = \value{num@rows}\relax
      \if@bonus
        \do@totalbpts@h
      \else
        \do@totalpts@h
      \fi
    \else
      \setcounter{@iterator}{\value{num@cols}}%
      \addtocounter{@iterator}{-\value{cols@done}}%
      \do@emptycols@h
    \fi
  \fi
  % Scores?
  \if@scores
    \\
    \hline
    \if@bonus
      \@bhsword \hidden@ampersand
    \else
      \@hsword \hidden@ampersand
    \fi
    \setcounter{cols@done}{0}%
    \do@sloop@h
  \fi
  \ifnum \value{current@row} = \value{num@rows}\relax
    \\
    \hline
    \end{tabular}%
  \else
    \\
    \hline\hline
  \fi
  \ifnum \value{current@row} < \value{num@rows}\relax
    \let\nextdo@lines@h=\do@lines@h
  \else
    \let\nextdo@lines@h=\relax
  \fi
  \nextdo@lines@h
} %do@lines@h

\def\do@comblines@h{%
  \addtocounter{current@row}{1}%
  \iftbl@pgs
    \@chpgword
  \else
    \@chqword
  \fi
  \setcounter{cols@done}{0}%
  \do@pq@indexloop@h
  \ifnum \value{cols@done} < \value{num@cols}\relax
    \ifnum \value{current@row} = \value{num@rows}\relax
      \do@htword@h
    \else
      \setcounter{@iterator}{\value{num@cols}}%
      \addtocounter{@iterator}{-\value{cols@done}}%
      \do@emptycols@h
    \fi
  \fi
  \\
  \hline
  \iftbl@pgs
    %Do nothing
  \else
    \if@partial
      %Do nothing
    \else
      \setcounter{pq@index}{0}%
      \setcounter{cols@done}{0}%
      \@chcword
      \do@catloop@h
      \hidden@ampersand
      \\
      \hline
    \fi
  \fi
  % Point values go here!
  \@chpword
  \setcounter{cols@done}{0}%
  \do@ptloop@h
  \ifnum \value{cols@done} < \value{num@cols}\relax
    \ifnum \value{current@row} = \value{num@rows}\relax
      \do@totalpts@h
    \else
      \setcounter{@iterator}{\value{num@cols}}%
      \addtocounter{@iterator}{-\value{cols@done}}%
      \do@emptycols@h
    \fi
  \fi
  \\
  \hline
  % Bonus point values go here!
  \@chbpword
  \setcounter{cols@done}{0}%
  \do@bptloop@h
  \ifnum \value{cols@done} < \value{num@cols}\relax
    \ifnum \value{current@row} = \value{num@rows}\relax
      \do@totalbpts@h
    \else
      \setcounter{@iterator}{\value{num@cols}}%
      \addtocounter{@iterator}{-\value{cols@done}}%
      \do@emptycols@h
    \fi
  \fi
  % Scores?
  \if@scores
    \\
    \hline
    \@chsword \hidden@ampersand
    \setcounter{cols@done}{0}%
    \do@sloop@h
  \fi
  \ifnum \value{current@row} = \value{num@rows}\relax
    \\
    \hline
    \end{tabular}%
  \else
    \\
    \hline\hline
  \fi
  \ifnum \value{current@row} < \value{num@rows}\relax
    \let\nextdo@comblines@h=\do@comblines@h
  \else
    \let\nextdo@comblines@h=\relax
  \fi
  \nextdo@comblines@h
}% do@comblines@h
2
  • great work. It works as I expect. This should be added to exam class. Please reach the developer and ask for it. Best Regards.
    – tarik
    Commented Dec 29, 2019 at 11:13
  • 1
    Great! Please let me know if you find any issues with these commands. I guess I could try to make it into a complete package and upload it to CTAN, but it will take some more time.
    – Vincent
    Commented Dec 29, 2019 at 17:29

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .