pydartdiags 0.0.43__tar.gz → 0.5.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.

Potentially problematic release.


This version of pydartdiags might be problematic. Click here for more details.

Files changed (27) hide show
  1. {pydartdiags-0.0.43/src/pydartdiags.egg-info → pydartdiags-0.5.0}/PKG-INFO +9 -5
  2. {pydartdiags-0.0.43 → pydartdiags-0.5.0}/README.md +3 -3
  3. {pydartdiags-0.0.43 → pydartdiags-0.5.0}/pyproject.toml +3 -2
  4. {pydartdiags-0.0.43 → pydartdiags-0.5.0}/setup.py +1 -1
  5. pydartdiags-0.5.0/src/pydartdiags/matplots/matplots.py +243 -0
  6. pydartdiags-0.5.0/src/pydartdiags/obs_sequence/obs_sequence.py +1123 -0
  7. pydartdiags-0.5.0/src/pydartdiags/plots/__init__.py +0 -0
  8. pydartdiags-0.5.0/src/pydartdiags/plots/plots.py +191 -0
  9. pydartdiags-0.5.0/src/pydartdiags/stats/__init__.py +0 -0
  10. pydartdiags-0.5.0/src/pydartdiags/stats/stats.py +323 -0
  11. {pydartdiags-0.0.43 → pydartdiags-0.5.0/src/pydartdiags.egg-info}/PKG-INFO +9 -5
  12. {pydartdiags-0.0.43 → pydartdiags-0.5.0}/src/pydartdiags.egg-info/SOURCES.txt +6 -1
  13. {pydartdiags-0.0.43 → pydartdiags-0.5.0}/src/pydartdiags.egg-info/requires.txt +1 -0
  14. pydartdiags-0.5.0/tests/test_obs_sequence.py +452 -0
  15. pydartdiags-0.5.0/tests/test_plots.py +8 -0
  16. pydartdiags-0.5.0/tests/test_stats.py +82 -0
  17. pydartdiags-0.0.43/src/pydartdiags/obs_sequence/obs_sequence.py +0 -825
  18. pydartdiags-0.0.43/src/pydartdiags/plots/plots.py +0 -339
  19. pydartdiags-0.0.43/tests/test_obs_sequence.py +0 -225
  20. pydartdiags-0.0.43/tests/test_plots.py +0 -52
  21. {pydartdiags-0.0.43 → pydartdiags-0.5.0}/LICENSE +0 -0
  22. {pydartdiags-0.0.43 → pydartdiags-0.5.0}/setup.cfg +0 -0
  23. {pydartdiags-0.0.43 → pydartdiags-0.5.0}/src/pydartdiags/__init__.py +0 -0
  24. {pydartdiags-0.0.43/src/pydartdiags/obs_sequence → pydartdiags-0.5.0/src/pydartdiags/matplots}/__init__.py +0 -0
  25. {pydartdiags-0.0.43/src/pydartdiags/plots → pydartdiags-0.5.0/src/pydartdiags/obs_sequence}/__init__.py +0 -0
  26. {pydartdiags-0.0.43 → pydartdiags-0.5.0}/src/pydartdiags.egg-info/dependency_links.txt +0 -0
  27. {pydartdiags-0.0.43 → pydartdiags-0.5.0}/src/pydartdiags.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: pydartdiags
3
- Version: 0.0.43
3
+ Version: 0.5.0
4
4
  Summary: Observation Sequence Diagnostics for DART
5
5
  Home-page: https://github.com/NCAR/pyDARTdiags.git
6
6
  Author: Helen Kershaw
@@ -18,22 +18,26 @@ Requires-Dist: pandas>=2.2.0
18
18
  Requires-Dist: numpy>=1.26
19
19
  Requires-Dist: plotly>=5.22.0
20
20
  Requires-Dist: pyyaml>=6.0.2
21
+ Requires-Dist: matplotlib>=3.9.4
22
+ Dynamic: author
23
+ Dynamic: home-page
24
+ Dynamic: requires-python
21
25
 
