Functions

Assume we have some wind speed and direction observations we wish to incorporate into a weather model. The assimilation process requires wind observations to be supplied as the u and v Cartesian components of the wind vector. The data here are supplied as a list of tuples representing the time of the observation, the speed in meters per second, and the degree direction where the winds are coming from.

In [1]:
data = [('2016-12-28T01:22:00Z', 26.24, 290.0),
        ('2016-12-28T04:22:00Z', 16.46, 280.0),
        ('2016-12-28T05:02:00Z', 18.01, 290.0),
        ('2016-12-28T05:22:00Z', 16.46, 290.0),
        ('2016-12-28T06:02:00Z', 18.01, 290.0),
        ('2016-12-28T06:22:00Z', 16.98, 280.0),
        ('2016-12-28T07:02:00Z', 17.49, 280.0),
        ('2016-12-28T07:22:00Z', 17.49, 280.0),
        ('2016-12-28T08:02:00Z', 17.49, 280.0),
        ('2016-12-28T08:22:00Z', 13.38, 290.0),
        ('2016-12-28T08:41:00Z', 15.95, 290.0),
        ('2016-12-28T09:02:00Z', 14.92, 270.0),
        ('2016-12-28T09:22:00Z', 19.03, 280.0),
        ('2016-12-28T11:43:00Z', 16.98, 290.0)]

With some basic trigonometry and a for loop we learned about in the last section, we can convert the data into u and v components.

In [2]:
# Import the Python math module
import math

# Constant for converting degrees to radians
RPERD = math.pi/180

# The list that will hold our u and v data
uvdata = []

# Loop through observations pulling apart the time, speed and direction
# components of the observation.

for time, speed, direction in data:
    u = -speed * math.sin(direction * RPERD)
    v = -speed * math.cos(direction * RPERD)
    uvdata.append((time, u, v))

uvdata
Out[2]:
[('2016-12-28T01:22:00Z', 24.657534369422233, -8.974608560865555),
 ('2016-12-28T04:22:00Z', 16.209935614580946, -2.8582490043976674),
 ('2016-12-28T05:02:00Z', 16.92386410035421, -6.1597827812953),
 ('2016-12-28T05:22:00Z', 15.467340538136051, -5.6296515591405125),
 ('2016-12-28T06:02:00Z', 16.92386410035421, -6.1597827812953),
 ('2016-12-28T06:22:00Z', 16.722035646147294, -2.948546056784471),
 ('2016-12-28T07:02:00Z', 17.22428760018352, -3.037106627394605),
 ('2016-12-28T07:22:00Z', 17.22428760018352, -3.037106627394605),
 ('2016-12-28T08:02:00Z', 17.22428760018352, -3.037106627394605),
 ('2016-12-28T08:22:00Z', 12.573087266115454, -4.576229517697452),
 ('2016-12-28T08:41:00Z', 14.988097301535237, -5.455221286044421),
 ('2016-12-28T09:02:00Z', 14.92, 2.7407595364917763e-15),
 ('2016-12-28T09:22:00Z', 18.74089153982232, -3.3045248210016775),
 ('2016-12-28T11:43:00Z', 15.955980700944723, -5.80750203366986)]

While this code works fine it can be refactored, a software engineering term to describe improving the design of existing code. In particular, we can pull the section that calculates the u and v vectors into its own reusable unit of code known as a function. Functions in Python are defined with the def keyword as illustrated with this pseudocode:

def function_name( function_parameters ):
   "function documentation string"
   function statements usually involving the function parameters
   return some value or expression based on the function statements

Armed with this knowledge, we can write a function called uandv to calculate the u and v components from wind speed and direction parameters:

In [3]:
def uandv(speed, direction):
    "calculate u and v components from speed and direction"
    u = -speed * math.sin(direction * RPERD)
    v = -speed * math.cos(direction * RPERD)
    # return the u and v tuple
    return u, v

The u and v vectors are returned as tuples. With this function in place, we can jump out of the for loop shown earlier, shift the flow of control to the uandv function, calculate the u and v wind components, and return control to where the function was initially invoked. We can now write the same for loop again, but this time with conciseness and clarity.

