bulum 0.3.2__tar.gz → 0.3.3__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.
- {bulum-0.3.2/src/bulum.egg-info → bulum-0.3.3}/PKG-INFO +37 -10
- {bulum-0.3.2 → bulum-0.3.3}/README.md +31 -0
- bulum-0.3.3/pyproject.toml +47 -0
- {bulum-0.3.2 → bulum-0.3.3}/setup.py +1 -1
- bulum-0.3.3/src/bulum/clim/tests/test_clim.py +71 -0
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum/io/__init__.py +0 -1
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum/io/csv_io.py +39 -16
- bulum-0.3.3/src/bulum/io/general_io.py +55 -0
- bulum-0.3.3/src/bulum/io/idx_io.py +341 -0
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum/io/iqqm_out_reader.py +98 -68
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum/io/lqn_io.py +13 -4
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum/io/res_csv_io.py +70 -31
- bulum-0.3.3/src/bulum/io/tests/test_csv_io.py +55 -0
- bulum-0.3.3/src/bulum/io/tests/test_general_io.py +22 -0
- bulum-0.3.3/src/bulum/io/tests/test_idx_io.py +112 -0
- bulum-0.3.3/src/bulum/io/tests/test_iqqm_out_reader.py +48 -0
- bulum-0.3.3/src/bulum/io/tests/test_lqn_io.py +52 -0
- bulum-0.3.3/src/bulum/io/tests/test_res_csv_io.py +60 -0
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum/maps/station_maps.py +1 -1
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum/plots/altair_plots.py +3 -3
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum/plots/ensemble_altair_plots.py +2 -0
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum/plots/tests/test_plot_functions.py +13 -15
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum/stats/__init__.py +0 -1
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum/stats/negflo.py +336 -126
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum/stats/reliability_stats_class.py +1 -1
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum/stats/stochastic_data_check.py +1 -1
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum/stats/storage_level_assessment.py +9 -7
- bulum-0.3.3/src/bulum/stats/swflo2s/tests/test_swflo2s.py +58 -0
- bulum-0.3.3/src/bulum/stats/tests/test_negflo.py +359 -0
- bulum-0.3.3/src/bulum/stats/tests/test_stats.py +365 -0
- bulum-0.3.3/src/bulum/stats/tests/test_storage_level_assessment.py +750 -0
- bulum-0.3.3/src/bulum/stoch/tests/test_stoch.py +13 -0
- bulum-0.3.3/src/bulum/trans/tests/test_trans.py +20 -0
- bulum-0.3.3/src/bulum/utils/dataframe_extensions.py +1224 -0
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum/utils/dataframe_functions.py +23 -12
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum/utils/datetime_functions.py +215 -76
- bulum-0.3.3/src/bulum/utils/tests/test_ensembles.py +710 -0
- bulum-0.3.3/src/bulum/utils/tests/test_interpolation.py +88 -0
- bulum-0.3.3/src/bulum/utils/tests/test_parametrized.py +219 -0
- bulum-0.3.3/src/bulum/utils/tests/test_tsdf.py +540 -0
- bulum-0.3.3/src/bulum/utils/tests/test_utils.py +701 -0
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum/version.py +1 -1
- {bulum-0.3.2 → bulum-0.3.3/src/bulum.egg-info}/PKG-INFO +37 -10
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum.egg-info/SOURCES.txt +20 -3
- bulum-0.3.2/src/bulum/io/general_io.py +0 -33
- bulum-0.3.2/src/bulum/io/idx_io.py +0 -65
- bulum-0.3.2/src/bulum/io/idx_io_native.py +0 -159
- bulum-0.3.2/src/bulum/stats/negflo_helpers.py +0 -154
- bulum-0.3.2/src/bulum/utils/dataframe_extensions.py +0 -252
- {bulum-0.3.2 → bulum-0.3.3}/LICENSE.md +0 -0
- {bulum-0.3.2 → bulum-0.3.3}/setup.cfg +0 -0
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum/__init__.py +0 -0
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum/clim/__init__.py +0 -0
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum/clim/clim.py +0 -0
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum/demo.py +0 -0
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum/maps/__init__.py +0 -0
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum/plots/__init__.py +0 -0
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum/plots/node_diagrams.py +0 -0
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum/plots/plot_functions.py +0 -0
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum/plots/plotly_helpers.py +0 -0
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum/plots/tests/__init__.py +0 -0
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum/stats/aggregate_stats.py +0 -0
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum/stats/ensemble_stats.py +0 -0
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum/stats/swflo2s/__init__.py +0 -0
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum/stats/swflo2s/swflo2s.py +0 -0
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum/stoch/__init__.py +0 -0
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum/stoch/analyse.py +0 -0
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum/stoch/generate.py +0 -0
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum/test_circular_import.py +0 -0
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum/trans/__init__.py +0 -0
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum/trans/transformers.py +0 -0
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum/utils/__init__.py +0 -0
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum/utils/interpolation.py +0 -0
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum.egg-info/dependency_links.txt +0 -0
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum.egg-info/requires.txt +0 -0
- {bulum-0.3.2 → bulum-0.3.3}/src/bulum.egg-info/top_level.txt +0 -0
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: bulum
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: Open source python library for assessing hydrologic model results in Queensland
|
|
5
5
|
Home-page: https://github.com/odhydrology/bulum
|
|
6
6
|
Author: Chas Egan
|
|
7
|
-
Author-email: chas@odhydrology.com
|
|
7
|
+
Author-email: Chas Egan <chas@odhydrology.com>
|
|
8
|
+
Project-URL: Homepage, https://github.com/odhydrology/bulum
|
|
8
9
|
Classifier: Programming Language :: Python :: 3
|
|
9
10
|
Classifier: Operating System :: OS Independent
|
|
10
|
-
Requires-Python: >=3.
|
|
11
|
+
Requires-Python: >=3.11
|
|
11
12
|
Description-Content-Type: text/markdown
|
|
12
13
|
License-File: LICENSE.md
|
|
13
14
|
Requires-Dist: altair[all]>=5.5.0
|
|
@@ -17,14 +18,9 @@ Requires-Dist: numpy>=1.26.4
|
|
|
17
18
|
Requires-Dist: pandas>=2.2.0
|
|
18
19
|
Requires-Dist: plotly>=5.18.0
|
|
19
20
|
Dynamic: author
|
|
20
|
-
Dynamic: author-email
|
|
21
|
-
Dynamic: classifier
|
|
22
|
-
Dynamic: description
|
|
23
|
-
Dynamic: description-content-type
|
|
24
21
|
Dynamic: home-page
|
|
25
|
-
Dynamic:
|
|
22
|
+
Dynamic: license-file
|
|
26
23
|
Dynamic: requires-python
|
|
27
|
-
Dynamic: summary
|
|
28
24
|
|
|
29
25
|
# bulum
|
|
30
26
|
|
|
@@ -81,8 +77,39 @@ Where can I find the API token password? Chas has it in his emails. It is also h
|
|
|
81
77
|
|
|
82
78
|
How do I make a new API token? Go to your PyPi account settings, and click on "API tokens". Then click on "Add API token", and give it a name. The token will be displayed on the next screen.
|
|
83
79
|
|
|
80
|
+
Alternatively,
|
|
81
|
+
```bash
|
|
82
|
+
uv build
|
|
83
|
+
uv publish --token XXXXX
|
|
84
|
+
```
|
|
85
|
+
|
|
84
86
|
## Unit Tests
|
|
85
87
|
|
|
88
|
+
For development testing, first sync with extras=dev via
|
|
89
|
+
```bash
|
|
90
|
+
uv sync --extra dev
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
To run tests with the current or a particular version of python, from the project root run pytest via (for example):
|
|
94
|
+
```bash
|
|
95
|
+
uv run pytest
|
|
96
|
+
uv run
|
|
97
|
+
uv run tox -e py312 # to run a particular version
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Before publishing, check the library is functional across all supported versions of python via (one of):
|
|
101
|
+
```bash
|
|
102
|
+
uv run tox
|
|
103
|
+
uv run tox -p auto # This is run in parallel
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Note: if you are missing python versions, install via (e.g.)
|
|
107
|
+
```bash
|
|
108
|
+
uv python install 3.12
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Legacy
|
|
112
|
+
|
|
86
113
|
WARNING: Run unit tests from an anaconda environment with compatible dependencies!
|
|
87
114
|
|
|
88
115
|
Install the nose2 test-runner framework.
|
|
@@ -53,8 +53,39 @@ Where can I find the API token password? Chas has it in his emails. It is also h
|
|
|
53
53
|
|
|
54
54
|
How do I make a new API token? Go to your PyPi account settings, and click on "API tokens". Then click on "Add API token", and give it a name. The token will be displayed on the next screen.
|
|
55
55
|
|
|
56
|
+
Alternatively,
|
|
57
|
+
```bash
|
|
58
|
+
uv build
|
|
59
|
+
uv publish --token XXXXX
|
|
60
|
+
```
|
|
61
|
+
|
|
56
62
|
## Unit Tests
|
|
57
63
|
|
|
64
|
+
For development testing, first sync with extras=dev via
|
|
65
|
+
```bash
|
|
66
|
+
uv sync --extra dev
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
To run tests with the current or a particular version of python, from the project root run pytest via (for example):
|
|
70
|
+
```bash
|
|
71
|
+
uv run pytest
|
|
72
|
+
uv run
|
|
73
|
+
uv run tox -e py312 # to run a particular version
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Before publishing, check the library is functional across all supported versions of python via (one of):
|
|
77
|
+
```bash
|
|
78
|
+
uv run tox
|
|
79
|
+
uv run tox -p auto # This is run in parallel
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Note: if you are missing python versions, install via (e.g.)
|
|
83
|
+
```bash
|
|
84
|
+
uv python install 3.12
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Legacy
|
|
88
|
+
|
|
58
89
|
WARNING: Run unit tests from an anaconda environment with compatible dependencies!
|
|
59
90
|
|
|
60
91
|
Install the nose2 test-runner framework.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "bulum"
|
|
3
|
+
dynamic = ["version"]
|
|
4
|
+
description = "Open source python library for assessing hydrologic model results in Queensland"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
authors = [
|
|
8
|
+
{ name = "Chas Egan", email = "chas@odhydrology.com" }
|
|
9
|
+
]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Programming Language :: Python :: 3",
|
|
12
|
+
"Operating System :: OS Independent",
|
|
13
|
+
]
|
|
14
|
+
dependencies = [
|
|
15
|
+
"altair[all]>=5.5.0",
|
|
16
|
+
"folium>=0.14",
|
|
17
|
+
"matplotlib>=3.8.3",
|
|
18
|
+
"numpy>=1.26.4",
|
|
19
|
+
"pandas>=2.2.0",
|
|
20
|
+
"plotly>=5.18.0",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[project.urls]
|
|
24
|
+
Homepage = "https://github.com/odhydrology/bulum"
|
|
25
|
+
|
|
26
|
+
[build-system]
|
|
27
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
28
|
+
build-backend = "setuptools.build_meta"
|
|
29
|
+
|
|
30
|
+
[tool.setuptools.packages.find]
|
|
31
|
+
where = ["src"]
|
|
32
|
+
|
|
33
|
+
[tool.setuptools.dynamic]
|
|
34
|
+
version = { attr = "bulum.version.__version__" }
|
|
35
|
+
|
|
36
|
+
[tool.pytest.ini_options]
|
|
37
|
+
filterwarnings = [
|
|
38
|
+
"ignore::DeprecationWarning:matplotlib",
|
|
39
|
+
"ignore::DeprecationWarning:PIL",
|
|
40
|
+
"ignore::PendingDeprecationWarning",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[dependency-groups]
|
|
44
|
+
dev = [
|
|
45
|
+
"pytest>=9.0.3",
|
|
46
|
+
"tox>=4.54.0",
|
|
47
|
+
]
|
|
@@ -8,7 +8,7 @@ exec(open('src/bulum/version.py').read())
|
|
|
8
8
|
setuptools.setup(
|
|
9
9
|
name="bulum",
|
|
10
10
|
version=__version__,
|
|
11
|
-
python_requires=">=3.
|
|
11
|
+
python_requires=">=3.11",
|
|
12
12
|
author="Chas Egan",
|
|
13
13
|
author_email="chas@odhydrology.com",
|
|
14
14
|
description="Open source python library for assessing hydrologic model results in Queensland",
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
import os
|
|
3
|
+
import bulum.io as bio
|
|
4
|
+
import bulum.clim as ocl
|
|
5
|
+
import bulum.utils as out
|
|
6
|
+
import pandas as pd
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Tests(unittest.TestCase):
|
|
10
|
+
|
|
11
|
+
@classmethod
|
|
12
|
+
def setUpClass(cls):
|
|
13
|
+
os.makedirs("./trash", exist_ok=True)
|
|
14
|
+
return super().setUpClass()
|
|
15
|
+
|
|
16
|
+
def test_load_data_into_df(self):
|
|
17
|
+
"""
|
|
18
|
+
The purpose of this is just to test that if I load 2 different files as
|
|
19
|
+
dataframes, and then simply put them together. I.e. does it join on the
|
|
20
|
+
dates? YES IT APPEARS TO LEFT-JOIN on the dates from the first file.
|
|
21
|
+
"""
|
|
22
|
+
df1 = bio.read_ts_csv("./src/bulum/clim/tests/Flow.out.withmissing.csv", date_format=r"%Y-%m-%d")["FRex_FR.qqm"]
|
|
23
|
+
df2 = bio.read_ts_csv("./src/bulum/clim/tests/Flow.out.RCP85_P2050_GCM8.csv", date_format=r"%Y-%m-%d")["FRex_FR.qqm"]
|
|
24
|
+
df3 = pd.DataFrame()
|
|
25
|
+
df3["x"] = df1
|
|
26
|
+
df3["y"] = df2
|
|
27
|
+
# print(df3.head())
|
|
28
|
+
# print(df3.tail())
|
|
29
|
+
self.assertTrue(len(df3) == len(df1))
|
|
30
|
+
self.assertAlmostEqual(df3.x.values[-1], 136) # these values should line up despite different starting dates for each file
|
|
31
|
+
self.assertAlmostEqual(df3.y.values[-1], 62) # these values should line up despite different starting dates for each file
|
|
32
|
+
# And now doe the same again with files loaded in the opposite order
|
|
33
|
+
df1 = bio.read_ts_csv("./src/bulum/clim/tests/Flow.out.RCP85_P2050_GCM8.csv", date_format=r"%Y-%m-%d")["FRex_FR.qqm"]
|
|
34
|
+
df2 = bio.read_ts_csv("./src/bulum/clim/tests/Flow.out.withmissing.csv", date_format=r"%Y-%m-%d")["FRex_FR.qqm"]
|
|
35
|
+
df3 = pd.DataFrame()
|
|
36
|
+
df3["x"] = df1
|
|
37
|
+
df3["y"] = df2
|
|
38
|
+
# print(df3.head())
|
|
39
|
+
# print(df3.tail())
|
|
40
|
+
self.assertTrue(len(df3) == len(df1))
|
|
41
|
+
self.assertAlmostEqual(df3.x.values[-1], 62) # these values should line up despite different starting dates for each file
|
|
42
|
+
self.assertAlmostEqual(df3.y.values[-1], 136) # these values should line up despite different starting dates for each file
|
|
43
|
+
|
|
44
|
+
def test_get_exc_transform(self):
|
|
45
|
+
"""
|
|
46
|
+
This gets transformation curves based on current & future climate datasets, and then checks that the transformation
|
|
47
|
+
curves have the same total number of points regardless of whether we use default seasons (monthly) or define 3 custom
|
|
48
|
+
seasons.
|
|
49
|
+
"""
|
|
50
|
+
current_climate = bio.read_ts_csv("./src/bulum/clim/tests/Flow.out.withmissing.csv", date_format=r"%Y-%m-%d")
|
|
51
|
+
future_climate = bio.read_ts_csv("./src/bulum/clim/tests/Flow.out.RCP85_P2050_GCM8.csv", date_format=r"%Y-%m-%d")
|
|
52
|
+
answer1 = ocl.derive_transformation_curves(current_climate["FRex_FR.qqm"], future_climate["FRex_FR.qqm"])
|
|
53
|
+
answer2 = ocl.derive_transformation_curves(current_climate["FRex_FR.qqm"], future_climate["FRex_FR.qqm"], season_start_months=[3, 7, 9])
|
|
54
|
+
total_len_1 = sum([len(a[1]) for a in answer1.values()])
|
|
55
|
+
total_len_2 = sum([len(a[1]) for a in answer2.values()])
|
|
56
|
+
self.assertEqual(total_len_1, total_len_2)
|
|
57
|
+
self.assertTrue(total_len_1 > 0)
|
|
58
|
+
|
|
59
|
+
def test_apply_transformation_curves(self):
|
|
60
|
+
current_climate = bio.read_ts_csv("./src/bulum/clim/tests/Flow.out.withmissing.csv", date_format=r"%Y-%m-%d")
|
|
61
|
+
future_climate = bio.read_ts_csv("./src/bulum/clim/tests/Flow.out.RCP85_P2050_GCM8.csv", date_format=r"%Y-%m-%d")
|
|
62
|
+
t = ocl.derive_transformation_curves(current_climate["FRex_FR.qqm"], future_climate["FRex_FR.qqm"])
|
|
63
|
+
answer = pd.DataFrame()
|
|
64
|
+
answer["RexCurrent"] = current_climate["FRex_FR.qqm"]
|
|
65
|
+
answer["RexCurrent"] = future_climate["FRex_FR.qqm"]
|
|
66
|
+
answer["Transf"] = ocl.apply_transformation_curves(t, current_climate["FRex_FR.qqm"])
|
|
67
|
+
# print(answer["Current"].sum())
|
|
68
|
+
# print(answer["Future"].sum())
|
|
69
|
+
# print(answer["Transf"].sum())
|
|
70
|
+
self.assertAlmostEqual(answer["Transf"].sum(), 4845710.795475168) # Actually haven't checked that this is the correct answer but looks plausible.
|
|
71
|
+
answer.to_csv("./trash/transformed.csv")
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""
|
|
2
2
|
Functions for reading CSVs, particularly time-series CSVs.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
import os
|
|
6
|
+
from typing import Union
|
|
7
|
+
|
|
5
8
|
import numpy as np
|
|
6
9
|
import pandas as pd
|
|
7
10
|
from bulum import utils
|
|
8
|
-
import os
|
|
9
11
|
|
|
10
12
|
na_values = ['', ' ', 'null', 'NULL', 'NAN', 'NaN', 'nan', 'NA', 'na', 'N/A' 'n/a', '#N/A', '#NA', '-NaN', '-nan']
|
|
11
13
|
|
|
@@ -20,33 +22,43 @@ def read_ts_csv(filename: str | os.PathLike, date_format=None,
|
|
|
20
22
|
|
|
21
23
|
Parameters
|
|
22
24
|
----------
|
|
23
|
-
filename : str
|
|
25
|
+
filename : str or PathLike
|
|
26
|
+
Path to the CSV file to read.
|
|
24
27
|
date_format : str, optional
|
|
25
28
|
defaults to "%d/%m/%Y" as per Fors. Other common formats include "%Y-%m-%d", "%Y/%m/%d".
|
|
26
29
|
df : pd.DataFrame, optional
|
|
27
30
|
If provided, the reader will append columns to this dataframe. Defaults to None.
|
|
28
31
|
colprefix : str, optional
|
|
29
|
-
If provided, the reader will
|
|
32
|
+
If provided, the reader will prepend this to column name. Defaults to None.
|
|
30
33
|
allow_nonnumeric : bool, optional
|
|
31
|
-
If
|
|
34
|
+
If False, raises if any column is not numeric. Default is False.
|
|
32
35
|
assert_date : bool, optional
|
|
33
|
-
If
|
|
36
|
+
If True, asserts the date index conforms to ``"%Y-%m-%d"``. Default is True.
|
|
37
|
+
|
|
38
|
+
Returns
|
|
39
|
+
-------
|
|
40
|
+
utils.TimeseriesDataframe
|
|
34
41
|
|
|
35
|
-
|
|
36
|
-
|
|
42
|
+
Raises
|
|
43
|
+
------
|
|
44
|
+
TypeError
|
|
45
|
+
If a column is not numeric and ``allow_nonnumeric`` is False.
|
|
46
|
+
ValueError
|
|
47
|
+
If the new date range does not overlap with ``df``.
|
|
37
48
|
"""
|
|
49
|
+
new_df: pd.DataFrame
|
|
38
50
|
new_df = pd.read_csv(filename, na_values=na_values, **kwargs)
|
|
39
51
|
# Date index
|
|
40
52
|
new_df.set_index(new_df.columns[0], inplace=True)
|
|
41
53
|
if assert_date:
|
|
42
|
-
new_df.index = utils.standardize_datestring_format(new_df.index)
|
|
43
|
-
new_df.index.name = "Date"
|
|
54
|
+
new_df.index = utils.standardize_datestring_format(new_df.index, as_index=True)
|
|
55
|
+
new_df.index.name = "Date" # type: ignore
|
|
44
56
|
# df = df.replace(r'^\s*$', np.nan, regex=True)
|
|
45
57
|
# Check values
|
|
46
58
|
if not allow_nonnumeric:
|
|
47
59
|
for col in new_df.columns:
|
|
48
|
-
if not np.issubdtype(new_df[col].dtype, np.number):
|
|
49
|
-
raise
|
|
60
|
+
if not np.issubdtype(new_df[col].dtype, np.number): # type: ignore
|
|
61
|
+
raise TypeError(f"ERROR: Column '{col}' is not numeric!")
|
|
50
62
|
# Rename columns if required
|
|
51
63
|
if colprefix is not None:
|
|
52
64
|
for c in new_df.columns:
|
|
@@ -55,17 +67,28 @@ def read_ts_csv(filename: str | os.PathLike, date_format=None,
|
|
|
55
67
|
if df is None:
|
|
56
68
|
df = new_df
|
|
57
69
|
else:
|
|
70
|
+
assert isinstance(df, pd.DataFrame), f"Dataframe {df=} is not a dataframe!"
|
|
58
71
|
if len(df) > 0:
|
|
59
72
|
# Check that the dates overlap
|
|
60
73
|
newdf_ends_before_df_starts = new_df.index[0] < df.index[-1]
|
|
61
74
|
df_ends_before_newdf_starts = df.index[-1] < new_df.index[0]
|
|
62
75
|
if newdf_ends_before_df_starts or df_ends_before_newdf_starts:
|
|
63
|
-
raise
|
|
76
|
+
raise ValueError("ERROR: The dates in the new dataframe do not"
|
|
77
|
+
" overlap with the existing dataframe!")
|
|
64
78
|
df = df.join(new_df, how="outer")
|
|
65
79
|
return utils.TimeseriesDataframe.from_dataframe(df)
|
|
66
80
|
|
|
67
81
|
|
|
68
|
-
def write_ts_csv(df: pd.DataFrame, filename: str,
|
|
69
|
-
|
|
70
|
-
|
|
82
|
+
def write_ts_csv(df: pd.DataFrame, filename: str, *args, **kwargs) -> None:
|
|
83
|
+
"""Write a dataframe to CSV via :meth:`pandas.DataFrame.to_csv`.
|
|
84
|
+
|
|
85
|
+
Parameters
|
|
86
|
+
----------
|
|
87
|
+
df : pd.DataFrame
|
|
88
|
+
DataFrame to write.
|
|
89
|
+
filename : str or PathLike
|
|
90
|
+
Path to the output CSV file.
|
|
91
|
+
*args, **kwargs
|
|
92
|
+
Passed through to :meth:`pandas.DataFrame.to_csv`.
|
|
93
|
+
"""
|
|
71
94
|
df.to_csv(filename, *args, **kwargs)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""
|
|
2
|
+
General use IO functions.
|
|
3
|
+
"""
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import bulum.io as bio
|
|
9
|
+
from bulum import utils
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def read(filename: str | os.PathLike, **kwargs) -> utils.TimeseriesDataframe:
|
|
13
|
+
"""Read a timeseries file, dispatching to the appropriate reader based on file extension.
|
|
14
|
+
|
|
15
|
+
Supported formats: ``.res.csv``, ``.csv``, ``.idx``, and IQQM listquan
|
|
16
|
+
outputs (e.g. ``.01d``).
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
filename : str or PathLike
|
|
21
|
+
Path to the file to read.
|
|
22
|
+
**kwargs
|
|
23
|
+
Passed through to the underlying reader function.
|
|
24
|
+
|
|
25
|
+
Returns
|
|
26
|
+
-------
|
|
27
|
+
utils.TimeseriesDataframe
|
|
28
|
+
|
|
29
|
+
Raises
|
|
30
|
+
------
|
|
31
|
+
ValueError
|
|
32
|
+
If the file extension is not recognised, or if a ``.res.csv`` file
|
|
33
|
+
cannot be parsed.
|
|
34
|
+
"""
|
|
35
|
+
if not isinstance(filename, Path):
|
|
36
|
+
filename = Path(filename)
|
|
37
|
+
filename_suffix = filename.suffix.lower()
|
|
38
|
+
|
|
39
|
+
df = None
|
|
40
|
+
if filename.name.lower().endswith(".res.csv"):
|
|
41
|
+
df = bio.read_res_csv(filename, **kwargs)
|
|
42
|
+
if df is None:
|
|
43
|
+
raise ValueError("Res csv could not be read.")
|
|
44
|
+
elif filename_suffix == ".csv":
|
|
45
|
+
df = bio.read_ts_csv(filename, **kwargs)
|
|
46
|
+
elif filename_suffix == ".idx":
|
|
47
|
+
df = bio.read_idx(filename, **kwargs)
|
|
48
|
+
elif re.search(".[0-9a-f]{2}d$", filename_suffix):
|
|
49
|
+
df = bio.read_iqqm_lqn_output(filename, **kwargs)
|
|
50
|
+
else:
|
|
51
|
+
raise ValueError(f"Unknown file extension: {filename}")
|
|
52
|
+
|
|
53
|
+
assert isinstance(df, utils.TimeseriesDataframe), \
|
|
54
|
+
"Output of `read` is not a TimeseriesDataframe."
|
|
55
|
+
return df
|