Getting started

This “Getting started” tutorial is a brief introduction to Pymicra. This is in no way supposed to be a complete representation of everything that can be done with Pymicra.

In this tutorial we use some example data and refer to some example python scripts that can be downloaded here. These data and scripts are from a measurement campaign in a very small island (about 20 meters across) in a large artificial lake. At the time of these measurements the island was almost completely immersed into about 5 cm of water. Please feel free to explore both the example data and the example programs, as well as modify the programs for your own learning process!

Notation

Pymicra uses a specific notation to name each one of its columns. This notation is extremely important, because it is by these labels that Pymicra knows which variable is in each column. You can check the default notation with

In [1]: %%capture
   ...: import pymicra as pm
   ...: print(pm.notation)
   ...: 

The output is too long to be reproduced here, but on the left you’ll see the full name of the variables (which corresponds to a notation namespace/attribute) and on the right you’ll see the default notation for that variable.

We recommend to use the default notation for the sake of simplicity, however, you can change Pymicra’s notation at any time by altering the attributes of pm.notation. For example, by default the notation for the mean is '%s_mean', and every variable follows this base notation:

In [2]: pm.notation.mean

NameErrorTraceback (most recent call last)
<ipython-input-2-f467b081da4a> in <module>()
----> 1 pm.notation.mean

NameError: name 'pm' is not defined

In [3]: pm.notation.mean_u

NameErrorTraceback (most recent call last)
<ipython-input-3-26c02fa0b0aa> in <module>()
----> 1 pm.notation.mean_u

NameError: name 'pm' is not defined

In [4]: pm.notation.mean_h2o_mass_concentration

NameErrorTraceback (most recent call last)
<ipython-input-4-0bc2297ca326> in <module>()
----> 1 pm.notation.mean_h2o_mass_concentration

NameError: name 'pm' is not defined

To change this, you have to change the mean notation and then re-build the whole notation with the build method:

In [5]: pm.notation.mean = 'm_%s'

NameErrorTraceback (most recent call last)
<ipython-input-5-3b9a5520351c> in <module>()
----> 1 pm.notation.mean = 'm_%s'

NameError: name 'pm' is not defined

In [6]: pm.notation.build()

NameErrorTraceback (most recent call last)
<ipython-input-6-64160d8c1e70> in <module>()
----> 1 pm.notation.build()

NameError: name 'pm' is not defined

In [7]: pm.notation.mean_u

NameErrorTraceback (most recent call last)
<ipython-input-7-26c02fa0b0aa> in <module>()
----> 1 pm.notation.mean_u

NameError: name 'pm' is not defined

In [8]: pm.notation.mean_h2o_mass_concentration

NameErrorTraceback (most recent call last)
<ipython-input-8-0bc2297ca326> in <module>()
----> 1 pm.notation.mean_h2o_mass_concentration

NameError: name 'pm' is not defined

In [9]: pm.notation.h2o='v'

NameErrorTraceback (most recent call last)
<ipython-input-9-6c16ab414fc4> in <module>()
----> 1 pm.notation.h2o='v'

NameError: name 'pm' is not defined

In [10]: pm.notation.build()

NameErrorTraceback (most recent call last)
<ipython-input-10-64160d8c1e70> in <module>()
----> 1 pm.notation.build()

NameError: name 'pm' is not defined

In [11]: pm.notation.mean_h2o_mass_concentration

NameErrorTraceback (most recent call last)
<ipython-input-11-0bc2297ca326> in <module>()
----> 1 pm.notation.mean_h2o_mass_concentration

NameError: name 'pm' is not defined

If you just want to change the notation of one variable, but not the full notation, just don’t re-build. For example:

In [12]: pm.notation.mean_co2_mass_concentration = 'c_m'

NameErrorTraceback (most recent call last)
<ipython-input-12-c7dd423c4bb8> in <module>()
----> 1 pm.notation.mean_co2_mass_concentration = 'c_m'

NameError: name 'pm' is not defined

In [13]: pm.notation.mean_co2_mass_concentration

NameErrorTraceback (most recent call last)
<ipython-input-13-619ef1eb3e68> in <module>()
----> 1 pm.notation.mean_co2_mass_concentration

NameError: name 'pm' is not defined

In [14]: pm.notation.mean_h2o_mass_concentration

NameErrorTraceback (most recent call last)
<ipython-input-14-0bc2297ca326> in <module>()
----> 1 pm.notation.mean_h2o_mass_concentration

