hydrobot 0.2.2__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {hydrobot-0.2.2 → hydrobot-0.3.0}/CONTRIBUTING.rst +55 -8
- {hydrobot-0.2.2 → hydrobot-0.3.0}/HISTORY.rst +5 -0
- {hydrobot-0.2.2 → hydrobot-0.3.0}/PKG-INFO +7 -2
- {hydrobot-0.2.2 → hydrobot-0.3.0}/docs/installation.rst +20 -0
- {hydrobot-0.2.2 → hydrobot-0.3.0}/hydrobot/__init__.py +1 -1
- hydrobot-0.3.0/hydrobot/config/TwoLevelMeasurement_QC_config.csv +1 -0
- hydrobot-0.3.0/hydrobot/config/measurement_QC_config.csv +2 -0
- {hydrobot-0.2.2 → hydrobot-0.3.0}/hydrobot/data_acquisition.py +1 -0
- {hydrobot-0.2.2 → hydrobot-0.3.0}/hydrobot/data_sources.py +4 -1
- {hydrobot-0.2.2 → hydrobot-0.3.0}/hydrobot/evaluator.py +3 -3
- {hydrobot-0.2.2 → hydrobot-0.3.0}/hydrobot/filters.py +33 -6
- {hydrobot-0.2.2 → hydrobot-0.3.0}/hydrobot/plotter.py +1 -0
- {hydrobot-0.2.2 → hydrobot-0.3.0}/hydrobot/processor.py +55 -19
- {hydrobot-0.2.2 → hydrobot-0.3.0}/hydrobot.egg-info/PKG-INFO +7 -2
- {hydrobot-0.2.2 → hydrobot-0.3.0}/hydrobot.egg-info/SOURCES.txt +2 -0
- {hydrobot-0.2.2 → hydrobot-0.3.0}/setup.cfg +1 -1
- {hydrobot-0.2.2 → hydrobot-0.3.0}/setup.py +9 -3
- {hydrobot-0.2.2 → hydrobot-0.3.0}/tests/test_filters.py +16 -4
- {hydrobot-0.2.2 → hydrobot-0.3.0}/tests/test_processor.py +11 -9
- {hydrobot-0.2.2 → hydrobot-0.3.0}/AUTHORS.rst +0 -0
- {hydrobot-0.2.2 → hydrobot-0.3.0}/LICENSE +0 -0
- {hydrobot-0.2.2 → hydrobot-0.3.0}/MANIFEST.in +0 -0
- {hydrobot-0.2.2 → hydrobot-0.3.0}/README.rst +0 -0
- {hydrobot-0.2.2 → hydrobot-0.3.0}/docs/Makefile +0 -0
- {hydrobot-0.2.2 → hydrobot-0.3.0}/docs/_build/_static/file.png +0 -0
- {hydrobot-0.2.2 → hydrobot-0.3.0}/docs/_build/_static/minus.png +0 -0
- {hydrobot-0.2.2 → hydrobot-0.3.0}/docs/_build/_static/plus.png +0 -0
- {hydrobot-0.2.2 → hydrobot-0.3.0}/docs/authors.rst +0 -0
- {hydrobot-0.2.2 → hydrobot-0.3.0}/docs/conf.py +0 -0
- {hydrobot-0.2.2 → hydrobot-0.3.0}/docs/contributing.rst +0 -0
- {hydrobot-0.2.2 → hydrobot-0.3.0}/docs/history.rst +0 -0
- {hydrobot-0.2.2 → hydrobot-0.3.0}/docs/hydrobot.rst +0 -0
- {hydrobot-0.2.2 → hydrobot-0.3.0}/docs/index.rst +0 -0
- {hydrobot-0.2.2 → hydrobot-0.3.0}/docs/make.bat +0 -0
- {hydrobot-0.2.2 → hydrobot-0.3.0}/docs/modules.rst +0 -0
- {hydrobot-0.2.2 → hydrobot-0.3.0}/docs/readme.rst +0 -0
- {hydrobot-0.2.2 → hydrobot-0.3.0}/docs/usage.rst +0 -0
- {hydrobot-0.2.2 → hydrobot-0.3.0}/hydrobot.egg-info/dependency_links.txt +0 -0
- {hydrobot-0.2.2 → hydrobot-0.3.0}/hydrobot.egg-info/not-zip-safe +0 -0
- {hydrobot-0.2.2 → hydrobot-0.3.0}/hydrobot.egg-info/requires.txt +0 -0
- {hydrobot-0.2.2 → hydrobot-0.3.0}/hydrobot.egg-info/top_level.txt +0 -0
- {hydrobot-0.2.2 → hydrobot-0.3.0}/tests/__init__.py +0 -0
- {hydrobot-0.2.2 → hydrobot-0.3.0}/tests/site_list_response.xml +0 -0
- {hydrobot-0.2.2 → hydrobot-0.3.0}/tests/test_data_sources.py +0 -0
- {hydrobot-0.2.2 → hydrobot-0.3.0}/tests/test_evaluator.py +0 -0
|
@@ -141,7 +141,6 @@ Before you submit a pull request, check that it meets these guidelines:
|
|
|
141
141
|
2. If the pull request adds functionality, the docs should be updated. Put
|
|
142
142
|
your new functionality into a function with a docstring, and add the
|
|
143
143
|
feature to the appropriate location in the documentation.
|
|
144
|
-
3. The pull request should work for Python 3.5, 3.6, 3.7 and 3.8, and for PyPy. [*NOTE: This is a matter of trust for now, but I will figure out how to enforce this with "tox" at some point*]
|
|
145
144
|
|
|
146
145
|
Tips
|
|
147
146
|
----
|
|
@@ -151,13 +150,61 @@ To run a subset of tests::
|
|
|
151
150
|
$ pytest tests.test_hydrobot
|
|
152
151
|
|
|
153
152
|
|
|
154
|
-
|
|
155
|
-
|
|
153
|
+
Releasing to PyPI
|
|
154
|
+
------------------
|
|
156
155
|
|
|
157
156
|
A reminder for the maintainers on how to deploy.
|
|
158
|
-
Make sure all your changes are committed (including an entry in HISTORY.rst).
|
|
159
|
-
Then run::
|
|
160
157
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
158
|
+
1. Make sure all your changes are committed (including an entry in HISTORY.rst, documentation, etc.).
|
|
159
|
+
|
|
160
|
+
2. Then run `bump2version` to increment the release tags in the appropriate places. Consider running `bump2version --dry-run` to make sure there are no errors first::
|
|
161
|
+
|
|
162
|
+
$ bump2version --dry-run --verbose patch # Optional, just to test if it runs without errors
|
|
163
|
+
$ bump2version patch # For real this time. Possible values: major / minor / patch
|
|
164
|
+
|
|
165
|
+
3. Install the local development version of the package (make sure you're in the package root directory where setup.py is). You should see the package install with the correct version number.::
|
|
166
|
+
|
|
167
|
+
$ pip install -e .
|
|
168
|
+
|
|
169
|
+
4. Run the tests to see that they still work with this local install::
|
|
170
|
+
|
|
171
|
+
$ pytest
|
|
172
|
+
|
|
173
|
+
5. Push the commit::
|
|
174
|
+
|
|
175
|
+
$ git push
|
|
176
|
+
|
|
177
|
+
6. Push the tags to GitHub. (Note that we don't actually release on GitHub though. We want to keep the releases to PyPI so there's less ambiguity about how to install it.)::
|
|
178
|
+
|
|
179
|
+
$ git push --tags
|
|
180
|
+
|
|
181
|
+
7. Do the release.
|
|
182
|
+
|
|
183
|
+
* If using the Makefile (i.e. you have `make` installed and can run `make help` without errors) you can simply run::
|
|
184
|
+
|
|
185
|
+
$ make release
|
|
186
|
+
|
|
187
|
+
* Otherwise, you would have to do the release manually.
|
|
188
|
+
|
|
189
|
+
a. Clean up all the artifact files::
|
|
190
|
+
|
|
191
|
+
$ rm -fr build/
|
|
192
|
+
$ rm -fr dist/
|
|
193
|
+
$ rm -fr .eggs/
|
|
194
|
+
$ find . -name '*.egg-info' -exec rm -fr {} +
|
|
195
|
+
$ find . -name '*.egg' -exec rm -f {} +
|
|
196
|
+
$ find . -name '*.pyc' -exec rm -f {} +
|
|
197
|
+
$ find . -name '*.pyo' -exec rm -f {} +
|
|
198
|
+
$ find . -name '*~' -exec rm -f {} +
|
|
199
|
+
$ find . -name '__pycache__' -exec rm -fr {} +
|
|
200
|
+
$ rm -fr .pytest_cache
|
|
201
|
+
|
|
202
|
+
b. Build the source and wheel packages::
|
|
203
|
+
|
|
204
|
+
$ python setup.py sdist
|
|
205
|
+
$ python setup.py bdist_wheel
|
|
206
|
+
$ ls -l dist
|
|
207
|
+
|
|
208
|
+
c. Use twine to release to PyPI. You'll be asked for authentication. Use the username `__token__`, along with the API key I gave you.::
|
|
209
|
+
|
|
210
|
+
$ twine upload dist/*
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: hydrobot
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Python Package providing a suite of processing tools and utilities for Hilltop hydrological data.
|
|
5
5
|
Home-page: https://github.com/nicmostert/hydrobot
|
|
6
6
|
Author: Nic Mostert
|
|
@@ -12,7 +12,7 @@ Classifier: Intended Audience :: Developers
|
|
|
12
12
|
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
13
13
|
Classifier: Natural Language :: English
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
-
Requires-Python: ==3.11
|
|
15
|
+
Requires-Python: ==3.11.6
|
|
16
16
|
License-File: LICENSE
|
|
17
17
|
License-File: AUTHORS.rst
|
|
18
18
|
|
|
@@ -80,3 +80,8 @@ History
|
|
|
80
80
|
------------------
|
|
81
81
|
|
|
82
82
|
* Ok, actually including the config files (promise, but my fingers are crossed behind my back)
|
|
83
|
+
|
|
84
|
+
0.2.3 (2023-11-30)
|
|
85
|
+
------------------
|
|
86
|
+
|
|
87
|
+
* Fixed some tests and hopefully fixed the config file issue this time.
|
|
@@ -49,3 +49,23 @@ Once you have a copy of the source, you can install it with:
|
|
|
49
49
|
|
|
50
50
|
.. _Github repo: https://github.com/nicmostert/hydrobot
|
|
51
51
|
.. _tarball: https://github.com/nicmostert/hydrobot/tarball/master
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
Usage
|
|
55
|
+
-----
|
|
56
|
+
|
|
57
|
+
For purposes of auditing data changes, you may want to track what changes to
|
|
58
|
+
the data are made, and what version of hydrobot (and it's dependencies) are
|
|
59
|
+
used.
|
|
60
|
+
|
|
61
|
+
Windows
|
|
62
|
+
|
|
63
|
+
In Powershell:
|
|
64
|
+
|
|
65
|
+
.. code-block:: console
|
|
66
|
+
|
|
67
|
+
cd path/to/Hydrobot_install
|
|
68
|
+
python -m venv /path/to/Hydrobot_install/venv
|
|
69
|
+
path/to/Hydrobot_install/venv/Scripts/Activate.ps1
|
|
70
|
+
pip install hydrobot
|
|
71
|
+
pip freeze > requirements.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Water level statistics: Point Sample,10,3,0.5,0.2,2000
|
|
@@ -196,7 +196,10 @@ def get_measurement(measurement_name):
|
|
|
196
196
|
if measurement_name in m_dict:
|
|
197
197
|
return m_dict[measurement_name]
|
|
198
198
|
else:
|
|
199
|
-
raise Exception(
|
|
199
|
+
raise Exception(
|
|
200
|
+
f"Measurement {measurement_name} not found in the config file. "
|
|
201
|
+
f"Available measurements are {list(m_dict.keys())}."
|
|
202
|
+
)
|
|
200
203
|
|
|
201
204
|
|
|
202
205
|
def series_export_to_csv(
|
|
@@ -311,10 +311,10 @@ def diagnose_data(base_series, check_series, qc_series, frequency):
|
|
|
311
311
|
split_data = splitter(base_series, qc_series, frequency)
|
|
312
312
|
for qc in split_data:
|
|
313
313
|
print(
|
|
314
|
-
f"Data that is QC{qc} makes up {len(split_data[qc].dropna()) / len(base_series.dropna()) * 100}% of
|
|
315
|
-
f"
|
|
314
|
+
f"Data that is QC{qc} makes up {len(split_data[qc].dropna()) / len(base_series.dropna()) * 100:.2f}% of "
|
|
315
|
+
f"the "
|
|
316
|
+
f"workable data and {len(split_data[qc].dropna()) / len(base_series) * 100:.2f}% of the time period"
|
|
316
317
|
)
|
|
317
|
-
print("Now it's your job to figure out if that's good enough")
|
|
318
318
|
|
|
319
319
|
|
|
320
320
|
def splitter(base_series, qc_series, frequency):
|
|
@@ -23,7 +23,8 @@ def clip(unclipped, low_clip: float, high_clip: float):
|
|
|
23
23
|
Returns
|
|
24
24
|
-------
|
|
25
25
|
pandas.Series
|
|
26
|
-
A Series containing the clipped values with the same index as the
|
|
26
|
+
A Series containing the clipped values with the same index as the
|
|
27
|
+
input Series.
|
|
27
28
|
"""
|
|
28
29
|
unclipped_arr = unclipped.values
|
|
29
30
|
|
|
@@ -33,11 +34,11 @@ def clip(unclipped, low_clip: float, high_clip: float):
|
|
|
33
34
|
# Use pandas' where function to clip values to NaN where the condition is
|
|
34
35
|
# True
|
|
35
36
|
clipped_series = unclipped.where(~clip_cond, np.nan)
|
|
36
|
-
clipped_series = unclipped.where(~clip_cond, np.nan)
|
|
37
37
|
|
|
38
38
|
return clipped_series
|
|
39
39
|
|
|
40
40
|
|
|
41
|
+
# noinspection SpellCheckingInspection
|
|
41
42
|
def fbewma(input_data, span: int):
|
|
42
43
|
"""
|
|
43
44
|
Calculate the Forward-Backward Exponentially Weighted Moving Average (FBEWMA) of a pandas Series.
|
|
@@ -52,7 +53,8 @@ def fbewma(input_data, span: int):
|
|
|
52
53
|
Returns
|
|
53
54
|
-------
|
|
54
55
|
pandas.Series
|
|
55
|
-
A Series containing the FBEWMA values with the same index as the
|
|
56
|
+
A Series containing the FBEWMA values with the same index as the
|
|
57
|
+
input Series.
|
|
56
58
|
"""
|
|
57
59
|
# Calculate the Forward EWMA.
|
|
58
60
|
fwd = input_data.ewm(span=span).mean()
|
|
@@ -74,14 +76,14 @@ def remove_outliers(input_data, span: int, delta: float):
|
|
|
74
76
|
Remove outliers.
|
|
75
77
|
|
|
76
78
|
Remove outliers from a time series by comparing it to the
|
|
77
|
-
Forward-Backward Exponentially Weighted Moving Average (
|
|
79
|
+
Forward-Backward Exponentially Weighted Moving Average (FBEWMA).
|
|
78
80
|
|
|
79
81
|
Parameters
|
|
80
82
|
----------
|
|
81
83
|
input_data : pandas.Series
|
|
82
84
|
Input time series data.
|
|
83
85
|
span : int
|
|
84
|
-
Span parameter for exponential weighting used in the
|
|
86
|
+
Span parameter for exponential weighting used in the FBEWMA.
|
|
85
87
|
delta : float
|
|
86
88
|
Threshold for identifying outliers. Values greater than this
|
|
87
89
|
threshold will be set to NaN.
|
|
@@ -92,7 +94,7 @@ def remove_outliers(input_data, span: int, delta: float):
|
|
|
92
94
|
A Series containing the time series with outliers removed with
|
|
93
95
|
the same index as the input Series.
|
|
94
96
|
"""
|
|
95
|
-
# Calculate the
|
|
97
|
+
# Calculate the FBEWMA of the time series
|
|
96
98
|
fbewma_series = fbewma(input_data, span)
|
|
97
99
|
|
|
98
100
|
# Create a condition to identify outliers based on the absolute difference
|
|
@@ -143,3 +145,28 @@ def remove_spikes(
|
|
|
143
145
|
# interp_series = gaps_series.interpolate()
|
|
144
146
|
|
|
145
147
|
return gaps_series
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def remove_range(input_series: pd.Series, from_date, to_date):
|
|
151
|
+
"""
|
|
152
|
+
Remove data from series in given range.
|
|
153
|
+
|
|
154
|
+
Returns the input series without data between from_date and to_date
|
|
155
|
+
inclusive
|
|
156
|
+
|
|
157
|
+
Parameters
|
|
158
|
+
----------
|
|
159
|
+
input_series : pd.Series
|
|
160
|
+
The series to have a section removed
|
|
161
|
+
from_date : str
|
|
162
|
+
Start of removed section
|
|
163
|
+
to_date : str
|
|
164
|
+
End of removed section
|
|
165
|
+
|
|
166
|
+
Returns
|
|
167
|
+
-------
|
|
168
|
+
pd.Series
|
|
169
|
+
The series with relevant slice removed
|
|
170
|
+
"""
|
|
171
|
+
slice_to_remove = input_series.loc[from_date:to_date]
|
|
172
|
+
return input_series.drop(slice_to_remove.index)
|
|
@@ -16,14 +16,16 @@ def stale_warning(method):
|
|
|
16
16
|
Decorate dangerous functions.
|
|
17
17
|
|
|
18
18
|
Check whether the data is stale, and warn user if so.
|
|
19
|
-
Warning will then take input form user to determine whether to proceed
|
|
20
|
-
|
|
19
|
+
Warning will then take input form user to determine whether to proceed
|
|
20
|
+
or cancel.
|
|
21
|
+
Cancelling will return a null function, which returns None with no side
|
|
22
|
+
effects no matter what the input
|
|
21
23
|
|
|
22
24
|
Parameters
|
|
23
25
|
----------
|
|
24
26
|
method : function
|
|
25
|
-
A function that might have some problems if the parameters have been
|
|
26
|
-
updated
|
|
27
|
+
A function that might have some problems if the parameters have been
|
|
28
|
+
changed but the data hasn't been updated
|
|
27
29
|
|
|
28
30
|
Returns
|
|
29
31
|
-------
|
|
@@ -35,7 +37,8 @@ def stale_warning(method):
|
|
|
35
37
|
def _impl(self, *method_args, **method_kwargs):
|
|
36
38
|
if self._stale:
|
|
37
39
|
warnings.warn(
|
|
38
|
-
"Warning: a key parameter of the data has changed but the
|
|
40
|
+
"Warning: a key parameter of the data has changed but the "
|
|
41
|
+
"data itself has not been reloaded."
|
|
39
42
|
)
|
|
40
43
|
while True:
|
|
41
44
|
user_input = input("Do you want to continue? y/n: ")
|
|
@@ -71,7 +74,7 @@ class Processor:
|
|
|
71
74
|
to_date: str | None = None,
|
|
72
75
|
check_hts: str | None = None,
|
|
73
76
|
check_measurement: str | None = None,
|
|
74
|
-
defaults: dict =
|
|
77
|
+
defaults: dict | None = None,
|
|
75
78
|
**kwargs,
|
|
76
79
|
):
|
|
77
80
|
"""Initialize a Processor instance."""
|
|
@@ -90,27 +93,30 @@ class Processor:
|
|
|
90
93
|
else:
|
|
91
94
|
raise ValueError(
|
|
92
95
|
f"Site '{site}' not found for both base_url and hts combos."
|
|
93
|
-
f"Available sites in standard_hts are
|
|
94
|
-
f"
|
|
96
|
+
f"Available sites in standard_hts are: "
|
|
97
|
+
f"{[s for s in standard_hilltop.available_sites]}"
|
|
98
|
+
f"Available sites in check_hts are: "
|
|
99
|
+
f"{[s for s in check_hilltop.available_sites]}"
|
|
95
100
|
)
|
|
96
101
|
|
|
97
|
-
|
|
98
|
-
if standard_measurement in
|
|
102
|
+
standard_measurement_list = standard_hilltop.get_measurement_list(site)
|
|
103
|
+
if standard_measurement in list(standard_measurement_list.MeasurementName):
|
|
99
104
|
self._standard_measurement = standard_measurement
|
|
100
105
|
else:
|
|
101
106
|
raise ValueError(
|
|
102
|
-
f"Standard measurement '{standard_measurement}' not found at
|
|
107
|
+
f"Standard measurement '{standard_measurement}' not found at "
|
|
108
|
+
f"site '{site}'. "
|
|
103
109
|
"Available measurements are "
|
|
104
|
-
f"{
|
|
110
|
+
f"{list(standard_measurement_list.MeasurementName)}"
|
|
105
111
|
)
|
|
106
|
-
|
|
107
|
-
if check_measurement in
|
|
112
|
+
check_measurement_list = check_hilltop.get_measurement_list(site)
|
|
113
|
+
if check_measurement in list(check_measurement_list.MeasurementName):
|
|
108
114
|
self._check_measurement = check_measurement
|
|
109
115
|
else:
|
|
110
116
|
raise ValueError(
|
|
111
117
|
f"Check measurement '{check_measurement}' not found at site '{site}'. "
|
|
112
118
|
"Available measurements are "
|
|
113
|
-
f"{
|
|
119
|
+
f"{list(check_measurement_list.MeasurementName)}"
|
|
114
120
|
)
|
|
115
121
|
|
|
116
122
|
self._base_url = base_url
|
|
@@ -281,10 +287,9 @@ class Processor:
|
|
|
281
287
|
tstype="Standard",
|
|
282
288
|
)
|
|
283
289
|
insert_series = insert_series.asfreq(self.frequency)
|
|
284
|
-
|
|
285
|
-
insert_series.index[0]
|
|
286
|
-
|
|
287
|
-
cleaned_series = self.standard_series.drop(slice_to_remove.index)
|
|
290
|
+
cleaned_series = filters.remove_range(
|
|
291
|
+
self.standard_series, insert_series.index[0], insert_series.index[-1]
|
|
292
|
+
)
|
|
288
293
|
self.standard_series = pd.concat(
|
|
289
294
|
[cleaned_series, insert_series]
|
|
290
295
|
).sort_index()
|
|
@@ -328,6 +333,9 @@ class Processor:
|
|
|
328
333
|
quality: bool = False,
|
|
329
334
|
):
|
|
330
335
|
"""Import data using class parameter range."""
|
|
336
|
+
self.standard_series = pd.Series({})
|
|
337
|
+
self.check_series = pd.Series({})
|
|
338
|
+
self.quality_series = pd.Series({})
|
|
331
339
|
self.import_range(self._from_date, self._to_date, standard, check, quality)
|
|
332
340
|
self._stale = False
|
|
333
341
|
|
|
@@ -410,6 +418,34 @@ class Processor:
|
|
|
410
418
|
self._standard_series, span, low_clip, high_clip, delta
|
|
411
419
|
)
|
|
412
420
|
|
|
421
|
+
@ClassLogger
|
|
422
|
+
def delete_range(
|
|
423
|
+
self,
|
|
424
|
+
from_date,
|
|
425
|
+
to_date,
|
|
426
|
+
tstype_standard=True,
|
|
427
|
+
tstype_check=False,
|
|
428
|
+
tstype_quality=False,
|
|
429
|
+
):
|
|
430
|
+
"""Delete range of data a la remove_range."""
|
|
431
|
+
if tstype_standard:
|
|
432
|
+
self.standard_series = filters.remove_range(
|
|
433
|
+
self.standard_series, from_date, to_date
|
|
434
|
+
)
|
|
435
|
+
if tstype_check:
|
|
436
|
+
self.standard_series = filters.remove_range(
|
|
437
|
+
self.standard_series, from_date, to_date
|
|
438
|
+
)
|
|
439
|
+
if tstype_quality:
|
|
440
|
+
self.standard_series = filters.remove_range(
|
|
441
|
+
self.standard_series, from_date, to_date
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
@ClassLogger
|
|
445
|
+
def insert_missing_nans(self):
|
|
446
|
+
"""Set the data to the correct frequency, filled with NaNs as appropriate."""
|
|
447
|
+
self.standard_series = self.standard_series.asfreq(self.frequency)
|
|
448
|
+
|
|
413
449
|
@ClassLogger
|
|
414
450
|
def data_exporter(self, file_location):
|
|
415
451
|
"""Export data to csv."""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: hydrobot
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Python Package providing a suite of processing tools and utilities for Hilltop hydrological data.
|
|
5
5
|
Home-page: https://github.com/nicmostert/hydrobot
|
|
6
6
|
Author: Nic Mostert
|
|
@@ -12,7 +12,7 @@ Classifier: Intended Audience :: Developers
|
|
|
12
12
|
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
13
13
|
Classifier: Natural Language :: English
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
-
Requires-Python: ==3.11
|
|
15
|
+
Requires-Python: ==3.11.6
|
|
16
16
|
License-File: LICENSE
|
|
17
17
|
License-File: AUTHORS.rst
|
|
18
18
|
|
|
@@ -80,3 +80,8 @@ History
|
|
|
80
80
|
------------------
|
|
81
81
|
|
|
82
82
|
* Ok, actually including the config files (promise, but my fingers are crossed behind my back)
|
|
83
|
+
|
|
84
|
+
0.2.3 (2023-11-30)
|
|
85
|
+
------------------
|
|
86
|
+
|
|
87
|
+
* Fixed some tests and hopefully fixed the config file issue this time.
|
|
@@ -34,6 +34,8 @@ hydrobot.egg-info/dependency_links.txt
|
|
|
34
34
|
hydrobot.egg-info/not-zip-safe
|
|
35
35
|
hydrobot.egg-info/requires.txt
|
|
36
36
|
hydrobot.egg-info/top_level.txt
|
|
37
|
+
hydrobot/config/TwoLevelMeasurement_QC_config.csv
|
|
38
|
+
hydrobot/config/measurement_QC_config.csv
|
|
37
39
|
tests/__init__.py
|
|
38
40
|
tests/site_list_response.xml
|
|
39
41
|
tests/test_data_sources.py
|
|
@@ -12,12 +12,17 @@ with open("HISTORY.rst") as history_file:
|
|
|
12
12
|
|
|
13
13
|
requirements: list = ["hilltop-py>=2.3.1", "data-annalist>=0.3.6", "matplotlib>=3.8.0"]
|
|
14
14
|
|
|
15
|
-
test_requirements = [
|
|
15
|
+
test_requirements = [
|
|
16
|
+
"pytest>=3",
|
|
17
|
+
"data-annalist>=0.3.6",
|
|
18
|
+
"pytest-mock>=3.12.0",
|
|
19
|
+
"pytest-dependency>=0.5.1",
|
|
20
|
+
]
|
|
16
21
|
|
|
17
22
|
setup(
|
|
18
23
|
author="Nic Mostert",
|
|
19
24
|
author_email="nicolas.mostert@horizons.govt.nz",
|
|
20
|
-
python_requires="==3.11",
|
|
25
|
+
python_requires="==3.11.6",
|
|
21
26
|
classifiers=[
|
|
22
27
|
"Development Status :: 2 - Pre-Alpha",
|
|
23
28
|
"Intended Audience :: Developers",
|
|
@@ -42,6 +47,7 @@ setup(
|
|
|
42
47
|
test_suite="tests",
|
|
43
48
|
tests_require=test_requirements,
|
|
44
49
|
url="https://github.com/nicmostert/hydrobot",
|
|
45
|
-
version="0.
|
|
50
|
+
version="0.3.0",
|
|
46
51
|
zip_safe=False,
|
|
52
|
+
package_data={"hydrobot": ["config/*"]},
|
|
47
53
|
)
|
|
@@ -36,9 +36,10 @@ def raw_data():
|
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
def insert_raw_data_gaps(gaps):
|
|
39
|
+
gap_data_dict = dict(raw_data_dict)
|
|
39
40
|
for gap in gaps:
|
|
40
|
-
|
|
41
|
-
data_series = pd.Series(
|
|
41
|
+
gap_data_dict[gap] = np.nan
|
|
42
|
+
data_series = pd.Series(gap_data_dict)
|
|
42
43
|
return data_series
|
|
43
44
|
|
|
44
45
|
|
|
@@ -81,10 +82,10 @@ def test_fbewma(raw_data, fbewma_data):
|
|
|
81
82
|
span = 3
|
|
82
83
|
|
|
83
84
|
# Testing
|
|
84
|
-
|
|
85
|
+
fbewma_df = filters.fbewma(raw_data, span)
|
|
85
86
|
|
|
86
87
|
# pytest.approx accounts for floating point errors and such
|
|
87
|
-
assert
|
|
88
|
+
assert fbewma_df.values == pytest.approx(fbewma_data.values), "FBEWMA failed!"
|
|
88
89
|
|
|
89
90
|
|
|
90
91
|
def test_remove_outliers(raw_data, fbewma_data, mocker, span=2, delta=2):
|
|
@@ -127,3 +128,14 @@ def test_remove_spike(raw_data, fbewma_data, mocker):
|
|
|
127
128
|
|
|
128
129
|
spike_removed = filters.remove_spikes(raw_data, span, high_clip, low_clip, delta)
|
|
129
130
|
assert math.isnan(spike_removed["2021-01-01 00:10"]), "Spike not removed!"
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def test_remove_range(raw_data):
|
|
134
|
+
a = filters.remove_range(raw_data, "2021-01-01 00:05", "2021-01-01 00:10")
|
|
135
|
+
assert len(a) == 3, "Incorrect number of values removed"
|
|
136
|
+
assert a["2021-01-01 00:00"] == 1.0, "first value compromised"
|
|
137
|
+
assert a["2021-01-01 00:15"] == 4.0, "after value compromised"
|
|
138
|
+
assert a["2021-01-01 00:20"] == 5.0, "end value compromised"
|
|
139
|
+
|
|
140
|
+
b = filters.remove_range(raw_data, "2021-01-01 00:03", "2021-01-01 00:14")
|
|
141
|
+
assert b.equals(a), "time rounding error"
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
'''
|
|
2
1
|
import random
|
|
3
2
|
from datetime import datetime, timedelta
|
|
4
3
|
|
|
@@ -20,6 +19,7 @@ SITES = [
|
|
|
20
19
|
|
|
21
20
|
MEASUREMENTS = [
|
|
22
21
|
"General Nastiness (out of 10)",
|
|
22
|
+
"Atmospheric Pressure",
|
|
23
23
|
"Number of Actual Whole Turds Floating By (t/s)",
|
|
24
24
|
"Dead Cow Concentration (ppm)",
|
|
25
25
|
]
|
|
@@ -27,14 +27,13 @@ MEASUREMENTS = [
|
|
|
27
27
|
|
|
28
28
|
@pytest.fixture(autouse=True)
|
|
29
29
|
def no_requests(monkeypatch):
|
|
30
|
-
"""Don't allow requests to make requests"""
|
|
30
|
+
"""Don't allow requests to make requests."""
|
|
31
31
|
monkeypatch.delattr("requests.sessions.Session.request")
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
@pytest.fixture
|
|
35
35
|
def mock_site_list():
|
|
36
|
-
"""Mock response from SiteList server call method"""
|
|
37
|
-
|
|
36
|
+
"""Mock response from SiteList server call method."""
|
|
38
37
|
data = {
|
|
39
38
|
"SiteName": SITES,
|
|
40
39
|
}
|
|
@@ -45,8 +44,7 @@ def mock_site_list():
|
|
|
45
44
|
|
|
46
45
|
@pytest.fixture
|
|
47
46
|
def mock_measurement_list():
|
|
48
|
-
"""Mock response from MeasurementList server call method"""
|
|
49
|
-
|
|
47
|
+
"""Mock response from MeasurementList server call method."""
|
|
50
48
|
data = {
|
|
51
49
|
"MeasurementName": MEASUREMENTS,
|
|
52
50
|
}
|
|
@@ -78,8 +76,9 @@ def mock_dataset():
|
|
|
78
76
|
return df
|
|
79
77
|
|
|
80
78
|
|
|
81
|
-
|
|
82
79
|
def test_processor(monkeypatch, mock_site_list, mock_measurement_list, mock_dataset):
|
|
80
|
+
"""Test the processor function."""
|
|
81
|
+
|
|
83
82
|
def get_mock_site_list(*args, **kwargs):
|
|
84
83
|
return mock_site_list
|
|
85
84
|
|
|
@@ -95,8 +94,11 @@ def test_processor(monkeypatch, mock_site_list, mock_measurement_list, mock_data
|
|
|
95
94
|
|
|
96
95
|
pr = processor.Processor(
|
|
97
96
|
"https://greenwashed.and.pleasant/",
|
|
98
|
-
"GreenPasturesAreNaturalAndEcoFriendlyISwear.hts",
|
|
99
97
|
SITES[1],
|
|
98
|
+
"GreenPasturesAreNaturalAndEcoFriendlyISwear.hts",
|
|
100
99
|
MEASUREMENTS[1],
|
|
100
|
+
"5T",
|
|
101
101
|
)
|
|
102
|
-
|
|
102
|
+
pr.import_data()
|
|
103
|
+
print(pr.standard_series)
|
|
104
|
+
assert pr.standard_series.loc["2020-10-01 08:00:00"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|