Units and Quantities (astropy.units)#

Introduction#

astropy.units handles defining, converting between, and performing arithmetic with physical quantities, such as meters, seconds, Hz, etc. It also handles logarithmic units such as magnitude and decibel.

astropy.units does not know spherical geometry or sexagesimal (hours, min, sec): if you want to deal with celestial coordinates, see the astropy.coordinates package.

Getting Started#

Most users of the astropy.units package will work with Quantity objects: the combination of a value and a unit. The most convenient way to create a Quantity is to multiply or divide a value by one of the built-in units. It works with scalars, sequences, and numpy arrays.

Examples#

To create a Quantity object:

>>> from astropy import units as u
>>> 42.0 * u.meter
<Quantity  42. m>
>>> [1., 2., 3.] * u.m
<Quantity [1., 2., 3.] m>
>>> import numpy as np
>>> np.array([1., 2., 3.]) * u.m
<Quantity [1., 2., 3.] m>


You can get the unit and value from a Quantity using the unit and value members:

>>> q = 42.0 * u.meter
>>> q.value
42.0
>>> q.unit
Unit("m")


From this basic building block, it is possible to start combining quantities with different units:

>>> 15.1 * u.meter / (32.0 * u.second)
<Quantity 0.471875 m / s>
>>> 3.0 * u.kilometer / (130.51 * u.meter / u.second)
<Quantity 0.022986744310780783 km s / m>
>>> (3.0 * u.kilometer / (130.51 * u.meter / u.second)).decompose()
<Quantity 22.986744310780782 s>


Unit conversion is done using the to() method, which returns a new Quantity in the given unit:

>>> x = 1.0 * u.parsec
>>> x.to(u.km)
<Quantity 30856775814671.914 km>


It is also possible to work directly with units at a lower level, for example, to create custom units:

>>> from astropy.units import imperial

>>> cms = u.cm / u.s
>>> # ...and then use some imperial units
>>> mph = imperial.mile / u.hour

>>> # And do some conversions
>>> q = 42.0 * cms
>>> q.to(mph)
<Quantity 0.939513242662849 mi / h>


Units that “cancel out” become a special unit called the “dimensionless unit”:

>>> u.m / u.m
Unit(dimensionless)


To create a basic dimensionless quantity, multiply a value by the unscaled dimensionless unit:

>>> q = 1.0 * u.dimensionless_unscaled
>>> q.unit
Unit(dimensionless)


astropy.units is able to match compound units against the units it already knows about:

>>> (u.s ** -1).compose()
[Unit("Bq"), Unit("Hz"), Unit("2.7027e-11 Ci")]


And it can convert between unit systems, such as SI or CGS:

>>> (1.0 * u.Pa).cgs
<Quantity 10. P / s>


The units mag, dex, and dB are special, being logarithmic units, for which a value is the logarithm of a physical quantity in a given unit. These can be used with a physical unit in parentheses to create a corresponding logarithmic quantity:

>>> -2.5 * u.mag(u.ct / u.s)
<Magnitude -2.5 mag(ct / s)>
>>> from astropy import constants as c
>>> u.Dex((c.G * u.M_sun / u.R_sun**2).cgs)
<Dex 4.438067627303133 dex(cm / s2)>


astropy.units also handles equivalencies, such as that between wavelength and frequency. To use that feature, equivalence objects are passed to the to() conversion method. For instance, a conversion from wavelength to frequency does not normally work:

>>> (1000 * u.nm).to(u.Hz)
Traceback (most recent call last):
...
UnitConversionError: 'nm' (length) and 'Hz' (frequency) are not convertible


But by passing an equivalency list, in this case spectral(), it does:

>>> (1000 * u.nm).to(u.Hz, equivalencies=u.spectral())
<Quantity  2.99792458e+14 Hz>


Quantities and units can be printed nicely to strings using the Format String Syntax. Format specifiers (like 0.03f) in strings will be used to format the quantity value:

>>> q = 15.1 * u.meter / (32.0 * u.second)
>>> q
<Quantity 0.471875 m / s>
>>> f"{q:0.03f}"
'0.472 m / s'


The value and unit can also be formatted separately. Format specifiers for units can be used to choose the unit formatter:

>>> q = 15.1 * u.meter / (32.0 * u.second)
>>> q
<Quantity 0.471875 m / s>
>>> f"{q.value:0.03f} {q.unit:FITS}"
'0.472 m s-1'


Acknowledgments#

This code was originally based on the pynbody units module written by Andrew Pontzen, who has granted the Astropy Project permission to use the code under a BSD license.

Performance Tips#

If you are attaching units to arrays to make Quantity objects, multiplying arrays by units will result in the array being copied in memory, which will slow things down. Furthermore, if you are multiplying an array by a composite unit, the array will be copied for each individual multiplication. Thus, in the following case, the array is copied four successive times:

In [1]: import numpy as np

In [2]: from astropy import units as u

In [3]: rng = np.random.default_rng()

In [4]: array = rng.random(10000000)

In [5]: %timeit array * u.m / u.s / u.kg / u.sr
92.5 ms ± 2.52 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)


There are several ways to speed this up. First, when you are using composite units, ensure that the entire unit gets evaluated first, then attached to the array. You can do this by using parentheses as for any other operation:

In [6]: %timeit array * (u.m / u.s / u.kg / u.sr)
21.5 ms ± 886 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)


In this case, this has sped things up by a factor of 4. If you use a composite unit several times in your code then you can define a variable for it:

In [7]: UNIT_MSKGSR = u.m / u.s / u.kg / u.sr

In [8]: %timeit array * UNIT_MSKGSR
22.2 ms ± 551 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)


In this case and the case with brackets, the array is still copied once when creating the Quantity. If you want to avoid any copies altogether, you can make use of the << operator to attach the unit to the array:

In [9]: %timeit array << u.m / u.s / u.kg / u.sr
47.1 µs ± 5.77 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)


Note that these are now microseconds, so this is 2000x faster than the original case with no brackets. Note that brackets are not needed when using << since * and / have a higher precedence, so the unit will be evaluated first. When using <<, be aware that because the data is not being copied, changing the original array will also change the Quantity object.

Note that for composite units, you will definitely see an impact if you can pre-compute the composite unit:

In [10]: %timeit array << UNIT_MSKGSR
6.51 µs ± 112 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)


Which is over 10000x faster than the original example. See Creating and Converting Quantities without Copies for more details about the << operator.