avoca 0.11.4__tar.gz → 0.12.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.
- {avoca-0.11.4 → avoca-0.12.0}/PKG-INFO +1 -1
- {avoca-0.11.4 → avoca-0.12.0}/avoca/bindings/ebas_flags.py +2 -8
- {avoca-0.11.4 → avoca-0.12.0}/avoca/bindings/qa_tool.py +65 -1
- {avoca-0.11.4 → avoca-0.12.0}/avoca/flags.py +8 -0
- avoca-0.12.0/avoca/plots.py +122 -0
- {avoca-0.11.4 → avoca-0.12.0}/pyproject.toml +1 -1
- {avoca-0.11.4 → avoca-0.12.0}/.gitignore +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/.gitlab-ci.yml +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/.readthedocs.yaml +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/.vscode/settings.json +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/LICENCE.txt +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/README.md +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/avoca/__init__.py +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/avoca/bindings/__init__.py +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/avoca/bindings/ebas.py +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/avoca/bindings/gcwerks-report.conf +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/avoca/bindings/gcwerks.py +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/avoca/bindings/gcwerks_gui.py +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/avoca/bindings/nabel.py +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/avoca/bindings/synspec.py +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/avoca/export_nas.py +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/avoca/flagging.py +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/avoca/io.py +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/avoca/logging.py +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/avoca/manager.py +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/avoca/qa_class/__init__.py +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/avoca/qa_class/abstract.py +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/avoca/qa_class/concs.py +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/avoca/qa_class/generate_classes_doc.py +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/avoca/qa_class/invalid.py +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/avoca/qa_class/rt.py +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/avoca/qa_class/test.py +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/avoca/qa_class/zscore.py +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/avoca/requirements.py +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/avoca/settings.py +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/avoca/testing/__init__.py +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/avoca/testing/df.py +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/avoca/utils/__init__.py +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/avoca/utils/flags_doc.py +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/avoca/utils/torch_models.py +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/data/.avoca/config.yaml +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/data/CH0001G.20240219123300.20240307132229.online_gc.NMHC.air.16d.61mn.CH01L_Agilent_GC-MS-MEDUSA_Medusa-12_JFJ.CH01L_gc_ms.lev0.nas +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/data/tests/missing_area_cols.csv +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/data/voc_jan2jun_2023.csv +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/docs/Makefile +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/docs/make.bat +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/docs/source/bindings/ebas.md +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/docs/source/bindings/gcwerks.md +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/docs/source/bindings/index.rst +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/docs/source/bindings/qa_tool.md +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/docs/source/conf.py +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/docs/source/index.rst +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/docs/source/quickstart.ipynb +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/examples/config.yaml +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/examples/convert_synspec_to_gcwerks.py +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/examples/data_qa.ipynb +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/examples/data_qa_gcwerks.ipynb +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/examples/export_gc_werks.py +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/examples/export_gc_werks_secondary_peaks.py +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/examples/get_tanks.ipynb +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/examples/read_nas.ipynb +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/tests/bindings/gcwerks.dat +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/tests/bindings/test_gcwerks.py +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/tests/bindings/test_qatool.py +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/tests/test_assigners.py +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/tests/test_flagging.py +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/tests/test_io.py +0 -0
- {avoca-0.11.4 → avoca-0.12.0}/tests/test_manager.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: avoca
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12.0
|
|
4
4
|
Summary: @voc@: Quality assessement of measurement data
|
|
5
5
|
Project-URL: Homepage, https://gitlab.com/empa503/atmospheric-measurements/avoca
|
|
6
6
|
Project-URL: Bug Tracker, https://gitlab.com/empa503/atmospheric-measurements/avoca/-/issues
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# https://projects.nilu.no/ccc/flags/flags.html for more info on what ebas uses
|
|
2
|
-
from avoca.flags import QA_Flag
|
|
2
|
+
from avoca.flags import QA_Flag, nan_flags
|
|
3
3
|
|
|
4
4
|
flags_to_ebas: dict[QA_Flag, int] = {
|
|
5
5
|
QA_Flag.MISSING: 999, # M Missing measurement, unspecified reason
|
|
@@ -40,13 +40,7 @@ if missing_flags:
|
|
|
40
40
|
f"Not all QA flags are mapped to Ebas flags. Missing: {missing_flags}"
|
|
41
41
|
)
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
nan_flags = [
|
|
45
|
-
QA_Flag.MISSING,
|
|
46
|
-
QA_Flag.ZERO_NEG_CONC_EXT,
|
|
47
|
-
QA_Flag.INVALIDATED_EXT,
|
|
48
|
-
QA_Flag.INVALID_VALUES,
|
|
49
|
-
]
|
|
43
|
+
nan_flags = nan_flags
|
|
50
44
|
|
|
51
45
|
# priority of the flag to appear in the output
|
|
52
46
|
# Useful when you can select only one flag value
|
|
@@ -12,7 +12,7 @@ import numpy as np
|
|
|
12
12
|
import pandas as pd
|
|
13
13
|
import pandas.errors
|
|
14
14
|
|
|
15
|
-
from avoca.bindings.ebas_flags import flag_order, flags_to_ebas
|
|
15
|
+
from avoca.bindings.ebas_flags import flag_order, flags_to_ebas, ebas_flag_to_avoca
|
|
16
16
|
from avoca.flags import QA_Flag
|
|
17
17
|
from avoca.utils import compounds_from_df
|
|
18
18
|
|
|
@@ -207,3 +207,67 @@ def export_EmpaQATool(
|
|
|
207
207
|
logger.info(f"Exported to `{out_filepath}`")
|
|
208
208
|
|
|
209
209
|
return out_filepath
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def read_empaqatool(file_path: Path, shift: timedelta | None = None) -> pd.DataFrame:
|
|
213
|
+
"""Read an EmpaQATool export file.
|
|
214
|
+
|
|
215
|
+
Data is exported through : https://voc-qc.nilu.no/ExportData
|
|
216
|
+
|
|
217
|
+
:arg file_path: Path to the EmpaQATool export file.
|
|
218
|
+
|
|
219
|
+
:returns: DataFrame with the data.
|
|
220
|
+
"""
|
|
221
|
+
|
|
222
|
+
# Pandas skips the 2 empty rows
|
|
223
|
+
df = pd.read_csv(file_path, sep=";", header=2)
|
|
224
|
+
|
|
225
|
+
# Convert the datetime columns
|
|
226
|
+
columns = {}
|
|
227
|
+
to_datetime = lambda x: pd.to_datetime(x, format="%Y-%m-%d %H:%M:%S")
|
|
228
|
+
columns[("-", "datetime_start")] = to_datetime(df["Start"])
|
|
229
|
+
columns[("-", "datetime_end")] = to_datetime(df["End"])
|
|
230
|
+
|
|
231
|
+
# Get the datetime column as the start time
|
|
232
|
+
dt = columns[("-", "datetime_start")].copy()
|
|
233
|
+
if shift is not None:
|
|
234
|
+
dt += shift
|
|
235
|
+
columns[("-", "datetime")] = dt
|
|
236
|
+
|
|
237
|
+
# Last column is empty
|
|
238
|
+
compounds = [ '-'.join(s[:-1]) for col in df.columns if len(s:=col.split("-")) >= 2]
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
for compound in compounds:
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
flag_col = f"{compound}-flag"
|
|
245
|
+
value_col = f"{compound}-value"
|
|
246
|
+
acc_col = f"{compound}-accuracy"
|
|
247
|
+
precision_col = f"{compound}-precision"
|
|
248
|
+
|
|
249
|
+
mapping = {
|
|
250
|
+
"conc": value_col,
|
|
251
|
+
"u_expanded":acc_col,
|
|
252
|
+
"u_precision":precision_col,
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
flag_values = (pd.to_numeric(df[flag_col]) * 1e3).astype(int).mod(1000)
|
|
256
|
+
# Flags are adding 1000 for specifying when set by qa tool or not
|
|
257
|
+
flags = flag_values.apply(
|
|
258
|
+
lambda x: ebas_flag_to_avoca[x].value if x else int(0)
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
for key, value in mapping.items():
|
|
262
|
+
# Since the nan flags values are set to 9999, we need to set them to nan
|
|
263
|
+
serie = pd.to_numeric(df[value])
|
|
264
|
+
mask_nan = flags == QA_Flag.MISSING.value
|
|
265
|
+
serie[mask_nan] = np.nan
|
|
266
|
+
columns[(compound, key)] = serie
|
|
267
|
+
|
|
268
|
+
columns[(compound, "flag")] = flags
|
|
269
|
+
|
|
270
|
+
mask_nan = columns[(compound, "conc")].isna()
|
|
271
|
+
columns[(compound, "flag")][mask_nan] |= QA_Flag.MISSING.value
|
|
272
|
+
|
|
273
|
+
return pd.DataFrame(columns)
|
|
@@ -46,6 +46,14 @@ class QA_Flag(Flag):
|
|
|
46
46
|
# Invalid Values
|
|
47
47
|
INVALID_VALUES = auto()
|
|
48
48
|
|
|
49
|
+
# Flags that are considered to have missing values
|
|
50
|
+
nan_flags = [
|
|
51
|
+
QA_Flag.MISSING,
|
|
52
|
+
QA_Flag.ZERO_NEG_CONC_EXT,
|
|
53
|
+
QA_Flag.INVALIDATED_EXT,
|
|
54
|
+
QA_Flag.INVALID_VALUES,
|
|
55
|
+
]
|
|
56
|
+
|
|
49
57
|
|
|
50
58
|
if __name__ == "__main__":
|
|
51
59
|
# Print the flages and their values
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import matplotlib.pyplot as plt
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def plot_historical_comparison(
|
|
6
|
+
df_new: pd.DataFrame, df_hist: pd.DataFrame, compound: str, ax=None
|
|
7
|
+
) -> tuple[plt.Figure, plt.Axes]:
|
|
8
|
+
if ax is None:
|
|
9
|
+
fig, ax = plt.subplots(figsize=(10, 6))
|
|
10
|
+
|
|
11
|
+
dt_column = ("-", "datetime")
|
|
12
|
+
|
|
13
|
+
for data_type, df in zip(["Historical", "New"], [df_hist, df_new]):
|
|
14
|
+
if data_type == "Historical":
|
|
15
|
+
color = "blue"
|
|
16
|
+
else:
|
|
17
|
+
color = "red"
|
|
18
|
+
|
|
19
|
+
serie = df[(compound, "conc")]
|
|
20
|
+
dt = df[dt_column]
|
|
21
|
+
if ("-", "type") in df.columns:
|
|
22
|
+
mask_air = df[("-", "type")] == "air"
|
|
23
|
+
serie = serie[mask_air]
|
|
24
|
+
dt = dt[mask_air]
|
|
25
|
+
|
|
26
|
+
ax.scatter(dt, serie, label=data_type, color=color, alpha=0.5, s=4)
|
|
27
|
+
ax.set_title(compound)
|
|
28
|
+
ax.set_xlabel("Date")
|
|
29
|
+
ax.set_ylabel("Concentration (ppt)")
|
|
30
|
+
ax.legend()
|
|
31
|
+
return fig, ax
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def plot_yearly_data(
|
|
35
|
+
df: pd.DataFrame, compound: str, ax=None
|
|
36
|
+
) -> tuple[plt.Figure, plt.Axes]:
|
|
37
|
+
if ax is None:
|
|
38
|
+
fig, ax = plt.subplots(figsize=(10, 6))
|
|
39
|
+
|
|
40
|
+
dt_column = ("-", "datetime")
|
|
41
|
+
serie = df[(compound, "conc")]
|
|
42
|
+
dt = df[dt_column]
|
|
43
|
+
if ("-", "type") in df.columns:
|
|
44
|
+
mask_air = df[("-", "type")] == "air"
|
|
45
|
+
serie = serie[mask_air]
|
|
46
|
+
dt = dt[mask_air]
|
|
47
|
+
|
|
48
|
+
years = dt.dt.year.unique()
|
|
49
|
+
x = dt.dt.day_of_year + dt.dt.hour / 24.0
|
|
50
|
+
for year in years:
|
|
51
|
+
mask_year = dt.dt.year == year
|
|
52
|
+
ax.scatter(x[mask_year], serie[mask_year], label=str(year), alpha=0.5, s=4)
|
|
53
|
+
|
|
54
|
+
ax.set_title(compound)
|
|
55
|
+
ax.set_xlabel("Time of Year")
|
|
56
|
+
ax.set_ylabel("Concentration (ppt)")
|
|
57
|
+
|
|
58
|
+
# Add ticks with the mounths
|
|
59
|
+
month_starts = pd.date_range(start="2024-01-01", end="2025-01-01", freq="MS")
|
|
60
|
+
month_days = month_starts.dayofyear
|
|
61
|
+
month_labels = month_starts.strftime("%b")
|
|
62
|
+
ax.set_xticks(month_days)
|
|
63
|
+
ax.set_xticklabels(month_labels)
|
|
64
|
+
ax.legend()
|
|
65
|
+
return fig, ax
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def plot_yearly_plotly(
|
|
69
|
+
df: pd.DataFrame,
|
|
70
|
+
compound: str,
|
|
71
|
+
df_new: pd.DataFrame | None = None,
|
|
72
|
+
) -> "plotly.graph_objs._figure.Figure":
|
|
73
|
+
"""Plot yearly data using plotly."""
|
|
74
|
+
import plotly.express as px
|
|
75
|
+
import plotly.graph_objects as go
|
|
76
|
+
|
|
77
|
+
dt_column = ("-", "datetime")
|
|
78
|
+
serie = df[(compound, "conc")]
|
|
79
|
+
dt = df[dt_column]
|
|
80
|
+
if ("-", "type") in df.columns:
|
|
81
|
+
mask_air = df[("-", "type")] == "air"
|
|
82
|
+
serie = serie[mask_air]
|
|
83
|
+
dt = dt[mask_air]
|
|
84
|
+
if ("-", "type") in df_new.columns:
|
|
85
|
+
mask_air_new = df_new[("-", "type")] == "air"
|
|
86
|
+
df_new = df_new[mask_air_new]
|
|
87
|
+
|
|
88
|
+
x = dt.dt.day_of_year + dt.dt.hour / 24.0
|
|
89
|
+
df_to_plot = pd.DataFrame(
|
|
90
|
+
{
|
|
91
|
+
"conc": serie.values,
|
|
92
|
+
"year": dt.dt.year.values,
|
|
93
|
+
},
|
|
94
|
+
index=x.values,
|
|
95
|
+
)
|
|
96
|
+
# Break down by year, to have year as columns and conc as values
|
|
97
|
+
df_to_plot = df_to_plot.pivot_table(
|
|
98
|
+
index=df_to_plot.index, columns="year", values="conc"
|
|
99
|
+
)
|
|
100
|
+
fig = px.scatter(df_to_plot)
|
|
101
|
+
x_values = pd.date_range(start="2024-01-01", end="2024-12-31", freq="MS")
|
|
102
|
+
|
|
103
|
+
dt_new = df_new[dt_column]
|
|
104
|
+
fig.add_trace(
|
|
105
|
+
go.Scatter(
|
|
106
|
+
x=dt_new.dt.dayofyear + dt_new.dt.hour / 24.0,
|
|
107
|
+
y=df_new[(compound, "conc")],
|
|
108
|
+
mode="markers",
|
|
109
|
+
name="New Data",
|
|
110
|
+
)
|
|
111
|
+
)
|
|
112
|
+
fig.update_layout(
|
|
113
|
+
xaxis_title="Time of Year",
|
|
114
|
+
yaxis_title=f"{compound} (ppt)",
|
|
115
|
+
xaxis=dict(
|
|
116
|
+
tickmode="array",
|
|
117
|
+
tickvals=x_values.dayofyear,
|
|
118
|
+
ticktext=x_values.strftime("%b"),
|
|
119
|
+
),
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
return fig
|
|
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
|
|
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
|
|
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
|