<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 X LINES OF PYTHON
Physical units with pint
This notebook goes with a blog post on the same subject.
Have you ever wished you could carry units around with your quantities &mdash; and have the computer figure out the best units and multipliers to use?
pint is a nince, compact library for doing just this, handling all your dimensional analysis needs. It can also detect units from strings. We can define our own units, it knows about multipliers (kilo, mega, etc), and it even works with numpy and pandas.
Install pint with pip or conda, e.g.
pip install pint
NB If you are running this on Google Colaboratory, you must uncomment these lines (delete the initial #) and run this first
Step1:  To use it in its typical mode, we import the library then instantiate a UnitRegistry object. The registry contains lots of physical units.
Step2:  Attaching and printing units
Step3:  In a Jupyter Notebook you see a 'pretty' version of the quantity. In the interpreter, you'll see something slightly different (the so-called repr of the class)
Step4:  You can also use the following abbreviations for magnitude and units
Step5:  But pint extends the string formatting options to include special options for Quantity objects. The most useful option is P for 'pretty', but there's also L for $\LaTeX$ and H for HTML. Adding a ~ (tilde) before the option tells pint to use unit abbreviations instead of the full names
Step6:  Doing maths
If we multiply by a scalar, pint produces the result you'd expect
Step7:  Note that you must use units when you need them
Step8:  Let's try defining an area of $60\ \mathrm{km}^2$, then multiplying it by our thickness. To make it more like a hydrocarbon volume, I'll also multiply by net
Step9:  We can convert to something more compact
Step10:  Or be completely explicit about the units and multipliers we want
Step11:  The to_compact() method can also take units, if you want to be more explicit; it applies multipliers automatically
Step12:  Oil barrels are already defined (careful, they are abbreviated as oil_bbl not bbl &mdash; that's a 31.5 gallon barrel, about the same as a beer barrel).
Step13:  If we use string formatting (see above), we can get pretty specific
Step14:  Defining new units
pint defines hundreads of units (here's the list), and it knows about tonnes of oil equivalent... but it doesn't know about barrels of oil equivalent (for more on conversion to BOE). So let's define a custom unit, using the USGS's conversion factor
Step15:  Let's suspend reality for a moment and imagine we now want to compute our gross rock volume in BOEs...
Step16:  Getting units from strings
pint can also parse strings and attempt to convert them to Quantity instances
Step17:  This looks useful! Let's try something less nicely formatted.
Step18:  You can also use the Quantity constructor, like this
Step19:  pint with numpy
pint works fine with NumPy arrays
Step20:  For some reason, this sometimes doesn't render properly. But we can always do this
Step21:  As expected, the magnitude of this quantity is just a NumPy array
Step22:  pint with pandas
Note that this functionality is fairly new and is still settling down. YMMV.
To use pint (version 0.9 and later) with pandas (version 0.24.2 works; 0.25.0 does not work at the time of writing), we must first install pintpandas, which must be done from source; get the code from GitHub. Here's how I do it
Step23:  To use this integration, we pass special pint data types to the pd.Series() object
Step24:  We can't convert the units of a whole Series but we can do one
Step25:  So to convert a whole series, we can use Series.apply()
Step27:  Bonus
<ASSISTANT_TASK:>
Python Code:
#!pip install pint
#!pip install git+https://github.com/hgrecco/pint-pandas#egg=Pint-Pandas-0.1.dev0
Explanation: X LINES OF PYTHON
Physical units with pint
This notebook goes with a blog post on the same subject.
Have you ever wished you could carry units around with your quantities &mdash; and have the computer figure out the best units and multipliers to use?
pint is a nince, compact library for doing just this, handling all your dimensional analysis needs. It can also detect units from strings. We can define our own units, it knows about multipliers (kilo, mega, etc), and it even works with numpy and pandas.
Install pint with pip or conda, e.g.
pip install pint
NB If you are running this on Google Colaboratory, you must uncomment these lines (delete the initial #) and run this first:
End of explanation
import pint
units = pint.UnitRegistry()
pint.__version__
Explanation: To use it in its typical mode, we import the library then instantiate a UnitRegistry object. The registry contains lots of physical units.
End of explanation
thickness = 68 * units.m
thickness
Explanation: Attaching and printing units
End of explanation
thickness.magnitude, thickness.units, thickness.dimensionality
Explanation: In a Jupyter Notebook you see a 'pretty' version of the quantity. In the interpreter, you'll see something slightly different (the so-called repr of the class):
&gt;&gt;&gt; thickness
&lt;Quantity(68, 'meter')&gt;
We can get at the magnitude, the units, and the dimensionality of this quantity:
End of explanation
f'{thickness**2}'
Explanation: You can also use the following abbreviations for magnitude and units:
thickness.m, thickness.u
For printing, we can use Python's string formatting:
End of explanation
print(f'{thickness**2:P}')
print(f'{thickness**2:~P}')
print(f'{thickness**2:~L}')
print(f'{thickness**2:~H}')
Explanation: But pint extends the string formatting options to include special options for Quantity objects. The most useful option is P for 'pretty', but there's also L for $\LaTeX$ and H for HTML. Adding a ~ (tilde) before the option tells pint to use unit abbreviations instead of the full names:
End of explanation
thickness * 2
Explanation: Doing maths
If we multiply by a scalar, pint produces the result you'd expect:
End of explanation
thickness + 10
# This is meant to produce an error...
Explanation: Note that you must use units when you need them:
End of explanation
area = 60 * units.km**2
n2g = 0.5 * units.dimensionless  # Optional dimensionless 'units'...
phi = 0.2                        # ... but you can just do this.
sat = 0.7  
volume = area * thickness * n2g * phi * sat
volume
Explanation: Let's try defining an area of $60\ \mathrm{km}^2$, then multiplying it by our thickness. To make it more like a hydrocarbon volume, I'll also multiply by net:gross n2g, porosity phi, and saturation sat, all of which are dimensionless:
End of explanation
volume.to_compact()
Explanation: We can convert to something more compact:
End of explanation
volume.to('m**3')  # Or use m^3
Explanation: Or be completely explicit about the units and multipliers we want:
End of explanation
volume.to_compact('L')
Explanation: The to_compact() method can also take units, if you want to be more explicit; it applies multipliers automatically:
End of explanation
volume.to_compact('oil_barrel')
Explanation: Oil barrels are already defined (careful, they are abbreviated as oil_bbl not bbl &mdash; that's a 31.5 gallon barrel, about the same as a beer barrel).
End of explanation
f"The volume is {volume.to_compact('oil_barrel'):~0.2fL}"
Explanation: If we use string formatting (see above), we can get pretty specific:
End of explanation
units.define('barrel_of_oil_equivalent = 6000 ft**3 = boe')
Explanation: Defining new units
pint defines hundreads of units (here's the list), and it knows about tonnes of oil equivalent... but it doesn't know about barrels of oil equivalent (for more on conversion to BOE). So let's define a custom unit, using the USGS's conversion factor:
End of explanation
volume.to('boe')
volume.to_compact('boe')
Explanation: Let's suspend reality for a moment and imagine we now want to compute our gross rock volume in BOEs...
End of explanation
units('2.34 km')
Explanation: Getting units from strings
pint can also parse strings and attempt to convert them to Quantity instances:
End of explanation
units('2.34*10^3 km')
units('-12,000.ft')
units('3.2 m')
Explanation: This looks useful! Let's try something less nicely formatted.
End of explanation
from uncertainties import ufloat
area = ufloat(64, 5) * units.km**2  # 64 +/- 5 km**2
(thickness * area).to('Goil_bbl')
Explanation: You can also use the Quantity constructor, like this:
&gt;&gt;&gt; qty = pint.Quantity
&gt;&gt;&gt; qty('2.34 km')
2.34 kilometer
But the UnitRegistry seems to do the same things and might be more convenient.
pint with uncertainties
Conveniently, pint works well with uncertainties. Maybe I'll do an X lines on that package in the future. Install it with conda or pip, e.g.
pip install uncertainties
End of explanation
import numpy as np
vp = np.array([2300, 2400, 2550, 3200]) * units.m/units.s
rho = np.array([2400, 2550, 2500, 2650]) * units.kg/units.m**3
z = vp * rho
z
Explanation: pint with numpy
pint works fine with NumPy arrays:
End of explanation
print(z)
Explanation: For some reason, this sometimes doesn't render properly. But we can always do this:
End of explanation
z.m
Explanation: As expected, the magnitude of this quantity is just a NumPy array:
End of explanation
pint._HAS_PINTPANDAS
Explanation: pint with pandas
Note that this functionality is fairly new and is still settling down. YMMV.
To use pint (version 0.9 and later) with pandas (version 0.24.2 works; 0.25.0 does not work at the time of writing), we must first install pintpandas, which must be done from source; get the code from GitHub. Here's how I do it:
cd pint-pandas
python setup.py sdist
pip install dist/Pint-Pandas-0.1.dev0.tar.gz
You could also do:
pip install git+https://github.com/hgrecco/pint-pandas#egg=Pint-Pandas-0.1.dev0
Once you have done that, the following should evaluate to True:
End of explanation
import pandas as pd
df = pd.DataFrame({
    "Vp": pd.Series(vp.m, dtype="pint[m/s]"),
    "Vs": pd.Series([1200, 1200, 1250, 1300], dtype="pint[m/s]"),
    "rho": pd.Series(rho.m, dtype="pint[kg/m**3]"),
})
df
import bruges as bg
df['E'] = bg.rockphysics.moduli.youngs(df.Vp, df.Vs, df.rho)
df.E
Explanation: To use this integration, we pass special pint data types to the pd.Series() object:
End of explanation
df.loc[0, 'E'].to('GPa')
Explanation: We can't convert the units of a whole Series but we can do one:
End of explanation
df.E.apply(lambda x: x.to('GPa'))
Explanation: So to convert a whole series, we can use Series.apply():
End of explanation
class UnitDataFrame(pd.DataFrame):
    def _repr_html_(self):
        New repr for Jupyter Notebook.
        html = super()._repr_html_()  # Get the old repr string.
        units = [''] + [f"{dtype.units:~H}" for dtype in self.dtypes]
        style = "text-align: right; color: gray;"
        new = f'<tr style="{style}"><th>' + "</th><th>".join(units) + "</th></tr></thead>"
        return html.replace('</thead>', new)
df = UnitDataFrame({
    "Vp": pd.Series(vp.m, dtype="pint[m/s]"),
    "Vs": pd.Series([1200, 1200, 1250, 1300], dtype="pint[m/s]"),
    "rho": pd.Series(rho.m, dtype="pint[kg/m**3]"),
})
df
Explanation: Bonus: dataframe display with units
We could subclass dataframes to tweak their _repr_html_() method, which would allow us to make units show up in the Notebook representation of the dataframe...
End of explanation
<END_TASK>