Basically the title. In pint, is there a way to define the default string formatting per dimension or per unit, instead of 'across the board'?
Stated more precisely: I want to format a quantity's numerical value (i.e., magnitude), based on its physical unit.
Here is what I tried, based on the code shown in the docs:
from pint import UnitRegistry, Unit
ureg = UnitRegistry()
# Specific format for km won't stick...
ureg.default_format = ".0f~"
ureg.km.default_format = ".2fP"
ureg.km.default_format # '.0f~'
# ...as also seen here:
dist = 3 * ureg("km")
time = 500 * ureg("s")
print(f"{dist}, {time}")
# wanted: 3.00 kilometer, 500 s
# got: 3 km, 500 s
Especially when dealing with prices, it's practical to be able to set a 2-digit-default, with all other units having different default format.
PS: I know it's possible to set a default formatting on an individual quantity (e.g. dist.default_format = '.2f~'), but that's too specific for my use case. I want all quantities with the unit 'km' to be displayed with 2 decimals.
I have constructed quite the hacky solution:
from pint import UnitRegistry, Quantity
ureg = UnitRegistry()
# Setting specific formats:
formatdict = {ureg.km: '.2fP'} # extend as required
Quantity.default_format = property(lambda self: formatdict.get(self.u, ".0f~"))
# Works:
dist = 3 * ureg("km")
time = 500 * ureg("s")
print(f"{dist}, {time}") # 3.00 kilometer, 500 s
This works, but I'd be surprised if there isn't a better solution.
EDIT
It only works in a limited sense. ureg.default_format gets changed as well, which prohibits its use in e.g. a pandas.DataFrame:
ureg.default_format # <property at 0x21148ed5ae0>
Related
I am working on a Python package for converting baking recipes. Ideally, the recipe is simply stored as a CSV file read in by the package. Given the recipe can be imperial or metric units of measurement, I am trying to internally convert any set of measurement units to metric for simplicity.
The main question I am trying to solve is a light-weight way to store a lot of conversions and ratios given a variety of names that a measurement unit can be.
For example, if a recipe has "tsp", I would want to classify it in the teaspoon family which would consist of ['tsp', 'tsps', 'teaspoon', 'teaspoons'] and have them all use the TSP_TO_METRIC conversion ratio.
Initially, I started as a list of lists but I feel like there may be a more elegant way to store and access these items. I was thinking a dictionary or some sort of JSON file to read in but unsure where the line is between needing an external file versus a long file of constants? I will continue to expand conversions as different ingredients are added so I am also looking for an easy way to scale.
Here is an example of the data conversions I am attempting to store. Then I use a series of if-else coupled with any(unit in sublist for sublist in VOLUME_NAMES): to check the lists of lists.
TSP_TO_METRIC = 5
TBSP_TO_METRIC = 15
OZ_TO_METRIC = 28.35
CUP_TO_METRIC = 8 * OZ_TO_METRIC
PINT_TO_METRIC = 2 * CUP_TO_METRIC
QUART_TO_METRIC = 4 * CUP_TO_METRIC
GALLON_TO_METRIC = 16 * CUP_TO_METRIC
LB_TO_METRIC = 16 * OZ_TO_METRIC
STICK_TO_METRIC = 8 * TBSP_TO_METRIC
TSP_NAMES = ['TSP', 'TSPS', 'TEASPOON', 'TEASPOONS']
TBSP_NAMES = ['TBSP', 'TBSPS', 'TABLESPOON', 'TABLESPOONS']
CUP_NAMES = ['CUP', 'CUPS']
LB_NAMES = ['LB', 'LBS', 'POUND', 'POUNDS']
OZ_NAMES = ['OZ', 'OUNCE', 'OUNCES']
BUTTER_NAMES = ['STICK', 'STICKS']
EGG_NAMES = ['CT', 'COUNT']
GALLON_NAMES = ['GAL', 'GALLON', 'GALLONS']
VOLUME_NAMES = [TSP_NAMES, TBSP_NAMES, CUP_NAMES, GALLON_NAMES]
WEIGHT_NAMES = [LB_NAMES, OZ_NAMES]
Say I have an arbitrary Pint quantity q. Is there a way to display its units in symbol short form, instead of as a full-length word?
In other words, how would I code unit_symbol() such that it returns "m", not "meter"; "kg" not "kilogram"; etc.? Is there a way to retrieve the short-form unit symbol that is synonym with the quantity's current unit?
import pint
ureg = pint.UnitRegistry()
Q_ = ureg.Quantity
def unit_symbol(q: pint.Quantity) -> str:
# Intended to return "m", not "meter"
# "kg" not "kilogram"
# etc.
# ???
return q.units # returns long-form unit, "meter", "kilogram" etc. :-(
q = Q_(42, ureg.m)
print(unit_symbol(q)) # "meter"... whereas I would like "m"
The above obviously fails to achieve this; it returns the long-form unit.
You can use '~' as a spec for the unit formatting:
q = Q_(42, "m") / Q_(1, "second")
print(format(q, '~')) # 42.0 m / s
print(format(q.u, '~')) # m / s
This feature is apparently undocumented, but can be inferred from the source code for Unit.__format__ (search for "~" on that page to quickly navigate to the relevant piece of code).
I found UnitRegistry.get_symbol(),
ureg.get_symbol(str(q.units)) # "m"
but it seems a bit clunky: converting unit to string, then parsing that string again...
Also this fails for composite units e.g.
q = Q_(42, "m") / Q_(1, "second")
ureg.get_symbol(str(q.units))
# UndefinedUnitError: 'meter / second' is not defined in the unit registry
Use ureg.default_format = '~' if you want the short notation by default. These are also valid options for short units: ~L (LaTeX), ~H (HTML) and ~P (Pretty print).
Suppose I have an object Trace, say trace, already and I want to have an amplitude data at the time 30 sec.
I think I can do it like below, assuming begin time is 0 for simplicity,
delta = trace.stats.delta
count = int(30 / delta)
target_value = trace.data[count]
Do they prepare a good way to do it?
something like....
trace.foo(time=30)
Right now we do not have such a convenience method, but I agree that it would be a good addition. You could make a feature request in our GitHub issue tracker: https://github.com/obspy/obspy/issues/new
In the meantime, you could make use of the sample times convenience functions mixed with some numpy to achieve what you want..
If you're looking for times relative to start of trace:
from obspy import read
tr = read()[0]
my_time = 4 # 4 seconds after start of trace
times = tr.times()
index = times.searchsorted(my_time)
print(times[index])
print(tr.data[index])
4.0
204.817965896
If you're looking for absolute times:
from obspy import read, UTCDateTime
tr = read()[0]
my_time = UTCDateTime("2009-08-24T00:20:12.0Z") # 4 seconds after start of trace
times = tr.times('utcdatetime')
index = times.searchsorted(my_time)
print(times[index])
print(tr.data[index])
2009-08-24T00:20:12.000000Z
156.68994731
If you're not matching the exact time of one sample, you will have to manually interpolate.
Hope this gets you started.
The objective is to handle cell densities expressed as "1000/mm^3", i.e. thousands per cubic millimeter.
Currently I do this to handle "1/mm^3":
import quantities as pq
d1 = pq.Quantity(500000, "1/mm**3")
which gives:
array(500000) * 1/mm**3
But what I really need to do is to accept the values with units of "1000/mm^3". This should also be the form in which values are printed. When I try something like:
d1 = pq.Quantity(5, 1000/pq.mm**3)
I get the following error:
ValueError: units must be a scalar Quantity with unit magnitude, got 1000.0 1/mm**3
And if I try:
a = pq.Quantity(500, "1000/mm**3")
The output is:
array(500) * 1/mm**3
i.e. The 1000 just gets ignored.
Any idea how can I fix this? Any workaround?
(The requirement arises from the standard practice followed in the domain.)
One possible solution I have found is to create new units such as this:
k_per_mm3 = pq.UnitQuantity('1000/mm3', 1e3/pq.mm**3, symbol='1000/mm3')
d1 = pq.Quantity(500, k_per_mm3)
Then on printing 'd1', I get:
array(500) * 1000/mm3
which is what I required.
Is this the only way to do this? Or can the same be achieved with existing units (which is preferable)?
I'm looking to determine the alt/az of (un-famous) stars at given RA/Dec at specific times from Mauna Kea. I'm trying to compute these parameters using pyephem, but the resulting alt/az don't agree with other sources. Here's the calculation for HAT-P-32 from Keck:
import ephem
telescope = ephem.Observer()
telescope.lat = '19.8210'
telescope.long = '-155.4683'
telescope.elevation = 4154
telescope.date = '2013/1/18 10:04:14'
star = ephem.FixedBody()
star._ra = ephem.degrees('02:04:10.278')
star._dec = ephem.degrees('+46:41:16.21')
star.compute(telescope)
print star.alt, star.az
which returns -28:43:54.0 73:22:55.3, though according to Stellarium, the proper alt/az should be: 62:26:03 349:15:13. What am I doing wrong?
EDIT: Corrected latitude and longitude, which were formerly reversed.
First, you've got long and latitude backwards; second, you need to provide the strings in hexadecimal form; and third, you need to provide the RA as hours, not degrees:
import ephem
telescope = ephem.Observer()
# Reversed longitude and latitude for Mauna Kea
telescope.lat = '19:49:28' # from Wikipedia
telescope.long = '-155:28:24'
telescope.elevation = 4154.
telescope.date = '2013/1/18 00:04:14'
star = ephem.FixedBody()
star._ra = ephem.hours('02:04:10.278') # in hours for RA
star._dec = ephem.degrees('+46:41:16.21')
star.compute(telescope)
This way, you get:
>>> print star.alt, star.az
29:11:57.2 46:43:19.6
PyEphem always uses UTC for time, so that programs operate the same and give the same output wherever they are run. You simply need to convert the date you are using to UTC, instead of using your local time zone, and the results agree fairly closely with Stellarium; use:
telescope.date = '2013/1/18 05:04:14'
The result is this alt/az:
62:27:19.0 349:26:19.4
To know where the small remaining difference comes from, I would have to look into how the two programs handle each step of their computation; but does this get you close enough?