22
26
  [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
23
27
  [![codecov](https://codecov.io/gh/NCAR/pyDARTdiags/graph/badge.svg?token=VK55SQZSVD)](https://codecov.io/gh/NCAR/pyDARTdiags)
24
28
  [![PyPI version](https://badge.fury.io/py/pydartdiags.svg)](https://pypi.org/project/pydartdiags/)
25
-
29
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
26
30
 
27
31
  # pyDARTdiags
28
32
 
29
- pyDARTdiags is a Python library for obsevation space diagnostics for the Data Assimilation Research Testbed ([DART](https://github.com/NCAR/DART)).
33
+ pyDARTdiags is a Python library for observation space diagnostics for the Data Assimilation Research Testbed ([DART](https://github.com/NCAR/DART)).
30
34
 
31
35
  pyDARTdiags is under initial development, so please use caution.
32
36
  The MATLAB [observation space diagnostics](https://docs.dart.ucar.edu/en/latest/guide/matlab-observation-space.html) are available through [DART](https://github.com/NCAR/DART).
33
37
 
34
38
 
35
39
  pyDARTdiags can be installed through pip: https://pypi.org/project/pydartdiags/
36
- Documenation : https://ncar.github.io/pyDARTdiags/
40
+ Documentation : https://ncar.github.io/pyDARTdiags/
37
41
 
38
42
  ## Contributing
39
43
  Contributions are welcome! If you have a feature request, bug report, or a suggestion, please open an issue on our GitHub repository.
@@ -1,18 +1,18 @@
1
1
  [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
2
2
  [![codecov](https://codecov.io/gh/NCAR/pyDARTdiags/graph/badge.svg?token=VK55SQZSVD)](https://codecov.io/gh/NCAR/pyDARTdiags)
3
3
  [![PyPI version](https://badge.fury.io/py/pydartdiags.svg)](https://pypi.org/project/pydartdiags/)
4
-
4
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
5
5
 
6
6
  # pyDARTdiags
7
7
 
8
- pyDARTdiags is a Python library for obsevation space diagnostics for the Data Assimilation Research Testbed ([DART](https://github.com/NCAR/DART)).
8
+ pyDARTdiags is a Python library for observation space diagnostics for the Data Assimilation Research Testbed ([DART](https://github.com/NCAR/DART)).
9
9
 
10
10
  pyDARTdiags is under initial development, so please use caution.
11
11
  The MATLAB [observation space diagnostics](https://docs.dart.ucar.edu/en/latest/guide/matlab-observation-space.html) are available through [DART](https://github.com/NCAR/DART).
12
12
 
13
13
 
14
14
  pyDARTdiags can be installed through pip: https://pypi.org/project/pydartdiags/
15
- Documenation : https://ncar.github.io/pyDARTdiags/
15
+ Documentation : https://ncar.github.io/pyDARTdiags/
16
16
 
17
17
  ## Contributing
18
18
  Contributions are welcome! If you have a feature request, bug report, or a suggestion, please open an issue on our GitHub repository.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pydartdiags"
7
- version = "0.0.43"
7
+ version = "0.5.0"
8
8
  authors = [
9
9
  { name="Helen Kershaw", email="hkershaw@ucar.edu" },
10
10
  ]
@@ -20,7 +20,8 @@ dependencies = [
20
20
  "pandas>=2.2.0",
21
21
  "numpy>=1.26",
22
22
  "plotly>=5.22.0",
23
- "pyyaml>=6.0.2"
23
+ "pyyaml>=6.0.2",
24
+ "matplotlib>=3.9.4"
24
25
  ]
25
26
 
26
27
  [project.urls]
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="pydartdiags",
5
- version="0.0.41",
5
+ version="0.0.43",
6
6
  packages=find_packages(where="src"),
7
7
  package_dir={"": "src"},
8
8
  author="Helen Kershaw",
@@ -0,0 +1,243 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ from pydartdiags.stats import stats
3
+ import matplotlib.pyplot as plt
4
+
5
+ # HK @todo color scheme class
6
+ dacolors = ["green", "magenta", "orange", "red"]
7
+
8
+
9
+ def plot_profile(obs_seq, levels, type, bias=True, rmse=True, totalspread=True):
10
+ """
11
+ plot_profile on the levels for prior and posterior if present
12
+ - bias
13
+ - rmse
14
+ - totalspread
15
+
16
+ Args:
17
+ obs_seq, levels, type, bias=True, rmse=True, totalspread=True
18
+
19
+ Example:
20
+
21
+ type = 'RADIOSONDE_U_WIND_COMPONENT'
22
+ hPalevels = [0.0, 100.0, 150.0, 200.0, 250.0, 300.0, 400.0, 500.0, 700, 850, 925, 1000]
23
+ levels = [i * 100 for i in hPalevels]
24
+
25
+ plot_profile(obs_seq, levels, type, bias=True, rmse=True, totalspread=True)
26
+
27
+ """
28
+
29
+ # calculate stats and add to dataframe
30
+ stats.diag_stats(obs_seq.df)
31
+ qc0 = obs_seq.select_by_dart_qc(0) # filter only qc=0
32
+
33
+ # filter by type
34
+ qc0 = qc0[qc0["type"] == type]
35
+ all_df = obs_seq.df[obs_seq.df["type"] == type]
36
+
37
+ # grand statistics
38
+ grand = stats.grand_statistics(qc0)
39
+
40
+ # add level bins to the dataframe
41
+ stats.bin_by_layer(all_df, levels) # have to count used vs possible
42
+ stats.bin_by_layer(qc0, levels)
43
+
44
+ # aggregate by layer
45
+ df_pvu = stats.possible_vs_used_by_layer(all_df) # possible vs used
46
+ df = stats.layer_statistics(qc0) # bias, rmse, totalspread for plotting
47
+
48
+ fig, ax1 = plt.subplots()
49
+
50
+ # convert to hPa HK @todo only for Pressure (Pa)
51
+ df["midpoint"] = df["midpoint"].astype(float)
52
+ df["midpoint"] = df["midpoint"] / 100.0
53
+
54
+ df_pvu["midpoint"] = df_pvu["midpoint"].astype(float)
55
+ df_pvu["midpoint"] = df_pvu["midpoint"] / 100.0
56
+
57
+ # Add horizontal stripes alternating between gray and white to represent the vertical levels
58
+ left = df["vlevels"].apply(lambda x: x.left / 100.0) # todo convert to HPa
59
+ right = df["vlevels"].apply(lambda x: x.right / 100.0)
60
+ for i in range(len(left)):
61
+ color = "gray" if i % 2 == 0 else "white"
62
+ ax1.axhspan(left.iloc[i], right.iloc[i], color=color, alpha=0.3)
63
+
64
+ # Plot the 'bias' data on the first y-axis
65
+ if bias:
66
+ ax1.plot(
67
+ df["prior_bias"],
68
+ df["midpoint"],
69
+ color=dacolors[0],
70
+ marker=".",
71
+ linestyle="-",
72
+ label="prior bias",
73
+ )
74
+ bias_prior = grand.loc[0, "prior_bias"]
75
+ if "posterior_bias" in df.columns:
76
+ ax1.plot(
77
+ df["posterior_bias"],
78
+ df["midpoint"],
79
+ color=dacolors[0],
80
+ marker=".",
81
+ linestyle="--",
82
+ label="posterior bias",
83
+ )
84
+ bias_posterior = grand.loc[0, "posterior_bias"]
85
+ if rmse:
86
+ ax1.plot(
87
+ df["prior_rmse"],
88
+ df["midpoint"],
89
+ color=dacolors[1],
90
+ marker=".",
91
+ linestyle="-",
92
+ label="prior RMSE",
93
+ )
94
+ rmse_prior = grand.loc[0, "prior_rmse"]
95
+ if "posterior_rmse" in df.columns:
96
+ ax1.plot(
97
+ df["posterior_rmse"],
98
+ df["midpoint"],
99
+ color=dacolors[1],
100
+ marker=".",
101
+ linestyle="--",
102
+ label="posterior RMSE",
103
+ )
104
+ rmse_posterior = grand.loc[0, "posterior_rmse"]
105
+ if totalspread:
106
+ ax1.plot(
107
+ df["prior_totalspread"],
108
+ df["midpoint"],
109
+ color=dacolors[2],
110
+ marker=".",
111
+ linestyle="-",
112
+ label="prior totalspread",
113
+ )
114
+ totalspread_prior = grand.loc[0, "prior_totalspread"]
115
+ if "posterior_totalspread" in df.columns:
116
+ totalspread_posterior = grand.loc[0, "posterior_totalspread"]
117
+ ax1.plot(
118
+ df["posterior_totalspread"],
119
+ df["midpoint"],
120
+ color=dacolors[2],
121
+ marker=".",
122
+ linestyle="--",
123
+ label="posterior totalspread",
124
+ )
125
+
126
+ ax1.set_ylabel("hPa")
127
+ ax1.tick_params(axis="y")
128
+ ax1.set_yticks(df["midpoint"])
129
+ # ax1.set_yticklabels(df['midpoint'])
130
+
131
+ ax3 = ax1.twiny()
132
+ ax3.set_xlabel("# obs (o=possible; +=assimilated)", color=dacolors[-1])
133
+ ax3.tick_params(axis="x", colors=dacolors[-1])
134
+ ax3.plot(
135
+ df_pvu["possible"],
136
+ df_pvu["midpoint"],
137
+ color=dacolors[-1],
138
+ marker="o",
139
+ linestyle="",
140
+ markerfacecolor="none",
141
+ label="possible",
142
+ )
143
+ ax3.plot(
144
+ df_pvu["used"],
145
+ df_pvu["midpoint"],
146
+ color=dacolors[-1],
147
+ marker="+",
148
+ linestyle="",
149
+ label="possible",
150
+ )
151
+ ax3.set_xlim(left=0)
152
+
153
+ ax1.invert_yaxis()
154
+ ax1.set_title(type)
155
+ datalabel = "bias," + " " + "rmse," + " " + "totalspread"
156
+ ax1.set_xlabel(datalabel)
157
+
158
+ lines1, labels1 = ax1.get_legend_handles_labels()
159
+ ax1.legend(lines1, labels1, loc="upper left", bbox_to_anchor=(1.05, 1))
160
+
161
+ ax1.text(
162
+ 0.5, -0.15, obs_seq.file, ha="center", va="center", transform=ax1.transAxes
163
+ )
164
+
165
+ # Add a text box with information below the legend
166
+ textstr = "Grand statistics:\n"
167
+ if bias:
168
+ textstr += f"- prior_bias: {bias_prior:.7f}\n"
169
+ if rmse:
170
+ textstr += f"- rmse_prior: {rmse_prior:.7f}\n"
171
+ if totalspread:
172
+ textstr += f"- totalspread_prior: {totalspread_prior:.7f}\n"
173
+ if "posterior_bias" in df.columns:
174
+ if bias:
175
+ textstr += f"- posterior_bias: {bias_posterior:.7f}\n"
176
+ if rmse:
177
+ textstr += f"- rmse_posterior: {rmse_posterior:.7f}\n"
178
+ if totalspread:
179
+ textstr += f"- totalspread_posterior: {totalspread_posterior:.7f}\n"
180
+
181
+ props = dict(boxstyle="round", facecolor="wheat", alpha=0.5)
182
+ ax1.text(
183
+ 1.05,
184
+ 0.5,
185
+ textstr,
186
+ transform=ax1.transAxes,
187
+ fontsize=10,
188
+ verticalalignment="top",
189
+ bbox=props,
190
+ )
191
+
192
+ plt.show()
193
+
194
+ return fig
195
+
196
+
197
+ def plot_rank_histogram(obs_seq, levels, type, ens_size):
198
+
199
+ qc0 = obs_seq.select_by_dart_qc(0) # filter only qc=0
200
+ qc0 = qc0[qc0["type"] == type] # filter by type
201
+ stats.bin_by_layer(qc0, levels) # bin by level
202
+
203
+ midpoints = qc0["midpoint"].unique()
204
+
205
+ for level in sorted(midpoints):
206
+
207
+ df = qc0[qc0["midpoint"] == level]
208
+
209
+ df = stats.calculate_rank(qc0)
210
+
211
+ if "posterior_rank" in df.columns:
212
+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 6))
213
+ else:
214
+ fig, ax1 = plt.subplots()
215
+
216
+ # Plot the prior rank histogram
217
+ bins = list(range(1, ens_size + 2))
218
+ ax1.hist(
219
+ df["prior_rank"], bins=bins, color="blue", alpha=0.5, label="prior rank"
220
+ )
221
+ ax1.set_title("Prior Rank Histogram")
222
+ ax1.set_xlabel("Observation Rank (among ensemble members)")
223
+ ax1.set_ylabel("Count")
224
+
225
+ # Plot the posterior rank histogram if it exists
226
+ if "posterior_rank" in df.columns:
227
+ ax2.hist(
228
+ df["posterior_rank"],
229
+ bins=bins,
230
+ color="green",
231
+ alpha=0.5,
232
+ label="posterior rank",
233
+ )
234
+ ax2.set_title("Posterior Rank Histogram")
235
+ ax2.set_xlabel("Observation Rank (among ensemble members)")
236
+ ax2.set_ylabel("Count")
237
+
238
+ fig.suptitle(f"{type} at Level {level}", fontsize=14)
239
+
240
+ plt.tight_layout(rect=[0, 0.03, 1, 0.95])
241
+ plt.show()
242
+
243
+ return fig