NameError: name 'pm' is not defined

It is important to note that this changes the notation used throughout every Pymicra function. If, however, you want to use a different notation in a specific part of the program (in one specific function for example) you can create a Notation object and pass it to the function, such as

In [15]: mynotation = pm.Notation()

In [16]: mynotation.co2='c'

In [17]: mynotation.build()

In [18]: fluxes = pm.eddyCovariance(data, units, notation=mynotation) # For example

In the example above the default Pymicra notation is left untouched, and a separate notation is defined which is then used in a Pymicra function separately.

Creating file configurations file

The easiest way to read data files is using a fileConfig object. This object holds the configuration of the data files so you can just call this object when reading these files. To make it easier, Pymicra prefers to read this configurations from a file. That way you can write the configurations for some data files once, store it into a configuration file and then use it from then on every time you want to read those data files. That is what Pymicra calls a “file configuration file”, or “config file” for short. From that file, Pymicra can create a pymicra.fileConfig object. Consider, for example, the config file below

description='datalogger configuration file for a lake. Located at examples/lake.config'

variables={
0:'%Y-%m-%d',
1:'%H:%M:%S.%f',
2:'u',
3:'v',
4:'w',
5:'theta_v',
6:'mrho_h2o',
7:'mrho_co2',
8:'p',
9:'theta'}

units={
'u':'m/s',
'v':'m/s',
'w':'m/s',
'theta_v':'celsius',
'mrho_co2':'mmol/m**3',
'mrho_h2o':'mmol/m**3',
'p':'kPa',
'theta':'celsius'
}

columns_separator=','
frequency=20
header_lines=None

filename_format='%Y%m%d-%H%M.csv'
date_cols = [0, 1]

First of all, note that the .config file is written in Python syntax, so it has to be able to actually be run on python. This has to be true for all .config files.

Furthermore, the extension of the file does not matter. We adopt the .config extension for clarity, but it could be anything else.

The previous config file describes the data files in the directory ../examples/ex_data/. Here’s an example of one such file for comparison:

2013-11-08,10:00:00.000000,2.375,-5.206,-0.103,27.06,1238.0,14.675,99.19,30.43,-0.303,-0.274,-0.269,-0.261
2013-11-08,10:00:00.050000,2.4930000000000003,-5.098,-0.018000000000000002,27.12,1196.0,14.409,99.199,30.43,-0.308,-0.275,-0.271,-0.263
2013-11-08,10:00:00.100000,2.263,-5.114,0.014,27.11,1220.0,14.636,99.102,30.43,-0.306,-0.277,-0.273,-0.263
2013-11-08,10:00:00.150000,2.21,-5.235,-0.012,27.11,1238.0,14.688,99.154,30.43,-0.308,-0.277,-0.273,-0.264
2013-11-08,10:00:00.200000,2.158,-5.174,-0.112,27.12,1174.0,14.476,99.154,30.44,-0.31,-0.277,-0.273,-0.264
2013-11-08,10:00:00.250000,2.334,-5.279,-0.092,27.1,1195.0,14.671,99.154,30.43,-0.308,-0.278,-0.273,-0.265
2013-11-08,10:00:00.300000,2.396,-5.2970000000000015,0.005,27.15,1198.0,14.669,99.154,30.43,-0.309,-0.279,-0.272,-0.264
2013-11-08,10:00:00.350000,2.494,-5.246,0.039,27.13,1197.0,14.722,99.154,30.44,-0.311,-0.279,-0.273,-0.264
2013-11-08,10:00:00.400000,2.263,-5.317,-0.079,27.12,1202.0,14.709,99.154,30.43,-0.311,-0.279,-0.275,-0.265
2013-11-08,10:00:00.450000,2.135,-5.176,-0.036000000000000004,27.08,1202.0,14.731,99.154,30.44,-0.314,-0.279,-0.275,-0.267

Note that not all columns of this file are described. Columns that are not described are also read but are discarded by default. You can change that using only_named_columns=False in the timeSeries function.

We obtain the config object with

In [19]: fconfig = pm.fileConfig('../examples/lake.config')

NameErrorTraceback (most recent call last)
<ipython-input-19-2002138cada7> in <module>()
----> 1 fconfig = pm.fileConfig('../examples/lake.config')

NameError: name 'pm' is not defined

In [20]: print(fconfig)

