Round off dict values to 2 decimals

11,499

Solution 1

Taking the best bits from a couple of other answers:

class LessPrecise(float):
    def __repr__(self):
        return str(self)

def roundingVals_toTwoDeci(y):
    for d in y:
        for k, v in d.items():
            v = LessPrecise(round(v, 2))
            print v
            d[k] = v

>>> roundingVals_toTwoDeci(y)
80.0
10.0
0.08
10.67
80.73
10.78
0.0
10.0
80.72
10.0
0.78
10.0
80.78
10.0
0.0
10.98
>>> s=json.dumps(y)
>>> s
'[{"a": 80.0, "c": 10.0, "b": 0.08, "d": 10.67}, {"a": 80.73, "c": 10.78, "b": 0.0, "d": 10.0}, {"a": 80.72, "c": 10.0, "b": 0.78, "d": 10.0}, {"a": 80.78, "c": 10.0, "b": 0.0, "d": 10.98}]'

Solution 2

import json


y = [{'a': 80.0, 'b': 0.0786235, 'c': 10.0, 'd': 10.6742903}, {'a': 80.73246, 'b': 0.0, 'c':   
10.780323, 'd': 10.0}, {'a': 80.7239, 'b': 0.7823640, 'c': 10.0, 'd': 10.0}, {'a': 
80.7802313217234, 'b': 0.0, 'c': 10.0, 'd': 10.9762304}]

def roundingVals_toTwoDeci(y):

    for d in y:
        for k, v in d.items():
            v = round(v,2) # <--- round() does exact that.
            d[k] = v # <--- You need to put the rounded v back in d
            print v
    return

roundingVals_toTwoDeci(y)
s = json.dumps(y)
print s

Solution 3

JSONEncoder uses repr, and repr prints floats with all their available precision. The only possible solutions are to inherit from JSONEncoder and round while actually converting the values to a string (which implies to copy and adapt some code from the json.encoder module), or else wrap the floats into your own type RoundedFloat and register a serializer for that. Also note that repr's behaviour depends on the Python version used.

As often with non-obvious behaviour, the observation during debugging can trick you: print uses str(), and str() rounds at a certain point, unlike repr() which shows the naked ugliness of floating point maths.

The proof is in the code:

>>> class F(float):
...     def __str__(self): return "str"
...     def __repr__(self): return "repr"
...     
... 
>>> print F(1)
str
>>> F(1)
repr
>>> repr(1-1e-15)
'0.999999999999999'
>>> str(1-1e-15)
'1.0'

Solution 4

Answering the second part of your question

Try replacing line 5 of your code with:

 v = round(v, 2)

This will round the number to two decimal places. Using round, I get

[{'a': 80.0, 'c': 10.0, 'b': 0.08, 'd': 10.67}, {'a': 80.73, 'c': 10.78, 'b': 0.0, 'd': 10.0}, {'a': 80.72, 'c': 10.0, 'b': 0.78, 'd': 10.0}, {'a': 80.78, 'c': 10.0, 'b': 0.0, 'd': 10.98}]

I am using Python 2.7.2. Here's all the code:

from math import ceil 
import json

y = [{'a': 80.0, 'b': 0.0786235, 'c': 10.0, 'd': 10.6742903},
     {'a': 80.73246, 'b': 0.0, 'c': 10.780323, 'd': 10.0},
     {'a': 80.7239, 'b': 0.7823640, 'c': 10.0, 'd': 10.0},
     {'a': 80.7802313217234, 'b': 0.0, 'c': 10.0, 'd': 10.9762304}]

def roundingVals_toTwoDeci(y):
    for d in y:
        for k, v in d.items():
            v = round(v, 2)
            #print v
            d[k] = v
    return

roundingVals_toTwoDeci(y)
s = json.dumps(y)
print s

Solution 5

I don't understand what relates to json, but I can propose:

from math import ceil

y = [{'a': 80.0, 'b': 0.0786235, 'c': 10.0, 'd': 10.6742903},
     {'a': 80.73246, 'b': 0.0, 'c': 10.780323, 'd': 10.0},
     {'a': 80.7239, 'b': 0.7823640, 'c': 10.0, 'd': 10.0},
     {'a': 80.7802313217234, 'b': 0.0, 'c': 10.0, 'd': 10.9762304}]

class TwoDec(float):
    def __repr__(self):
        return "%.2f" % self

def roundingVals_to_TwoDeci(y,ceil=ceil,TwoDec=TwoDec):
    for d in y:
        for k, v in d.iteritems():
            d[k] = TwoDec(ceil(v*100)/100)

roundingVals_to_TwoDeci(y)
for el in y:
    print el

result

{'a': 80.00, 'c': 10.00, 'b': 0.08, 'd': 10.68}
{'a': 80.74, 'c': 10.79, 'b': 0.00, 'd': 10.00}
{'a': 80.73, 'c': 10.00, 'b': 0.79, 'd': 10.00}
{'a': 80.79, 'c': 10.00, 'b': 0.00, 'd': 10.98}
Share:
11,499
askance
Author by

