AeroViz 0.1.2__py3-none-any.whl → 0.1.3b0__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 AeroViz might be problematic. Click here for more details.
- AeroViz/__init__.py +4 -4
- AeroViz/config/DEFAULT_DATA.csv +1417 -0
- AeroViz/config/DEFAULT_PNSD_DATA.csv +1417 -0
- AeroViz/dataProcess/Chemistry/__init__.py +38 -38
- AeroViz/dataProcess/Chemistry/_calculate.py +15 -15
- AeroViz/dataProcess/Chemistry/_isoropia.py +69 -68
- AeroViz/dataProcess/Chemistry/_mass_volume.py +158 -158
- AeroViz/dataProcess/Chemistry/_ocec.py +109 -109
- AeroViz/dataProcess/Chemistry/_partition.py +19 -18
- AeroViz/dataProcess/Chemistry/_teom.py +8 -11
- AeroViz/dataProcess/Optical/_IMPROVE.py +40 -39
- AeroViz/dataProcess/Optical/__init__.py +35 -35
- AeroViz/dataProcess/Optical/_absorption.py +35 -35
- AeroViz/dataProcess/Optical/_extinction.py +25 -24
- AeroViz/dataProcess/Optical/_mie.py +5 -6
- AeroViz/dataProcess/Optical/_mie_sd.py +89 -90
- AeroViz/dataProcess/Optical/_scattering.py +16 -16
- AeroViz/dataProcess/SizeDistr/__init__.py +37 -37
- AeroViz/dataProcess/SizeDistr/__merge.py +159 -158
- AeroViz/dataProcess/SizeDistr/_merge.py +155 -154
- AeroViz/dataProcess/SizeDistr/_merge_v1.py +162 -161
- AeroViz/dataProcess/SizeDistr/_merge_v2.py +153 -152
- AeroViz/dataProcess/SizeDistr/_merge_v3.py +326 -326
- AeroViz/dataProcess/SizeDistr/_merge_v4.py +272 -274
- AeroViz/dataProcess/SizeDistr/_size_distr.py +51 -51
- AeroViz/dataProcess/VOC/__init__.py +7 -7
- AeroViz/dataProcess/VOC/_potential_par.py +53 -55
- AeroViz/dataProcess/VOC/voc_par.json +464 -0
- AeroViz/dataProcess/__init__.py +4 -4
- AeroViz/dataProcess/core/__init__.py +59 -58
- AeroViz/plot/__init__.py +6 -1
- AeroViz/plot/bar.py +126 -0
- AeroViz/plot/box.py +68 -0
- AeroViz/plot/distribution/distribution.py +421 -427
- AeroViz/plot/meteorology/meteorology.py +240 -292
- AeroViz/plot/optical/__init__.py +0 -1
- AeroViz/plot/optical/optical.py +230 -230
- AeroViz/plot/pie.py +198 -0
- AeroViz/plot/regression.py +210 -0
- AeroViz/plot/scatter.py +99 -0
- AeroViz/plot/templates/__init__.py +0 -3
- AeroViz/plot/templates/contour.py +25 -25
- AeroViz/plot/templates/corr_matrix.py +86 -93
- AeroViz/plot/templates/diurnal_pattern.py +24 -24
- AeroViz/plot/templates/koschmieder.py +106 -106
- AeroViz/plot/templates/metal_heatmap.py +34 -34
- AeroViz/plot/timeseries/timeseries.py +53 -60
- AeroViz/plot/utils/__init__.py +2 -1
- AeroViz/plot/utils/_color.py +57 -57
- AeroViz/plot/utils/_unit.py +48 -48
- AeroViz/plot/utils/fRH.json +390 -0
- AeroViz/plot/utils/plt_utils.py +92 -0
- AeroViz/plot/utils/sklearn_utils.py +49 -0
- AeroViz/plot/utils/units.json +84 -0
- AeroViz/plot/violin.py +79 -0
- AeroViz/process/__init__.py +15 -15
- AeroViz/process/core/DataProc.py +9 -9
- AeroViz/process/core/SizeDist.py +81 -81
- AeroViz/process/method/PyMieScatt_update.py +488 -488
- AeroViz/process/method/mie_theory.py +231 -229
- AeroViz/process/method/prop.py +40 -40
- AeroViz/process/script/AbstractDistCalc.py +103 -103
- AeroViz/process/script/Chemical.py +166 -166
- AeroViz/process/script/IMPACT.py +40 -40
- AeroViz/process/script/IMPROVE.py +152 -152
- AeroViz/process/script/Others.py +45 -45
- AeroViz/process/script/PSD.py +26 -26
- AeroViz/process/script/PSD_dry.py +69 -70
- AeroViz/process/script/retrieve_RI.py +50 -51
- AeroViz/rawDataReader/__init__.py +57 -57
- AeroViz/rawDataReader/core/__init__.py +328 -326
- AeroViz/rawDataReader/script/AE33.py +18 -18
- AeroViz/rawDataReader/script/AE43.py +20 -20
- AeroViz/rawDataReader/script/APS_3321.py +30 -30
- AeroViz/rawDataReader/script/Aurora.py +23 -23
- AeroViz/rawDataReader/script/BC1054.py +40 -40
- AeroViz/rawDataReader/script/EPA_vertical.py +9 -9
- AeroViz/rawDataReader/script/GRIMM.py +21 -21
- AeroViz/rawDataReader/script/IGAC_TH.py +67 -67
- AeroViz/rawDataReader/script/IGAC_ZM.py +59 -59
- AeroViz/rawDataReader/script/MA350.py +39 -39
- AeroViz/rawDataReader/script/NEPH.py +74 -74
- AeroViz/rawDataReader/script/OCEC_LCRES.py +21 -21
- AeroViz/rawDataReader/script/OCEC_RES.py +16 -16
- AeroViz/rawDataReader/script/SMPS_TH.py +25 -25
- AeroViz/rawDataReader/script/SMPS_aim11.py +32 -32
- AeroViz/rawDataReader/script/SMPS_genr.py +31 -31
- AeroViz/rawDataReader/script/TEOM.py +28 -28
- AeroViz/rawDataReader/script/Table.py +12 -12
- AeroViz/rawDataReader/script/VOC_TH.py +16 -16
- AeroViz/rawDataReader/script/VOC_ZM.py +28 -28
- AeroViz/rawDataReader/script/__init__.py +20 -20
- AeroViz/rawDataReader/utils/config.py +161 -161
- AeroViz/tools/database.py +65 -65
- AeroViz/tools/dataclassifier.py +106 -106
- AeroViz/tools/dataprinter.py +51 -51
- AeroViz/tools/datareader.py +38 -38
- {AeroViz-0.1.2.dist-info → AeroViz-0.1.3b0.dist-info}/METADATA +5 -4
- AeroViz-0.1.3b0.dist-info/RECORD +110 -0
- AeroViz/config/__init__.py +0 -0
- AeroViz/plot/improve/__init__.py +0 -1
- AeroViz/plot/improve/improve.py +0 -240
- AeroViz/plot/optical/aethalometer.py +0 -77
- AeroViz/plot/templates/event_evolution.py +0 -65
- AeroViz/plot/templates/regression.py +0 -256
- AeroViz/plot/templates/scatter.py +0 -130
- AeroViz/plot/templates/templates.py +0 -398
- AeroViz/plot/utils/_decorator.py +0 -74
- AeroViz-0.1.2.dist-info/RECORD +0 -106
- {AeroViz-0.1.2.dist-info → AeroViz-0.1.3b0.dist-info}/LICENSE +0 -0
- {AeroViz-0.1.2.dist-info → AeroViz-0.1.3b0.dist-info}/WHEEL +0 -0
- {AeroViz-0.1.2.dist-info → AeroViz-0.1.3b0.dist-info}/top_level.txt +0 -0
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import math
|
|
2
2
|
from typing import Literal
|
|
3
3
|
|
|
4
|
-
import matplotlib.colors as plc
|
|
5
4
|
import matplotlib.pyplot as plt
|
|
6
5
|
import numpy as np
|
|
7
6
|
import pandas as pd
|
|
8
|
-
import seaborn as sns
|
|
9
7
|
import windrose
|
|
10
8
|
from matplotlib.pyplot import Figure, Axes
|
|
11
9
|
from pandas import DataFrame, Series
|
|
@@ -13,305 +11,255 @@ from scipy.ndimage import gaussian_filter
|
|
|
13
11
|
|
|
14
12
|
from AeroViz.plot.utils import *
|
|
15
13
|
|
|
16
|
-
__all__ = ['
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
]
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
@set_figure(fs=6)
|
|
23
|
-
def wind_tms(df: DataFrame,
|
|
24
|
-
WS: Series | str,
|
|
25
|
-
WD: Series | str,
|
|
26
|
-
**kwargs
|
|
27
|
-
) -> tuple[Figure, Axes]:
|
|
28
|
-
def drawArrow(A, B, ax: plt.Axes): # 畫箭頭
|
|
29
|
-
_ax = ax.twinx()
|
|
30
|
-
if A[0] == B[0] and A[1] == B[1]: # 靜風畫點
|
|
31
|
-
_ax.plot(A[0], A[1], 'ko')
|
|
32
|
-
else:
|
|
33
|
-
_ax.annotate("", xy=(B[0], B[1]), xytext=(A[0], A[1]), arrowprops=dict(arrowstyle="->"))
|
|
34
|
-
|
|
35
|
-
_ax.spines['left'].set_visible(False)
|
|
36
|
-
_ax.spines['right'].set_visible(False)
|
|
37
|
-
_ax.spines['top'].set_visible(False)
|
|
38
|
-
_ax.spines['bottom'].set_visible(False)
|
|
39
|
-
_ax.set_xlim(0, )
|
|
40
|
-
_ax.set_ylim(0, 5)
|
|
41
|
-
_ax.get_yaxis().set_visible(False)
|
|
42
|
-
_ax.set_aspect('equal') # x轴y轴等比例
|
|
43
|
-
|
|
44
|
-
_ax.tick_params(axis='x', rotation=90)
|
|
45
|
-
ax.tick_params(axis='x', rotation=90)
|
|
46
|
-
plt.tight_layout()
|
|
47
|
-
|
|
48
|
-
fig, ax = plt.subplots(figsize=(8, 2))
|
|
49
|
-
uniform_data = [WS]
|
|
50
|
-
colors = ['lightskyblue', 'darkturquoise', 'lime', 'greenyellow', 'orangered', 'red']
|
|
51
|
-
clrmap = plc.LinearSegmentedColormap.from_list("mycmap", colors) # 自定义色标
|
|
52
|
-
sns.heatmap(uniform_data, square=True, annot=True, fmt=".2f", linewidths=.5, cmap=clrmap,
|
|
53
|
-
yticklabels=['Wind speed (m/s)'], xticklabels=kwargs.get('xticklabels', None), cbar=False, vmin=0,
|
|
54
|
-
vmax=5, ax=ax)
|
|
55
|
-
ax.set_xticklabels(ax.get_xticklabels(), rotation=90)
|
|
56
|
-
ax.set_yticklabels(ax.get_yticklabels(), rotation=0)
|
|
57
|
-
ax.spines['bottom'].set_position(('data', 1)) # 移动x轴
|
|
58
|
-
|
|
59
|
-
for idx, (x, value) in enumerate(WD.items()):
|
|
60
|
-
if not pd.isna(value):
|
|
61
|
-
a = np.array([0.5 + 0.5 * np.sin(value / 180 * np.pi) + idx, 3.5 + 0.5 * np.cos(value / 180 * np.pi)])
|
|
62
|
-
b = np.array([0.5 - 0.5 * np.sin(value / 180 * np.pi) + idx, 3.5 - 0.5 * np.cos(value / 180 * np.pi)])
|
|
63
|
-
drawArrow(a, b, ax)
|
|
64
|
-
else:
|
|
65
|
-
a = np.array([0.5 + idx, 3.5])
|
|
66
|
-
drawArrow(a, a, ax)
|
|
67
|
-
|
|
68
|
-
plt.show()
|
|
69
|
-
|
|
70
|
-
return fig, ax
|
|
14
|
+
__all__ = ['wind_rose',
|
|
15
|
+
'CBPF'
|
|
16
|
+
]
|
|
71
17
|
|
|
72
18
|
|
|
73
19
|
@set_figure(figsize=(4.3, 4))
|
|
74
20
|
def wind_rose(df: DataFrame,
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
21
|
+
WS: Series | str,
|
|
22
|
+
WD: Series | str,
|
|
23
|
+
val: Series | str | None = None,
|
|
24
|
+
typ: Literal['bar', 'scatter'] = 'scatter',
|
|
25
|
+
rlabel_pos: float = 30,
|
|
26
|
+
**kwargs
|
|
27
|
+
) -> tuple[Figure, Axes]:
|
|
28
|
+
# conditional bivariate probability function (cbpf) python
|
|
29
|
+
# https://davidcarslaw.github.io/openair/reference/polarPlot.html
|
|
30
|
+
# https://github.com/davidcarslaw/openair/blob/master/R/polarPlot.R
|
|
31
|
+
windrose.WindroseAxes._info = 'WindroseAxes'
|
|
32
|
+
|
|
33
|
+
df = df.dropna(subset=[WS, WD] + ([val] if val is not None else []))
|
|
34
|
+
|
|
35
|
+
radius = df[WS].to_numpy()
|
|
36
|
+
theta = df[WD].to_numpy()
|
|
37
|
+
radian = np.radians(theta)
|
|
38
|
+
values = df[val].to_numpy() if val is not None else None
|
|
39
|
+
|
|
40
|
+
# In this case, the windrose is a simple frequency diagram,
|
|
41
|
+
# the function automatically calculates the radians of the given wind direction.
|
|
42
|
+
if typ == 'bar':
|
|
43
|
+
fig, ax = plt.subplots(figsize=(5.5, 4), subplot_kw={'projection': 'windrose'})
|
|
44
|
+
fig.subplots_adjust(left=0)
|
|
45
|
+
|
|
46
|
+
ax.bar(theta, radius, bins=[0, 1, 2, 3], normed=True, colors=['#0F1035', '#365486', '#7FC7D9', '#DCF2F1'])
|
|
47
|
+
ax.set(
|
|
48
|
+
ylim=(0, 30),
|
|
49
|
+
yticks=[0, 15, 30],
|
|
50
|
+
yticklabels=['', '15 %', '30 %'],
|
|
51
|
+
rlabel_position=rlabel_pos
|
|
52
|
+
)
|
|
53
|
+
ax.set_thetagrids(angles=[0, 45, 90, 135, 180, 225, 270, 315],
|
|
54
|
+
labels=["E", "NE", "N", "NW", "W", "SW", "S", "SE"])
|
|
55
|
+
|
|
56
|
+
ax.legend(units='m/s', bbox_to_anchor=[1.1, 0.5], loc='center left', ncol=1)
|
|
57
|
+
|
|
58
|
+
# In this case, the windrose is a scatter plot,
|
|
59
|
+
# in contrary, this function does not calculate the radians, so user have to input the radian.
|
|
60
|
+
else:
|
|
61
|
+
fig, ax = plt.subplots(figsize=(5, 4), subplot_kw={'projection': 'windrose'})
|
|
62
|
+
fig.subplots_adjust(left=0)
|
|
63
|
+
|
|
64
|
+
scatter = ax.scatter(radian, radius, s=15, c=values, vmax=np.quantile(values, 0.90), edgecolors='none',
|
|
65
|
+
cmap='jet', alpha=0.8)
|
|
66
|
+
ax.set(
|
|
67
|
+
ylim=(0, 7),
|
|
68
|
+
yticks=[1, 3, 5, 7],
|
|
69
|
+
yticklabels=['1 m/s', '3 m/s', '5 m/s', '7 m/s'],
|
|
70
|
+
rlabel_position=rlabel_pos,
|
|
71
|
+
theta_direction=-1,
|
|
72
|
+
theta_zero_location='N',
|
|
73
|
+
)
|
|
74
|
+
ax.set_thetagrids(angles=[0, 45, 90, 135, 180, 225, 270, 315],
|
|
75
|
+
labels=["N", "NE", "E", "SE", "S", "SW", "W", "NW"])
|
|
76
|
+
|
|
77
|
+
plt.colorbar(scatter, ax=ax, label=Unit(val), pad=0.1, fraction=0.04)
|
|
78
|
+
|
|
79
|
+
plt.show()
|
|
80
|
+
|
|
81
|
+
return fig, ax
|
|
136
82
|
|
|
137
83
|
|
|
138
84
|
@set_figure(figsize=(4.3, 4))
|
|
139
85
|
def CBPF(df: DataFrame,
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
86
|
+
WS: Series | str,
|
|
87
|
+
WD: Series | str,
|
|
88
|
+
val: Series | str | None = None,
|
|
89
|
+
percentile: list | float | int | None = None,
|
|
90
|
+
max_ws: float | None = 5,
|
|
91
|
+
resolution: int = 100,
|
|
92
|
+
sigma: float | tuple = 2,
|
|
93
|
+
rlabel_pos: float = 30,
|
|
94
|
+
bottom_text: str | bool | None = None,
|
|
95
|
+
**kwargs
|
|
96
|
+
) -> tuple[Figure, Axes]:
|
|
97
|
+
# conditional bivariate probability function (cbpf) python
|
|
98
|
+
# https://davidcarslaw.github.io/openair/reference/polarPlot.html
|
|
99
|
+
# https://github.com/davidcarslaw/openair/blob/master/R/polarPlot.R
|
|
100
|
+
|
|
101
|
+
df = df.dropna(subset=[WS, WD] + ([val] if val is not None else [])).copy()
|
|
102
|
+
|
|
103
|
+
df['u'] = df[WS].to_numpy() * np.sin(np.radians(df[WD].to_numpy()))
|
|
104
|
+
df['v'] = df[WS].to_numpy() * np.cos(np.radians(df[WD].to_numpy()))
|
|
105
|
+
|
|
106
|
+
u_bins = np.linspace(df.u.min(), df.u.max(), resolution)
|
|
107
|
+
v_bins = np.linspace(df.v.min(), df.v.max(), resolution)
|
|
108
|
+
|
|
109
|
+
# 使用 u_group 和 v_group 進行分組
|
|
110
|
+
df['u_group'] = pd.cut(df['u'], u_bins)
|
|
111
|
+
df['v_group'] = pd.cut(df['v'], v_bins)
|
|
112
|
+
grouped = df.groupby(['u_group', 'v_group'], observed=False)
|
|
113
|
+
|
|
114
|
+
X, Y = np.meshgrid(u_bins, v_bins)
|
|
115
|
+
|
|
116
|
+
# Note:
|
|
117
|
+
# The CBPF is the ratio between the number of points in each cell and the total number of points.
|
|
118
|
+
# So, it is not equal to the probability density function (PDF) of the wind speed and wind direction.
|
|
119
|
+
|
|
120
|
+
if percentile is None:
|
|
121
|
+
histogram = (grouped[val].count() / grouped[val].count().sum()).unstack().values.T
|
|
122
|
+
# histogram, v_edges, u_edges = np.histogram2d(df.v, df.u, bins=(v_bins, u_bins))
|
|
123
|
+
# histogram = histogram / histogram.sum()
|
|
124
|
+
histogram = np.where(histogram == 0, np.nan, histogram)
|
|
125
|
+
bottom_text = rf'$PDF\ plot$'
|
|
126
|
+
|
|
127
|
+
else:
|
|
128
|
+
if not all(0 <= p <= 100 for p in (percentile if isinstance(percentile, list) else [percentile])):
|
|
129
|
+
raise ValueError("Percentile must be between 0 and 100")
|
|
130
|
+
|
|
131
|
+
if isinstance(percentile, (float, int)):
|
|
132
|
+
bottom_text = rf'$CPF:\ >{int(percentile)}^{{th}}$'
|
|
133
|
+
thershold = df[val].quantile(percentile / 100)
|
|
134
|
+
cond = lambda x: (x >= thershold).sum()
|
|
135
|
+
|
|
136
|
+
elif isinstance(percentile, list) and len(percentile) == 1:
|
|
137
|
+
# Extract the single element from the list
|
|
138
|
+
single_percentile = percentile[0]
|
|
139
|
+
bottom_text = rf'$CPF:\ >{int(single_percentile)}^{{th}}$'
|
|
140
|
+
threshold = df[val].quantile(single_percentile / 100)
|
|
141
|
+
cond = lambda x: (x >= threshold).sum()
|
|
142
|
+
|
|
143
|
+
else:
|
|
144
|
+
bottom_text = rf'$CPF:\ {int(percentile[0])}^{{th}}\ to\ {int(percentile[1])}^{{th}}$'
|
|
145
|
+
thershold_small, thershold_large = df[val].quantile([percentile[0] / 100, percentile[1] / 100])
|
|
146
|
+
cond = lambda x: ((x >= thershold_small) & (x < thershold_large)).sum()
|
|
147
|
+
|
|
148
|
+
histogram = (grouped[val].apply(cond) / grouped[val].count()).unstack().values.T
|
|
149
|
+
|
|
150
|
+
# if np.isnan(histogram).all():
|
|
151
|
+
# raise "CBPF_array contains only NaN values."
|
|
152
|
+
# else:
|
|
153
|
+
# print(f"\nHistogram contains NaN before masking: {np.isnan(histogram).sum()}")
|
|
154
|
+
|
|
155
|
+
histogram_filled = np.nan_to_num(histogram, nan=0) # 將 NaN 替換為 0
|
|
156
|
+
|
|
157
|
+
filtered_histogram = gaussian_filter(histogram_filled, sigma=sigma)
|
|
158
|
+
filtered_histogram[np.isnan(histogram)] = np.nan
|
|
159
|
+
|
|
160
|
+
def is_within_circle(center_row, center_col, row, col, radius):
|
|
161
|
+
return np.sqrt((center_row - row) ** 2 + (center_col - col) ** 2) <= radius
|
|
162
|
+
|
|
163
|
+
def remove_lonely_point(filtered_histogram, radius=4, magic_num=13):
|
|
164
|
+
rows, cols = filtered_histogram.shape
|
|
165
|
+
data_positions = np.where(~np.isnan(filtered_histogram))
|
|
166
|
+
|
|
167
|
+
for row, col in zip(*data_positions):
|
|
168
|
+
valid_data_count = 0
|
|
169
|
+
for i in range(max(0, row - radius), min(rows, row + radius + 1)):
|
|
170
|
+
for j in range(max(0, col - radius), min(cols, col + radius + 1)):
|
|
171
|
+
if (i, j) != (row, col) and is_within_circle(row, col, i, j, radius):
|
|
172
|
+
if not np.isnan(filtered_histogram[i, j]):
|
|
173
|
+
valid_data_count += 1
|
|
174
|
+
|
|
175
|
+
if valid_data_count <= magic_num:
|
|
176
|
+
filtered_histogram[row, col] = np.nan
|
|
177
|
+
|
|
178
|
+
return filtered_histogram
|
|
179
|
+
|
|
180
|
+
def fill_nan_with_mean(filtered_histogram, radius=4, magic_num=13):
|
|
181
|
+
rows, cols = filtered_histogram.shape
|
|
182
|
+
nan_positions = np.where(np.isnan(filtered_histogram))
|
|
183
|
+
|
|
184
|
+
for row, col in zip(*nan_positions):
|
|
185
|
+
surrounding_values = []
|
|
186
|
+
surrounding_values_within_one = []
|
|
187
|
+
nan_count = 0
|
|
188
|
+
|
|
189
|
+
for i in range(max(0, row - radius), min(rows, row + radius + 1)):
|
|
190
|
+
for j in range(max(0, col - radius), min(cols, col + radius + 1)):
|
|
191
|
+
if (i, j) != (row, col) and is_within_circle(row, col, i, j, radius):
|
|
192
|
+
if np.isnan(filtered_histogram[i, j]):
|
|
193
|
+
nan_count += 1
|
|
194
|
+
else:
|
|
195
|
+
surrounding_values.append(filtered_histogram[i, j])
|
|
196
|
+
|
|
197
|
+
for i in range(max(0, row - 2), min(rows, row + 2 + 1)):
|
|
198
|
+
for j in range(max(0, col - 2), min(cols, col + 2 + 1)):
|
|
199
|
+
if (i, j) != (row, col) and is_within_circle(row, col, i, j, 2):
|
|
200
|
+
if np.isnan(filtered_histogram[i, j]):
|
|
201
|
+
pass
|
|
202
|
+
else:
|
|
203
|
+
surrounding_values_within_one.append(filtered_histogram[i, j])
|
|
204
|
+
|
|
205
|
+
if nan_count < magic_num and surrounding_values_within_one:
|
|
206
|
+
filtered_histogram[row, col] = np.mean(surrounding_values)
|
|
207
|
+
|
|
208
|
+
return filtered_histogram
|
|
209
|
+
|
|
210
|
+
# Apply the function to your data
|
|
211
|
+
fil_radius, magic_num = 3, 13
|
|
212
|
+
filtered_histogram = remove_lonely_point(filtered_histogram, fil_radius, magic_num)
|
|
213
|
+
filtered_histogram = fill_nan_with_mean(filtered_histogram, fil_radius, magic_num)
|
|
214
|
+
if np.all(np.isnan(filtered_histogram)):
|
|
215
|
+
raise ValueError("All values in the filtered histogram are NaN. Please decrease the resolution.")
|
|
216
|
+
# plot
|
|
217
|
+
fig, ax = plt.subplots()
|
|
218
|
+
fig.subplots_adjust(left=0)
|
|
219
|
+
|
|
220
|
+
surf = ax.pcolormesh(X, Y, filtered_histogram, shading='auto', cmap='jet', antialiased=True)
|
|
221
|
+
|
|
222
|
+
max_ws = max_ws or np.concatenate((abs(df.u), abs(df.v))).max() # Get the maximum value of the wind speed
|
|
223
|
+
|
|
224
|
+
radius_lst = np.arange(1, math.ceil(max_ws) + 1) # Create a list of radius
|
|
225
|
+
|
|
226
|
+
for i, radius in enumerate(radius_lst):
|
|
227
|
+
circle = plt.Circle((0, 0), radius, fill=False, color='gray', linewidth=1, linestyle='--', alpha=0.5)
|
|
228
|
+
ax.add_artist(circle)
|
|
229
|
+
|
|
230
|
+
for angle, label in zip(range(0, 360, 90), ["E", "N", "W", "S"]):
|
|
231
|
+
radian = np.radians(angle)
|
|
232
|
+
line_x, line_y = radius * np.cos(radian), radius * np.sin(radian)
|
|
233
|
+
|
|
234
|
+
if i + 2 == len(radius_lst): # Add wind direction line and direction label at the edge of the circle
|
|
235
|
+
ax.plot([0, line_x * 1.05], [0, line_y * 1.05], color='k', linestyle='-', linewidth=1, alpha=0.5)
|
|
236
|
+
ax.text(line_x * 1.15, line_y * 1.15, label, ha='center', va='center')
|
|
237
|
+
|
|
238
|
+
ax.text(radius * np.cos(np.radians(rlabel_pos)), radius * np.sin(np.radians(rlabel_pos)),
|
|
239
|
+
str(radius) + ' m/s', ha='center', va='center', fontsize=8)
|
|
267
240
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
surf = ax.pcolormesh(X, Y, filtered_histogram, shading='auto', cmap='jet', antialiased=True)
|
|
273
|
-
|
|
274
|
-
max_ws = max_ws or np.concatenate((abs(df.u), abs(df.v))).max() # Get the maximum value of the wind speed
|
|
275
|
-
|
|
276
|
-
radius_lst = np.arange(1, math.ceil(max_ws) + 1) # Create a list of radius
|
|
277
|
-
|
|
278
|
-
for i, radius in enumerate(radius_lst):
|
|
279
|
-
circle = plt.Circle((0, 0), radius, fill=False, color='gray', linewidth=1, linestyle='--', alpha=0.5)
|
|
280
|
-
ax.add_artist(circle)
|
|
281
|
-
|
|
282
|
-
for angle, label in zip(range(0, 360, 90), ["E", "N", "W", "S"]):
|
|
283
|
-
radian = np.radians(angle)
|
|
284
|
-
line_x, line_y = radius * np.cos(radian), radius * np.sin(radian)
|
|
285
|
-
|
|
286
|
-
if i + 2 == len(radius_lst): # Add wind direction line and direction label at the edge of the circle
|
|
287
|
-
ax.plot([0, line_x * 1.05], [0, line_y * 1.05], color='k', linestyle='-', linewidth=1, alpha=0.5)
|
|
288
|
-
ax.text(line_x * 1.15, line_y * 1.15, label, ha='center', va='center')
|
|
241
|
+
for radius in range(math.ceil(max_ws) + 1, 10):
|
|
242
|
+
circle = plt.Circle((0, 0), radius, fill=False, color='gray', linewidth=1, linestyle='--', alpha=0.5)
|
|
243
|
+
ax.add_artist(circle)
|
|
289
244
|
|
|
290
|
-
|
|
291
|
-
|
|
245
|
+
ax.set(xlim=(-max_ws * 1.02, max_ws * 1.02),
|
|
246
|
+
ylim=(-max_ws * 1.02, max_ws * 1.02),
|
|
247
|
+
xticks=[],
|
|
248
|
+
yticks=[],
|
|
249
|
+
xticklabels=[],
|
|
250
|
+
yticklabels=[],
|
|
251
|
+
aspect='equal')
|
|
292
252
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
253
|
+
if bottom_text:
|
|
254
|
+
ax.text(0.50, -0.05, bottom_text, fontweight='bold', fontsize=8, va='center', ha='center',
|
|
255
|
+
transform=ax.transAxes)
|
|
296
256
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
ax.text(0.50, -0.05, bottom_text, fontweight='bold', fontsize=8, va='center', ha='center',
|
|
307
|
-
transform=ax.transAxes)
|
|
308
|
-
|
|
309
|
-
ax.text(0.5, 1.05, Unit(val), fontweight='bold', fontsize=12, va='center', ha='center', transform=ax.transAxes)
|
|
310
|
-
|
|
311
|
-
cbar = plt.colorbar(surf, ax=ax, label='Frequency', pad=0.01, fraction=0.04)
|
|
312
|
-
cbar.ax.yaxis.label.set_fontsize(8)
|
|
313
|
-
cbar.ax.tick_params(labelsize=8)
|
|
314
|
-
|
|
315
|
-
plt.show()
|
|
316
|
-
|
|
317
|
-
return fig, ax
|
|
257
|
+
ax.text(0.5, 1.05, Unit(val), fontweight='bold', fontsize=12, va='center', ha='center', transform=ax.transAxes)
|
|
258
|
+
|
|
259
|
+
cbar = plt.colorbar(surf, ax=ax, label='Frequency', pad=0.01, fraction=0.04)
|
|
260
|
+
cbar.ax.yaxis.label.set_fontsize(8)
|
|
261
|
+
cbar.ax.tick_params(labelsize=8)
|
|
262
|
+
|
|
263
|
+
plt.show()
|
|
264
|
+
|
|
265
|
+
return fig, ax
|
AeroViz/plot/optical/__init__.py
CHANGED