In [4]:
uvdata = []

for time, speed, direction in data:
    u, v = uandv(speed, direction)
    uvdata.append((time, u, v))
uvdata
Out[4]:
[('2016-12-28T01:22:00Z', 24.657534369422233, -8.974608560865555),
 ('2016-12-28T04:22:00Z', 16.209935614580946, -2.8582490043976674),
 ('2016-12-28T05:02:00Z', 16.92386410035421, -6.1597827812953),
 ('2016-12-28T05:22:00Z', 15.467340538136051, -5.6296515591405125),
 ('2016-12-28T06:02:00Z', 16.92386410035421, -6.1597827812953),
 ('2016-12-28T06:22:00Z', 16.722035646147294, -2.948546056784471),
 ('2016-12-28T07:02:00Z', 17.22428760018352, -3.037106627394605),
 ('2016-12-28T07:22:00Z', 17.22428760018352, -3.037106627394605),
 ('2016-12-28T08:02:00Z', 17.22428760018352, -3.037106627394605),
 ('2016-12-28T08:22:00Z', 12.573087266115454, -4.576229517697452),
 ('2016-12-28T08:41:00Z', 14.988097301535237, -5.455221286044421),
 ('2016-12-28T09:02:00Z', 14.92, 2.7407595364917763e-15),
 ('2016-12-28T09:22:00Z', 18.74089153982232, -3.3045248210016775),
 ('2016-12-28T11:43:00Z', 15.955980700944723, -5.80750203366986)]

Moreover, we can reuse our uandv function whenever we need to calculate u and v components from speed and direction.

Python, a Functional Language

A chief attribute and advantage of Python is that it is a functional language. In functional languages, functions are treated as "first class citizens". Functions can be passed as arguments or they can be defined on-the-fly as with lambda expressions. A Pythonista would have a feeling of disquietude if we stopped at the code above. Indeed, we can further take advantage of the functional programming paradigm by employing list comprehension. A more Pythonic way of achieving the same result using list comprehension is done in this manner:

In [5]:
[(time,) + uandv(speed, direction) for time, speed, direction in data]
Out[5]:
[('2016-12-28T01:22:00Z', 24.657534369422233, -8.974608560865555),
 ('2016-12-28T04:22:00Z', 16.209935614580946, -2.8582490043976674),
 ('2016-12-28T05:02:00Z', 16.92386410035421, -6.1597827812953),
 ('2016-12-28T05:22:00Z', 15.467340538136051, -5.6296515591405125),
 ('2016-12-28T06:02:00Z', 16.92386410035421, -6.1597827812953),
 ('2016-12-28T06:22:00Z', 16.722035646147294, -2.948546056784471),
 ('2016-12-28T07:02:00Z', 17.22428760018352, -3.037106627394605),
 ('2016-12-28T07:22:00Z', 17.22428760018352, -3.037106627394605),
 ('2016-12-28T08:02:00Z', 17.22428760018352, -3.037106627394605),
 ('2016-12-28T08:22:00Z', 12.573087266115454, -4.576229517697452),
 ('2016-12-28T08:41:00Z', 14.988097301535237, -5.455221286044421),
 ('2016-12-28T09:02:00Z', 14.92, 2.7407595364917763e-15),
 ('2016-12-28T09:22:00Z', 18.74089153982232, -3.3045248210016775),
 ('2016-12-28T11:43:00Z', 15.955980700944723, -5.80750203366986)]

List comprehensions are enclosed by square brackets starting by a statement and followed with a for loop. Once again, here we are pulling apart the individual tuple in place into the time, speed, and direction components. This syntax allows us to easily pass the necessary arguments to the uandv function. Also, to stay consistent with the previous results, we are prepending the time to the u, v tuple with the + operator. In short, we can do our conversion in one clear, concise line of code. Note, we did not need an intermediate uvdata variable. This style of programming is the essence of functional programming where we eschew "mutable" variables such as uvdata. These "side-effects" can be difficult and error prone to reason about in more complex programs. Functional programming tends to be a natural fit for those with a scientific and mathematical background.