2

So our assinment is to create a function that take the input grades~(An N ×M matrix containing grades on the 7-step-scale given to N students on M different assignments.), and compute gradesFinal(A vector of length n containing the final grade for each of the N students.).

For each student, the final grade must be computed in the following way:

  1. If there is only one assignment (M = 1) the final grade is equal to the grade of that assignment.

  2. If there are two or more assignments (M > 1) the lowest grade is discarded. The final grade is computed as the mean of M − 1 highest grades rounded to the nearest grade on the scale (using the function roundGrade).

  3. Irrespective of the above, if a student has received the grade −3 in one or more assignments, the final grade must always be −3.

I have inserted my roundGrade function below, but when I try to run a matrix I recieve. "TypeError: unsupported operand type(s) for /: 'NoneType' and 'int'". This means I probably misunderstood something completely.

import numpy as np
def computeFinalGrades(grades):
    if len(grades)==1:
    gradesFinal = grades
elif len(grades)>=2:
    trimmed = grades.remove(min(grades))
    finalgrade = np.mean(trimmed)
    gradesFinal = roundGrade(finalgrade)
elif grades[grades==-3]:
    gradesFinal= -3
    return gradesFinal

def roundGrade(grades):
   grade_groups = [-3,0,2,4,7,10,12]
   gradesRounded = [min(grade_groups,key=lambda x:abs(grade-x)) for grade in     grades]
   return gradesRounded

I appreciate your help, and feel welcome to show me a smarter method.

Levon
  • 138,105
  • 33
  • 200
  • 191
T.DOGG
  • 223
  • 2
  • 7

3 Answers3

2

Here is a solution which works directly on the NxM 2d array, and not on each vector individually, so I assume that M is the same for all students, and missing values (assignments) are set to nan. G can also be just 1d, which means there is only 1 assignment per student.

def grades(G):
    import numpy

    # return G directly if there is only 1 assignment (M=1)
    if len(G.shape) == 1 or G.shape[1] == 1:
        return G[:]

    # sort for easier calculation of mean
    SG = numpy.sort(G)

    # set all values to -3 if a -3 is found on any assignment of that student
    m3 = numpy.where(SG==-3)[0]
    SG[m3] = -3

    levels = numpy.array((-3, 0, 2, 4, 7, 10, 12))
    # take the mean of all assigments except the worst (index=0 of each student)
    MG = numpy.nanmean(SG[:,1:], axis=-1)

    # find the nearest grade in the list of levels, rounding down if two are equally close
    diffs = numpy.abs(MG[:,None] - levels)
    index = numpy.nanargmin(diffs, axis=-1)

    return levels[index]
Jan Christoph Terasa
  • 5,781
  • 24
  • 34
1

grades.remove(min(grades)) doesn't return anything. So trimmed is None. Also, your roundGrade function is wrong, because you are passing a number to roundGrade, and are trying to iterate in gradesRounded = [min(grade_groups,key=lambda x:abs(grade-x)) for grade in grades].

The following code must do the trick for you.

def computeFinalGrade(g_vector):
    if -3 in g_vector: # Irrespective of the length of the g_vector
        return -3
    if len(g_vector) == 1:
        return g_vector[0]
    g_vector.remove(min(g_vector))
    return roundGrade(sum(g_vector)/float(len(g_vector)))

def roundGrade(meanGrade):
   gradeGroups = [-3,0,2,4,7,10,12]
   return min(gradeGroups,key=lambda x:abs(meanGrade-x))

gradesFinal = []
grades = [
    [1, 2, 3, 4],
    [1, -3, 2, 4],
    [5],
    [9, 6, 2, 1]
]

for grade_vector in grades: # grades is a N*M matrix [[1, 2, 3 ... M], [1, 2, 3 ... M], ... N times]
    gradesFinal.append(computeFinalGrade(grade_vector))
    # >>>  gradesFinal
    #      [2, -3, 5, 7]
