Reputation: 223
In Denmark we have an odd grading system that goes as follows. [-3,00,02,4,7,10,12] Our assignment is to take a vector with different decimal numbers, and round it to the nearest valid grade. Here is our code so far.
import numpy as np
def roundGrade(grades):
if (-5<grades<-1.5):
gradesRounded = -3
elif (-1.5<=grades<1.5):
gradesRounded = 00
elif (1.5<=grades<3):
gradesRounded = 2
elif (3<=grades<5.5):
gradesRounded = 4
elif (5.5<=grades<8.5):
gradesRounded = 7
elif (8.5<=grades<11):
gradesRounded = 10
elif (11<=grades<15):
gradesRounded = 12
return gradesRounded
print(roundGrade(np.array[-2.1,6.3,8.9,9]))
Our console doesn't seem to like this and retuns: TypeError: builtin_function_or_method' object is not subscriptable
All help is appreciated, and if you have a smarter method you are welcome to put us in our place.
Upvotes: 18
Views: 3778
Reputation: 51165
I would recommend using numpy.digitize
here, as it allows you to easily vectorize this operation.
Setup
bins = np.array([-5, -1.5, 1.5, 3, 5.5, 8.5, 11, 15])
outputs = np.array([-3, 0, 2, 4, 7, 10, 12])
grades = np.array([-2.1, 6.3, 8.9, 9. ])
Using numpy.digitize
:
outputs[np.digitize(grades, bins)-1]
array([-3, 7, 10, 10])
Even though other answers have shown how to use np.vectorize
, digitize
will still provide you with a large performance increase when binning values:
_v_round_grade = np.vectorize(roundGrade)
grades = np.random.randint(-4, 14, 10000)
In [496]: %timeit _v_round_grade(grades)
5.64 ms ± 24.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [497]: %timeit outputs[np.digitize(grades, bins)-1]
210 µs ± 567 ns per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Validation
>>> np.array_equal(_v_round_grade(grades), outputs[np.digitize(grades, bins)-1])
True
Using this builtin digitize
function is over 25x faster than simply vectorizing your own function.
Upvotes: 1
Reputation: 29690
You could simply take the minimum distance from each grade to each grade group, like so. This assumes you actually want to round to the nearest grade from your grade group, which your current code doesn't do exactly.
grade_groups = [-3,0,2,4,7,10,12]
sample_grades = [-2.1,6.3,8.9,9]
grouped = [min(grade_groups,key=lambda x:abs(grade-x)) for grade in sample_grades]
print(grouped)
Outputs:
[-3, 7, 10, 10]
Note that even after fixing your error your approach won't yet work because roundGrade
expects a single number as a parameter. As shown by juanapa you could vectorize your function.
Upvotes: 18
Reputation: 161
You are getting that error because of the extra parenthesis in the line
print(roundGrade(np.array([-2.1,6.3,8.9,9])))
is should be
print(roundGrade(np.array[-2.1,6.3,8.9,9]))
Upvotes: 1
Reputation: 3221
I think you might be able to do it in a one liner using only lists:
l = [-2.1,6.3,8.9,9]
b = [-3,0,02,4,7,10,12]
a = [b[min(enumerate([abs(j - item) for j in b]), key=lambda p:p[1])[0]] for item in l]
>>> [-3, 7, 10, 10]
You can break this down as:
min(enumerate([abs(j - item) for j in b]), key=lambda p:p[1])[0] # Find the index of the nearest grade boundary
[b[...] for item in l] # Get the associated grade boundary for all the items in the original list
Upvotes: 2
Reputation: 3786
def roundGrade(grade):
d = {x:abs(x - grade) for x in [-3,00,02,4,7,10,12]}
return min(d, key=d.get)
print list(map(roundGrade, [-2.1, 6.3, 8.9, 9]))
It is less efficient (I think) but is much smaller code
It creates a dictionary where the key is a round grade and the value is the difference between the round grade and the given grade
Then it finds the minimum value (smallest difference) and returns the key (the rounded value
Then it just uses map
to apply this function over every item in the list
Upvotes: 2
Reputation: 33
I would round by calculating which valid grade it is closest to and returning that one:
import numpy as np
def roundGrade(grade):
validGrades = np.array([-3,0,2,4,7,10,12])
return validGrades[np.argmin((grade-validGrades)**2)]
This of course only allows you to pass a single grade at a time, but you can just loop over your array of decimals, either outside the function or inside it to make it array compatible.
Upvotes: 2
Reputation: 95927
You are getting that error because when you print, you are using incorrect syntax:
print(roundGrade(np.array[-2.1,6.3,8.9,9]))
needs to be
print(roundGrade(np.array([-2.1,6.3,8.9,9])))
Notice the extra parentheses: np.array(<whatever>)
However, this won't work, since your function expects a single number. Fortunately, numpy provides a function which can fix that for you:
In [15]: roundGrade = np.vectorize(roundGrade)
In [16]: roundGrade(np.array([-2.1,6.3,8.9,9]))
Out[16]: array([-3, 7, 10, 10])
http://docs.scipy.org/doc/numpy-1.10.1/reference/generated/numpy.vectorize.html
Upvotes: 25
Reputation: 15204
That's how i would do it:
def roundGrade(grades_in):
grades_out = []
for grades in grades_in:
if grades < -5 or grades > 15:
gradesRounded = '??'
print('Grade input out of range ({:})!'.format(grades))
if (-5<grades<-1.5):
gradesRounded = '-3'
elif (-1.5<=grades<1.5):
gradesRounded = '00'
elif (1.5<=grades<3):
gradesRounded = '2'
elif (3<=grades<5.5):
gradesRounded = '4'
elif (5.5<=grades<8.5):
gradesRounded = '7'
elif (8.5<=grades<11):
gradesRounded = '10'
elif (11<=grades<15):
gradesRounded = '12'
grades_out.append(gradesRounded)
return grades_out
grades_in = [-7, -2.1, 0.1, 6.3, 8.9, 9]
print(roundGrade(grades_in)) #prints: ['??', '-3', '00', '7', '10', '10']
Takes a list and returns one. Handles out-of-range input and list items returned are strings instead of integers to add that fancy "00" and not "0".
Upvotes: 1
Reputation: 349
As for the exception, numpy array is a function , and needed to be called with () So your method call should be like:
print(roundGrade(np.array([-2.1,6.3,8.9,9])))
and not:
print(roundGrade(np.array[-2.1,6.3,8.9,9]))
As for the function itself, either use grades.any() or grades.all() to test the entire elements, otherwise the comparison is not defined.
Upvotes: 0
Reputation: 2411
Well, even without testing your code, I can see some problems here.
Your function, roundGrade
, takes a number and returns a number, but when you call it, you provide an array to it. Assuming your indentation is ok and the call is not inside the function, I would do something like that:
def roundGrade(grades):
if (-5<grades<-1.5):
gradesRounded = -3
elif (-1.5<=grades<1.5):
gradesRounded = 00
elif (1.5<=grades<3):
gradesRounded = 2
elif (3<=grades<5.5):
gradesRounded = 4
elif (5.5<=grades<8.5):
gradesRounded = 7
elif (8.5<=grades<11):
gradesRounded = 10
elif (11<=grades<15):
gradesRounded = 12
return gradesRounded
#print(roundGrade(np.array[-2.1,6.3,8.9,9]))
# Here, I assume you want to round each of the grades in the array. If I'm wrong, comment, please!
for i in [-2.1, 6.3, 8.9, 9]:
print roundGrade(i)
Calling the method and providing an array is not ok, while calling that method with each of the elements is ok, because the method is supposed to receive a number and not an array.
Upvotes: 1