pydartdiags 0.0.43__py3-none-any.whl → 0.5.0__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.

Potentially problematic release.


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

File without changes
@@ -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