bulum 0.3.0__tar.gz → 0.3.2__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.
Files changed (58) hide show
  1. {bulum-0.3.0/src/bulum.egg-info → bulum-0.3.2}/PKG-INFO +1 -1
  2. {bulum-0.3.0 → bulum-0.3.2}/src/bulum/io/lqn_io.py +25 -13
  3. {bulum-0.3.0 → bulum-0.3.2}/src/bulum/stats/reliability_stats_class.py +114 -94
  4. bulum-0.3.2/src/bulum/stats/stochastic_data_check.py +541 -0
  5. bulum-0.3.2/src/bulum/stats/storage_level_assessment.py +836 -0
  6. {bulum-0.3.0 → bulum-0.3.2}/src/bulum/utils/dataframe_extensions.py +11 -11
  7. {bulum-0.3.0 → bulum-0.3.2}/src/bulum/utils/dataframe_functions.py +143 -39
  8. bulum-0.3.2/src/bulum/utils/datetime_functions.py +651 -0
  9. bulum-0.3.2/src/bulum/utils/interpolation.py +62 -0
  10. {bulum-0.3.0 → bulum-0.3.2}/src/bulum/version.py +1 -1
  11. {bulum-0.3.0 → bulum-0.3.2/src/bulum.egg-info}/PKG-INFO +1 -1
  12. bulum-0.3.0/src/bulum/stats/stochastic_data_check.py +0 -414
  13. bulum-0.3.0/src/bulum/stats/storage_level_assessment.py +0 -254
  14. bulum-0.3.0/src/bulum/utils/datetime_functions.py +0 -327
  15. bulum-0.3.0/src/bulum/utils/interpolation.py +0 -15
  16. {bulum-0.3.0 → bulum-0.3.2}/LICENSE.md +0 -0
  17. {bulum-0.3.0 → bulum-0.3.2}/README.md +0 -0
  18. {bulum-0.3.0 → bulum-0.3.2}/setup.cfg +0 -0
  19. {bulum-0.3.0 → bulum-0.3.2}/setup.py +0 -0
  20. {bulum-0.3.0 → bulum-0.3.2}/src/bulum/__init__.py +0 -0
  21. {bulum-0.3.0 → bulum-0.3.2}/src/bulum/clim/__init__.py +0 -0
  22. {bulum-0.3.0 → bulum-0.3.2}/src/bulum/clim/clim.py +0 -0
  23. {bulum-0.3.0 → bulum-0.3.2}/src/bulum/demo.py +0 -0
  24. {bulum-0.3.0 → bulum-0.3.2}/src/bulum/io/__init__.py +0 -0
  25. {bulum-0.3.0 → bulum-0.3.2}/src/bulum/io/csv_io.py +0 -0
  26. {bulum-0.3.0 → bulum-0.3.2}/src/bulum/io/general_io.py +0 -0
  27. {bulum-0.3.0 → bulum-0.3.2}/src/bulum/io/idx_io.py +0 -0
  28. {bulum-0.3.0 → bulum-0.3.2}/src/bulum/io/idx_io_native.py +0 -0
  29. {bulum-0.3.0 → bulum-0.3.2}/src/bulum/io/iqqm_out_reader.py +0 -0
  30. {bulum-0.3.0 → bulum-0.3.2}/src/bulum/io/res_csv_io.py +0 -0
  31. {bulum-0.3.0 → bulum-0.3.2}/src/bulum/maps/__init__.py +0 -0
  32. {bulum-0.3.0 → bulum-0.3.2}/src/bulum/maps/station_maps.py +0 -0
  33. {bulum-0.3.0 → bulum-0.3.2}/src/bulum/plots/__init__.py +0 -0
  34. {bulum-0.3.0 → bulum-0.3.2}/src/bulum/plots/altair_plots.py +0 -0
  35. {bulum-0.3.0 → bulum-0.3.2}/src/bulum/plots/ensemble_altair_plots.py +0 -0
  36. {bulum-0.3.0 → bulum-0.3.2}/src/bulum/plots/node_diagrams.py +0 -0
  37. {bulum-0.3.0 → bulum-0.3.2}/src/bulum/plots/plot_functions.py +0 -0
  38. {bulum-0.3.0 → bulum-0.3.2}/src/bulum/plots/plotly_helpers.py +0 -0
  39. {bulum-0.3.0 → bulum-0.3.2}/src/bulum/plots/tests/__init__.py +0 -0
  40. {bulum-0.3.0 → bulum-0.3.2}/src/bulum/plots/tests/test_plot_functions.py +0 -0
  41. {bulum-0.3.0 → bulum-0.3.2}/src/bulum/stats/__init__.py +0 -0
  42. {bulum-0.3.0 → bulum-0.3.2}/src/bulum/stats/aggregate_stats.py +0 -0
  43. {bulum-0.3.0 → bulum-0.3.2}/src/bulum/stats/ensemble_stats.py +0 -0
  44. {bulum-0.3.0 → bulum-0.3.2}/src/bulum/stats/negflo.py +0 -0
  45. {bulum-0.3.0 → bulum-0.3.2}/src/bulum/stats/negflo_helpers.py +0 -0
  46. {bulum-0.3.0 → bulum-0.3.2}/src/bulum/stats/swflo2s/__init__.py +0 -0
  47. {bulum-0.3.0 → bulum-0.3.2}/src/bulum/stats/swflo2s/swflo2s.py +0 -0
  48. {bulum-0.3.0 → bulum-0.3.2}/src/bulum/stoch/__init__.py +0 -0
  49. {bulum-0.3.0 → bulum-0.3.2}/src/bulum/stoch/analyse.py +0 -0
  50. {bulum-0.3.0 → bulum-0.3.2}/src/bulum/stoch/generate.py +0 -0
  51. {bulum-0.3.0 → bulum-0.3.2}/src/bulum/test_circular_import.py +0 -0
  52. {bulum-0.3.0 → bulum-0.3.2}/src/bulum/trans/__init__.py +0 -0
  53. {bulum-0.3.0 → bulum-0.3.2}/src/bulum/trans/transformers.py +0 -0
  54. {bulum-0.3.0 → bulum-0.3.2}/src/bulum/utils/__init__.py +0 -0
  55. {bulum-0.3.0 → bulum-0.3.2}/src/bulum.egg-info/SOURCES.txt +0 -0
  56. {bulum-0.3.0 → bulum-0.3.2}/src/bulum.egg-info/dependency_links.txt +0 -0
  57. {bulum-0.3.0 → bulum-0.3.2}/src/bulum.egg-info/requires.txt +0 -0
  58. {bulum-0.3.0 → bulum-0.3.2}/src/bulum.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: bulum
