AeroViz 0.1.3b0__py3-none-any.whl → 0.1.4__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 +5 -3
- AeroViz/{config → data}/DEFAULT_DATA.csv +1 -1
- AeroViz/dataProcess/Chemistry/__init__.py +7 -7
- AeroViz/dataProcess/Chemistry/_isoropia.py +5 -2
- AeroViz/dataProcess/Chemistry/_mass_volume.py +15 -18
- AeroViz/dataProcess/Chemistry/_ocec.py +2 -2
- AeroViz/dataProcess/Chemistry/_teom.py +2 -1
- AeroViz/dataProcess/Chemistry/isrpia.cnf +21 -0
- AeroViz/dataProcess/Optical/Angstrom_exponent.py +20 -0
- AeroViz/dataProcess/Optical/_IMPROVE.py +13 -15
- AeroViz/dataProcess/Optical/__init__.py +15 -30
- AeroViz/dataProcess/Optical/_absorption.py +21 -47
- AeroViz/dataProcess/Optical/_extinction.py +20 -15
- AeroViz/dataProcess/Optical/_mie.py +0 -1
- AeroViz/dataProcess/Optical/_scattering.py +19 -20
- AeroViz/dataProcess/SizeDistr/__init__.py +7 -7
- AeroViz/dataProcess/SizeDistr/_merge.py +2 -2
- AeroViz/dataProcess/SizeDistr/_merge_v1.py +2 -2
- AeroViz/dataProcess/SizeDistr/_merge_v2.py +2 -2
- AeroViz/dataProcess/SizeDistr/_merge_v3.py +1 -1
- AeroViz/dataProcess/SizeDistr/_merge_v4.py +1 -1
- AeroViz/dataProcess/VOC/__init__.py +3 -3
- AeroViz/dataProcess/__init__.py +28 -6
- AeroViz/dataProcess/core/__init__.py +10 -17
- AeroViz/plot/__init__.py +1 -1
- AeroViz/plot/box.py +2 -1
- AeroViz/plot/optical/optical.py +4 -4
- AeroViz/plot/regression.py +25 -39
- AeroViz/plot/scatter.py +68 -2
- AeroViz/plot/templates/__init__.py +2 -1
- AeroViz/plot/templates/ammonium_rich.py +34 -0
- AeroViz/plot/templates/diurnal_pattern.py +11 -9
- AeroViz/plot/templates/koschmieder.py +51 -115
- AeroViz/plot/templates/metal_heatmap.py +115 -17
- AeroViz/plot/timeseries/__init__.py +1 -0
- AeroViz/plot/timeseries/template.py +47 -0
- AeroViz/plot/timeseries/timeseries.py +275 -208
- AeroViz/plot/utils/plt_utils.py +2 -2
- AeroViz/plot/utils/units.json +5 -0
- AeroViz/plot/violin.py +9 -8
- AeroViz/process/__init__.py +2 -2
- AeroViz/process/script/AbstractDistCalc.py +1 -1
- AeroViz/process/script/Chemical.py +5 -4
- AeroViz/process/script/Others.py +1 -1
- AeroViz/rawDataReader/__init__.py +17 -22
- AeroViz/rawDataReader/{utils/config.py → config/supported_instruments.py} +38 -52
- AeroViz/rawDataReader/core/__init__.py +104 -229
- AeroViz/rawDataReader/script/AE33.py +10 -11
- AeroViz/rawDataReader/script/AE43.py +8 -11
- AeroViz/rawDataReader/script/APS_3321.py +6 -6
- AeroViz/rawDataReader/script/Aurora.py +18 -19
- AeroViz/rawDataReader/script/BC1054.py +11 -15
- AeroViz/rawDataReader/script/EPA_vertical.py +35 -7
- AeroViz/rawDataReader/script/GRIMM.py +2 -9
- AeroViz/rawDataReader/script/{IGAC_ZM.py → IGAC.py} +17 -17
- AeroViz/rawDataReader/script/MA350.py +7 -14
- AeroViz/rawDataReader/script/Minion.py +103 -0
- AeroViz/rawDataReader/script/NEPH.py +24 -29
- AeroViz/rawDataReader/script/SMPS_TH.py +4 -4
- AeroViz/rawDataReader/script/SMPS_aim11.py +6 -6
- AeroViz/rawDataReader/script/SMPS_genr.py +6 -6
- AeroViz/rawDataReader/script/Sunset_OCEC.py +60 -0
- AeroViz/rawDataReader/script/TEOM.py +8 -6
- AeroViz/rawDataReader/script/Table.py +7 -8
- AeroViz/rawDataReader/script/VOC.py +26 -0
- AeroViz/rawDataReader/script/__init__.py +10 -12
- AeroViz/tools/database.py +7 -9
- AeroViz/tools/datareader.py +3 -3
- {AeroViz-0.1.3b0.dist-info → AeroViz-0.1.4.dist-info}/METADATA +1 -1
- AeroViz-0.1.4.dist-info/RECORD +112 -0
- AeroViz/rawDataReader/script/IGAC_TH.py +0 -104
- AeroViz/rawDataReader/script/OCEC_LCRES.py +0 -34
- AeroViz/rawDataReader/script/OCEC_RES.py +0 -28
- AeroViz/rawDataReader/script/VOC_TH.py +0 -30
- AeroViz/rawDataReader/script/VOC_ZM.py +0 -37
- AeroViz-0.1.3b0.dist-info/RECORD +0 -110
- /AeroViz/{config → data}/DEFAULT_PNSD_DATA.csv +0 -0
- /AeroViz/rawDataReader/{utils → config}/__init__.py +0 -0
- {AeroViz-0.1.3b0.dist-info → AeroViz-0.1.4.dist-info}/LICENSE +0 -0
- {AeroViz-0.1.3b0.dist-info → AeroViz-0.1.4.dist-info}/WHEEL +0 -0
- {AeroViz-0.1.3b0.dist-info → AeroViz-0.1.4.dist-info}/top_level.txt +0 -0
|
@@ -1,38 +1,39 @@
|
|
|
1
|
-
from datetime import datetime
|
|
2
1
|
from typing import Literal
|
|
3
2
|
|
|
4
3
|
import matplotlib.pyplot as plt
|
|
4
|
+
import numpy as np
|
|
5
5
|
from matplotlib.cm import ScalarMappable
|
|
6
6
|
from matplotlib.pyplot import Figure, Axes
|
|
7
7
|
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
|
|
8
|
-
from pandas import DataFrame, date_range
|
|
8
|
+
from pandas import DataFrame, date_range
|
|
9
9
|
|
|
10
10
|
from AeroViz.plot.utils import *
|
|
11
11
|
|
|
12
|
-
__all__ = ['timeseries', '
|
|
12
|
+
__all__ = ['timeseries', 'timeseries_stacked']
|
|
13
|
+
|
|
13
14
|
|
|
14
15
|
default_bar_kws = dict(
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
width=0.0417,
|
|
17
|
+
edgecolor=None,
|
|
18
|
+
linewidth=0,
|
|
19
|
+
cmap='jet',
|
|
19
20
|
)
|
|
20
21
|
|
|
21
22
|
default_scatter_kws = dict(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
marker='o',
|
|
24
|
+
s=5,
|
|
25
|
+
edgecolor=None,
|
|
26
|
+
linewidths=0.3,
|
|
27
|
+
alpha=0.9,
|
|
28
|
+
cmap='jet',
|
|
28
29
|
)
|
|
29
30
|
|
|
30
31
|
default_insert_kws = dict(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
width="1.5%",
|
|
33
|
+
height="100%",
|
|
34
|
+
loc='lower left',
|
|
35
|
+
bbox_to_anchor=(1.01, 0, 1.2, 1),
|
|
36
|
+
borderpad=0
|
|
36
37
|
)
|
|
37
38
|
|
|
38
39
|
default_plot_kws = dict()
|
|
@@ -41,51 +42,62 @@ default_cbar_kws = dict()
|
|
|
41
42
|
|
|
42
43
|
|
|
43
44
|
def _scatter(ax, df, _y, _c, scatter_kws, cbar_kws, inset_kws):
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
if _c is None or _c not in df.columns:
|
|
46
|
+
scatter_kws.pop('cmap')
|
|
47
|
+
ax.scatter(df.index, df[_y], **scatter_kws)
|
|
48
|
+
else:
|
|
49
|
+
ax.scatter(df.index, df[_y], c=df[_c], **scatter_kws)
|
|
50
|
+
cax = inset_axes(ax, **inset_kws)
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
# Filter the children to find ScalarMappable objects
|
|
53
|
+
mappable_objects = [child for child in ax.get_children() if isinstance(child, ScalarMappable)]
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
# Use the first mappable object for the colorbar
|
|
56
|
+
if mappable_objects:
|
|
57
|
+
plt.colorbar(mappable=mappable_objects[0], cax=cax, **cbar_kws)
|
|
58
|
+
else:
|
|
59
|
+
print("No mappable objects found.")
|
|
59
60
|
|
|
60
61
|
|
|
61
62
|
def _bar(ax, df, _y, _c, bar_kws, cbar_kws, inset_kws):
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
scalar_map, colors = Color.color_maker(df[_c].values, cmap=bar_kws.pop('cmap'))
|
|
64
|
+
ax.bar(df.index, df[_y], color=scalar_map.to_rgba(colors), **bar_kws)
|
|
65
|
+
cax = inset_axes(ax, **inset_kws)
|
|
66
|
+
plt.colorbar(mappable=scalar_map, cax=cax, **cbar_kws)
|
|
66
67
|
|
|
67
68
|
|
|
68
69
|
def _plot(ax, df, _y, _color, plot_kws):
|
|
69
|
-
|
|
70
|
+
ax.plot(df.index, df[_y], color=_color, **plot_kws)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def process_timeseries_data(df, rolling=None, interpolate_limit=None):
|
|
74
|
+
# apply rolling window if specified
|
|
75
|
+
df = df.rolling(window=rolling, min_periods=1).mean(numeric_only=True) if rolling is not None else df
|
|
70
76
|
|
|
77
|
+
# apply interpolation if specified
|
|
78
|
+
df = df.interpolate(method='time', limit=interpolate_limit) if interpolate_limit is not None else df
|
|
79
|
+
return df
|
|
71
80
|
|
|
72
|
-
|
|
81
|
+
|
|
82
|
+
@set_figure(autolayout=False)
|
|
73
83
|
def timeseries(df: DataFrame,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
84
|
+
y: list[str] | str,
|
|
85
|
+
y2: list[str] | str = None,
|
|
86
|
+
yi: list[str] | str = None,
|
|
87
|
+
color: list[str] | str | None = None,
|
|
88
|
+
label: list[str] | str | None = None,
|
|
89
|
+
rolling: int | str | None = 3,
|
|
90
|
+
interpolate_limit: int | None = 6,
|
|
91
|
+
major_freq: str = '1MS',
|
|
92
|
+
minor_freq: str = '10d',
|
|
93
|
+
style: list[Literal['scatter', 'bar', 'line']] | str | None = None,
|
|
94
|
+
ax: Axes | None = None,
|
|
95
|
+
set_xaxis_visible: bool | None = None,
|
|
96
|
+
legend_loc: Literal['best', 'upper right', 'upper left', 'lower left', 'lower right'] = 'best',
|
|
97
|
+
legend_ncol: int = 1,
|
|
98
|
+
**kwargs
|
|
99
|
+
) -> tuple[Figure, Axes]:
|
|
100
|
+
"""
|
|
89
101
|
Plot the timeseries data with the option of scatterplot, barplot, and lineplot.
|
|
90
102
|
|
|
91
103
|
Parameters
|
|
@@ -96,14 +108,20 @@ def timeseries(df: DataFrame,
|
|
|
96
108
|
The primary y-axis data columns.
|
|
97
109
|
y2 : list[str] | str, optional
|
|
98
110
|
The secondary y-axis data columns. Defaults to None.
|
|
99
|
-
|
|
111
|
+
yi : list[str] | str, optional
|
|
112
|
+
The components for percentage calculation. Defaults to None.
|
|
113
|
+
color : str, optional
|
|
100
114
|
The column for color mapping or the color. Defaults to None.
|
|
115
|
+
label : str, optional
|
|
116
|
+
The label for the legend. Defaults to None.
|
|
101
117
|
rolling : str | int | None, optional
|
|
102
118
|
Rolling window size for smoothing. Defaults to None.
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
Frequency for x-axis ticks. Defaults to '
|
|
119
|
+
interpolate_limit : int, optional
|
|
120
|
+
Interpolation limit for missing values. Defaults to None.
|
|
121
|
+
major_freq : str, optional
|
|
122
|
+
Frequency for x-axis ticks. Defaults to '1MS'.
|
|
123
|
+
minor_freq : str, optional
|
|
124
|
+
Frequency for x-axis minor ticks. Defaults to '10d'.
|
|
107
125
|
style : Literal['scatter', 'bar', 'line'] | None, optional
|
|
108
126
|
Style of the plot. Defaults to 'scatter'.
|
|
109
127
|
ax : Axes | None, optional
|
|
@@ -137,159 +155,208 @@ def timeseries(df: DataFrame,
|
|
|
137
155
|
|
|
138
156
|
Example
|
|
139
157
|
-------
|
|
140
|
-
>>> timeseries(df, y='WS',
|
|
158
|
+
>>> timeseries(df, y='WS', color='WD', scatter_kws=dict(cmap='hsv'), cbar_kws=dict(ticks=[0, 90, 180, 270, 360]), ylim=[0, None])
|
|
141
159
|
"""
|
|
142
|
-
|
|
160
|
+
# Set the time
|
|
161
|
+
try:
|
|
162
|
+
st_tm, fn_tm = df.index[0], df.index[-1]
|
|
163
|
+
except IndexError:
|
|
164
|
+
raise IndexError("The DataFrame is empty. Please provide a valid DataFrame.")
|
|
143
165
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
raise IndexError("The DataFrame is empty. Please provide a valid DataFrame.")
|
|
166
|
+
# calculate the percentage of each component
|
|
167
|
+
if yi is not None:
|
|
168
|
+
df_pct = df[yi].div(df[yi].sum(axis=1), axis=0) * 100
|
|
169
|
+
mean = [f"{_label} : {df[comp].mean():.2f}" for _label, comp in zip(label, yi)]
|
|
170
|
+
pct = [f"{_label} : {df_pct[comp].mean():.2f}%" for _label, comp in zip(label, yi)]
|
|
171
|
+
df_pct = process_timeseries_data(df_pct, rolling, interpolate_limit)
|
|
151
172
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
df.loc[st_tm:fn_tm].rolling(window=rolling, min_periods=1).mean(numeric_only=True))
|
|
173
|
+
# process data
|
|
174
|
+
df = process_timeseries_data(df, rolling, interpolate_limit)
|
|
155
175
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
176
|
+
# Initialize figure and axis if not provided
|
|
177
|
+
fig, ax = plt.subplots(**{**{'figsize': (6, 2)}, **kwargs.get('fig_kws', {})}) if ax is None else (
|
|
178
|
+
ax.get_figure(), ax)
|
|
159
179
|
|
|
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
|
-
@set_figure(
|
|
256
|
-
def
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
180
|
+
# Ensure y, y2, c, and style are lists
|
|
181
|
+
y = [y] if isinstance(y, str) else y
|
|
182
|
+
y2 = [y2] if isinstance(y2, str) else y2 if y2 is not None else []
|
|
183
|
+
color = [color] if isinstance(color, str) else color if color is not None else [None] * (len(y) + len(y2))
|
|
184
|
+
label = [label] if isinstance(label, str) else label if label is not None else [None] * (len(y) + len(y2))
|
|
185
|
+
style = [style] if isinstance(style, str) else style if style is not None else ['plot'] * (len(y) + len(y2))
|
|
186
|
+
|
|
187
|
+
for name, lst in [("c", color), ("style", style), ("label", label)]:
|
|
188
|
+
if len(lst) != len(y) + len(y2):
|
|
189
|
+
raise ValueError(f"The length of {name} must match the combined length of y and y2")
|
|
190
|
+
|
|
191
|
+
# Create a secondary y-axis if y2 is not empty
|
|
192
|
+
ax2 = ax.twinx() if y2 else None
|
|
193
|
+
|
|
194
|
+
# # Set color cycle
|
|
195
|
+
ax.set_prop_cycle(Color.color_cycle)
|
|
196
|
+
if y2:
|
|
197
|
+
ax2.set_prop_cycle(Color.color_cycle[len(y):])
|
|
198
|
+
|
|
199
|
+
if y2 and ('scatter' or 'bar') in style:
|
|
200
|
+
fig.subplots_adjust(right=0.8)
|
|
201
|
+
|
|
202
|
+
for i, _c in enumerate(color):
|
|
203
|
+
if _c is not None and _c in df.columns:
|
|
204
|
+
style[i] = 'scatter'
|
|
205
|
+
|
|
206
|
+
for i, (_y, _c, _label, _style) in enumerate(zip(y, color, label, style)):
|
|
207
|
+
scatter_kws = {**default_scatter_kws, **{'label': Unit(_y)}, **kwargs.get('scatter_kws', {})}
|
|
208
|
+
bar_kws = {**default_bar_kws, **{'label': Unit(_y)}, **kwargs.get('bar_kws', {})}
|
|
209
|
+
plot_kws = {**default_plot_kws, **{'label': Unit(_y)}, **kwargs.get('plot_kws', {})}
|
|
210
|
+
|
|
211
|
+
if _style in ['scatter', 'bar']:
|
|
212
|
+
cbar_kws = {**default_cbar_kws, **{'label': Unit(_c), 'ticks': None}, **kwargs.get('cbar_kws', {})}
|
|
213
|
+
inset_kws = {**default_insert_kws, **{'bbox_transform': ax.transAxes}, **kwargs.get('inset_kws', {})}
|
|
214
|
+
|
|
215
|
+
if _style == 'scatter':
|
|
216
|
+
_scatter(ax, df, _y, _c, scatter_kws, cbar_kws, inset_kws)
|
|
217
|
+
|
|
218
|
+
elif _style == 'bar':
|
|
219
|
+
_bar(ax, df, _y, _c, bar_kws, cbar_kws, inset_kws)
|
|
220
|
+
|
|
221
|
+
else:
|
|
222
|
+
_plot(ax, df, _y, _c, plot_kws)
|
|
223
|
+
|
|
224
|
+
if y2:
|
|
225
|
+
for i, (_y, _c, _style) in enumerate(zip(y2, color[len(y):], style[len(y):])):
|
|
226
|
+
scatter_kws = {**default_scatter_kws, **{'label': Unit(_y)}, **kwargs.get('scatter_kws2', {})}
|
|
227
|
+
bar_kws = {**default_bar_kws, **{'label': Unit(_y)}, **kwargs.get('bar_kws2', {})}
|
|
228
|
+
plot_kws = {**default_plot_kws, **{'label': Unit(_y)}, **kwargs.get('plot_kws2', {})}
|
|
229
|
+
|
|
230
|
+
if _style in ['scatter', 'bar']:
|
|
231
|
+
cbar_kws = {**default_cbar_kws, **{'label': Unit(_c), 'ticks': None}, **kwargs.get('cbar_kws2', {})}
|
|
232
|
+
inset_kws = {**default_insert_kws, **{'bbox_transform': ax.transAxes}, **kwargs.get('inset_kws2', {})}
|
|
233
|
+
|
|
234
|
+
if _style == 'scatter':
|
|
235
|
+
_scatter(ax2, df, _y, _c, scatter_kws, cbar_kws, inset_kws)
|
|
236
|
+
|
|
237
|
+
elif _style == 'bar':
|
|
238
|
+
_bar(ax2, df, _y, _c, bar_kws, cbar_kws, inset_kws)
|
|
239
|
+
|
|
240
|
+
else: # line plot
|
|
241
|
+
_plot(ax2, df, _y, _c, plot_kws)
|
|
242
|
+
|
|
243
|
+
# Combine legends from ax and ax2
|
|
244
|
+
ax.legend(*combine_legends([ax, ax2]), loc=legend_loc, ncol=legend_ncol)
|
|
245
|
+
|
|
246
|
+
else:
|
|
247
|
+
ax.legend(loc=legend_loc, ncol=legend_ncol)
|
|
248
|
+
|
|
249
|
+
if set_xaxis_visible is not None:
|
|
250
|
+
ax.axes.xaxis.set_visible(set_xaxis_visible)
|
|
251
|
+
|
|
252
|
+
ax.set(xlabel=kwargs.get('xlabel', ''),
|
|
253
|
+
ylabel=kwargs.get('ylabel', Unit(y) if isinstance(y, str) else Unit(y[0])),
|
|
254
|
+
xlim=kwargs.get('xlim', (st_tm, fn_tm)),
|
|
255
|
+
ylim=kwargs.get('ylim', (None, None)),
|
|
256
|
+
title=kwargs.get('title', '')
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
xticks = kwargs.get('xticks', date_range(start=st_tm, end=fn_tm, freq=major_freq))
|
|
260
|
+
minor_xticks = kwargs.get('minor_xticks', date_range(start=st_tm, end=fn_tm, freq=minor_freq))
|
|
261
|
+
|
|
262
|
+
ax.set_xticks(ticks=xticks, labels=xticks.strftime("%F"))
|
|
263
|
+
ax.set_xticks(minor_xticks, minor=True)
|
|
264
|
+
|
|
265
|
+
if y2:
|
|
266
|
+
ax2.set(ylim=kwargs.get('ylim2', (None, None)),
|
|
267
|
+
ylabel=kwargs.get('ylabel2', Unit(y2) if isinstance(y2, str) else Unit(y2[0]))
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
plt.show()
|
|
271
|
+
|
|
272
|
+
return fig, ax
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
@set_figure(autolayout=False)
|
|
276
|
+
def timeseries_stacked(df,
|
|
277
|
+
y: list[str] | str,
|
|
278
|
+
yi: list[str] | str,
|
|
279
|
+
label: list[str] | str,
|
|
280
|
+
rolling: int | str | None = 6,
|
|
281
|
+
interpolate_limit: int | None = 6,
|
|
282
|
+
major_freq: str = '1MS',
|
|
283
|
+
minor_freq: str = '10d',
|
|
284
|
+
ax: Axes | None = None,
|
|
285
|
+
legend_ncol: int = 1,
|
|
286
|
+
**kwargs
|
|
287
|
+
) -> tuple[Figure, Axes]:
|
|
288
|
+
try:
|
|
289
|
+
st_tm, fn_tm = df.index[0], df.index[-1]
|
|
290
|
+
except IndexError:
|
|
291
|
+
raise IndexError("The DataFrame is empty. Please provide a valid DataFrame.")
|
|
292
|
+
|
|
293
|
+
# calculate the percentage of each component
|
|
294
|
+
df_pct = df[yi].div(df[yi].sum(axis=1), axis=0) * 100
|
|
295
|
+
mean = [f"{_label} : {df[comp].mean():.2f}" for _label, comp in zip(label, yi)]
|
|
296
|
+
pct = [f"{_label} : {df_pct[comp].mean():.2f}%" for _label, comp in zip(label, yi)]
|
|
297
|
+
|
|
298
|
+
# process data
|
|
299
|
+
df = process_timeseries_data(df, rolling, interpolate_limit)
|
|
300
|
+
df_pct = process_timeseries_data(df_pct, rolling, interpolate_limit)
|
|
301
|
+
|
|
302
|
+
fig, (ax1, ax2) = plt.subplots(2, 1, **{**{'figsize': (12, 3)}, **kwargs.get('fig_kws', {})})
|
|
303
|
+
|
|
304
|
+
width = 0.0417
|
|
305
|
+
color = Color.colors1
|
|
306
|
+
|
|
307
|
+
for name, lst in [("color", color), ("label", label)]:
|
|
308
|
+
if len(lst) != len(yi):
|
|
309
|
+
raise ValueError(f"The length of {name} must match the combined length of y and y2")
|
|
310
|
+
|
|
311
|
+
bottom = None # 初始化堆疊位置
|
|
312
|
+
for i, (_column, _color, _label) in enumerate(zip(yi, color, mean)):
|
|
313
|
+
if i == 0:
|
|
314
|
+
bottom = df[_column] * 0 # 第一個堆疊底部為零
|
|
315
|
+
ax1.bar(df.index, df[_column], color=_color, width=width, bottom=bottom, label=_label)
|
|
316
|
+
bottom += df[_column] # 更新堆疊底部位置
|
|
317
|
+
|
|
318
|
+
ax1.legend(loc='upper left', ncol=legend_ncol, prop={'weight': 'bold'}, bbox_to_anchor=(1, 0, 1.2, 1))
|
|
319
|
+
|
|
320
|
+
ax1.axes.xaxis.set_visible(False)
|
|
321
|
+
|
|
322
|
+
ax1.set(xlabel=kwargs.get('xlabel', ''),
|
|
323
|
+
ylabel=kwargs.get('ylabel', Unit(y) if isinstance(y, str) else Unit(y[0])),
|
|
324
|
+
xlim=kwargs.get('xlim', (st_tm, fn_tm)),
|
|
325
|
+
ylim=kwargs.get('ylim', (None, None)),
|
|
326
|
+
title=kwargs.get('title', ''),
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
xticks = kwargs.get('xticks', date_range(start=st_tm, end=fn_tm, freq=major_freq))
|
|
330
|
+
yticks = kwargs.get('yticks', np.linspace(*ax1.get_ylim(), num=6))
|
|
331
|
+
minor_xticks = kwargs.get('minor_xticks', date_range(start=st_tm, end=fn_tm, freq=minor_freq))
|
|
332
|
+
|
|
333
|
+
ax1.set_xticks(ticks=xticks, labels=xticks.strftime("%F"))
|
|
334
|
+
ax1.set_yticks(ticks=yticks, labels=[f'{tick:.0f}' for tick in yticks])
|
|
335
|
+
ax1.set_xticks(minor_xticks, minor=True)
|
|
336
|
+
|
|
337
|
+
# ax2
|
|
338
|
+
bottom = None # 初始化堆疊位置
|
|
339
|
+
for i, (_column, _color, _label) in enumerate(zip(yi, color, pct)):
|
|
340
|
+
if i == 0:
|
|
341
|
+
bottom = df_pct[_column] * 0 # 第一個堆疊底部為零
|
|
342
|
+
ax2.bar(df_pct.index, df_pct[_column], color=_color, width=width, bottom=bottom, label=_label)
|
|
343
|
+
bottom += df_pct[_column] # 更新堆疊底部位置
|
|
344
|
+
|
|
345
|
+
ax2.legend(loc='upper left', ncol=legend_ncol, prop={'weight': 'bold'}, bbox_to_anchor=(1, 0, 1.2, 1))
|
|
346
|
+
|
|
347
|
+
ax2.set(xlabel=kwargs.get('xlabel', ''),
|
|
348
|
+
ylabel=kwargs.get('ylabel', 'Percentage (%)'),
|
|
349
|
+
xlim=kwargs.get('xlim', (st_tm, fn_tm)),
|
|
350
|
+
ylim=(0, 100),
|
|
351
|
+
title=kwargs.get('title', '')
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
xticks = kwargs.get('xticks', date_range(start=st_tm, end=fn_tm, freq=major_freq))
|
|
355
|
+
yticks = kwargs.get('yticks', np.linspace(*ax2.get_ylim(), num=6))
|
|
356
|
+
minor_xticks = kwargs.get('minor_xticks', date_range(start=st_tm, end=fn_tm, freq=minor_freq))
|
|
357
|
+
|
|
358
|
+
ax2.set_xticks(ticks=xticks, labels=xticks.strftime("%F"))
|
|
359
|
+
ax2.set_yticks(ticks=yticks, labels=[f'{tick:.0f}' for tick in yticks])
|
|
360
|
+
ax2.set_xticks(minor_xticks, minor=True)
|
|
361
|
+
|
|
362
|
+
return fig, ax1
|
AeroViz/plot/utils/plt_utils.py
CHANGED
|
@@ -17,7 +17,7 @@ def set_figure(func=None,
|
|
|
17
17
|
def decorator(_func):
|
|
18
18
|
@wraps(_func)
|
|
19
19
|
def wrapper(*args, **kwargs):
|
|
20
|
-
print(f'\tPlot:\033[96m {_func.__name__}\033[0m')
|
|
20
|
+
print(f'\n\tPlot:\033[96m {_func.__name__}\033[0m')
|
|
21
21
|
|
|
22
22
|
plt.rcParams['mathtext.fontset'] = 'custom'
|
|
23
23
|
plt.rcParams['mathtext.rm'] = 'Times New Roman'
|
|
@@ -53,7 +53,7 @@ def set_figure(func=None,
|
|
|
53
53
|
plt.rcParams['figure.dpi'] = 200
|
|
54
54
|
plt.rcParams['figure.autolayout'] = autolayout
|
|
55
55
|
|
|
56
|
-
if
|
|
56
|
+
if not autolayout:
|
|
57
57
|
plt.rcParams['figure.subplot.left'] = 0.1
|
|
58
58
|
plt.rcParams['figure.subplot.right'] = 0.875
|
|
59
59
|
plt.rcParams['figure.subplot.top'] = 0.875
|
AeroViz/plot/utils/units.json
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
"T_OC": "OC (\u00b5g/m^3)",
|
|
7
7
|
"PM1": "PM_{1} (\u00b5g/m^3)",
|
|
8
8
|
"PM25": "PM_{2.5} (\u00b5g/m^3)",
|
|
9
|
+
"PM2.5": "PM_{2.5} (\u00b5g/m^3)",
|
|
10
|
+
"PM10": "PM_{10} (\u00b5g/m^3)",
|
|
9
11
|
"SIA": "SIA (\u00b5g/m^3)",
|
|
10
12
|
"POC": "POC (\u00b5g/m^3)",
|
|
11
13
|
"SOC": "SOC (\u00b5g/m^3)",
|
|
@@ -20,12 +22,15 @@
|
|
|
20
22
|
"Babs": "Mie Amb Absorption (1/Mm)",
|
|
21
23
|
"Babs_dry": "Mie Dry Absorption (1/Mm)",
|
|
22
24
|
"Absorption": "Absorption (1/Mm)",
|
|
25
|
+
"abs": "Absorption (1/Mm)",
|
|
23
26
|
"Bext": "Mie Amb Extinction (1/Mm)",
|
|
24
27
|
"Bext_dry": "Mie Dry Extinction (1/Mm)",
|
|
25
28
|
"Extinction": "Extinction (1/Mm)",
|
|
29
|
+
"ext": "Extinction (1/Mm)",
|
|
26
30
|
"Bsca": "Mie Amb Scattering (1/Mm)",
|
|
27
31
|
"Bsca_dry": "Mie Dry Scattering (1/Mm)",
|
|
28
32
|
"Scattering": "Scattering (1/Mm)",
|
|
33
|
+
"sca": "Scattering (1/Mm)",
|
|
29
34
|
"Diurnal": "Hour",
|
|
30
35
|
"PBLH": "PBLH (m)",
|
|
31
36
|
"VC": "VC (m²/s)",
|
AeroViz/plot/violin.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import matplotlib.pyplot as plt
|
|
2
2
|
import numpy as np
|
|
3
3
|
import pandas as pd
|
|
4
|
+
import seaborn as sns
|
|
4
5
|
from matplotlib.pyplot import Figure, Axes
|
|
5
6
|
from pandas import DataFrame
|
|
6
7
|
|
|
@@ -10,7 +11,7 @@ __all__ = ['violin']
|
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
@set_figure(fw='bold')
|
|
13
|
-
def violin(
|
|
14
|
+
def violin(df: DataFrame | dict,
|
|
14
15
|
unit: str,
|
|
15
16
|
ax: Axes | None = None,
|
|
16
17
|
**kwargs
|
|
@@ -20,7 +21,7 @@ def violin(data_set: DataFrame | dict,
|
|
|
20
21
|
|
|
21
22
|
Parameters
|
|
22
23
|
----------
|
|
23
|
-
|
|
24
|
+
df : pd.DataFrame or dict
|
|
24
25
|
A mapping from category names to pandas DataFrames containing the data.
|
|
25
26
|
unit : str
|
|
26
27
|
The unit for the data being plotted.
|
|
@@ -31,13 +32,15 @@ def violin(data_set: DataFrame | dict,
|
|
|
31
32
|
|
|
32
33
|
Returns
|
|
33
34
|
-------
|
|
34
|
-
|
|
35
|
-
The
|
|
35
|
+
fig : Figure
|
|
36
|
+
The matplotlib Figure object.
|
|
37
|
+
ax : Axes
|
|
38
|
+
The matplotlib Axes object with the scatter plot.
|
|
36
39
|
|
|
37
40
|
"""
|
|
38
41
|
fig, ax = plt.subplots(**kwargs.get('fig_kws', {})) if ax is None else (ax.get_figure(), ax)
|
|
39
42
|
|
|
40
|
-
data =
|
|
43
|
+
data = df.to_numpy()
|
|
41
44
|
|
|
42
45
|
data = data[~np.isnan(data).any(axis=1)]
|
|
43
46
|
|
|
@@ -67,13 +70,11 @@ def violin(data_set: DataFrame | dict,
|
|
|
67
70
|
ylim = kwargs.get('ylim') or (0, None)
|
|
68
71
|
xlabel = kwargs.get('xlabel') or ''
|
|
69
72
|
ylabel = kwargs.get('ylabel') or Unit(unit)
|
|
70
|
-
xticks = kwargs.get('xticks') or [x.replace('-', '\n') for x in list(
|
|
73
|
+
xticks = kwargs.get('xticks') or [x.replace('-', '\n') for x in list(df.keys())]
|
|
71
74
|
|
|
72
75
|
ax.set(xlim=xlim, ylim=ylim, xlabel=xlabel, ylabel=ylabel, title=kwargs.get('title'))
|
|
73
76
|
ax.set_xticks(x_position, xticks, fontweight='bold', fontsize=12)
|
|
74
77
|
|
|
75
|
-
# fig.savefig(f'Violin_{unit}')
|
|
76
|
-
|
|
77
78
|
plt.show()
|
|
78
79
|
|
|
79
80
|
return fig, ax
|