NameErrorTraceback (most recent call last)
<ipython-input-20-b8202bc48f0a> in <module>()
----> 1 print(fconfig)

NameError: name 'fconfig' is not defined

Each variable defined in this file works as a keyword, since it can also be input manually when calling pymicra.fileConfig(). Thus, for more information, you can also use help(pymicra.fileConfig). Now we explain the keywords one by one. In the next section we will explain how to use this object for reading a data file.

description

The description is optional. It’s a string that serves only to better identify the config file you’re dealing with. It might useful for storage purposes and useful when printing the config object.

variables

The most important keyword is variables. This is a python dictionary where each key is a column and its corresponding value is the variable in that column. Note that we are using here the default notation to indicate which variable is in which column. If a different notation is to be used here, then you will have to define a new notation in your program (refer back to Notation for that).

Note

From this point on, for simplicity, we will assume that the default notation is used.

It is imperative that the columns be named accordingly. For example, measuring H2O contents in mmol/m^3 is different from measuring it in g/m^3 or mg/g. The first is a molar density (moles per volume), the second is a mass density (mass per volume) and the third is a mass concentration (mass per mass). In the default notation these are indicated by the names 'mrho_h2o', 'rho_h2o' and 'conc_h2o', respectively, and Pymicra needs to know which one is which.

Columns that contain parts of the timestamp have to have their name matching Python’s date format string directive, which themselves are the 1989 version default C standard format dates, which is common in many platforms.

This is useful only in case you want to index your data by timestamp, which is a huge advantage in some cases (check out what Pandas can do with timestamp-indexed data) but Pymicra can also work well without this. If you don’t wish to work with timestamps and want to work only by line number in each file, you can ignore these columns and indicate that you don’t want to parse dates. In fact, parsing of dates makes Pymicra a lot slower. Reading a file parsing its dates is about 5.5 times slower than reading the same file without parsing any dates!

units

The units keyword is also very important. It tells Pymicra in which units each variable is being measured. Units are handled by Pint, so for more details on how to define the units please refer to their documentation. Suffices to say here that the format of the units are pretty intuitive. Some quick remarks are

  • prefer to define units unambiguously ('g/(m*(s**2))' is generally preferred to 'g/m/s**2', although both will work).
  • to define that a unit is dimensionless, '1' will not work. Define it as 'dimensionless' or 'g/g' and so on.
  • if one variable does not have a unit (such as a sensor flag), you don’t have to include that variable.
  • the keys of units should exactly match the values of variables.

columns_separator

The columns_separator keyword is what it sounds: what separates one column from the other. Generally it is one character, such as a comma. A special case happens is if the columns are separated by whitespaces of varying length, or tabs. In that case it should be "whitespace".

frequency

The frequency keyword is the frequency of the data collection in Hertz.

header_lines

The keyword header_lines tells us which of the first lines are part of the file header. If there is no header then is should be None. If there are header lines than it should be a list or int. For example, if the first two lines of the file are part of a header, it should be [0, 1]. If it were the 4 first lines, [0, 1, 2, 3] (range(4) would also be acceptable).

Header lines are not used by Pymicra and are therefore skipped.

filename_format

The filename_format keyword tells Pymicra how the data files are named.

date_cols

The date_cols keyword is optional. It is a list of integers that indicates which of the columns are a part of the timestamp. If it’s not provided, then Pymicra will assume that columns whose names have the character “%” in them are part of the date and will try to parse them. If the default notation is used, this should always be true.

Reading data

To read a data file or a list of data files we use the function timeSeries along with a config file. Let us use the config file defined in the previous subsection with one of the data file it describes:

In [21]: fname = '../examples/ex_data/20131108-1000.csv'

In [22]: fconfig = pm.fileConfig('../examples/lake.config')

NameErrorTraceback (most recent call last)
<ipython-input-22-2002138cada7> in <module>()
----> 1 fconfig = pm.fileConfig('../examples/lake.config')

NameError: name 'pm' is not defined

In [23]: data, units = pm.timeSeries(fname, fconfig, parse_dates=True)

NameErrorTraceback (most recent call last)
<ipython-input-23-93e03627fecc> in <module>()
----> 1 data, units = pm.timeSeries(fname, fconfig, parse_dates=True)

NameError: name 'pm' is not defined

In [24]: print(data)

NameErrorTraceback (most recent call last)
<ipython-input-24-dbd883db58b7> in <module>()
----> 1 print(data)

NameError: name 'data' is not defined