askance

Updated on June 04, 2022

Comments

  • askance
    askance almost 2 years

    I'm having a hard time rounding off values in dicts. What I have is a list of dicts like this:

    y = [{'a': 80.0, 'b': 0.0786235, 'c': 10.0, 'd': 10.6742903}, {'a': 80.73246, 'b': 0.0, 'c':   
    10.780323, 'd': 10.0}, {'a': 80.7239, 'b': 0.7823640, 'c': 10.0, 'd': 10.0}, {'a': 
    80.7802313217234, 'b': 0.0, 'c': 10.0, 'd': 10.9762304}]
    

    I need to round off the values to just 2 decimal places.

    When I try the following:

    def roundingVals_toTwoDeci(y):
    
        for d in y:
            for k, v in d.items():
                v = ceil(v*100)/100.0
                print v
                d[k] = v
        return
    roundingVals_toTwoDeci(y)
    s = json.dumps(y)
    print s
    

    I get:

    0.0
    0.0
    18.2
    0.0
    27.3
    54.5
    0.0
    0.0
    0.0
    [{"a": 0.0, "b": 0.0, "c": 27.300000000000001, "d": 0.0, "e": 54.5, "f": 0.0, "g": 18.199999999999999, "h": 0.0, "i": 0.0}]
    

    I need to make this work with versions 2.4+ and so am not using dict comprehensions. First, I am having a hard time looping through all the key, values in all the dicts in the original. Second, this result has just 1 decimal point instead of 2 when it prints inside the function? Third, why is the 'json.dumps' and then 'print' not showing the values from inside the function?

    EDIT:

    Working with @Mark Ransom's answer below, I get the desired o/p. However, I have to urlencode the json.dumps value and send it to a URL. At the URL, it decodes the values into all the decimal places. So, for example, if, josn.dumps gives {"a": 9.1}, the URL shows it (after urlencode) as 9.10034254344365. The modified code is as below:

    class LessPrecise(float):
        def __repr__(self):
            return str(self)
    
    def roundingVals_toTwoDeci(y):
        for d in y:
            for k, v in d.items():
                v = LessPrecise(round(v, 2))
                print v
                d[k] = v
    
    
    
    
    roundingVals_toTwoDeci(y)
    j = json.dumps(y)
    print j
    
    params = urllib.urlencode({'thekey': j}) 
    

    print json.dumps gives {"a": 9.1} At the URL after urlencode, it gives 9.1078667322034 instead of 9.1as in the following:

    Output:::

    100.0
    0.0
    0.0
    0.0
    100.0
    0.0
    0.0
    0.0
    81.8
    0.0
    18.2
    0.0
    90.0
    0.0
    0.0
    10.0
    [{"a": 100.0, "b": 0.0, "c": 0.0, "d": 0.0}, {"a": 100.0, "b": 0.0, "c": 0.0, "d": 0.0}, {"a":
    81.8,  "b": 0.0, "c": 18.2, "d": 0.0}, {"a": 90.0, "b": 0.0, "c": 0.0, "d": 10.0}]
    

    At the URL:

    9.100000381469727
    

    The JSON string after json.dumps()

    [{"a": 80.0, "b": 0.0, "c": 10.0, "d": 10.0}, {"a": 100.0, "b": 0.0, "c": 0.0, "d": 0.0}, {"a":  
    80.0, "b": 0.0, "c": 10.0, "d": 10.0}, {"a": 90.0, "b": 0.0, "c": 0.0, "d": 10.0}]
    

    The urlencode string - after decoding at http://meyerweb.com/eric/tools/dencoder/

    thekey=[{"a": 80.0, "b": 0.0, "c": 10.0, "d": 10.0}, {"a": 100.0, "b": 0.0, "c": 0.0, "d": 
    0.0}, {"a": 80.0, "b": 0.0, "c": 10.0, "d": 10.0}, {"a": 90.0, "b": 0.0, "c": 0.0, "d": 10.0}]
    

    At the URL, I get values like 18.200000762939453(this value is from a later script run)

    • jhermann
      jhermann over 10 years
      JSONEncoder uses repr, and repr prints floats with all their available precision. The only possible solutions are to inherit from JSONEncoder and round while actually converting the values to a string, or else wrapa the floats into your own type RoundedFloat and register a serializer for that. Also note that repr's behaviour depends on the Python version used.
    • askance
      askance over 10 years
      When I do a print y after calling roundingVals_toTwoDeci(y) but before s = json.dumps(y, I get the same exact problem. So, could it be an issue with the function/calling the function/return statement instead? (or, in addition to it)
    • Mark Ransom
      Mark Ransom over 10 years
      print y does the exact same repr to convert the floating point to a string when that number is inside a list or dictionary.
    • askance
      askance over 10 years
      Ok. What I did was add v = str(v) after v = round(v, 2) inside the function and now it seems to be working fine. But would str(v) be an issue when json sends the data to the external URL (if the URL is not conf. to accept strings or is conf. to accept only a certain datatype other than string, viz., 'float'? My other question is why does this print only upto 1 decimal point - my requirement is for 2 decimal points.
    • Mark Ransom
      Mark Ransom over 10 years
      @jhermann that deserves to be an answer, then you can format it properly.
    • Mark Ransom
      Mark Ransom over 10 years
      The new information doesn't make any sense. You're passing a string to urlencode, it's not going to change the number inside the string. Can you tell us the exact string you get from json.dumps and the exact complete string you get from urlencode?
    • askance
      askance over 10 years
      Edited the question to include the two strings from json.dumps() and the decoded values from urlencode.
    • Mark Ransom
      Mark Ransom over 10 years
      Thanks. As you can see, the encoded URL is correct so there's absolutely nothing you can do to fix anything on that end; the problem is on the page receiving the URL. Before you do anything else read stackoverflow.com/questions/2100490/…
    • askance
      askance over 10 years
      Thanks. Was waiting to hear back from the guys behind the URL. The decoded params are fine as you point above and it's their app (URL) that needs to be dealt with.
  • askance
    askance over 10 years
    I am getting the same exact result as before. Could it be the way I am calling the function or using the 'return' statement?
  • mdml
    mdml over 10 years
    I ran your code exactly as is besides replacing line 5 as in my answer, and commenting out line 6 where you print the rounded value. Doing that, I obtain the output in my answer.
  • askance
    askance over 10 years
    I am not sure why I am not getting it then. I am doing the exact same thing. I am using 2.6 (The values in the data, i.e., in 'y' - the list of dicts, keep changing whenever the script is called though I don't think it should matter.)
  • askance
    askance over 10 years
    I have all the imports (incl. JSON and 'from math import ceil').
  • mdml
    mdml over 10 years
    Do you mean that your output still isn't rounded, or is the output somehow different?
  • askance
    askance over 10 years
    By same as before I mean: [{"a": 0.0, "b": 0.0, "c": 0.0, "d": 90.900000000000006, "e": 9.0999999999999996, "f": 0.0, "g": 0.0, "h": 0.0, "i": 0.0}] When I do a print statement inside the loop, I get the earlier values- rounded off to 1 decimal place but the print statement outside the function gives what is above.
  • mdml
    mdml over 10 years
    I will add all my code to the answer. I would try running that completely separately just to see.
  • mdml
    mdml over 10 years
  • jhermann
    jhermann over 10 years
    PS: The json module's author probably intended json.encoder.FLOAT_REPR = lambda f: "%.2f" % f to be possible, but then that has a bug (FLOAT_REPR is only used at import time, not at runtime).
  • Mark Ransom
    Mark Ransom over 10 years
    I wonder if it has to do with your platform? With 2.7.1 on Windows I get the same results as @user2480526.
  • askance
    askance over 10 years
    The answer works fine - o/p is values without strings and rounded off to 1 decimal on my machine. When I send the data to the URL after urlencode, it appears there with all 9/10 decimal points instead of the 1 decimal place that it shows upon printing after json.dumps which is what I want to avoid.
  • Mark Ransom
    Mark Ransom over 10 years
    @user2480526 in that case urlencode is converting the string back into numbers before converting to another string. The problem needs to be fixed there. I wonder if urlencode would work with strings instead?
  • askance
    askance over 10 years
    Tried it with string values. It is decoded at the URL with all the decimal places - 9.100000381469727 while running my print json.dumps returns {"a": "9.1"}
  • askance
    askance over 10 years
    This answer leads to the same decoding at the URL.
  • Mark Ransom
    Mark Ransom over 10 years
    @user2480526 the second answer on that thread does something similar to what I did. What exactly are you passing to urlencode?
  • askance
    askance over 10 years
    I am doing a json.dumps() and then passing the value to urlencode. Right now, I am passing `[{"a": "88.9", "b": "0.0", "sc": "0.0", "d": "11.1"}, {"a": "100.0", "b": "0.0", "c": "0.0", "d": "0.0"}, {"a": "81.8", "b": "0.0", "c": "9.1", "d": "9.1"} and at the URL for "d" I get 9.1068906293. I tried your solution and got the dict with values as floats - 9.1 instead of "9.1" but at the URL I get the same 10-15 decimal places.
  • askance
    askance over 10 years
    I am trying several solutions given here and here. I now get the 2 decimal places but when the URL decodes it, it shows e.g., 9.1087978601324 instead of 9.1. After w=json.dumps, I do a urlencode(w).
  • Mark Ransom
    Mark Ransom over 10 years
    @user2480526, you're not being specific enough - what is the exact code you're using for urlencode? Maybe you should edit it into the question.
  • askance
    askance over 10 years
    Oops! I edited the question with a solution from elsewhere. Edited it again to include your code.
  • askance
    askance over 10 years
    Thanks. +1 for using iteritems() - forgot about that :) This gives the desired o/p but the JSON/urlencode thing is driving me nuts!
  • Admin
    Admin over 2 years
    As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.