Going one step back from the previous two posts (here and here), a Python code could be used to mark the answers submitted by students, returning the exam score for each student. With the data on hand, the distribution of the students’ choices for each question could be analysed, allowing the examiner to assess what are the common mistakes made by students.
We will consider an example where an exam has 10 questions. In a separate file, there will be the responses from five students, where a few of them did not manage to attempt some questions, as shown below.

The answer key is stored as the file “answer_key.csv“, while the students’ responses are stored as the file “student_response.csv“.
Let us consider the block of code below. The code shown would not start from Line 1 or have continuous Line numbering as with previous post, as some comments/notes present in the code has been omitted for simplicity sake.

Line 8 and Line 9 import the two modules, pandas and numpy that will be used in this code.
Line 11 first read the answer key as a pandas dataframe, with Line 13 to 14 displaying it in the IPython console for the user’s reference. Line 16 then reads the second .csv file with the students’ responses as another pandas dataframe.
Line 18 creates a new dataframe by concatenating the previous two dataframe earlier which will be used later.
Line 21 allows for the number of student to be counted dynamically, depending on the number of student in the .csv file. This is done by obtaining the number of columns from the dimensions of the student dataframe.

Line 24 creates an empty list that will be used to store the score for each student.
Line 25 creates a count variable that will be used in the following for loop in Line 27 to 32. Each loop iterates through the dataframe columnwise, marking each students’ responses. A temporary empty dataframe is first created in Line 28.
Line 29 creates a new column with the ‘result1‘ header. A comparison will be made between the student’s response for that question with the answer key in the first column. A correct answer will be tagged with ‘1’, while an incorrect answer will be tagged ‘0’.
Line 30 sums up the values in the list and Line 31 adds the value of the total score to the end of the list.
Line 35 then converts this list to a dataframe. Line 36 attempts to create a new dataframe by concatenating just the values from different columns (and also because they have different column headers).
Line 39 and 40 rearranges this dataframe to return just the score, with the student number as the index. Line 41 to 43 prints this out on the IPython console for reference as shown below.

Line 45 to 47 plots the student number (i.e. Student ID) against their individual score. The .plot() function as part of the DataFrame class in pandas is used here.


Next, we’ll look at how the response breakdown for each question could be obtained.

Line 50 processes the student dataframe created earlier in Line 16 to give the breakdown for each question. Since some responses were left blank, the NaN values were converted to a zero value. You can print out the answer_breakdown dataframe (code not shown, with row index starting from zero) to give the following on the IPython console for reference:

Line 52 then calculates the percentage response for each option (A to D) per question, rounded to two decimal places. Line 53 changes the index to start from one. The following is then printed on the IPython console by Line 54 to 55:

Similarly, Line 57 to 59 plots the bar chart with the response breakdown for each question.

Is there any way to make the above codes more concise and elegant? Feel free to comment.