Note that data is a pandas.DataFrame object which contains the whole data available in the datafile with each column being a variable. Since we indicated that we wanted to parse the dates with the option parse_dates=True, each row has its respective timestamp. If, otherwise, we were to ignore the dates, the result would be a integer-indexed dataset:

In [25]: data2, units = pm.timeSeries(fname, fconfig, parse_dates=False)

NameErrorTraceback (most recent call last)
<ipython-input-25-4c621b7fee67> in <module>()
----> 1 data2, units = pm.timeSeries(fname, fconfig, parse_dates=False)

NameError: name 'pm' is not defined

In [26]: print(data2)

NameErrorTraceback (most recent call last)
<ipython-input-26-2f17186c4c10> in <module>()
----> 1 print(data2)

NameError: name 'data2' is not defined

And, as mentioned, the latter way is a lot faster:

In [27]: %timeit pm.timeSeries(fname, fconfig, parse_dates=False)
   ....: %timeit pm.timeSeries(fname, fconfig, parse_dates=True)
   ....: 

NameErrorTraceback (most recent call last)
<ipython-input-27-bc0c5d8a249c> in <module>()
----> 1 get_ipython().magic(u'timeit pm.timeSeries(fname, fconfig, parse_dates=False)')

/home/docs/checkouts/readthedocs.org/user_builds/pymicra/envs/v0.3.0/local/lib/python2.7/site-packages/IPython/core/interactiveshell.pyc in magic(self, arg_s)
   2158         magic_name, _, magic_arg_s = arg_s.partition(' ')
   2159         magic_name = magic_name.lstrip(prefilter.ESC_MAGIC)
-> 2160         return self.run_line_magic(magic_name, magic_arg_s)
   2161 
   2162     #-------------------------------------------------------------------------

/home/docs/checkouts/readthedocs.org/user_builds/pymicra/envs/v0.3.0/local/lib/python2.7/site-packages/IPython/core/interactiveshell.pyc in run_line_magic(self, magic_name, line)
   2079                 kwargs['local_ns'] = sys._getframe(stack_depth).f_locals
   2080             with self.builtin_trap:
-> 2081                 result = fn(*args,**kwargs)
   2082             return result
   2083 

<decorator-gen-59> in timeit(self, line, cell)

/home/docs/checkouts/readthedocs.org/user_builds/pymicra/envs/v0.3.0/local/lib/python2.7/site-packages/IPython/core/magic.pyc in <lambda>(f, *a, **k)
    186     # but it's overkill for just that one bit of state.
    187     def magic_deco(arg):
--> 188         call = lambda f, *a, **k: f(*a, **k)
    189 
    190         if callable(arg):

/home/docs/checkouts/readthedocs.org/user_builds/pymicra/envs/v0.3.0/local/lib/python2.7/site-packages/IPython/core/magics/execution.pyc in timeit(self, line, cell)
   1055             number = 1
   1056             for _ in range(1, 10):
-> 1057                 time_number = timer.timeit(number)
   1058                 worst_tuning = max(worst_tuning, time_number / number)
   1059                 if time_number >= 0.2:

/home/docs/checkouts/readthedocs.org/user_builds/pymicra/envs/v0.3.0/local/lib/python2.7/site-packages/IPython/core/magics/execution.pyc in timeit(self, number)
    137         gc.disable()
    138         try:
--> 139             timing = self.inner(it, self.timer)
    140         finally:
    141             if gcold:

<magic-timeit> in inner(_it, _timer)

NameError: global name 'pm' is not defined

NameErrorTraceback (most recent call last)
<ipython-input-28-a473648643e0> in <module>()
----> 1 get_ipython().magic(u'timeit pm.timeSeries(fname, fconfig, parse_dates=True)')

/home/docs/checkouts/readthedocs.org/user_builds/pymicra/envs/v0.3.0/local/lib/python2.7/site-packages/IPython/core/interactiveshell.pyc in magic(self, arg_s)
   2158         magic_name, _, magic_arg_s = arg_s.partition(' ')
   2159         magic_name = magic_name.lstrip(prefilter.ESC_MAGIC)
-> 2160         return self.run_line_magic(magic_name, magic_arg_s)
   2161 
   2162     #-------------------------------------------------------------------------

/home/docs/checkouts/readthedocs.org/user_builds/pymicra/envs/v0.3.0/local/lib/python2.7/site-packages/IPython/core/interactiveshell.pyc in run_line_magic(self, magic_name, line)
   2079                 kwargs['local_ns'] = sys._getframe(stack_depth).f_locals
   2080             with self.builtin_trap:
