loone-data-prep 1.3.0__py3-none-any.whl → 1.3.1__py3-none-any.whl
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.
- loone_data_prep/dbhydro_insights.py +195 -0
- loone_data_prep/flow_data/S65E_total.py +57 -57
- loone_data_prep/flow_data/forecast_bias_correction.py +1 -1
- loone_data_prep/flow_data/get_forecast_flows.py +19 -105
- loone_data_prep/flow_data/get_inflows.py +18 -8
- loone_data_prep/flow_data/get_outflows.py +16 -7
- loone_data_prep/flow_data/hydro.py +62 -91
- loone_data_prep/utils.py +243 -30
- loone_data_prep/water_level_data/get_all.py +52 -44
- loone_data_prep/water_level_data/hydro.py +49 -68
- loone_data_prep/water_quality_data/get_inflows.py +69 -27
- loone_data_prep/water_quality_data/get_lake_wq.py +130 -33
- loone_data_prep/water_quality_data/wq.py +114 -88
- loone_data_prep/weather_data/get_all.py +5 -3
- loone_data_prep/weather_data/weather.py +117 -180
- {loone_data_prep-1.3.0.dist-info → loone_data_prep-1.3.1.dist-info}/METADATA +2 -8
- {loone_data_prep-1.3.0.dist-info → loone_data_prep-1.3.1.dist-info}/RECORD +20 -19
- {loone_data_prep-1.3.0.dist-info → loone_data_prep-1.3.1.dist-info}/WHEEL +1 -1
- {loone_data_prep-1.3.0.dist-info → loone_data_prep-1.3.1.dist-info}/licenses/LICENSE +0 -0
- {loone_data_prep-1.3.0.dist-info → loone_data_prep-1.3.1.dist-info}/top_level.txt +0 -0
|
@@ -1,116 +1,68 @@
|
|
|
1
1
|
import sys
|
|
2
2
|
from datetime import datetime
|
|
3
|
-
from glob import glob
|
|
4
3
|
from retry import retry
|
|
5
|
-
import os
|
|
6
4
|
import pandas as pd
|
|
7
|
-
from
|
|
8
|
-
from rpy2.rinterface_lib.embedded import RRuntimeError
|
|
5
|
+
from loone_data_prep.utils import df_replace_missing_with_nan, get_dbhydro_api
|
|
9
6
|
|
|
10
7
|
|
|
11
8
|
DATE_NOW = datetime.now().strftime("%Y-%m-%d")
|
|
12
9
|
|
|
13
10
|
|
|
14
|
-
@retry(
|
|
11
|
+
@retry(Exception, tries=5, delay=15, max_delay=60, backoff=2)
|
|
15
12
|
def get(
|
|
16
13
|
workspace: str,
|
|
17
14
|
dbkey: str,
|
|
18
15
|
date_min: str = "1990-01-01",
|
|
19
|
-
date_max: str = DATE_NOW
|
|
16
|
+
date_max: str = DATE_NOW,
|
|
17
|
+
station: str | None = None
|
|
20
18
|
) -> None:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
data <- get_hydro(dbkey = "{dbkey}", date_min = "{date_min}", date_max = "{date_max}", raw = TRUE)
|
|
30
|
-
|
|
31
|
-
# Check if data is empty or contains only the "date" column
|
|
32
|
-
if (ncol(data) <= 1) {{
|
|
33
|
-
cat("No data found for dbkey", "{dbkey}", "Skipping to the next dbkey.\n")
|
|
34
|
-
}}
|
|
35
|
-
|
|
36
|
-
# Give data.frame correct column names so it can be cleaned using the clean_hydro function
|
|
37
|
-
colnames(data) <- c("station", "dbkey", "date", "data.value", "qualifer", "revision.date")
|
|
38
|
-
|
|
39
|
-
# Check if the data.frame has any rows
|
|
40
|
-
if (nrow(data) == 0)
|
|
41
|
-
{{
|
|
42
|
-
# No data given back, It's possible that the dbkey has reached its end date.
|
|
43
|
-
print(paste("Empty data.frame returned for dbkey", "{dbkey}", "It's possible that the dbkey has reached its end date. Skipping to the next dbkey."))
|
|
44
|
-
return(list(success = FALSE, dbkey = "{dbkey}"))
|
|
45
|
-
}}
|
|
46
|
-
|
|
47
|
-
# Add a type and units column to data so it can be cleaned using the clean_hydro function
|
|
48
|
-
data$type <- "FLOW"
|
|
49
|
-
data$units <- "cfs"
|
|
50
|
-
|
|
51
|
-
# Get the station
|
|
52
|
-
station <- data$station[1]
|
|
53
|
-
|
|
54
|
-
# Clean the data.frame
|
|
55
|
-
data <- clean_hydro(data)
|
|
56
|
-
|
|
57
|
-
# Multiply all columns except "date" column by 0.0283168466 * 86400 to convert Flow rate from cfs to m³/day
|
|
58
|
-
data[, -1] <- data[, -1] * (0.0283168466 * 86400)
|
|
59
|
-
|
|
60
|
-
# Drop the " _FLOW_cfs" column
|
|
61
|
-
data <- data %>% select(-` _FLOW_cfs`)
|
|
62
|
-
|
|
63
|
-
# Sort the data by date
|
|
64
|
-
data <- data[order(data$date), ]
|
|
65
|
-
|
|
66
|
-
# Get the filename for the output CSV file
|
|
67
|
-
filename <- paste0(station, "_FLOW", "_{dbkey}_cmd.csv")
|
|
68
|
-
|
|
69
|
-
# Save data to a CSV file
|
|
70
|
-
write.csv(data, file = paste0("{workspace}/", filename))
|
|
71
|
-
|
|
72
|
-
# Print a message indicating the file has been saved
|
|
73
|
-
cat("CSV file", filename, "has been saved.\n")
|
|
74
|
-
|
|
75
|
-
# Add a delay between requests
|
|
76
|
-
Sys.sleep(1) # Wait for 1 second before the next iteration
|
|
77
|
-
|
|
78
|
-
# Return the station and dbkey to the python code
|
|
79
|
-
list(success = TRUE, station = station, dbkey = "{dbkey}")
|
|
80
|
-
}}
|
|
19
|
+
"""Fetches daily flow data from DBHYDRO and saves it to a CSV file.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
workspace (str): Path to the workspace directory where data will be saved.
|
|
23
|
+
dbkey (str): The DBHYDRO database key for the station.
|
|
24
|
+
date_min (str): Minimum date for data retrieval in 'YYYY-MM-DD' format.
|
|
25
|
+
date_max (str): Maximum date for data retrieval in 'YYYY-MM-DD' format.
|
|
26
|
+
station (str | None): The station name. If None, the station name will be fetched from DBHYDRO.
|
|
81
27
|
"""
|
|
82
|
-
|
|
83
|
-
|
|
28
|
+
# Get a DbHydroApi instance
|
|
29
|
+
api = get_dbhydro_api()
|
|
84
30
|
|
|
85
|
-
#
|
|
86
|
-
|
|
31
|
+
# Get the daily data from DbHydro
|
|
32
|
+
response = api.get_daily_data([dbkey], 'id', date_min, date_max, 'NGVD29', False)
|
|
87
33
|
|
|
88
34
|
# Check for failure
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if not success:
|
|
35
|
+
if not response.has_data():
|
|
92
36
|
return
|
|
93
37
|
|
|
94
|
-
# Get the station name for
|
|
95
|
-
station
|
|
38
|
+
# Get the station name for _reformat_flow_df()
|
|
39
|
+
if station is None:
|
|
40
|
+
station = response.get_site_codes()[0]
|
|
41
|
+
|
|
42
|
+
# Get the data as a dataframe
|
|
43
|
+
df = response.to_dataframe(True)
|
|
96
44
|
|
|
97
|
-
#
|
|
98
|
-
|
|
45
|
+
# Replace flagged 0 values and -99999.0 with NaN
|
|
46
|
+
df = df_replace_missing_with_nan(df)
|
|
47
|
+
|
|
48
|
+
# Convert flow from cfs to cmd
|
|
49
|
+
df['value'] = df['value'] * (0.0283168466 * 86400)
|
|
50
|
+
|
|
51
|
+
# Prepare the dataframe to be reformatted into the expected layout
|
|
52
|
+
df.reset_index(inplace=True)
|
|
53
|
+
df.rename(columns={'datetime': 'date', 'value': f'{station}_FLOW_cmd'}, inplace=True)
|
|
54
|
+
|
|
55
|
+
# Reformat the flow df to the expected layout
|
|
56
|
+
df = _reformat_flow_df(df, station)
|
|
99
57
|
|
|
100
58
|
# Check if the station name contains a space
|
|
101
|
-
if
|
|
59
|
+
if ' ' in station:
|
|
102
60
|
# Replace space with underscore in the station name
|
|
103
61
|
station_previous = station
|
|
104
|
-
station = station.replace(
|
|
105
|
-
|
|
106
|
-
# Rename the file
|
|
107
|
-
os.rename(f"{workspace}/{station_previous}_FLOW_{dbkey}_cmd.csv", f"{workspace}/{station}_FLOW_{dbkey}_cmd.csv")
|
|
62
|
+
station = station.replace(' ', '_')
|
|
108
63
|
|
|
109
|
-
#
|
|
110
|
-
|
|
111
|
-
df = pd.read_csv(file, index_col=False)
|
|
112
|
-
df.columns = df.columns.astype(str).str.replace("_cfs", "_cmd")
|
|
113
|
-
df.to_csv(file, index=False)
|
|
64
|
+
# Write the data to a CSV file
|
|
65
|
+
df.to_csv(f'{workspace}/{station}_FLOW_{dbkey}_cmd.csv', index=True)
|
|
114
66
|
|
|
115
67
|
|
|
116
68
|
def _reformat_flow_file(workspace:str, station: str, dbkey: str):
|
|
@@ -130,8 +82,27 @@ def _reformat_flow_file(workspace:str, station: str, dbkey: str):
|
|
|
130
82
|
# Read in the data
|
|
131
83
|
df = pd.read_csv(f"{workspace}/{station}_FLOW_{dbkey}_cmd.csv")
|
|
132
84
|
|
|
85
|
+
# Reformat the data
|
|
86
|
+
df = _reformat_flow_df(df, station)
|
|
87
|
+
|
|
88
|
+
# Write the updated data back to the file
|
|
89
|
+
df.to_csv(f"{workspace}/{station}_FLOW_{dbkey}_cmd.csv")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _reformat_flow_df(df: pd.DataFrame, station: str) -> pd.DataFrame:
|
|
93
|
+
'''
|
|
94
|
+
Reformat the flow data file to the expected layout.
|
|
95
|
+
Converts the format of the dates in the file to 'YYYY-MM-DD' then sorts the data by date.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
df (pd.DataFrame): The dataframe containing the flow data.
|
|
99
|
+
station (str): The station name.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
pd.DataFrame: The reformatted dataframe.
|
|
103
|
+
'''
|
|
133
104
|
# Grab only the columns we need
|
|
134
|
-
df = df[['date', f'{station}
|
|
105
|
+
df = df[['date', f'{station}_FLOW_cmd']].copy()
|
|
135
106
|
|
|
136
107
|
# Convert date column to datetime
|
|
137
108
|
df['date'] = pd.to_datetime(df['date'], format='%d-%b-%Y')
|
|
@@ -143,10 +114,10 @@ def _reformat_flow_file(workspace:str, station: str, dbkey: str):
|
|
|
143
114
|
df.reset_index(drop=True, inplace=True)
|
|
144
115
|
|
|
145
116
|
# Drop rows that are missing values for both the date and value columns
|
|
146
|
-
df = df.drop(df[(df['date'].isna()) & (df[f'{station}
|
|
117
|
+
df = df.drop(df[(df['date'].isna()) & (df[f'{station}_FLOW_cmd'].isna())].index)
|
|
147
118
|
|
|
148
|
-
#
|
|
149
|
-
df
|
|
119
|
+
# Return the updated dataframe
|
|
120
|
+
return df
|
|
150
121
|
|
|
151
122
|
|
|
152
123
|
if __name__ == "__main__":
|
loone_data_prep/utils.py
CHANGED
|
@@ -5,17 +5,14 @@ import math
|
|
|
5
5
|
from glob import glob
|
|
6
6
|
from calendar import monthrange
|
|
7
7
|
import traceback
|
|
8
|
+
from typing import Literal, Tuple
|
|
8
9
|
import numpy as np
|
|
9
10
|
import pandas as pd
|
|
10
11
|
from retry import retry
|
|
11
12
|
from scipy.optimize import fsolve
|
|
12
13
|
from scipy import interpolate
|
|
13
|
-
from
|
|
14
|
-
from
|
|
15
|
-
StrVector as rpy2StrVector,
|
|
16
|
-
DataFrame as rpy2DataFrame,
|
|
17
|
-
)
|
|
18
|
-
from rpy2.rinterface_lib.embedded import RRuntimeError
|
|
14
|
+
from dbhydro_py import DbHydroApi
|
|
15
|
+
from loone_data_prep.dbhydro_insights import get_dbhydro_station_metadata, get_dbhydro_continuous_timeseries_metadata, get_dbhydro_water_quality_metadata
|
|
19
16
|
|
|
20
17
|
|
|
21
18
|
DEFAULT_STATION_IDS = ["L001", "L005", "L006", "LZ40"]
|
|
@@ -224,7 +221,7 @@ DEFAULT_EXPFUNC_NITROGEN_CONSTANTS = {
|
|
|
224
221
|
"S135_P": {"a": 3.09890183766129, "b": 0.657896838486496},
|
|
225
222
|
}
|
|
226
223
|
|
|
227
|
-
@retry(
|
|
224
|
+
@retry(Exception, tries=5, delay=15, max_delay=60, backoff=2)
|
|
228
225
|
def get_dbkeys(
|
|
229
226
|
station_ids: list,
|
|
230
227
|
category: str,
|
|
@@ -232,9 +229,8 @@ def get_dbkeys(
|
|
|
232
229
|
stat: str,
|
|
233
230
|
recorder: str,
|
|
234
231
|
freq: str = "DA",
|
|
235
|
-
detail_level: str = "dbkey",
|
|
236
232
|
*args: str,
|
|
237
|
-
) ->
|
|
233
|
+
) -> list[str]:
|
|
238
234
|
"""Get dbkeys. See DBHydroR documentation for more information:
|
|
239
235
|
https://cran.r-project.org/web/packages/dbhydroR/dbhydroR.pdf
|
|
240
236
|
|
|
@@ -245,27 +241,68 @@ def get_dbkeys(
|
|
|
245
241
|
stat (str): Statistic of data to retrieve.
|
|
246
242
|
recorder (str): Recorder of data to retrieve.
|
|
247
243
|
freq (str, optional): Frequency of data to retrieve. Defaults to "DA".
|
|
248
|
-
detail_level (str, optional): Detail level of data to retrieve. Defaults to "dbkey". Options are "dbkey",
|
|
249
|
-
"summary", or "full".
|
|
250
244
|
|
|
251
245
|
Returns:
|
|
252
|
-
|
|
246
|
+
list[str]: dbkeys info for the specified parameters.
|
|
253
247
|
"""
|
|
248
|
+
# Retrieve the metadata for the specified parameters
|
|
249
|
+
metadata = get_dbhydro_continuous_timeseries_metadata(station_ids, [category], [param], [stat], [recorder], [freq])
|
|
250
|
+
|
|
251
|
+
# A set to hold the dbkeys to avoid duplicates
|
|
252
|
+
dbkeys = set()
|
|
253
|
+
|
|
254
|
+
# No data returned from API
|
|
255
|
+
if metadata is None:
|
|
256
|
+
return list(dbkeys)
|
|
257
|
+
|
|
258
|
+
# Get the dbkeys from the metadata
|
|
259
|
+
for result in metadata['results']:
|
|
260
|
+
dbkeys.add(result['timeseriesId'])
|
|
261
|
+
|
|
262
|
+
# Return the dbkeys as a list
|
|
263
|
+
return list(dbkeys)
|
|
254
264
|
|
|
255
|
-
station_ids_str = '"' + '", "'.join(station_ids) + '"'
|
|
256
265
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
library(dbhydroR)
|
|
266
|
+
def get_stations_latitude_longitude(station_ids: list[str]):
|
|
267
|
+
"""Gets the latitudes and longitudes of the given stations.
|
|
260
268
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
269
|
+
Args:
|
|
270
|
+
station_ids (list[str]): The ids of the stations to get the
|
|
271
|
+
latitudes/longitudes of. Example: ['L OKEE', 'FISHP']
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
(dict[str, tuple[numpy.float64, numpy.float64]]): A dictionary of
|
|
275
|
+
format dict<station_id:(latitude,longitude)>
|
|
267
276
|
|
|
268
|
-
|
|
277
|
+
If a station's latitude/longitude fails to download then its station_id
|
|
278
|
+
won't be a key in the returned dictionary.
|
|
279
|
+
"""
|
|
280
|
+
# Dictionary to hold the latitude/longitude of each station
|
|
281
|
+
station_data = {}
|
|
282
|
+
|
|
283
|
+
# Get the latitude and longitude for each station
|
|
284
|
+
for station_id in station_ids:
|
|
285
|
+
# Retrieve the current station's metadata
|
|
286
|
+
station_metadata = get_dbhydro_station_metadata(station_id)
|
|
287
|
+
|
|
288
|
+
# Check if the metadata was successfully retrieved
|
|
289
|
+
if station_metadata is None:
|
|
290
|
+
print(f'Failed to get latitude/longitude for station {station_id} - No data given back from API')
|
|
291
|
+
continue
|
|
292
|
+
|
|
293
|
+
# Extract the latitude and longitude from the metadata
|
|
294
|
+
try:
|
|
295
|
+
latitude = station_metadata['features'][0]['attributes']['LAT']
|
|
296
|
+
longitude = station_metadata['features'][0]['attributes']['LONG']
|
|
297
|
+
except KeyError:
|
|
298
|
+
print(f'Failed to get latitude/longitude for station {station_id} - Unexpected response structure from API')
|
|
299
|
+
continue
|
|
300
|
+
|
|
301
|
+
# Add the latitude and longitude to the dictionary
|
|
302
|
+
station_data[station_id] = latitude, longitude
|
|
303
|
+
|
|
304
|
+
# Return the dictionary of station latitudes and longitudes
|
|
305
|
+
return station_data
|
|
269
306
|
|
|
270
307
|
|
|
271
308
|
def data_interpolations(
|
|
@@ -916,9 +953,17 @@ def find_last_date_in_csv(workspace: str, file_name: str) -> str:
|
|
|
916
953
|
|
|
917
954
|
# Helper Functions
|
|
918
955
|
def is_valid_date(date_string):
|
|
956
|
+
# Check for date without time part
|
|
919
957
|
try:
|
|
920
958
|
datetime.datetime.strptime(date_string, "%Y-%m-%d")
|
|
921
959
|
return True
|
|
960
|
+
except ValueError:
|
|
961
|
+
pass
|
|
962
|
+
|
|
963
|
+
# Check for date with time part
|
|
964
|
+
try:
|
|
965
|
+
datetime.datetime.strptime(date_string, "%Y-%m-%d %H:%M:%S")
|
|
966
|
+
return True
|
|
922
967
|
except ValueError:
|
|
923
968
|
return False
|
|
924
969
|
|
|
@@ -955,23 +1000,69 @@ def find_last_date_in_csv(workspace: str, file_name: str) -> str:
|
|
|
955
1000
|
return None
|
|
956
1001
|
|
|
957
1002
|
|
|
958
|
-
def dbhydro_data_is_latest(date_latest: str):
|
|
1003
|
+
def dbhydro_data_is_latest(date_latest: str, dbkey: str | None = None) -> bool:
|
|
959
1004
|
"""
|
|
960
1005
|
Checks whether the given date is the most recent date possible to get data from dbhydro.
|
|
961
1006
|
Can be used to check whether dbhydro data is up-to-date.
|
|
962
1007
|
|
|
963
1008
|
Args:
|
|
964
1009
|
date_latest (str): The date of the most recent data of the dbhydro data you have
|
|
1010
|
+
dbkey (str | None, optional): The dbkey of the data you are checking. Defaults to None.
|
|
965
1011
|
|
|
966
1012
|
Returns:
|
|
967
1013
|
bool: True if the date_latest is the most recent date possible to get data from dbhydro, False otherwise
|
|
968
1014
|
"""
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
1015
|
+
# Convert date_latest to a date object
|
|
1016
|
+
date_latest_object = pd.to_datetime(date_latest).date()
|
|
1017
|
+
|
|
1018
|
+
# No dbkey provided
|
|
1019
|
+
if dbkey is None:
|
|
1020
|
+
# Assume latest data available is yesterday
|
|
1021
|
+
return date_latest_object == (datetime.datetime.now().date() - datetime.timedelta(days=1))
|
|
1022
|
+
|
|
1023
|
+
# Get dbhydro api
|
|
1024
|
+
dbhydro_api = get_dbhydro_api()
|
|
1025
|
+
|
|
1026
|
+
# Retrieve the last date available from dbhydro for the given dbkey
|
|
1027
|
+
data = dbhydro_api.get_daily_data([dbkey], 'id', '1900-01-01', '1900-01-02', 'NGVD29', False)
|
|
1028
|
+
last_date = data.time_series[0].period_of_record.por_last_date
|
|
1029
|
+
|
|
1030
|
+
# Use date part only (exclude time)
|
|
1031
|
+
last_date = last_date.split("T")[0]
|
|
1032
|
+
|
|
1033
|
+
# Convert last_date to a date object
|
|
1034
|
+
last_date_object = datetime.datetime.strptime(last_date, "%Y-%m-%d").date()
|
|
1035
|
+
|
|
1036
|
+
# Compare given date to last date from dbhydro
|
|
1037
|
+
return date_latest_object >= last_date_object
|
|
1038
|
+
|
|
1039
|
+
|
|
1040
|
+
def dbhydro_water_quality_data_is_latest(date_latest: str, station: str, station_type: Literal['SITE', 'STATION'], test_number: int) -> bool:
|
|
1041
|
+
"""
|
|
1042
|
+
Checks whether the given date is the most recent date possible to get water quality data from dbhydro.
|
|
1043
|
+
Can be used to check whether dbhydro water quality data is up-to-date.
|
|
1044
|
+
|
|
1045
|
+
Args:
|
|
1046
|
+
date_latest (str): The date of the most recent data of the dbhydro water quality data you have.
|
|
1047
|
+
station (str): The station ID of the water quality data you are checking.
|
|
1048
|
+
test_number (int): The test number of the water quality data you are checking. Test numbers map to parameters such as 'PHOSPHATE, TOTAL AS P'.
|
|
1049
|
+
|
|
1050
|
+
Returns:
|
|
1051
|
+
bool: True if the date_latest is the most recent date possible to get water quality data from dbhydro, False otherwise
|
|
1052
|
+
"""
|
|
1053
|
+
# Get the date range from dbhydro water quality data
|
|
1054
|
+
date_start, date_end = get_dbhydro_water_quality_date_range(station, station_type, test_number)
|
|
1055
|
+
|
|
1056
|
+
# No end date available
|
|
1057
|
+
if date_end is None:
|
|
1058
|
+
# Assume data is not up-to-date
|
|
1059
|
+
return False
|
|
1060
|
+
|
|
1061
|
+
# Convert date_latest to a datetime object
|
|
1062
|
+
date_latest_object = pd.to_datetime(date_latest)
|
|
1063
|
+
|
|
1064
|
+
# Compare given date to last date from dbhydro
|
|
1065
|
+
return date_latest_object >= date_end
|
|
975
1066
|
|
|
976
1067
|
|
|
977
1068
|
def get_synthetic_data(date_start: str, df: pd.DataFrame):
|
|
@@ -1038,6 +1129,128 @@ def get_synthetic_data(date_start: str, df: pd.DataFrame):
|
|
|
1038
1129
|
return df
|
|
1039
1130
|
|
|
1040
1131
|
|
|
1132
|
+
def df_replace_missing_with_nan(df: pd.DataFrame, qualifier_codes: set = {'M', 'N'}, no_data_value: float = -99999.0) -> pd.DataFrame:
|
|
1133
|
+
"""
|
|
1134
|
+
Replace values in the 'value' column of the DataFrame with NaN where the 'qualifier' column contains specified qualifier codes.
|
|
1135
|
+
|
|
1136
|
+
This was designed to work with dataframes created from dbhydro_py response data.
|
|
1137
|
+
The dataframe must have 'value' and 'qualifier' columns.
|
|
1138
|
+
Qualifier/Codes can be found here: https://insightsdata.sfwmd.gov/#/reference-tables?lookup=qualityCode
|
|
1139
|
+
|
|
1140
|
+
Args:
|
|
1141
|
+
df (pd.DataFrame): DataFrame that was created from a dbhydro_py response. Must have value and qualifier columns.
|
|
1142
|
+
qualifier_codes (set, optional): Set of qualifier codes indicating missing data. Defaults to {'M', 'N'}.
|
|
1143
|
+
no_data_value (float, optional): Value representing no data. Defaults to -99999.0. Values equal to this will also be replaced with NaN.
|
|
1144
|
+
|
|
1145
|
+
Returns:
|
|
1146
|
+
pd.DataFrame: DataFrame with specified values replaced with NaN.
|
|
1147
|
+
"""
|
|
1148
|
+
# Replace 0 values with NaN when their qualifier is in qualifier_codes
|
|
1149
|
+
# 'M' = Missing, 'N' = Not Yet Available
|
|
1150
|
+
# Qualifier/Codes can be found here: https://insightsdata.sfwmd.gov/#/reference-tables?lookup=qualityCode
|
|
1151
|
+
df.loc[df['qualifier'].isin(qualifier_codes), 'value'] = np.nan
|
|
1152
|
+
|
|
1153
|
+
# Also replace no_data_value with NaN
|
|
1154
|
+
df.loc[np.isclose(df['value'], no_data_value), 'value'] = np.nan
|
|
1155
|
+
|
|
1156
|
+
# Return modified dataframe
|
|
1157
|
+
return df
|
|
1158
|
+
|
|
1159
|
+
|
|
1160
|
+
def get_dbhydro_water_quality_date_range(station: str, station_type: Literal['SITE', 'STATION'], test_number: int) -> Tuple[pd.Timestamp | None, pd.Timestamp | None]:
|
|
1161
|
+
"""Get the start date and end date for the given station and test number from DBHYDRO water quality data.
|
|
1162
|
+
|
|
1163
|
+
Args:
|
|
1164
|
+
station (str): The station names.
|
|
1165
|
+
station_type (Literal['SITE', 'STATION']): The type of the station.
|
|
1166
|
+
test_number (int): The test number of the data. Test numbers map to parameters such as 'PHOSPHATE, TOTAL AS P'.
|
|
1167
|
+
|
|
1168
|
+
Returns:
|
|
1169
|
+
Tuple[pd.Timestamp | None, pd.Timestamp | None]: A tuple containing the start date and end date in 'MM/DD/YYYY' format.
|
|
1170
|
+
"""
|
|
1171
|
+
response = get_dbhydro_water_quality_metadata([(station, station_type)], [test_number])
|
|
1172
|
+
|
|
1173
|
+
# No data given back by api
|
|
1174
|
+
if response is None:
|
|
1175
|
+
return (None, None)
|
|
1176
|
+
|
|
1177
|
+
# Get the date range from the response
|
|
1178
|
+
if 'results' in response:
|
|
1179
|
+
results = response['results']
|
|
1180
|
+
if len(results) > 0:
|
|
1181
|
+
# Find the first non-None start and end dates
|
|
1182
|
+
date_start = None
|
|
1183
|
+
date_end = None
|
|
1184
|
+
for result in results:
|
|
1185
|
+
date_start = result.get('startDate', None)
|
|
1186
|
+
date_end = result.get('endDate', None)
|
|
1187
|
+
|
|
1188
|
+
# Dates found
|
|
1189
|
+
if date_start is not None and date_end is not None:
|
|
1190
|
+
break
|
|
1191
|
+
|
|
1192
|
+
# If no valid dates were found, return early
|
|
1193
|
+
if date_start is None or date_end is None:
|
|
1194
|
+
return (date_start, date_end)
|
|
1195
|
+
|
|
1196
|
+
# Find the earliest start date and latest end date
|
|
1197
|
+
for result in results:
|
|
1198
|
+
date_start_current = result.get('startDate', None)
|
|
1199
|
+
date_end_current = result.get('endDate', None)
|
|
1200
|
+
if date_start_current is not None and pd.to_datetime(date_start_current) < pd.to_datetime(date_start):
|
|
1201
|
+
date_start = date_start_current
|
|
1202
|
+
if date_end_current is not None and pd.to_datetime(date_end_current) > pd.to_datetime(date_end):
|
|
1203
|
+
date_end = date_end_current
|
|
1204
|
+
|
|
1205
|
+
# Convert dates to datetime objects
|
|
1206
|
+
if date_start is not None:
|
|
1207
|
+
date_start = pd.to_datetime(date_start)
|
|
1208
|
+
if date_end is not None:
|
|
1209
|
+
date_end = pd.to_datetime(date_end)
|
|
1210
|
+
|
|
1211
|
+
# Return the earliest start date and latest end date
|
|
1212
|
+
return (date_start, date_end)
|
|
1213
|
+
|
|
1214
|
+
# No results found
|
|
1215
|
+
return (None, None)
|
|
1216
|
+
|
|
1217
|
+
|
|
1218
|
+
def get_dbhydro_api_keys_from_environment() -> dict[str, str]:
|
|
1219
|
+
"""Get DBHYDRO API keys from environment variables.
|
|
1220
|
+
|
|
1221
|
+
Returns:
|
|
1222
|
+
Dict[str, str]: A dictionary containing the DBHYDRO API keys where dict keys are 'client_id' and 'client_secret'.
|
|
1223
|
+
"""
|
|
1224
|
+
# Get API keys from environment variables
|
|
1225
|
+
api_keys = {
|
|
1226
|
+
"client_id": os.environ.get("DBHYDRO_API_CLIENT_ID", ""),
|
|
1227
|
+
"client_secret": os.environ.get("DBHYDRO_API_CLIENT_SECRET", ""),
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
# Return the API keys
|
|
1231
|
+
return api_keys
|
|
1232
|
+
|
|
1233
|
+
|
|
1234
|
+
def get_dbhydro_api_keys() -> dict[str, str]:
|
|
1235
|
+
"""Get DBHYDRO API keys.
|
|
1236
|
+
|
|
1237
|
+
Returns:
|
|
1238
|
+
Dict[str, str]: A dictionary containing the DBHYDRO API keys where dict keys are 'client_id' and 'client_secret'.
|
|
1239
|
+
"""
|
|
1240
|
+
return get_dbhydro_api_keys_from_environment()
|
|
1241
|
+
|
|
1242
|
+
|
|
1243
|
+
def get_dbhydro_api() -> DbHydroApi:
|
|
1244
|
+
"""Get a configured DbHydroApi instance.
|
|
1245
|
+
|
|
1246
|
+
Returns:
|
|
1247
|
+
DbHydroApi: An instance of the DbHydroApi class.
|
|
1248
|
+
"""
|
|
1249
|
+
api_keys = get_dbhydro_api_keys()
|
|
1250
|
+
dbhydro_api = DbHydroApi.with_default_adapter(client_id=api_keys["client_id"], client_secret=api_keys["client_secret"])
|
|
1251
|
+
return dbhydro_api
|
|
1252
|
+
|
|
1253
|
+
|
|
1041
1254
|
if __name__ == "__main__":
|
|
1042
1255
|
if sys.argv[1] == "get_dbkeys":
|
|
1043
1256
|
get_dbkeys(
|