3
- Version: 0.3.0
3
+ Version: 0.3.2
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
@@ -1,5 +1,8 @@
1
1
  """
2
- IO functions for IQQM listquan.
2
+ IO functions for IQQM listquan output files.
3
+
4
+ This module provides functionality to read IQQM listquan output files,
5
+ which are space-separated text files containing time series data.
3
6
  """
4
7
  import os
5
8
 
@@ -9,28 +12,34 @@ import pandas as pd
9
12
  from bulum import utils
10
13
 
11
14
 
12
- def read_iqqm_lqn_output(filename, col_name=None, df=None,
13
- *, data_start_row=7) -> utils.TimeseriesDataframe:
15
+ def read_iqqm_lqn_output(filename: str | os.PathLike, col_name: str | None = None,
16
+ df: pd.DataFrame | None = None, *, data_start_row: int = 7,
17
+ dropna: bool = False) -> utils.TimeseriesDataframe:
14
18
  """
15
- Reads the output of IQQM listquan. This is a space-separated is format with
19
+ Reads the output of IQQM listquan. This is a space-separated format with
16
20
  two columns (date, value) and data starting on line 7.
17
21
 
18
22
  Parameters
19
23
  ----------
20
- filename
24
+ filename : str or :class:`os.PathLike`
21
25
  Path to the file to be read.
22
- col_name : optional
26
+ col_name : str, optional
23
27
  If supplied, sets the name for the resulting output column, otherwise
24
- uses the filename.
25
- df : DataFrame, optional
26
- If supplied, joins the output to `df`.
28
+ uses the filename. Default is None.
29
+ df : :class:`pandas.DataFrame`, optional
30
+ If supplied, joins the output to `df`. (Does not modify `df`.)
31
+ Default is None.
27
32
  data_start_row : int, optional
28
33
  Optionally specify the start row; may be useful for reading other IQQM
29
- TEXT outputs.
34
+ TEXT outputs. Default is 7.
35
+ dropna : bool, optional
36
+ Remove NA values from dataframe before reading. Default is False.
30
37
 
31
38
  Returns
32
39
  -------
33
- utils.TimeseriesDataframe
40
+ :class:`~bulum.utils.TimeseriesDataframe`
41
+ A time series dataframe containing the parsed data with standardized
42
+ date index and the specified column name.
34
43
  """
