AeroViz 0.1.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 AeroViz might be problematic. Click here for more details.
- AeroViz/__init__.py +15 -0
- AeroViz/dataProcess/Chemistry/__init__.py +63 -0
- AeroViz/dataProcess/Chemistry/_calculate.py +27 -0
- AeroViz/dataProcess/Chemistry/_isoropia.py +99 -0
- AeroViz/dataProcess/Chemistry/_mass_volume.py +175 -0
- AeroViz/dataProcess/Chemistry/_ocec.py +184 -0
- AeroViz/dataProcess/Chemistry/_partition.py +29 -0
- AeroViz/dataProcess/Chemistry/_teom.py +16 -0
- AeroViz/dataProcess/Optical/_IMPROVE.py +61 -0
- AeroViz/dataProcess/Optical/__init__.py +62 -0
- AeroViz/dataProcess/Optical/_absorption.py +54 -0
- AeroViz/dataProcess/Optical/_extinction.py +36 -0
- AeroViz/dataProcess/Optical/_mie.py +16 -0
- AeroViz/dataProcess/Optical/_mie_sd.py +143 -0
- AeroViz/dataProcess/Optical/_scattering.py +30 -0
- AeroViz/dataProcess/SizeDistr/__init__.py +61 -0
- AeroViz/dataProcess/SizeDistr/__merge.py +250 -0
- AeroViz/dataProcess/SizeDistr/_merge.py +245 -0
- AeroViz/dataProcess/SizeDistr/_merge_v1.py +254 -0
- AeroViz/dataProcess/SizeDistr/_merge_v2.py +243 -0
- AeroViz/dataProcess/SizeDistr/_merge_v3.py +518 -0
- AeroViz/dataProcess/SizeDistr/_merge_v4.py +424 -0
- AeroViz/dataProcess/SizeDistr/_size_distr.py +93 -0
- AeroViz/dataProcess/VOC/__init__.py +19 -0
- AeroViz/dataProcess/VOC/_potential_par.py +76 -0
- AeroViz/dataProcess/__init__.py +11 -0
- AeroViz/dataProcess/core/__init__.py +92 -0
- AeroViz/plot/__init__.py +7 -0
- AeroViz/plot/distribution/__init__.py +1 -0
- AeroViz/plot/distribution/distribution.py +582 -0
- AeroViz/plot/improve/__init__.py +1 -0
- AeroViz/plot/improve/improve.py +240 -0
- AeroViz/plot/meteorology/__init__.py +1 -0
- AeroViz/plot/meteorology/meteorology.py +317 -0
- AeroViz/plot/optical/__init__.py +2 -0
- AeroViz/plot/optical/aethalometer.py +77 -0
- AeroViz/plot/optical/optical.py +388 -0
- AeroViz/plot/templates/__init__.py +8 -0
- AeroViz/plot/templates/contour.py +47 -0
- AeroViz/plot/templates/corr_matrix.py +108 -0
- AeroViz/plot/templates/diurnal_pattern.py +42 -0
- AeroViz/plot/templates/event_evolution.py +65 -0
- AeroViz/plot/templates/koschmieder.py +156 -0
- AeroViz/plot/templates/metal_heatmap.py +57 -0
- AeroViz/plot/templates/regression.py +256 -0
- AeroViz/plot/templates/scatter.py +130 -0
- AeroViz/plot/templates/templates.py +398 -0
- AeroViz/plot/timeseries/__init__.py +1 -0
- AeroViz/plot/timeseries/timeseries.py +317 -0
- AeroViz/plot/utils/__init__.py +3 -0
- AeroViz/plot/utils/_color.py +71 -0
- AeroViz/plot/utils/_decorator.py +74 -0
- AeroViz/plot/utils/_unit.py +55 -0
- AeroViz/process/__init__.py +31 -0
- AeroViz/process/core/DataProc.py +19 -0
- AeroViz/process/core/SizeDist.py +90 -0
- AeroViz/process/core/__init__.py +4 -0
- AeroViz/process/method/PyMieScatt_update.py +567 -0
- AeroViz/process/method/__init__.py +2 -0
- AeroViz/process/method/mie_theory.py +258 -0
- AeroViz/process/method/prop.py +62 -0
- AeroViz/process/script/AbstractDistCalc.py +143 -0
- AeroViz/process/script/Chemical.py +176 -0
- AeroViz/process/script/IMPACT.py +49 -0
- AeroViz/process/script/IMPROVE.py +161 -0
- AeroViz/process/script/Others.py +65 -0
- AeroViz/process/script/PSD.py +103 -0
- AeroViz/process/script/PSD_dry.py +94 -0
- AeroViz/process/script/__init__.py +5 -0
- AeroViz/process/script/retrieve_RI.py +70 -0
- AeroViz/rawDataReader/__init__.py +68 -0
- AeroViz/rawDataReader/core/__init__.py +397 -0
- AeroViz/rawDataReader/script/AE33.py +31 -0
- AeroViz/rawDataReader/script/AE43.py +34 -0
- AeroViz/rawDataReader/script/APS_3321.py +47 -0
- AeroViz/rawDataReader/script/Aurora.py +38 -0
- AeroViz/rawDataReader/script/BC1054.py +46 -0
- AeroViz/rawDataReader/script/EPA_vertical.py +18 -0
- AeroViz/rawDataReader/script/GRIMM.py +35 -0
- AeroViz/rawDataReader/script/IGAC_TH.py +104 -0
- AeroViz/rawDataReader/script/IGAC_ZM.py +90 -0
- AeroViz/rawDataReader/script/MA350.py +45 -0
- AeroViz/rawDataReader/script/NEPH.py +57 -0
- AeroViz/rawDataReader/script/OCEC_LCRES.py +34 -0
- AeroViz/rawDataReader/script/OCEC_RES.py +28 -0
- AeroViz/rawDataReader/script/SMPS_TH.py +41 -0
- AeroViz/rawDataReader/script/SMPS_aim11.py +51 -0
- AeroViz/rawDataReader/script/SMPS_genr.py +51 -0
- AeroViz/rawDataReader/script/TEOM.py +46 -0
- AeroViz/rawDataReader/script/Table.py +28 -0
- AeroViz/rawDataReader/script/VOC_TH.py +30 -0
- AeroViz/rawDataReader/script/VOC_ZM.py +37 -0
- AeroViz/rawDataReader/script/__init__.py +22 -0
- AeroViz/tools/__init__.py +3 -0
- AeroViz/tools/database.py +94 -0
- AeroViz/tools/dataclassifier.py +117 -0
- AeroViz/tools/datareader.py +66 -0
- AeroViz-0.1.0.dist-info/LICENSE +21 -0
- AeroViz-0.1.0.dist-info/METADATA +117 -0
- AeroViz-0.1.0.dist-info/RECORD +102 -0
- AeroViz-0.1.0.dist-info/WHEEL +5 -0
- AeroViz-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Literal
|
|
3
|
+
|
|
4
|
+
import matplotlib.pyplot as plt
|
|
5
|
+
from matplotlib.cm import ScalarMappable
|
|
6
|
+
from matplotlib.pyplot import Figure, Axes
|
|
7
|
+
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
|
|
8
|
+
from pandas import DataFrame, date_range, Timestamp
|
|
9
|
+
|
|
10
|
+
from AeroViz.plot.utils import *
|
|
11
|
+
|
|
12
|
+
__all__ = ['timeseries', 'timeseries_template']
|
|
13
|
+
|
|
14
|
+
default_bar_kws = dict(
|
|
15
|
+
width=0.0417,
|
|
16
|
+
edgecolor=None,
|
|
17
|
+
linewidth=0,
|
|
18
|
+
cmap='jet',
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
default_scatter_kws = dict(
|
|
22
|
+
marker='o',
|
|
23
|
+
s=5,
|
|
24
|
+
edgecolor=None,
|
|
25
|
+
linewidths=0.3,
|
|
26
|
+
alpha=0.9,
|
|
27
|
+
cmap='jet',
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
default_insert_kws = dict(
|
|
31
|
+
width="1.5%",
|
|
32
|
+
height="100%",
|
|
33
|
+
loc='lower left',
|
|
34
|
+
bbox_to_anchor=(1.01, 0, 1.2, 1),
|
|
35
|
+
borderpad=0
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
default_plot_kws = dict()
|
|
39
|
+
|
|
40
|
+
default_cbar_kws = dict()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _scatter(ax, df, _y, _c, scatter_kws, cbar_kws, inset_kws):
|
|
44
|
+
if _c is None or _c not in df.columns:
|
|
45
|
+
scatter_kws.pop('cmap')
|
|
46
|
+
ax.scatter(df.index, df[_y], **scatter_kws)
|
|
47
|
+
else:
|
|
48
|
+
ax.scatter(df.index, df[_y], c=df[_c], **scatter_kws)
|
|
49
|
+
cax = inset_axes(ax, **inset_kws)
|
|
50
|
+
|
|
51
|
+
# Filter the children to find ScalarMappable objects
|
|
52
|
+
mappable_objects = [child for child in ax.get_children() if isinstance(child, ScalarMappable)]
|
|
53
|
+
|
|
54
|
+
# Use the first mappable object for the colorbar
|
|
55
|
+
if mappable_objects:
|
|
56
|
+
plt.colorbar(mappable=mappable_objects[0], cax=cax, **cbar_kws)
|
|
57
|
+
else:
|
|
58
|
+
print("No mappable objects found.")
|
|
59
|
+
|
|
60
|
+
# plt.colorbar(mappable=ax.get_children()[0], cax=cax, **cbar_kws)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _bar(ax, df, _y, _c, bar_kws, cbar_kws, inset_kws):
|
|
64
|
+
scalar_map, colors = Color.color_maker(df[_c].values, cmap=bar_kws.pop('cmap'))
|
|
65
|
+
ax.bar(df.index, df[_y], color=scalar_map.to_rgba(colors), **bar_kws)
|
|
66
|
+
cax = inset_axes(ax, **inset_kws)
|
|
67
|
+
plt.colorbar(mappable=scalar_map, cax=cax, **cbar_kws)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _plot(ax, df, _y, _color, plot_kws):
|
|
71
|
+
ax.plot(df.index, df[_y], color=_color, **plot_kws)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def combine_legends(axes_list: list[Axes]) -> tuple[list, list]:
|
|
75
|
+
return (
|
|
76
|
+
[legend for axes in axes_list for legend in axes.get_legend_handles_labels()[0]],
|
|
77
|
+
[label for axes in axes_list for label in axes.get_legend_handles_labels()[1]]
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@set_figure(fs=8, autolayout=False)
|
|
82
|
+
def timeseries(df: DataFrame,
|
|
83
|
+
y: list[str] | str,
|
|
84
|
+
y2: list[str] | str = None,
|
|
85
|
+
c: list[str] | str = None,
|
|
86
|
+
# color: list[str] | str = None,
|
|
87
|
+
rolling: str | int | None = None,
|
|
88
|
+
times: tuple[datetime | Timestamp | str, datetime | Timestamp | str] = None,
|
|
89
|
+
freq: str = '1MS',
|
|
90
|
+
style: list[Literal['scatter', 'bar', 'line']] | str | None = None,
|
|
91
|
+
ax: Axes | None = None,
|
|
92
|
+
set_xaxis_visible: bool | None = None,
|
|
93
|
+
legend_loc: Literal['best', 'upper right', 'upper left', 'lower left', 'lower right'] = 'best',
|
|
94
|
+
legend_ncol: int = 1,
|
|
95
|
+
**kwargs
|
|
96
|
+
) -> tuple[Figure, Axes]:
|
|
97
|
+
"""
|
|
98
|
+
Plot the timeseries data with the option of scatterplot, barplot, and lineplot.
|
|
99
|
+
|
|
100
|
+
Parameters
|
|
101
|
+
-----------
|
|
102
|
+
df : DataFrame
|
|
103
|
+
The data to plot.
|
|
104
|
+
y : list[str] | str
|
|
105
|
+
The primary y-axis data columns.
|
|
106
|
+
y2 : list[str] | str, optional
|
|
107
|
+
The secondary y-axis data columns. Defaults to None.
|
|
108
|
+
c : str, optional
|
|
109
|
+
The column for color mapping or the color. Defaults to None.
|
|
110
|
+
rolling : str | int | None, optional
|
|
111
|
+
Rolling window size for smoothing. Defaults to None.
|
|
112
|
+
times : tuple[datetime, datetime] | tuple[Timestamp, Timestamp], optional
|
|
113
|
+
Time range for the data. Defaults to None.
|
|
114
|
+
freq : str, optional
|
|
115
|
+
Frequency for x-axis ticks. Defaults to '2MS'.
|
|
116
|
+
style : Literal['scatter', 'bar', 'line'] | None, optional
|
|
117
|
+
Style of the plot. Defaults to 'scatter'.
|
|
118
|
+
ax : Axes | None, optional
|
|
119
|
+
Matplotlib Axes object to plot on. Defaults to None.
|
|
120
|
+
set_xaxis_visible : bool | None, optional
|
|
121
|
+
Whether to set x-axis visibility. Defaults to None.
|
|
122
|
+
legend_loc : Literal['best', 'upper right', 'upper left', 'lower left', 'lower right'], optional
|
|
123
|
+
Location of the legend. Defaults to 'best'.
|
|
124
|
+
legend_ncol : int, optional
|
|
125
|
+
Number of columns in the legend. Defaults to 1.
|
|
126
|
+
**kwargs : Additional keyword arguments for customization.
|
|
127
|
+
fig_kws : dict, optional
|
|
128
|
+
Additional keyword arguments for the figure. Defaults to {}.
|
|
129
|
+
scatter_kws : dict, optional
|
|
130
|
+
Additional keyword arguments for the scatter plot. Defaults to {}.
|
|
131
|
+
bar_kws : dict, optional
|
|
132
|
+
Additional keyword arguments for the bar plot. Defaults to {}.
|
|
133
|
+
ax_plot_kws : dict, optional
|
|
134
|
+
Additional keyword arguments for the primary y-axis plot. Defaults to {}.
|
|
135
|
+
ax2_plot_kws : dict, optional
|
|
136
|
+
Additional keyword arguments for the secondary y-axis plot. Defaults to {}.
|
|
137
|
+
cbar_kws : dict, optional
|
|
138
|
+
Additional keyword arguments for the colorbar. Defaults to {}.
|
|
139
|
+
inset_kws : dict, optional
|
|
140
|
+
Additional keyword arguments for the inset axes. Defaults to {}.
|
|
141
|
+
|
|
142
|
+
Returns
|
|
143
|
+
-------
|
|
144
|
+
ax : AxesSubplot
|
|
145
|
+
Matplotlib AxesSubplot.
|
|
146
|
+
|
|
147
|
+
Example
|
|
148
|
+
-------
|
|
149
|
+
>>> timeseries(df, y='WS', c='WD', scatter_kws=dict(cmap='hsv'), cbar_kws=dict(ticks=[0, 90, 180, 270, 360]), ylim=[0, None])
|
|
150
|
+
"""
|
|
151
|
+
# Set the time
|
|
152
|
+
st_tm, fn_tm = (df.index[0], df.index[-1]) if times is None else map(Timestamp, times)
|
|
153
|
+
|
|
154
|
+
# Apply rolling window if specified
|
|
155
|
+
df = df.loc[st_tm:fn_tm] if rolling is None else (
|
|
156
|
+
df.loc[st_tm:fn_tm].rolling(window=rolling, min_periods=1).mean(numeric_only=True))
|
|
157
|
+
|
|
158
|
+
# Initialize figure and axis if not provided
|
|
159
|
+
fig, ax = plt.subplots(**{**{'figsize': (6, 2)}, **kwargs.get('fig_kws', {})}) if ax is None else (
|
|
160
|
+
ax.get_figure(), ax)
|
|
161
|
+
|
|
162
|
+
# Ensure y, y2, c, and style are lists
|
|
163
|
+
y = [y] if isinstance(y, str) else y
|
|
164
|
+
y2 = [y2] if isinstance(y2, str) else y2 if y2 is not None else []
|
|
165
|
+
c = [c] if isinstance(c, str) else c if c is not None else [None] * (len(y) + len(y2))
|
|
166
|
+
style = [style] if isinstance(style, str) else style if style is not None else ['plot'] * (len(y) + len(y2))
|
|
167
|
+
|
|
168
|
+
if len(c) != len(y) + len(y2):
|
|
169
|
+
raise ValueError("The length of c must match the combined length of y and y2")
|
|
170
|
+
|
|
171
|
+
if len(style) != len(y) + len(y2):
|
|
172
|
+
raise ValueError("The length of style must match the combined length of y and y2")
|
|
173
|
+
|
|
174
|
+
# Create a secondary y-axis if y2 is not empty
|
|
175
|
+
ax2 = ax.twinx() if y2 else None
|
|
176
|
+
|
|
177
|
+
# # Set color cycle
|
|
178
|
+
ax.set_prop_cycle(Color.color_cycle)
|
|
179
|
+
if y2:
|
|
180
|
+
ax2.set_prop_cycle(Color.color_cycle[len(y):])
|
|
181
|
+
|
|
182
|
+
if y2 and ('scatter' or 'bar') in style:
|
|
183
|
+
fig.subplots_adjust(right=0.8)
|
|
184
|
+
|
|
185
|
+
for i, _c in enumerate(c):
|
|
186
|
+
if _c is not None and _c in df.columns:
|
|
187
|
+
style[i] = 'scatter'
|
|
188
|
+
|
|
189
|
+
for i, (_y, _c, _style) in enumerate(zip(y, c, style)):
|
|
190
|
+
scatter_kws = {**default_scatter_kws, **{'label': Unit(_y)}, **kwargs.get('scatter_kws', {})}
|
|
191
|
+
bar_kws = {**default_bar_kws, **{'label': Unit(_y)}, **kwargs.get('bar_kws', {})}
|
|
192
|
+
plot_kws = {**default_plot_kws, **{'label': Unit(_y)}, **kwargs.get('plot_kws', {})}
|
|
193
|
+
|
|
194
|
+
if _style in ['scatter', 'bar']:
|
|
195
|
+
cbar_kws = {**default_cbar_kws, **{'label': Unit(_c), 'ticks': None}, **kwargs.get('cbar_kws', {})}
|
|
196
|
+
inset_kws = {**default_insert_kws, **{'bbox_transform': ax.transAxes}, **kwargs.get('inset_kws', {})}
|
|
197
|
+
|
|
198
|
+
if _style == 'scatter':
|
|
199
|
+
_scatter(ax, df, _y, _c, scatter_kws, cbar_kws, inset_kws)
|
|
200
|
+
|
|
201
|
+
elif _style == 'bar':
|
|
202
|
+
_bar(ax, df, _y, _c, bar_kws, cbar_kws, inset_kws)
|
|
203
|
+
|
|
204
|
+
else:
|
|
205
|
+
_plot(ax, df, _y, _c, plot_kws)
|
|
206
|
+
|
|
207
|
+
if y2:
|
|
208
|
+
for i, (_y, _c, _style) in enumerate(zip(y2, c[len(y):], style[len(y):])):
|
|
209
|
+
scatter_kws = {**default_scatter_kws, **{'label': Unit(_y)}, **kwargs.get('scatter_kws2', {})}
|
|
210
|
+
bar_kws = {**default_bar_kws, **{'label': Unit(_y)}, **kwargs.get('bar_kws2', {})}
|
|
211
|
+
plot_kws = {**default_plot_kws, **{'label': Unit(_y)}, **kwargs.get('plot_kws2', {})}
|
|
212
|
+
|
|
213
|
+
if _style in ['scatter', 'bar']:
|
|
214
|
+
cbar_kws = {**default_cbar_kws, **{'label': Unit(_c), 'ticks': None}, **kwargs.get('cbar_kws2', {})}
|
|
215
|
+
inset_kws = {**default_insert_kws, **{'bbox_transform': ax.transAxes}, **kwargs.get('inset_kws2', {})}
|
|
216
|
+
|
|
217
|
+
if _style == 'scatter':
|
|
218
|
+
_scatter(ax2, df, _y, _c, scatter_kws, cbar_kws, inset_kws)
|
|
219
|
+
|
|
220
|
+
elif _style == 'bar':
|
|
221
|
+
_bar(ax2, df, _y, _c, bar_kws, cbar_kws, inset_kws)
|
|
222
|
+
|
|
223
|
+
else: # line plot
|
|
224
|
+
_plot(ax2, df, _y, _c, plot_kws)
|
|
225
|
+
|
|
226
|
+
# Combine legends from ax and ax2
|
|
227
|
+
ax.legend(*combine_legends([ax, ax2]), loc=legend_loc, ncol=legend_ncol)
|
|
228
|
+
|
|
229
|
+
else:
|
|
230
|
+
ax.legend(loc=legend_loc, ncol=legend_ncol)
|
|
231
|
+
|
|
232
|
+
if set_xaxis_visible is not None:
|
|
233
|
+
ax.axes.xaxis.set_visible(set_xaxis_visible)
|
|
234
|
+
|
|
235
|
+
ax.set(xlabel=kwargs.get('xlabel', ''),
|
|
236
|
+
ylabel=kwargs.get('ylabel', Unit(y) if isinstance(y, str) else Unit(y[0])),
|
|
237
|
+
xticks=kwargs.get('xticks', date_range(start=st_tm, end=fn_tm, freq=freq).strftime("%F")),
|
|
238
|
+
yticks=kwargs.get('yticks', ax.get_yticks()),
|
|
239
|
+
xticklabels=kwargs.get('xticklabels', date_range(start=st_tm, end=fn_tm, freq=freq).strftime("%F")),
|
|
240
|
+
yticklabels=kwargs.get('yticklabels', ax.get_yticklabels()),
|
|
241
|
+
xlim=kwargs.get('xlim', (st_tm, fn_tm)),
|
|
242
|
+
ylim=kwargs.get('ylim', (None, None)),
|
|
243
|
+
title=kwargs.get('title', '')
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
if y2:
|
|
247
|
+
ax2.set(ylabel=kwargs.get('ylabel2', Unit(y2) if isinstance(y2, str) else Unit(y2[0])),
|
|
248
|
+
yticks=kwargs.get('yticks2', ax2.get_yticks()),
|
|
249
|
+
yticklabels=kwargs.get('yticklabels2', ax2.get_yticklabels()),
|
|
250
|
+
ylim=kwargs.get('ylim2', (None, None)))
|
|
251
|
+
|
|
252
|
+
plt.show()
|
|
253
|
+
|
|
254
|
+
return fig, ax
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@set_figure(fs=8, autolayout=False)
|
|
258
|
+
def timeseries_template(df: DataFrame) -> tuple[Figure, Axes]:
|
|
259
|
+
fig, ax = plt.subplots(5, 1, figsize=(len(df.index) * 0.01, 4))
|
|
260
|
+
(ax1, ax2, ax3, ax4, ax5) = ax
|
|
261
|
+
|
|
262
|
+
timeseries(df,
|
|
263
|
+
y=['Extinction', 'Scattering', 'Absorption'],
|
|
264
|
+
rolling=30,
|
|
265
|
+
ax=ax1,
|
|
266
|
+
ylabel='Coefficient',
|
|
267
|
+
ylim=[0., None],
|
|
268
|
+
set_xaxis_visible=False,
|
|
269
|
+
legend_ncol=3,
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
# Temp, RH
|
|
273
|
+
timeseries(df,
|
|
274
|
+
y='AT',
|
|
275
|
+
y2='RH',
|
|
276
|
+
rolling=30,
|
|
277
|
+
ax=ax2,
|
|
278
|
+
ax_plot_kws=dict(color='r'),
|
|
279
|
+
ax2_plot_kws=dict(color='b'),
|
|
280
|
+
ylim=[10, 40],
|
|
281
|
+
ylim2=[20, 100],
|
|
282
|
+
set_xaxis_visible=False,
|
|
283
|
+
legend_ncol=2,
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
timeseries(df, y='WS', c='WD', style='scatter', ax=ax3, scatter_kws=dict(cmap='hsv'),
|
|
287
|
+
cbar_kws=dict(ticks=[0, 90, 180, 270, 360]),
|
|
288
|
+
ylim=[0, None], set_xaxis_visible=False)
|
|
289
|
+
|
|
290
|
+
timeseries(df, y='VC', c='PBLH', style='bar', ax=ax4, bar_kws=dict(cmap='Blues'), set_xaxis_visible=False,
|
|
291
|
+
ylim=[0, 5000])
|
|
292
|
+
|
|
293
|
+
timeseries(df, y='PM25', c='PM1/PM25', style='scatter', ax=ax5, ylim=[0, None])
|
|
294
|
+
|
|
295
|
+
plt.show()
|
|
296
|
+
|
|
297
|
+
return fig, ax
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
if __name__ == '__main__':
|
|
301
|
+
from AeroViz import *
|
|
302
|
+
|
|
303
|
+
df = DataBase('/Users/chanchihyu/NTU/2020能見度計畫/data/All_data.csv')
|
|
304
|
+
|
|
305
|
+
plot.timeseries.timeseries(df,
|
|
306
|
+
y=['Extinction', 'Scattering', 'Absorption'],
|
|
307
|
+
y2=['PBLH'],
|
|
308
|
+
c=['PM25', None, None, None],
|
|
309
|
+
style=['scatter', 'line', 'line', 'line'],
|
|
310
|
+
times=('2020-10-01', '2020-11-30'), ylim=[0, None], ylim2=[0, None], rolling=50,
|
|
311
|
+
inset_kws=dict(bbox_to_anchor=(1.12, 0, 1.2, 1)),
|
|
312
|
+
legend_ncol=4)
|
|
313
|
+
|
|
314
|
+
# timeseries(df, y='WS', c='WD', style='scatter', times=('2020-10-01', '2020-11-30'), scatter_kws=dict(cmap='hsv'), cbar_kws=dict(ticks=[0, 90, 180, 270, 360]),
|
|
315
|
+
# ylim=[0, None])
|
|
316
|
+
|
|
317
|
+
# timeseries_template(df.loc['2020-09-01':'2020-12-31'])
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import matplotlib.colors as plc
|
|
2
|
+
import matplotlib.pyplot as plt
|
|
3
|
+
import numpy as np
|
|
4
|
+
import seaborn as sns
|
|
5
|
+
from cycler import cycler
|
|
6
|
+
from matplotlib import colormaps
|
|
7
|
+
|
|
8
|
+
__all__ = ['Color']
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Color:
|
|
12
|
+
color_cycle = cycler(color=['b', 'g', 'r', 'c', 'm', 'y', 'k'])
|
|
13
|
+
|
|
14
|
+
linecolor = [{'line': '#1a56db', 'edge': '#0F50A6', 'face': '#5983D9'},
|
|
15
|
+
{'line': '#046c4e', 'edge': '#1B591F', 'face': '#538C4A'},
|
|
16
|
+
{'line': '#c81e1e', 'edge': '#f05252', 'face': '#f98080'}]
|
|
17
|
+
|
|
18
|
+
# colors = ['#FF3333', '#33FF33', '#FFFF33', '#5555FF', '#B94FFF', '#AAAAAA', '#748690'] # the last one is "unknown"
|
|
19
|
+
|
|
20
|
+
colors1 = ['#A65E58', '#A5BF6B', '#F2BF5E', '#3F83BF', '#B777C2', '#D1CFCB']
|
|
21
|
+
colors2 = ['#A65E58', '#A5BF6B', '#F2BF5E', '#3F83BF', '#B777C2', '#D1CFCB', '#96c8e6']
|
|
22
|
+
colors3 = ['#A65E58', '#A5BF6B', '#a6710d', '#F2BF5E', '#3F83BF', '#B777C2', '#D1CFCB', '#96c8e6'] # POC SOC
|
|
23
|
+
|
|
24
|
+
colors_mutiWater = ['#A65E58', '#c18e8a', '#A5BF6B', '#c5d6a0', '#F2BF5E', '#3F83BF', '#c089ca', '#d3acda',
|
|
25
|
+
'#D1CFCB']
|
|
26
|
+
colors_mutiWater2 = ['#A65E58', '#96c8e6', '#A5BF6B', '#96c8e6', '#F2BF5E', '#3F83BF', '#c089ca', '#96c8e6',
|
|
27
|
+
'#D1CFCB'] # water
|
|
28
|
+
|
|
29
|
+
color_choose = {'Clean': ['#1d4a9f', '#84a7e9'],
|
|
30
|
+
'Transition': ['#4a9f1d', '#a7e984'],
|
|
31
|
+
'Event': ['#9f1d4a', '#e984a7']}
|
|
32
|
+
|
|
33
|
+
paired = [plt.get_cmap('Paired')(i) for i in range(4)]
|
|
34
|
+
|
|
35
|
+
@staticmethod
|
|
36
|
+
def getColor(num: int = 6, cmap: str = 'jet_r'):
|
|
37
|
+
category_colors = plt.colormaps[cmap](np.linspace(0.1, 0.9, num))
|
|
38
|
+
return [plc.to_hex(category_colors[i]) for i in range(num)]
|
|
39
|
+
|
|
40
|
+
@staticmethod
|
|
41
|
+
def palplot(*args, **kwargs):
|
|
42
|
+
sns.palplot(*args, **kwargs)
|
|
43
|
+
|
|
44
|
+
@staticmethod
|
|
45
|
+
def adjust_opacity(colors: str | list[str], alpha: float):
|
|
46
|
+
if isinstance(colors, str):
|
|
47
|
+
colors = [colors]
|
|
48
|
+
|
|
49
|
+
adjusted_colors = []
|
|
50
|
+
for color in colors:
|
|
51
|
+
# 將顏色轉換為RGB表示
|
|
52
|
+
r, g, b = int(color[1:3], 16), int(color[3:5], 16), int(color[5:7], 16)
|
|
53
|
+
# 調整透明度
|
|
54
|
+
r_new = int(alpha * r + (1 - alpha) * 255)
|
|
55
|
+
g_new = int(alpha * g + (1 - alpha) * 255)
|
|
56
|
+
b_new = int(alpha * b + (1 - alpha) * 255)
|
|
57
|
+
# 轉換為新的色碼
|
|
58
|
+
new_color = '#{:02X}{:02X}{:02X}'.format(r_new, g_new, b_new)
|
|
59
|
+
adjusted_colors.append(new_color)
|
|
60
|
+
return adjusted_colors
|
|
61
|
+
|
|
62
|
+
@staticmethod
|
|
63
|
+
def color_maker(obj, cmap='Blues'):
|
|
64
|
+
colors = np.nan_to_num(obj, nan=0)
|
|
65
|
+
scalar_map = plt.cm.ScalarMappable(cmap=colormaps[cmap]) # create a scalar map for the colorbar
|
|
66
|
+
scalar_map.set_array(colors)
|
|
67
|
+
return scalar_map, colors
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
if __name__ == '__main__':
|
|
71
|
+
Color.palplot(Color.colors2)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from functools import wraps
|
|
2
|
+
|
|
3
|
+
import matplotlib.pyplot as plt
|
|
4
|
+
|
|
5
|
+
__all__ = ['set_figure']
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# For more details please see https://matplotlib.org/stable/users/explain/customizing.html
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def set_figure(func=None,
|
|
12
|
+
*,
|
|
13
|
+
figsize: tuple | None = None,
|
|
14
|
+
fs: int | None = None,
|
|
15
|
+
fw: str = None,
|
|
16
|
+
autolayout: bool = True
|
|
17
|
+
):
|
|
18
|
+
def decorator(_func):
|
|
19
|
+
@wraps(_func)
|
|
20
|
+
def wrapper(*args, **kwargs):
|
|
21
|
+
print(f'\t\t Plot: \033[96m{_func.__name__}\033[0m')
|
|
22
|
+
|
|
23
|
+
plt.rcParams['mathtext.fontset'] = 'custom'
|
|
24
|
+
plt.rcParams['mathtext.rm'] = 'Times New Roman'
|
|
25
|
+
plt.rcParams['mathtext.it'] = 'Times New Roman: italic'
|
|
26
|
+
plt.rcParams['mathtext.bf'] = 'Times New Roman: bold'
|
|
27
|
+
plt.rcParams['mathtext.default'] = 'regular'
|
|
28
|
+
|
|
29
|
+
# The font properties used by `text.Text`.
|
|
30
|
+
# The text, annotate, label, title, ticks, are used to create text
|
|
31
|
+
plt.rcParams['font.family'] = 'Times New Roman'
|
|
32
|
+
plt.rcParams['font.weight'] = fw or 'normal'
|
|
33
|
+
plt.rcParams['font.size'] = fs or 8
|
|
34
|
+
|
|
35
|
+
plt.rcParams['axes.titlesize'] = 'large'
|
|
36
|
+
plt.rcParams['axes.titleweight'] = 'bold'
|
|
37
|
+
plt.rcParams['axes.labelweight'] = 'bold'
|
|
38
|
+
|
|
39
|
+
# color
|
|
40
|
+
plt.rcParams['axes.prop_cycle'] = plt.cycler(color=['b', 'g', 'r', 'c', 'm', 'y', 'k'])
|
|
41
|
+
|
|
42
|
+
plt.rcParams['xtick.labelsize'] = 'medium'
|
|
43
|
+
plt.rcParams['ytick.labelsize'] = 'medium'
|
|
44
|
+
|
|
45
|
+
# matplotlib.font_manager.FontProperties ---> matplotlib.rcParams
|
|
46
|
+
plt.rcParams['legend.loc'] = 'best'
|
|
47
|
+
plt.rcParams['legend.frameon'] = False
|
|
48
|
+
plt.rcParams['legend.fontsize'] = 'small'
|
|
49
|
+
plt.rcParams['legend.title_fontsize'] = 'medium'
|
|
50
|
+
plt.rcParams['legend.handlelength'] = 1.5
|
|
51
|
+
plt.rcParams['legend.labelspacing'] = 0.7
|
|
52
|
+
|
|
53
|
+
plt.rcParams['figure.figsize'] = figsize or (5, 4)
|
|
54
|
+
plt.rcParams['figure.dpi'] = 200
|
|
55
|
+
plt.rcParams['figure.autolayout'] = autolayout
|
|
56
|
+
|
|
57
|
+
if ~autolayout:
|
|
58
|
+
plt.rcParams['figure.subplot.left'] = 0.1
|
|
59
|
+
plt.rcParams['figure.subplot.right'] = 0.875
|
|
60
|
+
plt.rcParams['figure.subplot.top'] = 0.875
|
|
61
|
+
plt.rcParams['figure.subplot.bottom'] = 0.125
|
|
62
|
+
|
|
63
|
+
# plt.rcParams['figure.constrained_layout.use'] = True
|
|
64
|
+
|
|
65
|
+
plt.rcParams['savefig.transparent'] = True
|
|
66
|
+
|
|
67
|
+
return _func(*args, **kwargs)
|
|
68
|
+
|
|
69
|
+
return wrapper
|
|
70
|
+
|
|
71
|
+
if func is None:
|
|
72
|
+
return decorator
|
|
73
|
+
|
|
74
|
+
return decorator(func)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
__all__ = ['Unit']
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Unit:
|
|
8
|
+
file_path = Path(__file__).parent / 'units.json'
|
|
9
|
+
data = None
|
|
10
|
+
|
|
11
|
+
def __new__(cls, unit: str):
|
|
12
|
+
cls.data = cls.load_jsonfile()
|
|
13
|
+
try:
|
|
14
|
+
value = cls.data[unit]
|
|
15
|
+
return r'${}$'.format(value.replace(' ', r'\ '))
|
|
16
|
+
except KeyError:
|
|
17
|
+
print(f"Attribute '{unit}' not found. Using default value.")
|
|
18
|
+
return r'${}$'.format(unit.replace(' ', r'\ ')) if unit is not None else 'None'
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def load_jsonfile(cls):
|
|
22
|
+
""" 讀取 JSON 檔中數據并將其變成屬性 """
|
|
23
|
+
try:
|
|
24
|
+
with open(cls.file_path, 'r', encoding='utf-8') as f:
|
|
25
|
+
return json.load(f)
|
|
26
|
+
|
|
27
|
+
except FileNotFoundError:
|
|
28
|
+
print(f"JSON file '{cls.file_path}' not found.")
|
|
29
|
+
except json.JSONDecodeError:
|
|
30
|
+
print(f"Invalid JSON format in '{cls.file_path}'.")
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def update_jsonfile(cls, key, value):
|
|
34
|
+
""" 更新JSON檔 """
|
|
35
|
+
with open(cls.file_path, 'r', encoding='utf-8') as f:
|
|
36
|
+
old_data = json.load(f)
|
|
37
|
+
|
|
38
|
+
old_data[key] = value
|
|
39
|
+
|
|
40
|
+
with open(cls.file_path, 'w', encoding='utf-8') as f:
|
|
41
|
+
json.dump(old_data, f, indent=4)
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
def del_jsonfile(cls, key):
|
|
45
|
+
""" 更新JSON檔 """
|
|
46
|
+
with open(cls.file_path, 'r', encoding='utf-8') as f:
|
|
47
|
+
old_data = json.load(f)
|
|
48
|
+
|
|
49
|
+
if key in old_data:
|
|
50
|
+
del old_data[key]
|
|
51
|
+
|
|
52
|
+
with open(cls.file_path, 'w', encoding='utf-8') as f:
|
|
53
|
+
json.dump(old_data, f, indent=4)
|
|
54
|
+
else:
|
|
55
|
+
print(f"Key '{key}' not found.")
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from pandas import read_csv, concat
|
|
4
|
+
|
|
5
|
+
from AeroViz.process.script import (ImpactProc, ImproveProc, ChemicalProc, ParticleSizeDistProc,
|
|
6
|
+
ExtinctionDistProc, OthersProc)
|
|
7
|
+
|
|
8
|
+
__all__ = ['DataProcess']
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DataProcess:
|
|
12
|
+
def __new__(cls, file_path, reset: bool = False, save_file: Path | str = 'All_data.csv'):
|
|
13
|
+
file_path = Path(file_path)
|
|
14
|
+
|
|
15
|
+
print(f'\t\t \033[96m --- Processing Data --- \033[0m')
|
|
16
|
+
|
|
17
|
+
if file_path.exists() and not reset:
|
|
18
|
+
return read_csv(file_path, parse_dates=['Time'], index_col='Time',
|
|
19
|
+
na_values=('-', 'E', 'F'), low_memory=False)
|
|
20
|
+
|
|
21
|
+
processor = [ImpactProc, ChemicalProc, ImproveProc, ParticleSizeDistProc, ExtinctionDistProc, OthersProc]
|
|
22
|
+
reset = [False, False, False, False, False, False]
|
|
23
|
+
save_filename = ['IMPACT.csv', 'chemical.csv', 'revised_IMPROVE.csv', 'PSD.csv', 'PESD.csv', 'Others.csv']
|
|
24
|
+
|
|
25
|
+
_df = concat([processor().process_data(reset, save_filename) for processor, reset, save_filename in
|
|
26
|
+
zip(processor, reset, save_filename)], axis=1)
|
|
27
|
+
|
|
28
|
+
# 7. save result
|
|
29
|
+
_df.to_csv(file_path)
|
|
30
|
+
|
|
31
|
+
return _df
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from pandas import DataFrame
|
|
5
|
+
|
|
6
|
+
__all__ = ['DataProc']
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DataProc(ABC):
|
|
10
|
+
def __init__(self):
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
@abstractmethod
|
|
14
|
+
def process_data(self,
|
|
15
|
+
reset: bool = False,
|
|
16
|
+
save_filename: str | Path = None
|
|
17
|
+
) -> DataFrame:
|
|
18
|
+
""" Implementation of processing data """
|
|
19
|
+
pass
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from pandas import DataFrame
|
|
5
|
+
|
|
6
|
+
__all__ = ['SizeDist']
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SizeDist:
|
|
10
|
+
"""
|
|
11
|
+
Attributes
|
|
12
|
+
----------
|
|
13
|
+
|
|
14
|
+
_data: DataFrame
|
|
15
|
+
The processed PSD data stored as a pandas DataFrame.
|
|
16
|
+
|
|
17
|
+
_dp: ndarray
|
|
18
|
+
The array of particle diameters from the PSD data.
|
|
19
|
+
|
|
20
|
+
_dlogdp: ndarray
|
|
21
|
+
The array of logarithmic particle diameter bin widths.
|
|
22
|
+
|
|
23
|
+
_index: DatetimeIndex
|
|
24
|
+
The index of the DataFrame representing time.
|
|
25
|
+
|
|
26
|
+
_state: str
|
|
27
|
+
The state of particle size distribution data.
|
|
28
|
+
|
|
29
|
+
Methods
|
|
30
|
+
-------
|
|
31
|
+
number()
|
|
32
|
+
Calculate number distribution properties.
|
|
33
|
+
|
|
34
|
+
surface(filename='PSSD_dSdlogdp.csv')
|
|
35
|
+
Calculate surface distribution properties.
|
|
36
|
+
|
|
37
|
+
volume(filename='PVSD_dVdlogdp.csv')
|
|
38
|
+
Calculate volume distribution properties.
|
|
39
|
+
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(self,
|
|
43
|
+
data: DataFrame,
|
|
44
|
+
state: Literal['dN', 'ddp', 'dlogdp'] = 'dlogdp',
|
|
45
|
+
weighting: Literal['n', 's', 'v', 'ext_in', 'ext_ex'] = 'n'
|
|
46
|
+
):
|
|
47
|
+
self._data = data
|
|
48
|
+
self._dp = np.array(self._data.columns, dtype=float)
|
|
49
|
+
self._dlogdp = np.full_like(self._dp, 0.014)
|
|
50
|
+
self._index = self._data.index.copy()
|
|
51
|
+
self._state = state
|
|
52
|
+
self._weighting = weighting
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def data(self) -> DataFrame:
|
|
56
|
+
return self._data
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def dp(self) -> np.ndarray:
|
|
60
|
+
return self._dp
|
|
61
|
+
|
|
62
|
+
@dp.setter
|
|
63
|
+
def dp(self, new_dp: np.ndarray):
|
|
64
|
+
self._dp = new_dp
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def dlogdp(self) -> np.ndarray:
|
|
68
|
+
return self._dlogdp
|
|
69
|
+
|
|
70
|
+
@dlogdp.setter
|
|
71
|
+
def dlogdp(self, new_dlogdp: np.ndarray):
|
|
72
|
+
self._dlogdp = new_dlogdp
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def index(self):
|
|
76
|
+
return self._index
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def state(self):
|
|
80
|
+
return self._state
|
|
81
|
+
|
|
82
|
+
@state.setter
|
|
83
|
+
def state(self, value):
|
|
84
|
+
if value not in ['dN', 'dlogdp', 'ddp']:
|
|
85
|
+
raise ValueError("state must be 'dlogdp' or 'ddp'")
|
|
86
|
+
self._state = value
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def weighting(self):
|
|
90
|
+
return self._weighting
|