Python: Rounding to Nearest 1/8% - python

I'm trying to calculate the average weighted interest rates of loans in Python 2.7.
I started out with floats, and that was problematic. Given these inputs (Loan #1 $20,000 6.80%; Loan #2 $10,000 7.90%; Loan #3 $10,000 5.41%) the correct result is 6.7275. Using Decimal, my code outputs that.
However, I am supposed to take that number and round up to the nearest 1/8% and display the result as 6.75 instead. When I use format to round to two places, I get 6.73. While that is what I would expect, is does not meet the scope of what this function should do.
from decimal import Decimal
def weightedAvgFixedArgs(r1,r2,r3,b1,b2,b3):
rates = [Decimal(r1),Decimal(r2),Decimal(r3)]
balances = [Decimal(b1),Decimal(b2),Decimal(b3)]
a = Decimal(b1) * Decimal(r1)
b = Decimal(b2) * Decimal(r2)
c = Decimal(b3) * Decimal(r3)
perLoanWghtFctr = a + b + c
totalLoanAmt = sum(balances)
intRate = (perLoanWghtFctr / totalLoanAmt) * 100
return format(intRate, '.2f')
Edit: Joachim Isaksson's suggestion provides the output I requested with the inputs I provided. I haven't tested this extensively in my particular use-case, but it correctly answers the question I asked.
# return format(intRate, '.2f')
z = intRate * 8
y = format(z, '.0f')
x = Decimal(y) / Decimal(8)
return x

To round up to the nearest 1/8%:
import math
>>> math.ceil(.73 * 8) / 8
0.75
>>> math.ceil(.76 * 8) / 8
0.875

If you're specifically told to round up, you should use ceiling.
import math
x = 6.7275
x = math.ceil(x * 8)/8 # x == 6.75

Multiply 8, round or truncate as you want, then divide by 8, here is the sample code:
for i in range(0,10):
v = i/10.0
v = round(v*8) / 8.0
print i/10.0, v
Run it you will get:
0.0 0.0
0.1 0.125
0.2 0.25
0.3 0.25
0.4 0.375
0.5 0.5
0.6 0.625
0.7 0.75
0.8 0.75
0.9 0.875

When calculating the Weighted Average Interest rate, you will have to ROUND UP.
Some source(s) about calculation related to interest rates:
Tutorial: Calculating the Weighted Average Interest Rate
Weighted Average Interest Rate Calculator
Interest Rates on Federal Consolidation Loans
Federal Student Financial Aid Handbook
I think this is the best way to do what you are asking for:
import math
x = 6.73
math.ceil(x / (1/8)) * (1/8)
I personally do not have a clear (mathematical) explanation of why doing first the multiplication and then later the division also works. Or doing it with value 8 instead of 1/8. The example below also seems to work:
math.ceil(x * (8)) / 8
Both works. In the first example, you are able to use it as the given value, which is (1/8), and at the second example you are able to use ut as a whole number, in this case, it's 8.
If you want to round to just the nearest 1/8% (and not round up to the nearest 1/8%, as it's supposed to be), that is a different case.

Related

Splitting a column with float numbers and add both int and decimal parts in a new column

I have a column (version number) with more than 200k occurences as float for instance 1.2, 0.2 ...
I need to sum both sides of the floating number into a new column (total version), so that it gives me in the example 3, 2. Just integer numbers
Any advice?
Here is a solution that should be very easy to understand. I can make a oneline also you want to have that.
mylist = [1.3, 2.6, 3.1]
number = 0
fractions = 0
for a in mylist:
(a,b)=str(a).split('.')
number = number + int(a)
fractions = fractions + int(b)
print ("Number: " + str(number))
print ("Fractions: " + str(fractions))
This gives:
Number: 6
Fractions: 10
Do not use str(x).split('.') !
The one comment and the two other answers are currently suggesting to get the integer and fractional parts of a number x using
i,f = (int(s) for s in str(x).split('.'))
While this does give a result, I believe it is a bad idea.
The problem is, if you expect a meaningful result, you need to specify the precision of the fractional part explicitly. "1.20" and "1.2" are two string representations of the same number, but 20 and 2 are two very different integers. In addition, floating-point numbers are subject to precision errors, and you could easily find yourself with a number like "1.19999999999999999999999", which is only a small rounding error away from "1.2", but results in a completely different result with this str(x).split('.') approach.
One way to avoid this chaotic behaviour is to set a precision, ie, a number of decimal places, and stick to it. For instance, when dealing with monetary values, we're used to talk about cents; although 1.5€ and 1.50€ are technically both valid, you'll always hear people say "one euro fifty" and never "one euro five". If you hear someone say "one euro oh five", it actually means 1.05€. We always add exactly two decimal places.
With this approach, there is no chaotic behaviour of 1.2 becoming (1,2) or (1,20) or (1,1999999999). If you fixed the number of decimal places to 2, then 1.2 will always map to (1,20) and that's that.
A more standard way
Here are two standard ways of getting the integer and fractional parts of a number in python:
x = 1.20
# method 1
i = int(x)
f = x - i
# i = 1 and f = 0.2; i is an int and f a float
# method 2
import math
f, i = math.modf(x)
# i = 1.0 and f = 0.2; i and f are both floats
(EDIT: There is also a third method, pandas' divmod function. See user2314737's answer.)
Once you've done that, you can turn the fractional part f into an integer by multiplying it with the chosen power of 10 and converting it to an integer:
f = int(f * 100)
# f = 20
Finally you can apply this method to a whole list:
data = [13.0, 14.20, 12.299, 4.414]
def intfrac_pair(x, decimal_places):
i = int(x)
f = int((10**decimal_places) * (x - i))
return (i, f)
data_as_pairs = [intfrac_pair(x, 2) for x in data]
# = [(13, 0), (14, 20), (12, 30), (4, 41)]
sum_of_integer_parts = sum(i for i,f in data_as_pairs) # = 43
sum_of_fractional_parts = sum(f for i,f in data_as_pairs) # = 91
The following should work:
df['total_number']=[sum([int(i) for i in str(k).split('.')]) for k in df.version_number]
You can use divmod on the column
df = pd.DataFrame([1.2, 2.3, 3.4, 4.5, 0.1])
df
# 0
# 0 1.2
# 1 2.3
# 2 3.4
# 3 4.5
# 4 0.1
df['i'], df['d'] = df[0].divmod(1)
df
# Out:
# 0 i d
# 0 1.2 1.0 0.2
# 1 2.3 2.0 0.3
# 2 3.4 3.0 0.4
# 3 4.5 4.0 0.5
# 4 0.1 0.0 0.1
To sum row-wise as integers (a precision is needed, here I use p=1 assuming the original floats contain only one decimal digit) :
p = 1
df['s'] = (df['i']+10**p*df['d'].round(decimals=p)).astype(np.int)
df
# Out:
# 0 i d s
# 0 1.2 1.0 0.2 3
# 1 2.3 2.0 0.3 5
# 2 3.4 3.0 0.4 7
# 3 4.5 4.0 0.5 9
# 4 0.1 0.0 0.1 1
Sum by columns:
df.sum()
# Out:
# 0 11.5
# i 10.0
# d 1.5
Note: this will only work for positive integers as for instance divmod(-3.4, 1) returns (-4.0, 0.6).
Thank you all guys. I finally managed in a quite stupid, but effictive way. Before splitting, I transformed it to a string:
Allfiles['Version'] = Allfiles['Version'].round(3).astype(str)
Note that I rounded to 3 digits because a number like 2.111 was transformed to 2.11099999999999999999
Then I just did the split, creating a new column for minor versions (and having the major in the original colum
Allfiles[['Version', 'minor']] = Allfiles['Version'].str.split('.', expand=True)
Then I converted again both files into integers and sum both in the first column.
Allfiles['Version'] = Allfiles['Version']+Allfiles['minor']
(My dataframe name is Allfiles and the column version, as you can imagine.

Python setting Decimal Place range without rounding?

How can I take a float variable, and control how far out the float goes without round()? For example.
w = float(1.678)
I want to take x and make the following variables out of it.
x = 1.67
y = 1.6
z = 1
If I use the respective round methods:
x = round(w, 2) # With round I get 1.68
y = round(y, 1) # With round I get 1.7
z = round(z, 0) # With round I get 2.0
It's going to round and alter the numbers to the point where there no use to me. I understand this is the point of round and its working properly. How would I go about getting the information that I need in the x,y,z variables and still be able to use them in other equations in a float format?
You can do:
def truncate(f, n):
return math.floor(f * 10 ** n) / 10 ** n
testing:
>>> f=1.923328437452
>>> [truncate(f, n) for n in range(7)]
[1.0, 1.9, 1.92, 1.923, 1.9233, 1.92332, 1.923328]
A super simple solution is to use strings
x = float (str (w)[:-1])
y = float (str (w)[:-2])
z = float (str (w)[:-3])
Any of the floating point library solutions would require you dodge some rounding, and using floor/powers of 10 to pick out the decimals can get a little hairy by comparison to the above.
Integers are faster to manipulate than floats/doubles which are faster than strings. In this case, I tried to get time with both approach :
timeit.timeit(stmt = "float(str(math.pi)[:12])", setup = "import math", number = 1000000)
~1.1929605630000424
for :
timeit.timeit(stmt = "math.floor(math.pi * 10 ** 10) / 10 ** 10", setup = "import math", number = 1000000)
~0.3455968870000561
So it's safe to use math.floor rather than string operation on it.
If you just need to control the precision in format
pi = 3.14159265
format(pi, '.3f') #print 3.142 # 3 precision after the decimal point
format(pi, '.1f') #print 3.1
format(pi, '.10f') #print 3.1415926500, more precision than the original
If you need to control the precision in floating point arithmetic
import decimal
decimal.getcontext().prec=4 #4 precision in total
pi = decimal.Decimal(3.14159265)
pi**2 #print Decimal('9.870') whereas '3.142 squared' would be off
--edit--
Without "rounding", thus truncating the number
import decimal
from decimal import ROUND_DOWN
decimal.getcontext().prec=4
pi*1 #print Decimal('3.142')
decimal.getcontext().rounding = ROUND_DOWN
pi*1 #print Decimal('3.141')
I think the easiest answer is :
from math import trunc
w = 1.678
x = trunc(w * 100) / 100
y = trunc(w * 10) / 10
z = trunc(w)
also this:
>>> f = 1.678
>>> n = 2
>>> int(f * 10 ** n) / 10 ** n
1.67
Easiest way to get integer:
series_col.round(2).apply(lambda x: float(str(x).split(".",1)[0]))

Python Percentage Rounding [duplicate]

This question already has answers here:
How to make rounded percentages add up to 100%
(23 answers)
Closed 8 years ago.
I know how to round a number in Python, this is not a simple technical issue.
My issue is that rounding will make a set of percentages not adding up to 100%, when, technically, they should.
For example:
a = 1
b = 14
I want to compute the percentage of a in (a + b) and b in (a + b).
The answer should be
a/(a + b) = 1/15
b/(a + b) = 14/15
When I try to round those numbers, I got
1/15 = 6.66
14/15 = 93.33
(I was doing the flooring), which makes those two number doesn't add up to 100%.
In this case, we should do ceiling for 1/15, which is 6.67, and flooring for 14/15, which is 93.33. And now they add up to 100%. The rule in this case should be "rounding to the nearest number"
However, if we have a more complicate case, say 3 numbers:
a = 1
b = 7
c = 7
flooring:
1/15 = 6.66
7/15 = 46.66
7/15 = 46.66
Doesn't add up to 100%.
ceiling:
1/15 = 6.67
7/15 = 46.67
7/15 = 46.67
doesn't add up to 100%.
Rounding (to nearest number) is same as ceiling. Still doesn't add up to 100%.
So my question is what should I do to make sure they all add up to 100% in any cases.
Thanks in advance.
UPDATE:
Thanks for the tips from comments. I have took the "Largest Remainder" solution from the duplicate Post answer.
The code are:
def round_to_100_percent(number_set, digit_after_decimal=2):
"""
This function take a list of number and return a list of percentage, which represents the portion of each number in sum of all numbers
Moreover, those percentages are adding up to 100%!!!
Notice: the algorithm we are using here is 'Largest Remainder'
The down-side is that the results won't be accurate, but they are never accurate anyway:)
"""
unround_numbers = [x / float(sum(number_set)) * 100 * 10 ** digit_after_decimal for x in number_set]
decimal_part_with_index = sorted([(index, unround_numbers[index] % 1) for index in range(len(unround_numbers))], key=lambda y: y[1], reverse=True)
remainder = 100 * 10 ** digit_after_decimal - sum([int(x) for x in unround_numbers])
index = 0
while remainder > 0:
unround_numbers[decimal_part_with_index[index][0]] += 1
remainder -= 1
index = (index + 1) % len(number_set)
return [int(x) / float(10 ** digit_after_decimal) for x in unround_numbers]
Tested, seems work fine.
As others have commented, if your numbers are nice and round as in your example, you can use the fractions module to retain the accuracy of the rational numbers:
In [2]: from fractions import Fraction
In [5]: a = Fraction(1)
In [6]: b = Fraction(14)
In [7]: a/(a+b)
Out[7]: Fraction(1, 15)
In [8]: a/(a+b) + (b/(a+b))
Out[8]: Fraction(1, 1)
This obviously doesn't look good if you have really odd fractions.
Welcome to IEEE Floats.
The floating point numbers returned from math operations in python are approximates. On some values, the sum of percentages will be greater than 100.
You have two solutions: use the fraction or decimal modules OR, simply not want them to add up to 100%.

Wanting to convert cm^3 to L in python

I want to write a function that when I input the dimensions of a truncated cone (a cup) and an amount of liquid in litres returns how many of these cups can be filled up with the amount of liquid.I understand that 1L = 1000 cm^3 but I do not understand how I would incorporate it into my code to return the outcome I expect
def number_of_cups(bottom_radius, top_radius, height, litres_of_liquid):
volume = math.pi / 3 * height * (bottom_radius**2 + top_radius * bottom_radius + top_radius**2)
return int(filled_cup)
This is as far as I have got, I know I am close but I don't understand how to word my conversion,
That depends on the unit in which bottom_radius, top_radius and height are given. If we assume that those length are given in cm then
def number_of_cups(bottom_radius, top_radius, height, litres_of_liquid):
volume = math.pi / 3 * height * (bottom_radius**2 + top_radius * bottom_radius + top_radius**2)
return int( litres_of_liquid * 1000 / volume )
litres_of_liquid * 1000 is litres converted to cm^3. The int() could be replaced by math.floor() in case the number of completely full cups is intended, math.ceil() will give the number of full or partially filled cups.
Finally, there is a nice package magnitude which encapsulates a physical quantity. You could use this package in case the user wants to specify different length units.
The Formula stated by the OP is correct.
OK, just want to point out, your volume calculation seems wrong.
def number_of_cups(bottom_radius, top_radius, height, litres_of_liquid):
volume = 4 * math.pi * height * (bottom_radius**2 + top_radius**2)/2
filled_cup = 1000 * litres_of_liquid / volume
return int(filled_cup)
And in case you did't know, division is different in Python2 and Python3.
Python 2
>>> 1/2
0
Python 3
>>> 1/2
0.5
>>> 1//2
0
Throwing my own anwer to the pile:
#!/usr/bin/python2
import math
# nothing about units here , but let's say it's cm
def cup_vol(b_rad=3, t_rad=4, h=5):
vol = math.pi/3 * (b_rad**2 + t_rad + b_rad + t_rad**2) * h
return vol
def n_cups(liquid_amount, whole_cups=True): # nothing about units here
# liquid amount is Liter then first convert it to CM^3
liquid_amount = liquid_amount*1000
# this yields an int
if whole_cups:
return int(liquid_amount/cup_vol())
# else, return a real number with fraction
return liquid_amount/cup_vol()
if __name__ == '__main__':
print "4L fill %f cups" % n_cups(4)
print "4L fill %f cups (real)" % n_cups(4, whole_cups=False)
Running the above script yields:
4L fill 23.000000 cups
4L fill 23.873241 cups (real)

Leibniz formula for π - Is this any good? (Python)

I'm doing an exercise that asks for a function that approximates the value of pi using Leibniz' formula. These are the explanations on Wikipedia:
Logical thinking comes to me easily, but I wasn't given much of a formal education in maths, so I'm a bit lost as to what the leftmost symbols in the second one represent. I tried to make the code pi = ( (-1)**n / (2*n + 1) ) * 4, but that returned 1.9999990000005e-06 instead of 3.14159..., so I used an accumulator pattern instead (since the chapter of the guide that this was in mentions them as well) and it worked fine. However, I can't help thinking that it's somewhat contrived and there's probably a better way to do it, given Python's focus on simplicity and making programmes as short as possible. This is the full code:
def myPi(n):
denominator = 1
addto = 1
for i in range(n):
denominator = denominator + 2
addto = addto - (1/denominator)
denominator = denominator + 2
addto = addto + (1/denominator)
pi = addto * 4
return(pi)
print(myPi(1000000))
Does anyone know a better function?
The Leibniz formula translates directly into Python with no muss or fuss:
>>> steps = 1000000
>>> sum((-1.0)**n / (2.0*n+1.0) for n in reversed(range(steps))) * 4
3.1415916535897934
The capital sigma here is sigma notation. It is notation used to represent a summation in concise form.
So your sum is actually an infinite sum. The first term, for n=0, is:
(-1)**0/(2*0+1)
This is added to
(-1)**1/(2*1+1)
and then to
(-1)**2/(2*2+1)
and so on for ever. The summation is what is known mathematically as a convergent sum.
In Python you would write it like this:
def estimate_pi(terms):
result = 0.0
for n in range(terms):
result += (-1.0)**n/(2.0*n+1.0)
return 4*result
If you wanted to optimise a little, you can avoid the exponentiation.
def estimate_pi(terms):
result = 0.0
sign = 1.0
for n in range(terms):
result += sign/(2.0*n+1.0)
sign = -sign
return 4*result
....
>>> estimate_pi(100)
3.1315929035585537
>>> estimate_pi(1000)
3.140592653839794
Using pure Python you can do something like:
def term(n):
return ( (-1.)**n / (2.*n + 1.) )*4.
def pi(nterms):
return sum(map(term,range(nterms)))
and then calculate pi with the number of terms you need to reach a given precision:
pi(100)
# 3.13159290356
pi(1000)
# 3.14059265384
The following version uses Ramanujan's formula as outlined in this SO post - it uses a relation between pi and the "monster group", as discussed in this article.
import math
def Pi(x):
Pi = 0
Add = 0
for i in range(x):
Add =(math.factorial(4*i) * (1103 + 26390*i))/(((math.factorial(i))**4)*(396**(4*i)))
Pi = Pi + (((math.sqrt(8))/(9801))*Add)
Pi = 1/Pi
print(Pi)
Pi(100)
This was my approach:
def estPi(terms):
outPut = 0.0
for i in range (1, (2 * terms), 4):
outPut = (outPut + (1/i) - (1/(i+2)))
return 4 * outPut
I take in the number of terms the user wants, then in the for loop I double it to account for only using odds.
at 100 terms I get 3.1315929035585537
at 1000 terms I get 3.140592653839794
at 10000 terms I get 3.1414926535900345
at 100000 terms I get 3.1415826535897198
at 1000000 terms I get 3.1415916535897743
at 10000000 terms I get 3.1415925535897915
at 100000000 terms I get 3.141592643589326
at 1000000000 terms I get 3.1415926525880504
Actual Pi is 3.1415926535897932
Got to love a convergent series.
def myPi(iters):
pi = 0
sign = 1
denominator = 1
for i in range(iters):
pi = pi + (sign/denominator)
# alternating between negative and positive
sign = sign * -1
denominator = denominator + 2
pi = pi * 4.0
return pi
pi_approx = myPi(10000)
print(pi_approx)
old thread, but i wanted to stuff around with this and coincidentally i came up with pretty much the same as user3220980
# gregory-leibnitz
# pi acurate to 8 dp in around 80 sec
# pi to 5 dp in .06 seconds
import time
start_time = time.time()
pi = 4 # start at 4
times = 100000000
for i in range(3,times,4):
pi -= (4/i) + (4/(i + 2))
print(pi)
print("{} seconds".format(time.time() - start_time))

Categories