35
44
  # If no df was supplied, instantiate a new one
36
45
  if df is None:
@@ -42,11 +51,14 @@ def read_iqqm_lqn_output(filename, col_name=None, df=None,
42
51
  temp = pd.read_csv(filename, skiprows=(data_start_row-2),
43
52
  sep=r'\s+', names=["Date", col_name], header=None)
44
53
  # temp = utils.set_index_dt(temp, format='%d/%m/%Y')
54
+ if dropna:
55
+ temp = temp.dropna()
45
56
  temp.set_index(temp.columns[0], inplace=True)
46
- temp.index = utils.standardize_datestring_format(temp.index)
47
- temp.index.name = "Date"
57
+ temp.index = utils.standardize_datestring_format(temp.index) # type: ignore
58
+ temp.index.name = "Date" # type: ignore
48
59
  temp = temp.replace(r'^\s*$', np.nan, regex=True)
49
60
  df = df.join(temp, how="outer").sort_index()
61
+
50
62
  # TODO: THERE IS NO GUARANTEE THAT THE DATES OVERLAP, THEREFORE WE MAY END UP WITH A DATAFRAME WITH INCOMPLETE DATES
51
63
  # TODO: I SHOULD MAKE DEFAULT BEHAVIOUR AUTO-DETECT FORMAT DEPENDING ON *TYPE* AND *LOCATION* OF DELIMIT CHARS
52
64
  # TODO: In the meantime we use the below to assert that the format of the resulting df meets our minimum standards.
@@ -8,8 +8,10 @@ from bulum import utils
8
8
 
9
9
 
10
10
  class Reliability:
11
- """
12
- Reliability of water supply statistics generator.
11
+ """Reliability of water supply statistics generator.
12
+
13
+ This class provides methods to calculate reliability statistics comparing
14
+ water supply against demand for different timescales (monthly, annual).
13
15
  """
14
16
 
15
17
  def __init__(self, demand: Union[pd.Series, list, float, int], supply: pd.Series,
@@ -17,17 +19,17 @@ class Reliability:
17
19
  demand_type: Literal["total", "daily_constant"] = "total",
18
20
  ignore_leap_years=False, quiet=False) -> None:
19
21
  """Initialise Reliability class. Functions available are
20
- - `Reliability.DemandTS`,
22
+ - `Reliability.ReliabilityTS`,
21
23
  - `Reliability.MonthlyReliability`, and
22
24
  - `Reliability.AnnualReliability`.
23
-
25
+
24
26
  Parameters
25
27
  ----------
26
- demand : Union[pd.Series,list,float,int]
28
+ demand : :class:`pandas.Series` or list or float or int
27
29
  Demand timeseries with date string as index. Alternatively, a list
28
30
  of monthly values or single demand which will be disaggregated
29
31
  according to other input parameters.
30
- supply : pd.Series
32
+ supply : :class:`pandas.Series`
31
33
  Daily supply timeseries with date as index.
32
34
  demand_timescale : Literal["daily","monthly","yearly"], optional
33
35
  If a float or int demand is provided, which timescale does it apply
@@ -42,38 +44,49 @@ class Reliability:
42
44
  days in Feb, and value for the 29th Feb will be equal to the 28th of
43
45
  Feb value. Defaults to False.
44
46
 
47
+ Raises
48
+ ------
49
+ TypeError
50
+ If demand is not one of :class:`~pandas.Series`, list, float, or int.
51
+ If supply is not a :class:`~pandas.Series`.
52
+ If ignore_leap_years is not a boolean.
53
+ ValueError
54
+ If demand_type is not "total" or "daily_constant".
55
+ If demand_timescale is not "daily", "monthly", or "yearly".
56
+ If demand list has fewer than 12 elements.
57
+
45
58
  """
46
59
 
47
60
  def maybe_print(string):
48
61
  if not quiet:
49
62
  print(string)
50
63
 
51
- if type(demand) not in [pd.Series,list,float,int]:
52
- raise Exception("Demand must be one of timeseries, monthly list or single demand.")
53
-
54
- if type(supply) != pd.Series:
55
- raise Exception("Supply must be a single column of a date-indexed dataframe (pd.Series).")
56
-
57
- if type(demand) == pd.Series:
64
+ if not isinstance(demand, (pd.Series, list, float, int)):
65
+ raise TypeError("Demand must be one of timeseries, monthly list or single demand.")
66
+
67
+ if not isinstance(supply, pd.Series):
68
+ raise TypeError("Supply must be a single column of a date-indexed dataframe (pd.Series).")
69
+
70
+ if isinstance(demand, pd.Series):
58
71
  maybe_print("Comparing provided demand timeseries with supply timeseries.")
59
72
  state = "ts"
60
73
 
61
- if type(demand) != pd.Series:
62
- if demand_type not in ["total","daily_constant"]:
63
- raise Exception("demand_type must be one of \"total\" or \"daily_constant\".")
64
-
65
- if demand_timescale not in ["daily","monthly","yearly"]:
66
- raise Exception("demand_timescale must be one of \"daily\", \"monthly\" or \"yearly\".")
67
-
74
+ if not isinstance(demand, pd.Series):
75
+ if demand_type not in ["total", "daily_constant"]:
76
+ raise ValueError("demand_type must be one of \"total\" or \"daily_constant\".")
77
+
78
+ if demand_timescale not in ["daily", "monthly", "yearly"]:
79
+ raise ValueError("demand_timescale must be one of \"daily\", \"monthly\" or \"yearly\".")
80
+
68
81
  if ignore_leap_years not in [True, False]:
69
- raise Exception(f"ignore_leap_years must be one of {True} or {False}.")
82
+ raise TypeError(f"ignore_leap_years must be one of {True} or {False}.")
70
83
 
71
84
  lookup_leap = {True: 28, False: 29}
72
85
 
73
- if type(demand) is list:
74
- if len(demand)<12:
75
- raise Exception("Monthly demand list must have a length of 12.")
76
-
86
+ if isinstance(demand, list):
87
+ if len(demand) < 12:
88
+ raise ValueError("Monthly demand list must have a length of 12.")
89
+
77
90
  maybe_print("Comparing list of monthly demands with supply timeseries. demand_timescale parameter is unused.")
78
91
 
79
92
  if demand_type == "daily_constant":
@@ -85,7 +98,7 @@ class Reliability:
85
98
  maybe_print(f"Leap years will be disaggregated assuming {lookup_leap[ignore_leap_years]} days in February.")
86
99
  state = "monthly_total_list"
87
100
 
88
- if type(demand) in [float, int]:
101
+ if isinstance(demand, (float, int)):
89
102
  if not quiet:
90
103
  maybe_print("Comparing provided demand with supply timeseries.")
91
104
  if (demand_timescale == "daily") or (demand_type == "daily_constant"):
@@ -97,86 +110,94 @@ class Reliability:
97
110
  maybe_print("demand_type and ignore_leap_years parameters are unused.")
98
111
  if (demand_type == "daily_constant") and (demand_timescale != "daily"):
99
112
  maybe_print("demand_timescale and ignore_leap_years parameters are unused.")
100
- state="daily_constant"
113
+ state = "daily_constant"
101
114
  else:
102
115
  maybe_print(f"Assumed that {demand} ML is the total demand over the {demand_timescale} timescale. {demand} ML {demand_timescale} will be disaggregated to ML/d.")
103
116
  maybe_print(f"Leap years will be disaggregated assuming {lookup_leap[ignore_leap_years]} days in February.")
104
117
  if demand_timescale == "yearly":
105
- state="yearly_total"
118
+ state = "yearly_total"
106
119
  if demand_timescale == "monthly":
107
- state="monthly_total"
108
-
109
- self.demand=demand
110
- self.supply=supply
111
- self.demand_timescale=demand_timescale
112
- self.demand_type=demand_type
113
- self.ignore_leap_years=ignore_leap_years
114
- self.state=state
115
-
116
- def ReliabilityTS(self, wy_month):
117
- """Returns demand as a timeseries for input to reliability statistics.
120
+ state = "monthly_total"
121
+
122
+ self.demand = demand
123
+ self.supply = supply
124
+ self.demand_timescale = demand_timescale
125
+ self.demand_type = demand_type
126
+ self.ignore_leap_years = ignore_leap_years
127
+ self.state = state
128
+
129
+ def ReliabilityTS(self, wy_month: int) -> pd.Series:
130
+ """Return demand as a timeseries for input to reliability statistics.
131
+
118
132
  Matches date range of supply timeseries input.
119
133
 
134
+ Parameters
135
+ ----------
136
+ wy_month : int
137
+ Water year start month
138
+
120
139
  Returns
121
140
  -------
122
- pd.Series
141
+ :class:`pandas.Series`
123
142
  Demand timeseries for input to reliability stats
124
143
  """
125
144
  # If provided demand is a timeseries, just return timeseries.
126
- if self.state=="ts":
127
- common_dates=np.intersect1d(self.demand.index,self.supply.index)
128
- demand_ts=self.demand[common_dates]
129
- self.supply=self.supply[common_dates]
145
+ if self.state == "ts":
146
+ common_dates = np.intersect1d(self.demand.index, self.supply.index)
147
+ demand_ts = self.demand[common_dates]
148
+ self.supply = self.supply[common_dates]
130
149
  return demand_ts
131
150
 
132
151
  else:
133
152
  # Copy date range of supply TS
134
- demand_ts=self.supply.copy(deep=True)
153
+ demand_ts = self.supply.copy(deep=True).astype(float)
135
154
 
136
155
  # Overwrite demand_ts with constant daily demand.
137
- if self.state=="daily_constant":
138
- demand_ts[:]=self.demand
156
+ if self.state == "daily_constant":
157
+ demand_ts[:] = self.demand
139
158
  return demand_ts
140
-
159
+
141
160
  # Overwrite demand_ts with respective month constant daily demand
142
- if self.state=="monthly_constant_list":
143
- month_list=list(utils.get_month(demand_ts.index))
144
- demand_ts[:]=[self.demand[x-1] for x in month_list]
161
+ if self.state == "monthly_constant_list":
162
+ month_list = list(utils.get_month(demand_ts.index))
163
+ demand_ts[:] = [self.demand[x-1] for x in month_list]
145
164
  return demand_ts
146
165
 
147
166
  # Overwrite demand_ts with respective total month demand disaggregated to daily.
148
- if self.state=="monthly_total_list":
149
- year_month=utils.get_year_and_month(demand_ts.index)
150
- if self.ignore_leap_years == False:
151
- demand_ts[:]=[self.demand[int(x[5:7])-1]/calendar.monthrange(int(x[0:4]),int(x[5:7]))[1] for x in year_month] # If using leap years, divide by 28 or 29 Feb days depending on year
167
+ if self.state == "monthly_total_list":
168
+ year_month = utils.get_year_and_month(demand_ts.index)
169
+ if not self.ignore_leap_years:
170
+ demand_ts[:] = [self.demand[int(x[5:7])-1]/calendar.monthrange(int(x[0:4]), int(x[5:7]))[1]
171
+ for x in year_month] # If using leap years, divide by 28 or 29 Feb days depending on year
152
172
  else:
153
- demand_ts[:]=[self.demand[int(x[5:7])-1]/calendar.monthrange(2002,int(x[5:7]))[1] for x in year_month] # If not using leap years, only divide by 28 Feb days
173
+ demand_ts[:] = [self.demand[int(x[5:7])-1]/calendar.monthrange(2002, int(x[5:7]))[1] for x in year_month] # If not using leap years, only divide by 28 Feb days
154
174
  return demand_ts
155
-
175
+
156
176
  # Overwrite demand_ts with total month demand disaggregated to daily.
157
- if self.state=="monthly_total":
158
- year_month=utils.get_year_and_month(demand_ts.index)
159
- if self.ignore_leap_years == False:
160
- demand_ts[:]=[self.demand/calendar.monthrange(int(x[0:4]),int(x[5:7]))[1] for x in year_month] # If using leap years, divide by 28 or 29 Feb days depending on year
177
+ if self.state == "monthly_total":
178
+ year_month = utils.get_year_and_month(demand_ts.index)
179
+ if not self.ignore_leap_years:
180
+ demand_ts[:] = [self.demand/calendar.monthrange(int(x[0:4]), int(x[5:7]))[1] for x in year_month] # If using leap years, divide by 28 or 29 Feb days depending on year
161
181
  else:
162
- demand_ts[:]=[self.demand/calendar.monthrange(2002,int(x[5:7]))[1] for x in year_month] # If not using leap years, only divide by 28 Feb days
182
+ demand_ts[:] = [self.demand/calendar.monthrange(2002, int(x[5:7]))[1] for x in year_month] # If not using leap years, only divide by 28 Feb days
163
183
  return demand_ts
164
184
 
165
185
  # Overwrite demand_ts with total annual demand disaggregated to daily.
166
- if self.state=="yearly_total":
167
- if self.ignore_leap_years == False:
168
- wy = utils.get_wy(demand_ts.index,wy_month,using_end_year=False)
169
- if wy_month>2:
170
- demand_ts[:]=[self.demand/(365+calendar.isleap(x+1)) for x in wy] # If using leap years, divide by 365 or 366 days depending on year. If wy_month > 2, leap day will occur in the next calendar year
186
+ if self.state == "yearly_total":
187
+ if not self.ignore_leap_years:
188
+ wy = utils.get_wy(demand_ts.index, wy_month, using_end_year=False)
189
+ if wy_month > 2:
190
+ # If using leap years, divide by 365 or 366 days depending on year. If wy_month > 2, leap day will occur in the next calendar year
191
+ demand_ts[:] = [self.demand/(365+calendar.isleap(x+1)) for x in wy]
171
192
  else:
172
- demand_ts[:]=[self.demand/(365+calendar.isleap(x)) for x in wy] # If using leap years, divide by 365 or 366 days depending on year. If wy_month <= 2, leap day will occur in this calendar year
193
+ # If using leap years, divide by 365 or 366 days depending on year. If wy_month <= 2, leap day will occur in this calendar year
194
+ demand_ts[:] = [self.demand/(365+calendar.isleap(x)) for x in wy]
173
195
  else:
174
- demand_ts[:]=self.demand/365 # If not using leap years, only ever divide by 365
196
+ demand_ts[:] = self.demand/365 # If not using leap years, only ever divide by 365
175
197
  return demand_ts
176
198
 
177
-
178
- def MonthlyReliability(self, tol=1, allow_part_months=False, wy_month=7):
179
- """Returns the monthly reliability statistic for a daily timeseries of demand and supply.
199
+ def MonthlyReliability(self, tol: float = 1, allow_part_months: bool = False, wy_month: int = 7) -> float:
200
+ """Calculate the monthly reliability statistic for daily timeseries of demand and supply.
180
201
 
181
202
  Parameters
182
203
  ----------
@@ -194,37 +215,36 @@ class Reliability:
194
215
 
195
216
  """
196
217
  # Pass input to reliability_ts first and enforce common date range.
197
- demand_ts=self.ReliabilityTS(wy_month)
218
+ demand_ts = self.ReliabilityTS(wy_month)
198
219
 
199
220
  # Collate timeseries data to monthly
200
221
  if allow_part_months:
201
- dem_month=demand_ts.groupby(utils.get_year_and_month(demand_ts.index)).sum()
202
- sup_month=self.supply.groupby(utils.get_year_and_month(self.supply.index)).sum()
222
+ dem_month = demand_ts.groupby(utils.get_year_and_month(demand_ts.index)).sum()
223
+ sup_month = self.supply.groupby(utils.get_year_and_month(self.supply.index)).sum()
203
224
  else:
204
- if self.supply.index[0][8:10]=="01": #0123-56-89
205
- #First date is the start of a month; use this as start date.
206
- start_date=self.supply.index[0]
225
+ if self.supply.index[0][8:10] == "01": # 0123-56-89
226
+ # First date is the start of a month; use this as start date.
227
+ start_date = self.supply.index[0]
207
228
  else:
208
- #Start on the first date of the next month
229
+ # Start on the first date of the next month
209
230
  start_date = utils.get_next_month_start(self.supply.index[0])
210
231
  if self.supply.index[-1] == utils.get_this_month_end(self.supply.index[-1]):
211
- end_date=self.supply.index[-1]
232
+ end_date = self.supply.index[-1]
212
233
  else:
213
- end_date=utils.get_prev_month_end(self.supply.index[-1])
234
+ end_date = utils.get_prev_month_end(self.supply.index[-1])
214
235
  demand_trim = demand_ts[start_date:end_date]
215
236
  supply_trim = self.supply[start_date:end_date]
216
237
  year_month = utils.get_year_and_month(demand_trim.index)
217
- dem_month=demand_trim.groupby(year_month).sum()
218
- sup_month=supply_trim.groupby(year_month).sum()
238
+ dem_month = demand_trim.groupby(year_month).sum()
239
+ sup_month = supply_trim.groupby(year_month).sum()
219
240
 
220
241
  # Check whether demand is met within given tolerance (to 6 decimal places) and express as a percentage
221
- rel=np.where((sup_month-(dem_month*tol)<-0.000001),0,1).sum()/sup_month.count()
242
+ rel = np.where((sup_month-(dem_month*tol) < -0.000001), 0, 1).sum()/sup_month.count()
222
243
 
223
244
  return rel
224
245
 
225
-
226
- def AnnualReliability(self, tol=1, wy_month=7, allow_part_years=False):
227
- """Returns the annual reliability statistic for a daily timeseries of demand and supply.
246
+ def AnnualReliability(self, tol: float = 1, wy_month: int = 7, allow_part_years: bool = False) -> float:
247
+ """Calculate the annual reliability statistic for daily timeseries of demand and supply.
228
248
 
229
249
  Parameters
230
250
  ----------
@@ -240,10 +260,10 @@ class Reliability:
240
260
  float
241
261
  Annual reliability (%)
242
262
  """
243
-
263
+
244
264
  # Pass input to reliability_ts first and enforce common date range.
245
- demand_ts=self.ReliabilityTS(wy_month)
246
-
265
+ demand_ts = self.ReliabilityTS(wy_month)
266
+
247
267
  # Collate timeseries data to annual
248
268
  if not allow_part_years:
249
269
  demand_ts = utils.crop_to_wy(demand_ts, wy_month)
@@ -252,11 +272,11 @@ class Reliability:
252
272
  supply = self.supply
253
273
  if (len(demand_ts) == 0):
254
274
  return np.nan
255
- dem_annual=demand_ts.groupby(utils.get_wy(demand_ts.index, wy_month)).sum()
256
- sup_annual=supply.groupby(utils.get_wy(supply.index, wy_month)).sum()
257
- no_years=sup_annual.count()
275
+ dem_annual = demand_ts.groupby(utils.get_wy(demand_ts.index, wy_month)).sum()
276
+ sup_annual = supply.groupby(utils.get_wy(supply.index, wy_month)).sum()
277
+ no_years = sup_annual.count()
258
278
 
259
279
  # Check whether demand is met within given tolerance (to 6 decimal places) and express as a percentage
260
- rel=np.where((sup_annual-(dem_annual*tol)<-0.000001),0,1).sum()/no_years
280
+ rel = np.where((sup_annual-(dem_annual*tol) < -0.000001), 0, 1).sum()/no_years
261
281
 
262
282
  return rel