AeroViz 0.1.3b0__py3-none-any.whl → 0.1.5__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.

Files changed (85) hide show
  1. AeroViz/__init__.py +5 -3
  2. AeroViz/{config → data}/DEFAULT_DATA.csv +1 -1
  3. AeroViz/dataProcess/Chemistry/__init__.py +28 -27
  4. AeroViz/dataProcess/Chemistry/_isoropia.py +11 -11
  5. AeroViz/dataProcess/Chemistry/_mass_volume.py +15 -18
  6. AeroViz/dataProcess/Chemistry/_ocec.py +21 -46
  7. AeroViz/dataProcess/Chemistry/_teom.py +2 -1
  8. AeroViz/dataProcess/Chemistry/isrpia.cnf +21 -0
  9. AeroViz/dataProcess/Chemistry/isrpia2.exe +0 -0
  10. AeroViz/dataProcess/Optical/Angstrom_exponent.py +20 -0
  11. AeroViz/dataProcess/Optical/_IMPROVE.py +13 -15
  12. AeroViz/dataProcess/Optical/__init__.py +15 -30
  13. AeroViz/dataProcess/Optical/_absorption.py +21 -47
  14. AeroViz/dataProcess/Optical/_extinction.py +20 -15
  15. AeroViz/dataProcess/Optical/_mie.py +0 -1
  16. AeroViz/dataProcess/Optical/_scattering.py +19 -20
  17. AeroViz/dataProcess/Optical/fRH.pkl +0 -0
  18. AeroViz/dataProcess/SizeDistr/__init__.py +7 -7
  19. AeroViz/dataProcess/SizeDistr/_merge.py +2 -2
  20. AeroViz/dataProcess/SizeDistr/_merge_v1.py +2 -2
  21. AeroViz/dataProcess/SizeDistr/_merge_v2.py +2 -2
  22. AeroViz/dataProcess/SizeDistr/_merge_v3.py +1 -1
  23. AeroViz/dataProcess/SizeDistr/_merge_v4.py +1 -1
  24. AeroViz/dataProcess/VOC/__init__.py +4 -9
  25. AeroViz/dataProcess/VOC/_potential_par.py +71 -37
  26. AeroViz/dataProcess/VOC/{voc_par.json → support_voc.json} +321 -339
  27. AeroViz/dataProcess/__init__.py +28 -6
  28. AeroViz/dataProcess/core/__init__.py +10 -17
  29. AeroViz/plot/__init__.py +1 -1
  30. AeroViz/plot/box.py +2 -1
  31. AeroViz/plot/optical/optical.py +4 -4
  32. AeroViz/plot/regression.py +25 -39
  33. AeroViz/plot/scatter.py +68 -2
  34. AeroViz/plot/templates/__init__.py +2 -1
  35. AeroViz/plot/templates/ammonium_rich.py +34 -0
  36. AeroViz/plot/templates/diurnal_pattern.py +11 -9
  37. AeroViz/plot/templates/koschmieder.py +51 -115
  38. AeroViz/plot/templates/metal_heatmap.py +115 -17
  39. AeroViz/plot/timeseries/__init__.py +1 -0
  40. AeroViz/plot/timeseries/template.py +47 -0
  41. AeroViz/plot/timeseries/timeseries.py +275 -208
  42. AeroViz/plot/utils/plt_utils.py +2 -2
  43. AeroViz/plot/utils/units.json +5 -0
  44. AeroViz/plot/violin.py +9 -8
  45. AeroViz/process/__init__.py +2 -2
  46. AeroViz/process/script/AbstractDistCalc.py +1 -1
  47. AeroViz/process/script/Chemical.py +5 -4
  48. AeroViz/process/script/Others.py +1 -1
  49. AeroViz/rawDataReader/__init__.py +66 -22
  50. AeroViz/rawDataReader/{utils/config.py → config/supported_instruments.py} +33 -54
  51. AeroViz/rawDataReader/core/__init__.py +116 -231
  52. AeroViz/rawDataReader/script/AE33.py +12 -13
  53. AeroViz/rawDataReader/script/AE43.py +10 -13
  54. AeroViz/rawDataReader/script/APS_3321.py +8 -8
  55. AeroViz/rawDataReader/script/Aurora.py +21 -19
  56. AeroViz/rawDataReader/script/BC1054.py +13 -17
  57. AeroViz/rawDataReader/script/EPA_vertical.py +36 -8
  58. AeroViz/rawDataReader/script/GRIMM.py +6 -13
  59. AeroViz/rawDataReader/script/{IGAC_ZM.py → IGAC.py} +18 -18
  60. AeroViz/rawDataReader/script/MA350.py +9 -16
  61. AeroViz/rawDataReader/script/Minion.py +103 -0
  62. AeroViz/rawDataReader/script/NEPH.py +28 -38
  63. AeroViz/rawDataReader/script/SMPS_TH.py +6 -6
  64. AeroViz/rawDataReader/script/SMPS_aim11.py +8 -8
  65. AeroViz/rawDataReader/script/SMPS_genr.py +8 -8
  66. AeroViz/rawDataReader/script/Sunset_OCEC.py +66 -0
  67. AeroViz/rawDataReader/script/TEOM.py +10 -8
  68. AeroViz/rawDataReader/script/Table.py +9 -10
  69. AeroViz/rawDataReader/script/VOC.py +33 -0
  70. AeroViz/rawDataReader/script/__init__.py +10 -12
  71. AeroViz/tools/database.py +7 -9
  72. AeroViz/tools/datareader.py +3 -3
  73. {AeroViz-0.1.3b0.dist-info → AeroViz-0.1.5.dist-info}/METADATA +1 -1
  74. AeroViz-0.1.5.dist-info/RECORD +114 -0
  75. AeroViz/rawDataReader/script/IGAC_TH.py +0 -104
  76. AeroViz/rawDataReader/script/OCEC_LCRES.py +0 -34
  77. AeroViz/rawDataReader/script/OCEC_RES.py +0 -28
  78. AeroViz/rawDataReader/script/VOC_TH.py +0 -30
  79. AeroViz/rawDataReader/script/VOC_ZM.py +0 -37
  80. AeroViz-0.1.3b0.dist-info/RECORD +0 -110
  81. /AeroViz/{config → data}/DEFAULT_PNSD_DATA.csv +0 -0
  82. /AeroViz/rawDataReader/{utils → config}/__init__.py +0 -0
  83. {AeroViz-0.1.3b0.dist-info → AeroViz-0.1.5.dist-info}/LICENSE +0 -0
  84. {AeroViz-0.1.3b0.dist-info → AeroViz-0.1.5.dist-info}/WHEEL +0 -0
  85. {AeroViz-0.1.3b0.dist-info → AeroViz-0.1.5.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, Timestamp
8
+ from pandas import DataFrame, date_range
9
9
 
10
10
  from AeroViz.plot.utils import *
11
11
 
12
- __all__ = ['timeseries', 'timeseries_template']
12
+ __all__ = ['timeseries', 'timeseries_stacked']
13
+
13
14
 
14
15
  default_bar_kws = dict(
15
- width=0.0417,
16
- edgecolor=None,
17
- linewidth=0,
18
- cmap='jet',
16
+ width=0.0417,
17
+ edgecolor=None,
18
+ linewidth=0,
19
+ cmap='jet',
19
20
  )
20
21
 
21
22
  default_scatter_kws = dict(
22
- marker='o',
23
- s=5,
24
- edgecolor=None,
25
- linewidths=0.3,
26
- alpha=0.9,
27
- cmap='jet',
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
- width="1.5%",
32
- height="100%",
33
- loc='lower left',
34
- bbox_to_anchor=(1.01, 0, 1.2, 1),
35
- borderpad=0
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
- 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)
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
- # Filter the children to find ScalarMappable objects
52
- mappable_objects = [child for child in ax.get_children() if isinstance(child, ScalarMappable)]
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
- # 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.")
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
- scalar_map, colors = Color.color_maker(df[_c].values, cmap=bar_kws.pop('cmap'))
63
- ax.bar(df.index, df[_y], color=scalar_map.to_rgba(colors), **bar_kws)
64
- cax = inset_axes(ax, **inset_kws)
65
- plt.colorbar(mappable=scalar_map, cax=cax, **cbar_kws)
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
- ax.plot(df.index, df[_y], color=_color, **plot_kws)
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
- @set_figure(fs=8, autolayout=False)
81
+
82
+ @set_figure(autolayout=False)
73
83
  def timeseries(df: DataFrame,
74
- y: list[str] | str,
75
- y2: list[str] | str = None,
76
- c: list[str] | str = None,
77
- # color: list[str] | str = None,
78
- rolling: str | int | None = None,
79
- times: list[datetime | Timestamp | str] = None,
80
- freq: str = '1MS',
81
- style: list[Literal['scatter', 'bar', 'line']] | str | None = None,
82
- ax: Axes | None = None,
83
- set_xaxis_visible: bool | None = None,
84
- legend_loc: Literal['best', 'upper right', 'upper left', 'lower left', 'lower right'] = 'best',
85
- legend_ncol: int = 1,
86
- **kwargs
87
- ) -> tuple[Figure, Axes]:
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
- c : str, optional
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
- times : tuple[datetime, datetime] | tuple[Timestamp, Timestamp], optional
104
- Time range for the data. Defaults to None.
105
- freq : str, optional
106
- Frequency for x-axis ticks. Defaults to '2MS'.
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', c='WD', scatter_kws=dict(cmap='hsv'), cbar_kws=dict(ticks=[0, 90, 180, 270, 360]), ylim=[0, None])
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
- # Set the time
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
- if times is not None:
145
- st_tm, fn_tm = map(Timestamp, times)
146
- else:
147
- try:
148
- st_tm, fn_tm = df.index[0], df.index[-1]
149
- except IndexError:
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
- # Apply rolling window if specified
153
- df = df.loc[st_tm:fn_tm] if rolling is None else (
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
- # Initialize figure and axis if not provided
157
- fig, ax = plt.subplots(**{**{'figsize': (6, 2)}, **kwargs.get('fig_kws', {})}) if ax is None else (
158
- ax.get_figure(), ax)
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
- # Ensure y, y2, c, and style are lists
161
- y = [y] if isinstance(y, str) else y
162
- y2 = [y2] if isinstance(y2, str) else y2 if y2 is not None else []
163
- c = [c] if isinstance(c, str) else c if c is not None else [None] * (len(y) + len(y2))
164
- style = [style] if isinstance(style, str) else style if style is not None else ['plot'] * (len(y) + len(y2))
165
-
166
- if len(c) != len(y) + len(y2):
167
- raise ValueError("The length of c must match the combined length of y and y2")
168
-
169
- if len(style) != len(y) + len(y2):
170
- raise ValueError("The length of style must match the combined length of y and y2")
171
-
172
- # Create a secondary y-axis if y2 is not empty
173
- ax2 = ax.twinx() if y2 else None
174
-
175
- # # Set color cycle
176
- ax.set_prop_cycle(Color.color_cycle)
177
- if y2:
178
- ax2.set_prop_cycle(Color.color_cycle[len(y):])
179
-
180
- if y2 and ('scatter' or 'bar') in style:
181
- fig.subplots_adjust(right=0.8)
182
-
183
- for i, _c in enumerate(c):
184
- if _c is not None and _c in df.columns:
185
- style[i] = 'scatter'
186
-
187
- for i, (_y, _c, _style) in enumerate(zip(y, c, style)):
188
- scatter_kws = {**default_scatter_kws, **{'label': Unit(_y)}, **kwargs.get('scatter_kws', {})}
189
- bar_kws = {**default_bar_kws, **{'label': Unit(_y)}, **kwargs.get('bar_kws', {})}
190
- plot_kws = {**default_plot_kws, **{'label': Unit(_y)}, **kwargs.get('plot_kws', {})}
191
-
192
- if _style in ['scatter', 'bar']:
193
- cbar_kws = {**default_cbar_kws, **{'label': Unit(_c), 'ticks': None}, **kwargs.get('cbar_kws', {})}
194
- inset_kws = {**default_insert_kws, **{'bbox_transform': ax.transAxes}, **kwargs.get('inset_kws', {})}
195
-
196
- if _style == 'scatter':
197
- _scatter(ax, df, _y, _c, scatter_kws, cbar_kws, inset_kws)
198
-
199
- elif _style == 'bar':
200
- _bar(ax, df, _y, _c, bar_kws, cbar_kws, inset_kws)
201
-
202
- else:
203
- _plot(ax, df, _y, _c, plot_kws)
204
-
205
- if y2:
206
- for i, (_y, _c, _style) in enumerate(zip(y2, c[len(y):], style[len(y):])):
207
- scatter_kws = {**default_scatter_kws, **{'label': Unit(_y)}, **kwargs.get('scatter_kws2', {})}
208
- bar_kws = {**default_bar_kws, **{'label': Unit(_y)}, **kwargs.get('bar_kws2', {})}
209
- plot_kws = {**default_plot_kws, **{'label': Unit(_y)}, **kwargs.get('plot_kws2', {})}
210
-
211
- if _style in ['scatter', 'bar']:
212
- cbar_kws = {**default_cbar_kws, **{'label': Unit(_c), 'ticks': None}, **kwargs.get('cbar_kws2', {})}
213
- inset_kws = {**default_insert_kws, **{'bbox_transform': ax.transAxes}, **kwargs.get('inset_kws2', {})}
214
-
215
- if _style == 'scatter':
216
- _scatter(ax2, df, _y, _c, scatter_kws, cbar_kws, inset_kws)
217
-
218
- elif _style == 'bar':
219
- _bar(ax2, df, _y, _c, bar_kws, cbar_kws, inset_kws)
220
-
221
- else: # line plot
222
- _plot(ax2, df, _y, _c, plot_kws)
223
-
224
- # Combine legends from ax and ax2
225
- ax.legend(*combine_legends([ax, ax2]), loc=legend_loc, ncol=legend_ncol)
226
-
227
- else:
228
- ax.legend(loc=legend_loc, ncol=legend_ncol)
229
-
230
- if set_xaxis_visible is not None:
231
- ax.axes.xaxis.set_visible(set_xaxis_visible)
232
-
233
- ax.set(xlabel=kwargs.get('xlabel', ''),
234
- ylabel=kwargs.get('ylabel', Unit(y) if isinstance(y, str) else Unit(y[0])),
235
- xticks=kwargs.get('xticks', date_range(start=st_tm, end=fn_tm, freq=freq).strftime("%F")),
236
- yticks=kwargs.get('yticks', ax.get_yticks()),
237
- xticklabels=kwargs.get('xticklabels', date_range(start=st_tm, end=fn_tm, freq=freq).strftime("%F")),
238
- yticklabels=kwargs.get('yticklabels', ax.get_yticklabels()),
239
- xlim=kwargs.get('xlim', (st_tm, fn_tm)),
240
- ylim=kwargs.get('ylim', (None, None)),
241
- title=kwargs.get('title', '')
242
- )
243
-
244
- if y2:
245
- ax2.set(ylabel=kwargs.get('ylabel2', Unit(y2) if isinstance(y2, str) else Unit(y2[0])),
246
- yticks=kwargs.get('yticks2', ax2.get_yticks()),
247
- yticklabels=kwargs.get('yticklabels2', ax2.get_yticklabels()),
248
- ylim=kwargs.get('ylim2', (None, None)))
249
-
250
- plt.show()
251
-
252
- return fig, ax
253
-
254
-
255
- @set_figure(fs=8, autolayout=False)
256
- def timeseries_template(df: DataFrame) -> tuple[Figure, Axes]:
257
- fig, ax = plt.subplots(5, 1, figsize=(len(df.index) * 0.01, 4))
258
- (ax1, ax2, ax3, ax4, ax5) = ax
259
-
260
- timeseries(df,
261
- y=['Extinction', 'Scattering', 'Absorption'],
262
- rolling=30,
263
- ax=ax1,
264
- ylabel='Coefficient',
265
- ylim=[0., None],
266
- set_xaxis_visible=False,
267
- legend_ncol=3,
268
- )
269
-
270
- # Temp, RH
271
- timeseries(df,
272
- y='AT',
273
- y2='RH',
274
- rolling=30,
275
- ax=ax2,
276
- ax_plot_kws=dict(color='r'),
277
- ax2_plot_kws=dict(color='b'),
278
- ylim=[10, 30],
279
- ylim2=[20, 100],
280
- set_xaxis_visible=False,
281
- legend_ncol=2,
282
- )
283
-
284
- timeseries(df, y='WS', c='WD', style='scatter', ax=ax3, scatter_kws=dict(cmap='hsv'),
285
- cbar_kws=dict(ticks=[0, 90, 180, 270, 360]),
286
- ylim=[0, None], set_xaxis_visible=False)
287
-
288
- timeseries(df, y='VC', c='PBLH', style='bar', ax=ax4, bar_kws=dict(cmap='Blues'), set_xaxis_visible=False,
289
- ylim=[0, 5000])
290
-
291
- timeseries(df, y='PM25', c='PM1/PM25', style='scatter', ax=ax5, ylim=[0, None])
292
-
293
- plt.show()
294
-
295
- return fig, ax
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
@@ -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 ~autolayout:
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
@@ -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(data_set: DataFrame | dict,
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
- data_set : pd.DataFrame or dict
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
- matplotlib.axes.Axes
35
- The Axes object containing the violin plot.
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 = data_set.to_numpy()
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(data_set.keys())]
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