There are few very modules for tidal analysis and prediction in python. In fact I can come up with just one name: tappy (Tidal Analysis Program in PYthon). Tappy has a command line interface and a syntax that is specific to its file format. In addition to that tappy is not being developed anymore. Luckily Sam Cox started pytides, in his own words:
Pytides is small Python package for the analysis and prediction of tides. Pytides can be used to extrapolate the tidal behaviour at a given location from its previous behaviour. The method used is that of harmonic constituents, in particular as presented by P. Schureman in Special Publication 98. The fitting of amplitudes and phases is handled by Scipy's leastsq minimisation function. Pytides currently supports the constituents used by NOAA, with plans to add more constituent sets. It is therefore possible to use the amplitudes and phases published by NOAA directly, without the need to perform the analysis again (although there may be slight discrepancies for some constituents).
It is recommended that all interactions with pytides which require times to be specified are in the format of naive UTC datetime instances. In particular, note that pytides makes no adjustment for summertime or any other civil variations within timezones.
This post is a quick example on how to perform tidal analysis and how to generate a prediction using pytides. First let's load some saved data that we will use as a basis for the tidal analysis.
(The data was downloaded from here).
from pandas import read_csv
df = read_csv('data/CO-OPS__8516945__hr.csv', index_col=0, parse_dates=True)
water_level = df[' Water Level']['2014-06-01':]
ax = water_level.plot(figsize=(13, 3.5))
Now let's perform the tidal analysis:
from pytides.tide import Tide
demeaned = water_level.values - water_level.values.mean()
tide = Tide.decompose(demeaned, water_level.index.to_datetime())
We can use pandas to create a nice table with the analysis results:
import numpy as np
from pandas import DataFrame
constituent = [c.name for c in tide.model['constituent']]
df = DataFrame(tide.model, index=constituent).drop('constituent', axis=1)
df.sort('amplitude', ascending=False).head(10)
The tide
object has some handy methods for computing the Form Number and
classifying the tide for us.
print('Form number %s, the tide is %s.' %
(tide.form_number()[0], tide.classify()))
With just a few lines we can generate an one-week prediction.
from pandas import Series, read_csv, date_range
dates = date_range(start='2014-07-01', end='2014-07-08', freq='6T')
hours = np.cumsum(np.r_[0, [t.total_seconds() / 3600.0
for t in np.diff(dates.to_pydatetime())]])
times = Tide._times(dates[0], hours)
prediction = Series(tide.at(times) + water_level.values.mean(), index=dates)
ax = water_level.plot(figsize=(13, 3.5), label='Observed data')
ax = prediction.plot(ax=ax, color='red', label='Prediction')
leg = ax.legend(loc='best')
from IPython import display
from IPython.core.magic import register_cell_magic, Magics, magics_class, cell_magic
import jinja2
@magics_class
class JinjaMagics(Magics):
'''Magics class containing the jinja2 magic and state'''
def __init__(self, shell):
super(JinjaMagics, self).__init__(shell)
# create a jinja2 environment to use for rendering
# this can be modified for desired effects (ie: using different variable syntax)
self.env = jinja2.Environment(loader=jinja2.FileSystemLoader('.'))
# possible output types
self.display_functions = dict(html=display.HTML,
latex=display.Latex,
json=display.JSON,
pretty=display.Pretty,
display=display.display)
@cell_magic
def jinja(self, line, cell):
'''
jinja2 cell magic function. Contents of cell are rendered by jinja2, and
the line can be used to specify output type.
ie: "%%jinja html" will return the rendered cell wrapped in an HTML object.
'''
f = self.display_functions.get(line.lower().strip(), display.display)
tmp = self.env.from_string(cell)
rend = tmp.render(dict((k,v) for (k,v) in self.shell.user_ns.items()
if not k.startswith('_') and k not in self.shell.user_ns_hidden))
return f(rend)
ip = get_ipython()
ip.register_magics(JinjaMagics)
import calendar
from pytz import timezone
from datetime import datetime, timedelta
from jinja2 import Environment, DictLoader
# Prepare our variables for the template
location = "King's Point"
tzname = "US/Eastern"
tz = timezone(tzname)
utc = timezone('UTC')
datum = "MLLW"
units = "meters"
year = 2014
month = 7
rows = []
for day in range(1, calendar.monthrange(year, month)[1] + 1):
start = tz.localize(datetime(year, month, day))
end = start + timedelta(days=1)
startUTC = utc.normalize(start.astimezone(utc))
endUTC = utc.normalize(end.astimezone(utc))
extremaUTC = tide.extrema(startUTC, endUTC)
date = {'date': day, 'day': calendar.day_abbr[start.weekday()]}
extrema = []
for e in extremaUTC:
time = tz.normalize(e[0].astimezone(tz))
# Round the time to the nearest minute.
time = time + timedelta(minutes=time.second > 30)
height = e[1]
extrema.append({'time': time.strftime('%H:%M'), 'height': "{0:.2f}".format(height)})
# This is just for nicer formatting of days with only three tides.
for _ in range(4 - len(extrema)):
extrema.append({'time': '', 'height': ''})
rows.append([date, extrema])
%%jinja html
<html>
<head>
<style>
h3 {font: sans-serif; color: #002277}
.datagrid table { border-collapse: collapse; text-align: left; width: 500px; }
.datagrid {font: normal 12px/150% Arial, Helvetica, sans-serif; background: #fff; overflow: hidden; border: 1px solid #006699; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; width:500px; }
.datagrid table td, .datagrid table th { padding: 3px 10px; }
.datagrid table thead th {background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #006699), color-stop(1, #00557F) );background:-moz-linear-gradient( center top, #006699 5%, #00557F 100% );filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#006699', endColorstr='#00557F');background-color:#006699; color:#FFFFFF; font-size: 15px; font-weight: bold; border-left: 1px solid #0070A8; }
.datagrid table thead th:first-child { border: none; }
.datagrid table tbody td { color: #00557F; border-left: 1px solid #E1EEF4;font-size: 12px;font-weight: normal; }
.datagrid table tbody .alt td { background: #E1EEf4; color: #00557F; }
.datagrid table tbody td:first-child { border-left: none; }
.datagrid table tbody tr:last-child td { border-bottom: none; }
.datagrid table tfoot td div { border-top: 1px solid #006699;background: #E1EEf4;}
.datagrid table tfoot td { padding: 0; font-size: 12px }
.datagrid table tfoot td div{ padding: 2px; }
.date {float:left}
.day {float:right}
.time {float:left}
.height {float:right}
</style>
</head>
<body>
<h3>Tide Table for {{location}} ({{month}} {{year}})</h3>
<div class="datagrid">
<table>
<thead><tr><th>Date</th><th colspan="4">Predictions</th></thead>
<tfoot><tr><td colspan="5"><div id="no-paging">Predictions given in {{units}} and {{tzname}} relative to {{datum}}</div></tr></tfoot>
<tbody>
{% for row in rows %}
<tr class="{{ loop.cycle('', 'alt') }}">
<td><strong class="date">{{row[0]['date']}}</strong><strong class="day">{{row[0]['day']}}</strong></td>
{% for e in row[1] %}
<td><span class="time">{{ e['time'] }}</span><span class="height">{{ e['height'] }}</span></td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table></div>
</body>
</html>
HTML(html)