Sreyantha Chary
  • 656
  • 4
  • 13
  • We still seem not to be able to use the g_vector.remove atribute you recomended. "AttributeError: 'numpy.ndarray' object has no attribute 'remove'". Any recomendations? – T.DOGG Jun 20 '16 at 09:20
  • 1
    g_vector in the above code snippet is a normal list and not a numpy.ndarray object. You should use the equivalent function like `.delete()` which requires the index of the element you want to delete. Check this link: http://stackoverflow.com/questions/10996140/how-to-remove-specific-elements-in-a-numpy-array – Sreyantha Chary Jun 20 '16 at 09:26
1

There are roughly 5 areas which need to be changed in the original code: Indentation, list.remove, if/elif order, the way the presence of a value is checked for in a list and passing of lists in the roundGrade() function.

1. Indentation

Firstly, your indentation is incorrect. The python interpreter takes into account the indentation so that's important. After each if statement, you should indent the code block for that particular condition. Besides that, you should also make sure your if and elifs are on the same indented level. So it should look something like this.

import numpy as np
def computeFinalGrades(grades):
    if len(grades)==1:
        gradesFinal = grades
    elif len(grades)>=2:
        trimmed = grades.remove(min(grades))
        finalgrade = np.mean(trimmed)
        gradesFinal = roundGrade(finalgrade)
    elif grades[grades==-3]:
        gradesFinal= -3
    return gradesFinal

def roundGrade(grades):
   grade_groups = [-3,0,2,4,7,10,12]
   gradesRounded = [min(grade_groups,key=lambda x:abs(grade-x)) for grade in     grades]
   return gradesRounded

2. list.remove returns None

Secondly, under the assumption that you are passing in a list to computeFinalGrades, grades.remove in trimmed = grades.remove(min(grades)) modifies the grades list inplace and returns None. Thus when u call the function roundGrade on it, you get the NoneType Error.

3. Order of if/elif blocks

Thirdly, the flow of the if blocks is wrong. Consider the scenario where you pass in the array [1,2,3,4,5,-3] to computeFinalGrades([1,2,3,4,5,-3]), then the if statement if len(grades)==1 will return True and evaluation will cease there. Instead, you should move grades[grades==-3] to the top as the first statement to be evaluated as once this statement is true, -3 should always be returned.

4. Checking for a value in a list

Fourth, grades[grades==-3] first evaluates if the variable grades refers to -3 which in turn returns false because grades is always a list and not the integer -3. At this point, you have grades[False] which is treated equivalently as grades[0] thus u always get the first result. Instead, the pythonic way is to use the in operator. I.E. elif -3 in grades:

5. roundGrade tries to iterate on non-iterables twice

Fifth, gradesFinal = roundGrade(finalgrade) does not work because finalgrade = np.mean(trimmed) which is a single number. However u call roundGrade on this single number and then u iterate on this single digit for grade in grades inside the function roundGrade. The same error occurs when you try to min(grade_groups,key=lambda x:abs(grade-x)). This is because you are calling min on a single value. If you wanted to min this pairing, a better way would be gradesRounded = [(grade,lambda x:abs(mean_grade-x)) for grade in grade_groups].Thus you should be aware of when you are passing in a list or variable.

I have edited roundGrade to take in a single value then iterate over your grade groupings to create a list of tuples of the absolute difference in value, followed by the grade in the grade groupings. Then i sort it in ascending order and simply return the second value in the first tuple which is your grade in the grade table. Be aware though that this has the drawback of always returning the lower grade in the grade_groups if the mean_grade is exactly between two neighboring values in grade_groups.

Putting it all together, one way of doing this is:

import numpy as np

def computeFinalGrades(grades):
    if -3 in grades:
        print grades
        gradesFinal= -3
    elif len(grades)>=2:
        grades.remove(min(grades))
        finalgrade = np.mean(grades)
        gradesFinal = roundGrade(finalgrade)
    elif len(grades)==1:
        gradesFinal = grades
    return gradesFinal

def roundGrade(mean_grade):

    grade_groups = [-3,0,2,4,7,10,12]
    gradesRounded = [(abs(grade-mean_grade),grade)for grade in grade_groups]
    gradesRounded.sort()
    return gradesRounded[0][1]
Yarnspinner
  • 852
  • 5
  • 7