-> 2081                 result = fn(*args,**kwargs)
   2082             return result
   2083 

<decorator-gen-59> in timeit(self, line, cell)

/home/docs/checkouts/readthedocs.org/user_builds/pymicra/envs/v0.3.0/local/lib/python2.7/site-packages/IPython/core/magic.pyc in <lambda>(f, *a, **k)
    186     # but it's overkill for just that one bit of state.
    187     def magic_deco(arg):
--> 188         call = lambda f, *a, **k: f(*a, **k)
    189 
    190         if callable(arg):

/home/docs/checkouts/readthedocs.org/user_builds/pymicra/envs/v0.3.0/local/lib/python2.7/site-packages/IPython/core/magics/execution.pyc in timeit(self, line, cell)
   1055             number = 1
   1056             for _ in range(1, 10):
-> 1057                 time_number = timer.timeit(number)
   1058                 worst_tuning = max(worst_tuning, time_number / number)
   1059                 if time_number >= 0.2:

/home/docs/checkouts/readthedocs.org/user_builds/pymicra/envs/v0.3.0/local/lib/python2.7/site-packages/IPython/core/magics/execution.pyc in timeit(self, number)
    137         gc.disable()
    138         try:
--> 139             timing = self.inner(it, self.timer)
    140         finally:
    141             if gcold:

<magic-timeit> in inner(_it, _timer)

NameError: global name 'pm' is not defined

Viewing and manipulating data

To view and manipulate data, mostly you have to follow Pandas’s DataFrame rules. For that we suggest that the user visit a Pandas tutorial. However, I’ll explain some main ideas here for the sake of completeness and introduce some few ideas specific for Pymicra that don’t exist for general Pandas DataFrames.

Printing and plotting

First, for viewing raw data on screen there’s printing. Slicing and indexing are supported by Pandas, but without support for units:

In [29]: print(data['theta_v'])

NameErrorTraceback (most recent call last)
<ipython-input-29-e94992a74455> in <module>()
----> 1 print(data['theta_v'])

NameError: name 'data' is not defined

In [30]: print(data[['u', 'v', 'w']])

NameErrorTraceback (most recent call last)
<ipython-input-30-1c17a554e0d5> in <module>()
----> 1 print(data[['u', 'v', 'w']])

NameError: name 'data' is not defined

In [31]: print(data['20131108 10:15:00.000':'20131108 10:17:00.000'])

NameErrorTraceback (most recent call last)
<ipython-input-31-b15ec8bdb617> in <module>()
----> 1 print(data['20131108 10:15:00.000':'20131108 10:17:00.000'])

NameError: name 'data' is not defined

Note that Pandas “guesses” if the argument you pass ('theta_v' or '2013-11-08 10:15:00' etc.) is a column indexer or a row indexer. To use these unambiguously, use the .loc method as

In [32]: print(data.loc['2013-11-08 10:15:00':'2013-11-08 10:17:00', ['u','v','w']])

NameErrorTraceback (most recent call last)
<ipython-input-32-e4fccd621429> in <module>()
----> 1 print(data.loc['2013-11-08 10:15:00':'2013-11-08 10:17:00', ['u','v','w']])

NameError: name 'data' is not defined

This method is actually preferred and you can find more information on this topic here.

To view these data with units, you can use the .with_units() method. The previous output would look like this using units:

In [33]: print(data.with_units(units)['theta_v'])

NameErrorTraceback (most recent call last)
<ipython-input-33-f205c87ca716> in <module>()
----> 1 print(data.with_units(units)['theta_v'])

NameError: name 'data' is not defined

In [34]: print(data.with_units(units)[['u', 'v', 'w']])

NameErrorTraceback (most recent call last)
<ipython-input-34-194962d4a60c> in <module>()
----> 1 print(data.with_units(units)[['u', 'v', 'w']])

NameError: name 'data' is not defined

In [35]: print(data.with_units(units)['2013-11-08 10:15:00'])

NameErrorTraceback (most recent call last)
<ipython-input-35-2ce7f3367470> in <module>()
----> 1 print(data.with_units(units)['2013-11-08 10:15:00'])

NameError: name 'data' is not defined

Warning

Note that, although this method returns a Pandas DataFrame, it is not meant for calculations. Currently the DataFrame it returns is meant for visualization purposes only!

