Float values as dictionary key

Question:

I am developing a class for the analysis of microtiter plates. The samples are described in a separate file and the entries are used for an ordered dictionary. One of the keys is pH, which is usually given as float. e.g 6.8

I could import it as decimal with Decimal('6.8') in order to avoid a float as dict key. Another solution would be to replace the dot with e.g p like 6p8 or to write 6p8 in my sample description and therefore eliminating the problem at the beginning. But this would cause troubles later on since i cannot plot pH of 6p8 in my figures.

How would you solve this issue?

Asked By: Moritz

||

Answers:

Perhaps you want to truncate your float prior to using is as key?

Maybe like this:

a = 0.122334
round(a, 4)       #<-- use this as your key?

Your key is now:

0.1223           # still a float, but you have control over its quality

You can use it as follows:

dictionary[round(a, 4)]   

to retrieve your values

Answered By: Reblochon Masque

There’s no problem using floats as dict keys.

Just round(n, 1) them to normalise them to your keyspace. eg.

>>> hash(round(6.84, 1))
3543446220
>>> hash(round(6.75, 1))
3543446220
Answered By: John La Rooy

Another option would be to use tuples:

dictionary = {(6.8,): 0.3985}
dictionary[(6.8,)]

Then Retrieving the values later for plotting is easily done with something like

points = [(pH, value) for (pH,), value in dictionary.items()]
    ...
Answered By: minkwe

Another quick option is to use strings of the float

a = 200.01234567890123456789
b = {str(a): 1}
for key in b:
    print(float(key), b[key])

would print out

(200.012345679, 1)

notice a gets truncated at tenth digit after decimal point.

Answered By: zyy

Another way would be enter the keys as strings with the point rather than a p and then recast them as floats for plotting.

Personally, if you don’t insist on the dict format, I would store the data as a pandas dataframe with the pH as a column as these are easier to pass to plotting libraries

Answered By: Plamen

If you want to use the float key dictionary at multiple places in your program, it might me worth to "hide" the complexity of how it ought to be used (i. e. with rounded keys) in a new dictionary class (full implementation).

Example:

>>> d = FloatKeyDictionary(2, {math.pi: "foo"})
>>> d
{3.14: 'foo'}

>>> d[3.1415]
'foo'

>>> 3.1 in d
False

>>> d[math.e] = "My hovercraft is full of eels!"
>>> d
{3.14: 'foo', 2.72: 'My hovercraft is full of eels!'}

You can find an abridged version with the general idea below:

import abc
import collections.abc


class KeyTransformDictionaryBase(dict, abc.ABC):

    @abc.abstractmethod
    def __key_transform__(self, key):
        raise NotImplementedError

    def __contains__(self, key):
        return super().__contains__(self.__key_transform__(key))

    def __getitem__(self, key):
        return super().__getitem__(self.__key_transform__(key))

    def __setitem__(self, key, value):
        return super().__setitem__(self.__key_transform__(key), value)

    def __delitem__(self, key):
        return super().__delitem__(self.__key_transform__(key))


class FloatKeyDictionary(KeyTransformDictionaryBase):

    def __init__(self, rounding_ndigits, data=None):
        super().__init__()
        self.rounding_ndigits = rounding_ndigits
        if data is not None:
            self.update(data)

    def __key_transform__(self, key):
        return round(key, self.rounding_ndigits)
Answered By: David Foerster