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.
Files changed (68) hide show
  1. {avoca-0.11.4 → avoca-0.12.0}/PKG-INFO +1 -1
  2. {avoca-0.11.4 → avoca-0.12.0}/avoca/bindings/ebas_flags.py +2 -8
  3. {avoca-0.11.4 → avoca-0.12.0}/avoca/bindings/qa_tool.py +65 -1
  4. {avoca-0.11.4 → avoca-0.12.0}/avoca/flags.py +8 -0
  5. avoca-0.12.0/avoca/plots.py +122 -0
  6. {avoca-0.11.4 → avoca-0.12.0}/pyproject.toml +1 -1
  7. {avoca-0.11.4 → avoca-0.12.0}/.gitignore +0 -0
  8. {avoca-0.11.4 → avoca-0.12.0}/.gitlab-ci.yml +0 -0
  9. {avoca-0.11.4 → avoca-0.12.0}/.readthedocs.yaml +0 -0
  10. {avoca-0.11.4 → avoca-0.12.0}/.vscode/settings.json +0 -0
  11. {avoca-0.11.4 → avoca-0.12.0}/LICENCE.txt +0 -0
  12. {avoca-0.11.4 → avoca-0.12.0}/README.md +0 -0
  13. {avoca-0.11.4 → avoca-0.12.0}/avoca/__init__.py +0 -0
  14. {avoca-0.11.4 → avoca-0.12.0}/avoca/bindings/__init__.py +0 -0
  15. {avoca-0.11.4 → avoca-0.12.0}/avoca/bindings/ebas.py +0 -0
  16. {avoca-0.11.4 → avoca-0.12.0}/avoca/bindings/gcwerks-report.conf +0 -0
  17. {avoca-0.11.4 → avoca-0.12.0}/avoca/bindings/gcwerks.py +0 -0
  18. {avoca-0.11.4 → avoca-0.12.0}/avoca/bindings/gcwerks_gui.py +0 -0
  19. {avoca-0.11.4 → avoca-0.12.0}/avoca/bindings/nabel.py +0 -0
  20. {avoca-0.11.4 → avoca-0.12.0}/avoca/bindings/synspec.py +0 -0
  21. {avoca-0.11.4 → avoca-0.12.0}/avoca/export_nas.py +0 -0
  22. {avoca-0.11.4 → avoca-0.12.0}/avoca/flagging.py +0 -0
  23. {avoca-0.11.4 → avoca-0.12.0}/avoca/io.py +0 -0
  24. {avoca-0.11.4 → avoca-0.12.0}/avoca/logging.py +0 -0
  25. {avoca-0.11.4 → avoca-0.12.0}/avoca/manager.py +0 -0
  26. {avoca-0.11.4 → avoca-0.12.0}/avoca/qa_class/__init__.py +0 -0
  27. {avoca-0.11.4 → avoca-0.12.0}/avoca/qa_class/abstract.py +0 -0
  28. {avoca-0.11.4 → avoca-0.12.0}/avoca/qa_class/concs.py +0 -0
  29. {avoca-0.11.4 → avoca-0.12.0}/avoca/qa_class/generate_classes_doc.py +0 -0
  30. {avoca-0.11.4 → avoca-0.12.0}/avoca/qa_class/invalid.py +0 -0
  31. {avoca-0.11.4 → avoca-0.12.0}/avoca/qa_class/rt.py +0 -0
  32. {avoca-0.11.4 → avoca-0.12.0}/avoca/qa_class/test.py +0 -0
  33. {avoca-0.11.4 → avoca-0.12.0}/avoca/qa_class/zscore.py +0 -0
  34. {avoca-0.11.4 → avoca-0.12.0}/avoca/requirements.py +0 -0
  35. {avoca-0.11.4 → avoca-0.12.0}/avoca/settings.py +0 -0
  36. {avoca-0.11.4 → avoca-0.12.0}/avoca/testing/__init__.py +0 -0
  37. {avoca-0.11.4 → avoca-0.12.0}/avoca/testing/df.py +0 -0
  38. {avoca-0.11.4 → avoca-0.12.0}/avoca/utils/__init__.py +0 -0
  39. {avoca-0.11.4 → avoca-0.12.0}/avoca/utils/flags_doc.py +0 -0
  40. {avoca-0.11.4 → avoca-0.12.0}/avoca/utils/torch_models.py +0 -0
  41. {avoca-0.11.4 → avoca-0.12.0}/data/.avoca/config.yaml +0 -0
  42. {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
  43. {avoca-0.11.4 → avoca-0.12.0}/data/tests/missing_area_cols.csv +0 -0
  44. {avoca-0.11.4 → avoca-0.12.0}/data/voc_jan2jun_2023.csv +0 -0
  45. {avoca-0.11.4 → avoca-0.12.0}/docs/Makefile +0 -0
  46. {avoca-0.11.4 → avoca-0.12.0}/docs/make.bat +0 -0
  47. {avoca-0.11.4 → avoca-0.12.0}/docs/source/bindings/ebas.md +0 -0
  48. {avoca-0.11.4 → avoca-0.12.0}/docs/source/bindings/gcwerks.md +0 -0
  49. {avoca-0.11.4 → avoca-0.12.0}/docs/source/bindings/index.rst +0 -0
  50. {avoca-0.11.4 → avoca-0.12.0}/docs/source/bindings/qa_tool.md +0 -0
  51. {avoca-0.11.4 → avoca-0.12.0}/docs/source/conf.py +0 -0
  52. {avoca-0.11.4 → avoca-0.12.0}/docs/source/index.rst +0 -0
  53. {avoca-0.11.4 → avoca-0.12.0}/docs/source/quickstart.ipynb +0 -0
  54. {avoca-0.11.4 → avoca-0.12.0}/examples/config.yaml +0 -0
  55. {avoca-0.11.4 → avoca-0.12.0}/examples/convert_synspec_to_gcwerks.py +0 -0
  56. {avoca-0.11.4 → avoca-0.12.0}/examples/data_qa.ipynb +0 -0
  57. {avoca-0.11.4 → avoca-0.12.0}/examples/data_qa_gcwerks.ipynb +0 -0
  58. {avoca-0.11.4 → avoca-0.12.0}/examples/export_gc_werks.py +0 -0
  59. {avoca-0.11.4 → avoca-0.12.0}/examples/export_gc_werks_secondary_peaks.py +0 -0
  60. {avoca-0.11.4 → avoca-0.12.0}/examples/get_tanks.ipynb +0 -0
  61. {avoca-0.11.4 → avoca-0.12.0}/examples/read_nas.ipynb +0 -0
  62. {avoca-0.11.4 → avoca-0.12.0}/tests/bindings/gcwerks.dat +0 -0
  63. {avoca-0.11.4 → avoca-0.12.0}/tests/bindings/test_gcwerks.py +0 -0
  64. {avoca-0.11.4 → avoca-0.12.0}/tests/bindings/test_qatool.py +0 -0
  65. {avoca-0.11.4 → avoca-0.12.0}/tests/test_assigners.py +0 -0
  66. {avoca-0.11.4 → avoca-0.12.0}/tests/test_flagging.py +0 -0
  67. {avoca-0.11.4 → avoca-0.12.0}/tests/test_io.py +0 -0
  68. {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.11.4
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
- # Flags that are considered to have missing values
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
@@ -5,7 +5,7 @@ build-backend = "hatchling.build"
5
5
 
6
6
  [project]
7
7
  name = "avoca"
8
- version = "0.11.4"
8
+ version = "0.12.0"
9
9
  authors = [
10
10
  { name="Lionel Constantin", email="lionel.constantin@empa.ch" },
11
11
  ]
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