9

I'm currently using Pint to handle units and unit conversions. This seems to work well for the units that are already defined in Pint, for example

>>> import pint
>>> ureg = pint.UnitRegistry()
>>> Q = ureg.Quantity
>>> a = Q(5, 'm/s')
>>> a
<Quantity(5, 'meter / second')>
>>> a.to('ft/s')
<Quantity(16.404199475065617, 'foot / second')>

I tried to define my own units, which represent percentage. As far as unit conversions go, a percentage is simply 100 times a dimensionless fraction, which is how I defined it.

>>> ureg.define('percent = dimensionless * 100 = pct')
>>> a = Q(5, 'pct')
>>> a
<Quantity(5, 'percent')>

However I cannot seem to convert back and forth between fraction ('dimensionless') and 'pct'.

>>> a.to('dimensionless')
Traceback (most recent call last):
  File "<pyshell#31>", line 1, in <module>
    a.to('dimensionless')
  File "C:\Python35\python-3.5.1.amd64\lib\site-packages\pint\quantity.py", line 263, in to
    magnitude = self._convert_magnitude_not_inplace(other, *contexts, **ctx_kwargs)
  File "C:\Python35\python-3.5.1.amd64\lib\site-packages\pint\quantity.py", line 231, in _convert_magnitude_not_inplace
    return self._REGISTRY.convert(self._magnitude, self._units, other)
  File "C:\Python35\python-3.5.1.amd64\lib\site-packages\pint\unit.py", line 1026, in convert
    return self._convert(value, src, dst, inplace)
  File "C:\Python35\python-3.5.1.amd64\lib\site-packages\pint\unit.py", line 1042, in _convert
    src_dim = self._get_dimensionality(src)
  File "C:\Python35\python-3.5.1.amd64\lib\site-packages\pint\unit.py", line 813, in _get_dimensionality
    self._get_dimensionality_recurse(input_units, 1.0, accumulator)
  File "C:\Python35\python-3.5.1.amd64\lib\site-packages\pint\unit.py", line 837, in _get_dimensionality_recurse
    self._get_dimensionality_recurse(reg.reference, exp2, accumulator)
  File "C:\Python35\python-3.5.1.amd64\lib\site-packages\pint\unit.py", line 835, in _get_dimensionality_recurse
    reg = self._units[self.get_name(key)]
KeyError: ''

What I'd essentially like to do is be able to convert between e.g. "0.73" and "73%". How can I define and use such a unit?

Cory Kramer
  • 114,268
  • 16
  • 167
  • 218

4 Answers4

8

It seems that GitHub issue hgrecco/pint#185 covers the case you're describing.

Using the work-around discussed in that issue works for me using Pint-0.7.2:

from pint.unit import ScaleConverter
from pint.unit import UnitDefinition
import pint

ureg = pint.UnitRegistry()
Q = ureg.Quantity

ureg.define(UnitDefinition('percent', 'pct', (), ScaleConverter(1 / 100.0)))
a = Q(5, 'pct')

print a
print a.to('dimensionless')

Output:

5 percent
0.05 dimensionless
Lukas Graf
  • 30,317
  • 8
  • 77
  • 92
  • 2
    I used `from pint.converters import ScaleConverter` – nowox Oct 06 '16 at 17:18
  • 4
    As of pint 0.8 it seems they've removed `pint.unit.ScaleConverter`, though `pint.converters.ScaleConverter` is still available, so the rest of the solution is still valid. – Cory Kramer Apr 17 '17 at 12:07
  • In pint 0.9 this can be done by first defining a unit `ureg.define('fraction = []')`, and then scaled versions can be defined using this rather than `dimensionless` directly. See [my answer](https://stackoverflow.com/a/51180482/2426909) for details. – GeoMatt22 Jul 04 '18 at 20:41
7

I had the same problem, and came up with a simple solution which seems to work well.

import pint

ureg = pint.UnitRegistry()
ureg.define('fraction = [] = frac')
ureg.define('percent = 1e-2 frac = pct')
ureg.define('ppm = 1e-6 fraction')

print(ureg('100 pct').to('dimensionless'))
print(ureg('0.5 dimensionless').to('pct'))
print(ureg('pct').to('ppm'))
print(ureg('1e4 ppm').to('pct'))

Output:

1.0 dimensionless
50.0 percent
10000.0 ppm
0.9999999999999999 percent

(Note slight rounding error on last line.)

I got the syntax from the default units dictionary, where base units are defined like

# reference
gram = [mass] = g  # dimensional
radian = [] = rad  # dimensionless
GeoMatt22
  • 255
  • 2
  • 6
  • There's a slight difference between both solutions: yours resolves to `fraction` when calling `to_base_units()` while the other solution resolves to `dimensionless`. – Morwenn Jun 01 '21 at 13:31
2

In pint>0.22, percent is supported out of the box:

These do the expected thing:

import pint
ureg = pint.UnitRegistry()
pint.__version__  # 0.22
ureg.Quantity(1,'%')  # 1 percent
ureg.Quantity(1,'').to('percent')  # 100.0 percent
ureg.Quantity(100,'percent').to('')  # 1.0 dimensionless

However, .to('%') is still buggy.

Matthias Arras
  • 565
  • 7
  • 25
0

In pint ≥0.16, try

ureg.define('percent = 1 / 100 = %')

or

ureg.define('percent = 0.01 = %')

If you want to use not only ureg('1 percent'), but also ureg('1 %'), add a preprocessor:

ureg = pint.UnitRegistry(preprocessors=[
     lambda s: s.replace('%', ' percent '),
])

Credits to this comment on GitHub for the preprocessor code example.

Nicolai Weitkemper
  • 403
  • 1
  • 9
  • 18
  • When I do this, then `x = ureg('1 %').to('')` works as expected (`x` is `` and I can also do `x.to('percent')`, but `x.to('%')` fails with `pint.errors.DefinitionSyntaxError: missing unary operator "%"` - any idea why and how to fix it? – Michal Kaut Oct 03 '22 at 08:17
  • @MichalKaut I've seen your [comment on GitHub](https://github.com/hgrecco/pint/issues/185#issuecomment-1264660139) already, but unfortunately am too unfamiliar with Pint's inner workings to help with this one. I'd love to hear back from you if you find a solution, though! – Nicolai Weitkemper Oct 03 '22 at 16:30