We can also plot the data on screen so we can view it interactively. This can be done directly from the DataFrame with

In [36]: from matplotlib import pyplot as plt

In [37]: data[['u', 'v', 'w']].plot()

NameErrorTraceback (most recent call last)
<ipython-input-37-112e9fb6c73e> in <module>()
----> 1 data[['u', 'v', 'w']].plot()

NameError: name 'data' is not defined

In [38]: plt.show()
_images/uvw_plot_basics.png

Using the plt.show() command, the plot above would plot interactively. If we had used plt.savefig('figure.png') instead, it would have saved the figure as png. For more on plotting, you can checkout Pandas’s visualization guide and find out ways to make this plot look nicer, how to render it with LaTeX and some more tricks.

Pymicra also has an .xplot method, which brings a little more options to Pandas’s .plot() method.

Todo

give xplot examples

Converting units

You can manually convert between units using the contents from Manipulating and the Pint package. But Pymicra has a very useful method to do this called .convert_cols (more exist, but let’s focus on this one).

Let’s, for example, convert some units:

In [39]: conversions = {'p':'pascal', 'mrho_h2o':'mole/m^3', 'theta_v':'kelvin'}

In [40]: print(data.convert_cols(conversions, units, inplace_units=False))

NameErrorTraceback (most recent call last)
<ipython-input-40-06dba93f4e84> in <module>()
----> 1 print(data.convert_cols(conversions, units, inplace_units=False))

NameError: name 'data' is not defined

Note that the units dictionary is updated automatically if the inplace_units keyword is true. The default is false for safety reasons, but passing this keyword as true is much simpler and compact:

In [41]: conversions = {'theta':'kelvin', 'theta_v':'kelvin'}

In [42]: data = data.convert_cols(conversions, units, inplace_units=True)

NameErrorTraceback (most recent call last)
<ipython-input-42-223bb8a15e35> in <module>()
----> 1 data = data.convert_cols(conversions, units, inplace_units=True)

NameError: name 'data' is not defined

In [43]: print(data.with_units(units))

NameErrorTraceback (most recent call last)
<ipython-input-43-8a66647eea32> in <module>()
----> 1 print(data.with_units(units))

NameError: name 'data' is not defined

Manipulating

Manipulating data is pretty intuitive with Pandas. For example

In [44]: data['rho_air'] = data['p']/(287.058*data['theta_v'])

NameErrorTraceback (most recent call last)
<ipython-input-44-2190c3db6903> in <module>()
----> 1 data['rho_air'] = data['p']/(287.058*data['theta_v'])

NameError: name 'data' is not defined

In [45]: print(data['rho_air'])

NameErrorTraceback (most recent call last)
<ipython-input-45-efd54506c8a9> in <module>()
----> 1 print(data['rho_air'])

NameError: name 'data' is not defined

If, however, you’re not familiar with Pandas and prefer to just stick with what you know, you can get Numpy arrays from columns using the .values attribute:

In [46]: P = data['p'].values

NameErrorTraceback (most recent call last)
<ipython-input-46-7834b8aa0781> in <module>()
----> 1 P = data['p'].values

NameError: name 'data' is not defined

In [47]: Tv = data['theta_v'].values

NameErrorTraceback (most recent call last)
<ipython-input-47-25320c940518> in <module>()
----> 1 Tv = data['theta_v'].values

NameError: name 'data' is not defined

In [48]: print(type(Tv))

NameErrorTraceback (most recent call last)
<ipython-input-48-d89e463e125b> in <module>()
----> 1 print(type(Tv))

NameError: name 'Tv' is not defined

In [49]: rho_air = P/(287.058*Tv)

NameErrorTraceback (most recent call last)
<ipython-input-49-818b77efd261> in <module>()
----> 1 rho_air = P/(287.058*Tv)

NameError: name 'P' is not defined

In [50]: print(rho_air)

NameErrorTraceback (most recent call last)
<ipython-input-50-c111f225b951> in <module>()
----> 1 print(rho_air)

NameError: name 'rho_air' is not defined

In [51]: print(type(rho_air))

NameErrorTraceback (most recent call last)
<ipython-input-51-a6bdff633dbb> in <module>()
----> 1 print(type(rho_air))

NameError: name 'rho_air' is not defined

Doing that you can step out of Pandas and do your own calculations using your own Python or Numpy code. This is pretty advantageous if you have a lot of routines that are already written in your own way.