AeroViz 0.1.21__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.
Files changed (180) hide show
  1. AeroViz/__init__.py +13 -0
  2. AeroViz/__pycache__/__init__.cpython-312.pyc +0 -0
  3. AeroViz/data/DEFAULT_DATA.csv +1417 -0
  4. AeroViz/data/DEFAULT_PNSD_DATA.csv +1417 -0
  5. AeroViz/data/hysplit_example_data.txt +101 -0
  6. AeroViz/dataProcess/Chemistry/__init__.py +149 -0
  7. AeroViz/dataProcess/Chemistry/__pycache__/__init__.cpython-312.pyc +0 -0
  8. AeroViz/dataProcess/Chemistry/_calculate.py +557 -0
  9. AeroViz/dataProcess/Chemistry/_isoropia.py +150 -0
  10. AeroViz/dataProcess/Chemistry/_mass_volume.py +487 -0
  11. AeroViz/dataProcess/Chemistry/_ocec.py +172 -0
  12. AeroViz/dataProcess/Chemistry/isrpia.cnf +21 -0
  13. AeroViz/dataProcess/Chemistry/isrpia2.exe +0 -0
  14. AeroViz/dataProcess/Optical/PyMieScatt_update.py +577 -0
  15. AeroViz/dataProcess/Optical/_IMPROVE.py +452 -0
  16. AeroViz/dataProcess/Optical/__init__.py +281 -0
  17. AeroViz/dataProcess/Optical/__pycache__/PyMieScatt_update.cpython-312.pyc +0 -0
  18. AeroViz/dataProcess/Optical/__pycache__/__init__.cpython-312.pyc +0 -0
  19. AeroViz/dataProcess/Optical/__pycache__/mie_theory.cpython-312.pyc +0 -0
  20. AeroViz/dataProcess/Optical/_derived.py +518 -0
  21. AeroViz/dataProcess/Optical/_extinction.py +123 -0
  22. AeroViz/dataProcess/Optical/_mie_sd.py +912 -0
  23. AeroViz/dataProcess/Optical/_retrieve_RI.py +243 -0
  24. AeroViz/dataProcess/Optical/coefficient.py +72 -0
  25. AeroViz/dataProcess/Optical/fRH.pkl +0 -0
  26. AeroViz/dataProcess/Optical/mie_theory.py +260 -0
  27. AeroViz/dataProcess/README.md +271 -0
  28. AeroViz/dataProcess/SizeDistr/__init__.py +245 -0
  29. AeroViz/dataProcess/SizeDistr/__pycache__/__init__.cpython-312.pyc +0 -0
  30. AeroViz/dataProcess/SizeDistr/__pycache__/_size_dist.cpython-312.pyc +0 -0
  31. AeroViz/dataProcess/SizeDistr/_size_dist.py +810 -0
  32. AeroViz/dataProcess/SizeDistr/merge/README.md +93 -0
  33. AeroViz/dataProcess/SizeDistr/merge/__init__.py +20 -0
  34. AeroViz/dataProcess/SizeDistr/merge/_merge_v0.py +251 -0
  35. AeroViz/dataProcess/SizeDistr/merge/_merge_v0_1.py +246 -0
  36. AeroViz/dataProcess/SizeDistr/merge/_merge_v1.py +255 -0
  37. AeroViz/dataProcess/SizeDistr/merge/_merge_v2.py +244 -0
  38. AeroViz/dataProcess/SizeDistr/merge/_merge_v3.py +518 -0
  39. AeroViz/dataProcess/SizeDistr/merge/_merge_v4.py +422 -0
  40. AeroViz/dataProcess/SizeDistr/prop.py +62 -0
  41. AeroViz/dataProcess/VOC/__init__.py +14 -0
  42. AeroViz/dataProcess/VOC/__pycache__/__init__.cpython-312.pyc +0 -0
  43. AeroViz/dataProcess/VOC/_potential_par.py +108 -0
  44. AeroViz/dataProcess/VOC/support_voc.json +446 -0
  45. AeroViz/dataProcess/__init__.py +66 -0
  46. AeroViz/dataProcess/__pycache__/__init__.cpython-312.pyc +0 -0
  47. AeroViz/dataProcess/core/__init__.py +272 -0
  48. AeroViz/dataProcess/core/__pycache__/__init__.cpython-312.pyc +0 -0
  49. AeroViz/mcp_server.py +352 -0
  50. AeroViz/plot/__init__.py +13 -0
  51. AeroViz/plot/__pycache__/__init__.cpython-312.pyc +0 -0
  52. AeroViz/plot/__pycache__/bar.cpython-312.pyc +0 -0
  53. AeroViz/plot/__pycache__/box.cpython-312.pyc +0 -0
  54. AeroViz/plot/__pycache__/pie.cpython-312.pyc +0 -0
  55. AeroViz/plot/__pycache__/radar.cpython-312.pyc +0 -0
  56. AeroViz/plot/__pycache__/regression.cpython-312.pyc +0 -0
  57. AeroViz/plot/__pycache__/scatter.cpython-312.pyc +0 -0
  58. AeroViz/plot/__pycache__/violin.cpython-312.pyc +0 -0
  59. AeroViz/plot/bar.py +126 -0
  60. AeroViz/plot/box.py +69 -0
  61. AeroViz/plot/distribution/__init__.py +1 -0
  62. AeroViz/plot/distribution/__pycache__/__init__.cpython-312.pyc +0 -0
  63. AeroViz/plot/distribution/__pycache__/distribution.cpython-312.pyc +0 -0
  64. AeroViz/plot/distribution/distribution.py +576 -0
  65. AeroViz/plot/meteorology/CBPF.py +295 -0
  66. AeroViz/plot/meteorology/__init__.py +3 -0
  67. AeroViz/plot/meteorology/__pycache__/CBPF.cpython-312.pyc +0 -0
  68. AeroViz/plot/meteorology/__pycache__/__init__.cpython-312.pyc +0 -0
  69. AeroViz/plot/meteorology/__pycache__/hysplit.cpython-312.pyc +0 -0
  70. AeroViz/plot/meteorology/__pycache__/wind_rose.cpython-312.pyc +0 -0
  71. AeroViz/plot/meteorology/hysplit.py +93 -0
  72. AeroViz/plot/meteorology/wind_rose.py +77 -0
  73. AeroViz/plot/optical/__init__.py +1 -0
  74. AeroViz/plot/optical/__pycache__/__init__.cpython-312.pyc +0 -0
  75. AeroViz/plot/optical/__pycache__/optical.cpython-312.pyc +0 -0
  76. AeroViz/plot/optical/optical.py +388 -0
  77. AeroViz/plot/pie.py +210 -0
  78. AeroViz/plot/radar.py +184 -0
  79. AeroViz/plot/regression.py +200 -0
  80. AeroViz/plot/scatter.py +174 -0
  81. AeroViz/plot/templates/__init__.py +6 -0
  82. AeroViz/plot/templates/__pycache__/__init__.cpython-312.pyc +0 -0
  83. AeroViz/plot/templates/__pycache__/ammonium_rich.cpython-312.pyc +0 -0
  84. AeroViz/plot/templates/__pycache__/contour.cpython-312.pyc +0 -0
  85. AeroViz/plot/templates/__pycache__/corr_matrix.cpython-312.pyc +0 -0
  86. AeroViz/plot/templates/__pycache__/diurnal_pattern.cpython-312.pyc +0 -0
  87. AeroViz/plot/templates/__pycache__/koschmieder.cpython-312.pyc +0 -0
  88. AeroViz/plot/templates/__pycache__/metal_heatmap.cpython-312.pyc +0 -0
  89. AeroViz/plot/templates/ammonium_rich.py +34 -0
  90. AeroViz/plot/templates/contour.py +47 -0
  91. AeroViz/plot/templates/corr_matrix.py +267 -0
  92. AeroViz/plot/templates/diurnal_pattern.py +61 -0
  93. AeroViz/plot/templates/koschmieder.py +95 -0
  94. AeroViz/plot/templates/metal_heatmap.py +164 -0
  95. AeroViz/plot/timeseries/__init__.py +2 -0
  96. AeroViz/plot/timeseries/__pycache__/__init__.cpython-312.pyc +0 -0
  97. AeroViz/plot/timeseries/__pycache__/template.cpython-312.pyc +0 -0
  98. AeroViz/plot/timeseries/__pycache__/timeseries.cpython-312.pyc +0 -0
  99. AeroViz/plot/timeseries/template.py +47 -0
  100. AeroViz/plot/timeseries/timeseries.py +446 -0
  101. AeroViz/plot/utils/__init__.py +4 -0
  102. AeroViz/plot/utils/__pycache__/__init__.cpython-312.pyc +0 -0
  103. AeroViz/plot/utils/__pycache__/_color.cpython-312.pyc +0 -0
  104. AeroViz/plot/utils/__pycache__/_unit.cpython-312.pyc +0 -0
  105. AeroViz/plot/utils/__pycache__/plt_utils.cpython-312.pyc +0 -0
  106. AeroViz/plot/utils/__pycache__/sklearn_utils.cpython-312.pyc +0 -0
  107. AeroViz/plot/utils/_color.py +71 -0
  108. AeroViz/plot/utils/_unit.py +55 -0
  109. AeroViz/plot/utils/fRH.json +390 -0
  110. AeroViz/plot/utils/plt_utils.py +92 -0
  111. AeroViz/plot/utils/sklearn_utils.py +49 -0
  112. AeroViz/plot/utils/units.json +89 -0
  113. AeroViz/plot/violin.py +80 -0
  114. AeroViz/rawDataReader/FLOW.md +138 -0
  115. AeroViz/rawDataReader/__init__.py +220 -0
  116. AeroViz/rawDataReader/__pycache__/__init__.cpython-312.pyc +0 -0
  117. AeroViz/rawDataReader/config/__init__.py +0 -0
  118. AeroViz/rawDataReader/config/__pycache__/__init__.cpython-312.pyc +0 -0
  119. AeroViz/rawDataReader/config/__pycache__/supported_instruments.cpython-312.pyc +0 -0
  120. AeroViz/rawDataReader/config/supported_instruments.py +135 -0
  121. AeroViz/rawDataReader/core/__init__.py +658 -0
  122. AeroViz/rawDataReader/core/__pycache__/__init__.cpython-312.pyc +0 -0
  123. AeroViz/rawDataReader/core/__pycache__/logger.cpython-312.pyc +0 -0
  124. AeroViz/rawDataReader/core/__pycache__/pre_process.cpython-312.pyc +0 -0
  125. AeroViz/rawDataReader/core/__pycache__/qc.cpython-312.pyc +0 -0
  126. AeroViz/rawDataReader/core/__pycache__/report.cpython-312.pyc +0 -0
  127. AeroViz/rawDataReader/core/logger.py +171 -0
  128. AeroViz/rawDataReader/core/pre_process.py +308 -0
  129. AeroViz/rawDataReader/core/qc.py +961 -0
  130. AeroViz/rawDataReader/core/report.py +579 -0
  131. AeroViz/rawDataReader/script/AE33.py +173 -0
  132. AeroViz/rawDataReader/script/AE43.py +151 -0
  133. AeroViz/rawDataReader/script/APS.py +339 -0
  134. AeroViz/rawDataReader/script/Aurora.py +191 -0
  135. AeroViz/rawDataReader/script/BAM1020.py +90 -0
  136. AeroViz/rawDataReader/script/BC1054.py +161 -0
  137. AeroViz/rawDataReader/script/EPA.py +79 -0
  138. AeroViz/rawDataReader/script/GRIMM.py +68 -0
  139. AeroViz/rawDataReader/script/IGAC.py +140 -0
  140. AeroViz/rawDataReader/script/MA350.py +179 -0
  141. AeroViz/rawDataReader/script/Minion.py +218 -0
  142. AeroViz/rawDataReader/script/NEPH.py +199 -0
  143. AeroViz/rawDataReader/script/OCEC.py +173 -0
  144. AeroViz/rawDataReader/script/Q-ACSM.py +12 -0
  145. AeroViz/rawDataReader/script/SMPS.py +389 -0
  146. AeroViz/rawDataReader/script/TEOM.py +181 -0
  147. AeroViz/rawDataReader/script/VOC.py +106 -0
  148. AeroViz/rawDataReader/script/Xact.py +244 -0
  149. AeroViz/rawDataReader/script/__init__.py +28 -0
  150. AeroViz/rawDataReader/script/__pycache__/AE33.cpython-312.pyc +0 -0
  151. AeroViz/rawDataReader/script/__pycache__/AE43.cpython-312.pyc +0 -0
  152. AeroViz/rawDataReader/script/__pycache__/APS.cpython-312.pyc +0 -0
  153. AeroViz/rawDataReader/script/__pycache__/Aurora.cpython-312.pyc +0 -0
  154. AeroViz/rawDataReader/script/__pycache__/BAM1020.cpython-312.pyc +0 -0
  155. AeroViz/rawDataReader/script/__pycache__/BC1054.cpython-312.pyc +0 -0
  156. AeroViz/rawDataReader/script/__pycache__/EPA.cpython-312.pyc +0 -0
  157. AeroViz/rawDataReader/script/__pycache__/GRIMM.cpython-312.pyc +0 -0
  158. AeroViz/rawDataReader/script/__pycache__/IGAC.cpython-312.pyc +0 -0
  159. AeroViz/rawDataReader/script/__pycache__/MA350.cpython-312.pyc +0 -0
  160. AeroViz/rawDataReader/script/__pycache__/Minion.cpython-312.pyc +0 -0
  161. AeroViz/rawDataReader/script/__pycache__/NEPH.cpython-312.pyc +0 -0
  162. AeroViz/rawDataReader/script/__pycache__/OCEC.cpython-312.pyc +0 -0
  163. AeroViz/rawDataReader/script/__pycache__/Q-ACSM.cpython-312.pyc +0 -0
  164. AeroViz/rawDataReader/script/__pycache__/SMPS.cpython-312.pyc +0 -0
  165. AeroViz/rawDataReader/script/__pycache__/TEOM.cpython-312.pyc +0 -0
  166. AeroViz/rawDataReader/script/__pycache__/VOC.cpython-312.pyc +0 -0
  167. AeroViz/rawDataReader/script/__pycache__/Xact.cpython-312.pyc +0 -0
  168. AeroViz/rawDataReader/script/__pycache__/__init__.cpython-312.pyc +0 -0
  169. AeroViz/tools/__init__.py +2 -0
  170. AeroViz/tools/__pycache__/__init__.cpython-312.pyc +0 -0
  171. AeroViz/tools/__pycache__/database.cpython-312.pyc +0 -0
  172. AeroViz/tools/__pycache__/dataclassifier.cpython-312.pyc +0 -0
  173. AeroViz/tools/database.py +95 -0
  174. AeroViz/tools/dataclassifier.py +117 -0
  175. AeroViz/tools/dataprinter.py +58 -0
  176. aeroviz-0.1.21.dist-info/METADATA +294 -0
  177. aeroviz-0.1.21.dist-info/RECORD +180 -0
  178. aeroviz-0.1.21.dist-info/WHEEL +5 -0
  179. aeroviz-0.1.21.dist-info/licenses/LICENSE +21 -0
  180. aeroviz-0.1.21.dist-info/top_level.txt +1 -0
@@ -0,0 +1,267 @@
1
+ import matplotlib.pyplot as plt
2
+ import numpy as np
3
+ import pandas as pd
4
+ import seaborn as sns
5
+ from matplotlib import colormaps
6
+ from matplotlib.pyplot import Figure, Axes
7
+ from mpl_toolkits.axes_grid1.inset_locator import inset_axes
8
+ from scipy.stats import pearsonr
9
+
10
+ from AeroViz.plot.utils import *
11
+
12
+ __all__ = ['corr_matrix', 'cross_corr_matrix']
13
+
14
+
15
+ @set_figure
16
+ def corr_matrix(data: pd.DataFrame,
17
+ cmap: str = "RdBu",
18
+ ax: Axes | None = None,
19
+ items_order: list = None, # 新增參數用於指定順序
20
+ **kwargs
21
+ ) -> tuple[Figure, Axes]:
22
+ fig, ax = plt.subplots(**kwargs.get('fig_kws', {})) if ax is None else (ax.get_figure(), ax)
23
+
24
+ _corr = data.corr()
25
+ breakpoint()
26
+ corr = pd.melt(_corr.reset_index(), id_vars='index')
27
+ corr.columns = ['x', 'y', 'value']
28
+
29
+ p_values = _corr.apply(lambda col1: _corr.apply(lambda col2: pearsonr(col1, col2)[1]))
30
+ p_values = p_values.mask(p_values > 0.05)
31
+ p_values = pd.melt(p_values.reset_index(), id_vars='index').dropna()
32
+ p_values.columns = ['x', 'y', 'value']
33
+
34
+ # Mapping from column names to integer coordinates
35
+ x_labels = [v for v in sorted(corr['x'].unique())]
36
+ y_labels = [v for v in sorted(corr['y'].unique())]
37
+ x_to_num = {p[1]: p[0] for p in enumerate(x_labels)}
38
+ y_to_num = {p[1]: p[0] for p in enumerate(y_labels)}
39
+
40
+ # Show column labels on the axes
41
+ ax.set_xticks([x_to_num[v] for v in x_labels])
42
+ ax.set_xticklabels(x_labels, rotation=90, horizontalalignment='center')
43
+ ax.set_yticks([y_to_num[v] for v in y_labels])
44
+ ax.set_yticklabels(y_labels)
45
+
46
+ # ax.tick_params(axis='both', which='major', direction='out', top=True, left=True)
47
+
48
+ ax.grid(False, 'major')
49
+ ax.grid(True, 'minor')
50
+ ax.set_xticks([t + 0.5 for t in ax.get_xticks()], minor=True)
51
+ ax.set_yticks([t + 0.5 for t in ax.get_yticks()], minor=True)
52
+
53
+ ax.set_xlim([-0.5, max([v for v in x_to_num.values()]) + 0.5])
54
+ ax.set_ylim([-0.5, max([v for v in y_to_num.values()]) + 0.5])
55
+
56
+ n_colors = 256 # Use 256 colors for the diverging color palette
57
+ palette = sns.color_palette(cmap, n_colors=n_colors) # Create the palette
58
+
59
+ # Range of values that will be mapped to the palette, i.e. min and max possible correlation
60
+ color_min, color_max = [-1, 1]
61
+
62
+ def value_to_color(val):
63
+ val_position = float((val - color_min)) / (color_max - color_min)
64
+ ind = int(val_position * (n_colors - 1)) # target index in the color palette
65
+ return palette[ind]
66
+
67
+ point = ax.scatter(
68
+ x=corr['x'].map(x_to_num),
69
+ y=corr['y'].map(y_to_num),
70
+ s=corr['value'].abs() * 70,
71
+ c=corr['value'].apply(value_to_color), # Vector of square color values, mapped to color palette
72
+ marker='s',
73
+ label='$R^{2}$'
74
+ )
75
+
76
+ axes_image = plt.cm.ScalarMappable(cmap=colormaps[cmap])
77
+
78
+ cax = inset_axes(ax, width="5%",
79
+ height="100%",
80
+ loc='lower left',
81
+ bbox_to_anchor=(1.02, 0., 1, 1),
82
+ bbox_transform=ax.transAxes,
83
+ borderpad=0)
84
+
85
+ cbar = plt.colorbar(mappable=axes_image, cax=cax, label=r'$R^{2}$')
86
+
87
+ cbar.set_ticks([0, 0.25, 0.5, 0.75, 1])
88
+ cbar.set_ticklabels(np.linspace(-1, 1, 5))
89
+
90
+ point2 = ax.scatter(
91
+ x=p_values['x'].map(x_to_num),
92
+ y=p_values['y'].map(y_to_num),
93
+ s=10,
94
+ marker='*',
95
+ color='k',
96
+ label='p < 0.05'
97
+ )
98
+
99
+ ax.legend(handles=[point2], labels=['p < 0.05'], bbox_to_anchor=(0.02, 1, 0.05, 0.05))
100
+
101
+ plt.show()
102
+
103
+ return fig, ax
104
+
105
+
106
+ @set_figure(figsize=(6, 6))
107
+ def cross_corr_matrix(data1: pd.DataFrame,
108
+ data2: pd.DataFrame,
109
+ cmap: str = "RdBu",
110
+ ax: Axes | None = None,
111
+ items_order: list = None, # 新增參數用於指定順序
112
+ **kwargs
113
+ ) -> tuple[Figure, Axes]:
114
+ """
115
+ Create a correlation matrix between two different DataFrames.
116
+
117
+ Parameters:
118
+ -----------
119
+ data1 : pd.DataFrame
120
+ First DataFrame
121
+ data2 : pd.DataFrame
122
+ Second DataFrame
123
+ cmap : str, optional
124
+ Color map for the correlation matrix
125
+ ax : Axes, optional
126
+ Matplotlib axes to plot on
127
+ items_order : list, optional
128
+ List specifying the order of items to display
129
+ **kwargs : dict
130
+ Additional keyword arguments
131
+ """
132
+ if ax is None:
133
+ fig_kws = kwargs.get('fig_kws', {})
134
+ default_figsize = fig_kws.get('figsize', (8, 8))
135
+ fig = plt.figure(figsize=default_figsize)
136
+ ax = fig.add_axes([0.1, 0.1, 0.8, 0.8])
137
+ else:
138
+ fig = ax.get_figure()
139
+
140
+ # 如果沒有指定順序,使用原始列名順序
141
+ if items_order is None:
142
+ x_labels = list(data1.columns)
143
+ y_labels = list(data2.columns)
144
+ else:
145
+ # 使用指定順序,但只包含實際存在於數據中的列
146
+ x_labels = [item for item in items_order if item in data1.columns]
147
+ y_labels = [item for item in items_order if item in data2.columns]
148
+
149
+ # Calculate cross-correlation between the two DataFrames
150
+ correlations = []
151
+ p_values_list = []
152
+
153
+ for col1 in x_labels: # 使用指定順序的列名
154
+ for col2 in y_labels:
155
+ try:
156
+ mask = ~(np.isnan(data1[col1]) | np.isnan(data2[col2]))
157
+ if mask.sum() > 2:
158
+ corr, p_val = pearsonr(data1[col1][mask], data2[col2][mask])
159
+ else:
160
+ corr, p_val = np.nan, np.nan
161
+ except Exception as e:
162
+ print(f"Error calculating correlation for {col1} and {col2}: {str(e)}")
163
+ corr, p_val = np.nan, np.nan
164
+
165
+ correlations.append({
166
+ 'x': col1,
167
+ 'y': col2,
168
+ 'value': corr
169
+ })
170
+ if p_val is not None and p_val < 0.05:
171
+ p_values_list.append({
172
+ 'x': col1,
173
+ 'y': col2,
174
+ 'value': p_val
175
+ })
176
+
177
+ corr = pd.DataFrame(correlations)
178
+ p_values = pd.DataFrame(p_values_list)
179
+
180
+ # Create mapping using the specified order
181
+ x_to_num = {label: i for i, label in enumerate(x_labels)}
182
+ y_to_num = {label: i for i, label in enumerate(y_labels)}
183
+
184
+ # 調整標籤顯示
185
+ ax.set_xticks([x_to_num[v] for v in x_labels])
186
+ ax.set_xticklabels(x_labels, rotation=45, ha='right')
187
+ ax.set_yticks([y_to_num[v] for v in y_labels])
188
+ ax.set_yticklabels(y_labels)
189
+
190
+ ax.grid(False, 'major')
191
+ ax.grid(True, 'minor')
192
+ ax.set_xticks([t + 0.5 for t in ax.get_xticks()], minor=True)
193
+ ax.set_yticks([t + 0.5 for t in ax.get_yticks()], minor=True)
194
+
195
+ ax.set_xlim([-0.5, max([v for v in x_to_num.values()]) + 0.5])
196
+ ax.set_ylim([-0.5, max([v for v in y_to_num.values()]) + 0.5])
197
+
198
+ # Color mapping
199
+ n_colors = 256
200
+ palette = sns.color_palette(cmap, n_colors=n_colors)
201
+ color_min, color_max = [-1, 1]
202
+
203
+ def value_to_color(val):
204
+ if pd.isna(val):
205
+ return (1, 1, 1)
206
+ val_position = float((val - color_min)) / (color_max - color_min)
207
+ val_position = np.clip(val_position, 0, 1)
208
+ ind = int(val_position * (n_colors - 1))
209
+ return palette[ind]
210
+
211
+ # Plot correlation squares
212
+ x_coords = corr['x'].map(x_to_num)
213
+ y_coords = corr['y'].map(y_to_num)
214
+ sizes = corr['value'].abs().fillna(0) * 70
215
+ colors = [value_to_color(val) for val in corr['value']]
216
+
217
+ point = ax.scatter(
218
+ x=x_coords,
219
+ y=y_coords,
220
+ s=sizes,
221
+ c=colors,
222
+ marker='s',
223
+ label='$R^{2}$'
224
+ )
225
+
226
+ # 調整顏色軸的位置和大小
227
+ cax = fig.add_axes([0.91, 0.1, 0.02, 0.8])
228
+ axes_image = plt.cm.ScalarMappable(cmap=colormaps[cmap])
229
+ cbar = plt.colorbar(mappable=axes_image, cax=cax, label=r'$R^{2}$')
230
+ cbar.set_ticks([0, 0.25, 0.5, 0.75, 1])
231
+ cbar.set_ticklabels(np.linspace(-1, 1, 5))
232
+
233
+ # Plot significance markers
234
+ if not p_values.empty:
235
+ point2 = ax.scatter(
236
+ x=p_values['x'].map(x_to_num),
237
+ y=p_values['y'].map(y_to_num),
238
+ s=10,
239
+ marker='*',
240
+ color='k',
241
+ label='p < 0.05'
242
+ )
243
+ ax.legend(handles=[point2], labels=['p < 0.05'],
244
+ bbox_to_anchor=(0.005, 1.04), loc='upper left')
245
+
246
+ # Add labels
247
+ ax.set_xlabel('NZ', labelpad=10)
248
+ ax.set_ylabel('FS', labelpad=10)
249
+
250
+ plt.show()
251
+
252
+ return fig, ax
253
+
254
+
255
+ if __name__ == '__main__':
256
+ import pandas as pd
257
+ from pandas import to_numeric
258
+
259
+ df_NZ = pd.read_csv('/Users/chanchihyu/Desktop/NZ_minion_202402-202411.csv', parse_dates=True, index_col=0)
260
+ df_FS = pd.read_csv('/Users/chanchihyu/Desktop/FS_minion_202402-202411.csv', parse_dates=True, index_col=0)
261
+
262
+ items = ['Ext', 'Sca', 'Abs', 'PNC', 'PSC', 'PVC', 'SO2', 'NO', 'NOx', 'NO2', 'CO', 'O3', 'THC', 'NMHC', 'CH4',
263
+ 'PM10', 'PM2.5', 'WS', 'AT', 'RH',
264
+ 'OC', 'EC', 'Na+', 'NH4+', 'NO3-', 'SO42-', 'Al', 'Si', 'Ca', 'Ti', 'V', 'Cr', 'Mn', 'Fe', 'Cu', 'Zn']
265
+ df_NZ = df_NZ.apply(to_numeric, errors='coerce')
266
+
267
+ corr_matrix(df_NZ[items], items_order=items)
@@ -0,0 +1,61 @@
1
+ import matplotlib.pyplot as plt
2
+ from matplotlib.pyplot import Figure, Axes
3
+ from matplotlib.ticker import AutoMinorLocator
4
+ from pandas import DataFrame
5
+
6
+ from AeroViz.plot.utils import *
7
+
8
+ __all__ = ['diurnal_pattern']
9
+
10
+
11
+ @set_figure
12
+ def diurnal_pattern(df: DataFrame,
13
+ y: str | list[str],
14
+ std_area: float = 0.5,
15
+ ax: Axes | None = None,
16
+ **kwargs
17
+ ) -> tuple[Figure, Axes]:
18
+ if 'hour' not in df.columns and 'Hour' not in df.columns:
19
+ df['Hour'] = df.index.hour
20
+
21
+ Hour = range(0, 24)
22
+ mean = df.groupby('Hour')[y].mean()
23
+ std = df.groupby('Hour')[y].std() * std_area
24
+
25
+ fig, ax = plt.subplots() if ax is None else (ax.get_figure(), ax)
26
+
27
+ # Plot Diurnal pattern
28
+ ax.plot(Hour, mean, 'blue', zorder=3)
29
+ ax.fill_between(Hour, y1=mean + std, y2=mean - std, alpha=0.2, color='blue', edgecolor=None, zorder=2)
30
+
31
+ # Plot Boxplot for each hour
32
+ bp = ax.boxplot([df[df['Hour'] == h][y].dropna() for h in Hour],
33
+ positions=Hour,
34
+ widths=0.5,
35
+ patch_artist=True,
36
+ showfliers=False,
37
+ zorder=1)
38
+
39
+ # Customize boxplot colors
40
+ for element in ['boxes', 'whiskers', 'fliers', 'means', 'medians', 'caps']:
41
+ plt.setp(bp[element], color='gray')
42
+
43
+ for patch in bp['boxes']:
44
+ patch.set(facecolor='lightgray', alpha=0.5)
45
+
46
+ ax.set(xlabel=kwargs.get('xlabel', 'Hours'),
47
+ ylabel=kwargs.get('ylabel', Unit(y)),
48
+ xlim=kwargs.get('xlim', (-0.5, 23.5)),
49
+ ylim=kwargs.get('ylim', (None, None)),
50
+ xticks=kwargs.get('xticks', range(0, 24, 4)),
51
+ xticklabels=kwargs.get('xticklabels', range(0, 24, 4)))
52
+
53
+ ax.tick_params(axis='both', which='major')
54
+ ax.tick_params(axis='x', which='minor')
55
+ ax.xaxis.set_minor_locator(AutoMinorLocator())
56
+ ax.ticklabel_format(axis='y', style='sci', scilimits=(-2, 3), useMathText=True)
57
+
58
+ plt.tight_layout()
59
+ plt.show()
60
+
61
+ return fig, ax
@@ -0,0 +1,95 @@
1
+
2
+ import matplotlib.pyplot as plt
3
+ import numpy as np
4
+ import pandas as pd
5
+ from matplotlib.pyplot import Figure, Axes
6
+ from scipy.optimize import curve_fit
7
+
8
+ from AeroViz.plot.utils import *
9
+
10
+ __all__ = ['koschmieder']
11
+
12
+
13
+ @set_figure(figsize=(2.4, 3))
14
+ def koschmieder(df: pd.DataFrame,
15
+ vis: str,
16
+ ext: list[str],
17
+ ax: Axes | None = None,
18
+ **kwargs
19
+ ) -> tuple[Figure, Axes]:
20
+ """
21
+ Plot Koschmieder relationship between Visibility and Extinction.
22
+
23
+ x = Visibility, y = Extinction, log-log fit!!
24
+ """
25
+ def _log_fit(x, y, func=lambda x, a: -x + a):
26
+ x_log, y_log = np.log(x), np.log(y)
27
+ popt, pcov = curve_fit(func, x_log, y_log)
28
+
29
+ return np.exp(popt)[0], pcov
30
+
31
+ fig, ax = plt.subplots(**kwargs.get('fig_kws', {})) if ax is None else (ax.get_figure(), ax)
32
+
33
+ boxcolors = ['#a5bf6b', '#3f83bf']
34
+ scattercolor = ['green', 'blue']
35
+ arts = []
36
+ labels = []
37
+
38
+ for i, ext_col in enumerate(ext):
39
+ _df = df[[ext_col, vis]].dropna().copy()
40
+ x_data = _df[vis]
41
+ y_data = _df[ext_col]
42
+
43
+ bins = np.linspace(0, 50, 25)
44
+ wid = (bins + (bins[1] - bins[0]) / 2)[0:-1]
45
+
46
+ _df[f'{vis}_bins'] = pd.cut(x_data, bins=bins, labels=wid)
47
+
48
+ grouped = _df.groupby(f'{vis}_bins', observed=False)
49
+
50
+ vis_labels, vals, median_vals = [], [], []
51
+ for _, subdf in grouped:
52
+ if len(subdf[ext_col].dropna()) > 3:
53
+ vis_labels.append(subdf[vis].mean())
54
+ vals.append(subdf[ext_col].dropna().values)
55
+ median_vals.append(subdf[ext_col].mean())
56
+
57
+ plt.boxplot(vals, labels=vis_labels, positions=np.array(vis_labels, dtype='float'),
58
+ widths=(bins[1] - bins[0]) / 2.5,
59
+ showfliers=False, showmeans=True, meanline=False, patch_artist=True,
60
+ boxprops=dict(facecolor=boxcolors[i], alpha=.7),
61
+ meanprops=dict(marker='o', markerfacecolor='white', markeredgecolor='k', markersize=4),
62
+ medianprops=dict(color='#000000', ls='-'))
63
+
64
+ plt.scatter(x_data, y_data, marker='.', s=10, facecolor='white', edgecolor=boxcolors[i], alpha=0.1)
65
+
66
+ # fit curve
67
+ coeff, _ = _log_fit(np.array(vis_labels, dtype='float'), np.array(median_vals, dtype='float'))
68
+
69
+ # Plot lines (ref & Measurement)
70
+ x_fit = np.linspace(0.1, 50, 1000)
71
+
72
+ func = lambda x, a: a / x
73
+ line, = ax.plot(x_fit, func(x_fit, coeff), c=scattercolor[i], lw=3,
74
+ label=f'Vis (km) = {round(coeff)} / Ext')
75
+
76
+ arts.append(line)
77
+ if 'dry' in ext_col:
78
+ labels.append(f'Vis (km) = {round(coeff)} / Ext (dry)')
79
+ else:
80
+ labels.append(f'Vis (km) = {round(coeff)} / Ext (amb)')
81
+
82
+ ax.legend(handles=arts, labels=labels, loc='upper right', prop=dict(weight='bold'), bbox_to_anchor=(0.99, 0.99))
83
+
84
+ ax.set(xlabel=kwargs.get('xlabel', 'Visibility (km)'),
85
+ ylabel=kwargs.get('ylabel', 'Extinction (1/Mm)'),
86
+ title=kwargs.get('title', 'Koschmieder relationship'),
87
+ xlim=kwargs.get('xlim', (0, 30)),
88
+ ylim=kwargs.get('ylim', (0, 800))
89
+ )
90
+
91
+ plt.xticks(ticks=np.array(range(0, 31, 5)), labels=np.array(range(0, 31, 5)))
92
+ fig.savefig('koschmieder.png', dpi=600)
93
+ plt.show()
94
+
95
+ return fig, ax
@@ -0,0 +1,164 @@
1
+ import matplotlib.pyplot as plt
2
+ import numpy as np
3
+ import seaborn as sns
4
+ from matplotlib.pyplot import Figure, Axes
5
+ from pandas import DataFrame, date_range, concat
6
+ from sklearn.preprocessing import StandardScaler
7
+
8
+ from AeroViz.plot.utils import *
9
+
10
+ __all__ = ['metal_heatmaps', 'process_data_with_two_df']
11
+
12
+
13
+ def process_data(df, detected_limit=True, outlier_threshold=5, smoothing_window=6, fill_method='MDL'):
14
+ # Fill missing values based on the specified method
15
+ df = fill_missing_values(df.copy(), method=fill_method)
16
+
17
+ # Normalize the data
18
+ df = normalize_data(df)
19
+
20
+ # Remove outliers
21
+ df = remove_outliers(df, threshold=outlier_threshold)
22
+
23
+ # Interpolate missing values
24
+ df = df.interpolate(method='linear')
25
+
26
+ # Smooth the data
27
+ df = smooth_data(df, window=smoothing_window)
28
+
29
+ return df
30
+
31
+
32
+ def process_data_with_two_df(df, df2, outlier_threshold=5, smoothing_window=6, fill_method='MDL'):
33
+ # Shift the first DataFrame by 30 minutes
34
+ df = df.shift(freq='30min')
35
+
36
+ # Fill missing values for both DataFrames
37
+ df = fill_missing_values(df.copy(), method=fill_method)
38
+ df2 = fill_missing_values(df2.copy(), method=fill_method)
39
+
40
+ # Normalize both DataFrames together
41
+ df, df2 = normalize_and_split(df, df2)
42
+
43
+ # Shift the first DataFrame back by 30 minutes
44
+ df = df.shift(freq='-30min')
45
+
46
+ # Remove outliers for both DataFrames
47
+ df = remove_outliers(df, threshold=outlier_threshold)
48
+ df2 = remove_outliers(df2, threshold=outlier_threshold)
49
+
50
+ # Interpolate missing values
51
+ df = df.interpolate(method='linear')
52
+ df2 = df2.interpolate(method='linear')
53
+
54
+ # Smooth the data
55
+ df = smooth_data(df, window=smoothing_window)
56
+ df2 = smooth_data(df2, window=smoothing_window)
57
+
58
+ return df, df2
59
+
60
+
61
+ def fill_missing_values(df, method='MDL'):
62
+ if method == 'interpolate':
63
+ return df.interpolate(method='linear')
64
+ else:
65
+ return fill_with_mdl(df)
66
+
67
+
68
+ def fill_with_mdl(df):
69
+ # Minimum detection limit (MDL) dictionary
70
+ MDL = {
71
+ 'Al': 100, 'Si': 18, 'P': 5.2, 'S': 3.2,
72
+ 'Cl': 1.7, 'K': 1.2, 'Ca': 0.3, 'Ti': 1.6,
73
+ 'V': 0.12, 'Cr': 0.12, 'Mn': 0.14, 'Fe': 0.17,
74
+ 'Co': 0.14, 'Ni': 0.096, 'Cu': 0.079, 'Zn': 0.067,
75
+ 'Ga': 0.059, 'Ge': 0.056, 'As': 0.063, 'Se': 0.081,
76
+ 'Br': 0.1, 'Rb': 0.19, 'Sr': 0.22, 'Y': 0.28,
77
+ 'Zr': 0.33, 'Nb': 0.41, 'Mo': 0.48, 'Pd': 2.2,
78
+ 'Ag': 1.9, 'Cd': 2.5, 'In': 3.1, 'Sn': 4.1,
79
+ 'Sb': 5.2, 'Te': 0.6, 'I': 0.49, 'Cs': 0.37,
80
+ 'Ba': 0.39, 'La': 0.36, 'Ce': 0.3, 'Pt': 0.12,
81
+ 'Au': 0.1, 'Hg': 0.12, 'Tl': 0.12, 'Pb': 0.13,
82
+ 'Bi': 0.13
83
+ }
84
+
85
+ # Replace values below MDL with 5/6 * MDL
86
+ for element, threshold in MDL.items():
87
+ if element in df.columns:
88
+ df.loc[:, element] = df[element].where(df[element] >= threshold, 5 / 6 * threshold)
89
+
90
+ return df
91
+
92
+
93
+ def normalize_data(df):
94
+ # Standardize the data (z-score normalization)
95
+ return DataFrame(StandardScaler().fit_transform(df), index=df.index, columns=df.columns)
96
+
97
+
98
+ def remove_outliers(df, threshold=5):
99
+ # Remove rows where any column value exceeds the threshold
100
+ return df[(np.abs(df) < threshold)]
101
+
102
+
103
+ def smooth_data(df, window=6):
104
+ # Apply rolling mean to smooth the data
105
+ return df.rolling(window=window, min_periods=1).mean()
106
+
107
+
108
+ def normalize_and_split(df, df2):
109
+ # Concatenate DataFrames for combined normalization
110
+ combined_df = concat([df, df2])
111
+ normalized_combined_df = normalize_data(combined_df)
112
+
113
+ # Split the normalized DataFrame back into df and df2
114
+ df = normalized_combined_df.loc[df.index]
115
+ df2 = normalized_combined_df.loc[df2.index]
116
+
117
+ return df, df2
118
+
119
+
120
+ @set_figure(figsize=(6, 3), fs=8, fw='normal')
121
+ def metal_heatmaps(df,
122
+ process=True,
123
+ major_freq='10d',
124
+ minor_freq='1d',
125
+ cmap='jet',
126
+ ax: Axes | None = None,
127
+ **kwargs
128
+ ) -> tuple[Figure, Axes]:
129
+ if process:
130
+ df = process_data(df)
131
+
132
+ fig, ax = plt.subplots(**kwargs.get('fig_kws', {})) if ax is None else (ax.get_figure(), ax)
133
+
134
+ sns.heatmap(df.T, vmin=None, vmax=3, cmap=cmap, xticklabels=True, yticklabels=True,
135
+ cbar_kws={'label': 'Z score', "pad": 0.02})
136
+ ax.grid(color='gray', linestyle='-', linewidth=0.3)
137
+
138
+ # Set x-tick positions and labels
139
+ major_tick = date_range(start=df.index[0], end=df.index[-1], freq=major_freq)
140
+ minor_tick = date_range(start=df.index[0], end=df.index[-1], freq=minor_freq)
141
+
142
+ # Set the major and minor ticks
143
+ ax.set_xticks(ticks=[df.index.get_loc(t) for t in major_tick])
144
+ ax.set_xticks(ticks=[df.index.get_loc(t) for t in minor_tick], minor=True)
145
+ ax.set_xticklabels(major_tick.strftime('%F'), rotation=0)
146
+ ax.tick_params(axis='y', rotation=0)
147
+
148
+ ax.set(xlabel='',
149
+ ylabel='Trace metals',
150
+ title=kwargs.get('title', None)
151
+ )
152
+
153
+ if kwargs.get('savefig'):
154
+ plt.savefig(kwargs.get('savefig'), dpi=600)
155
+
156
+ plt.show()
157
+
158
+ return fig, ax
159
+
160
+
161
+ if __name__ == '__main__':
162
+ fig, ax = plt.subplots(1, 1, figsize=(6, 6))
163
+ plt.title('text', font={'weight': 'bold'})
164
+ plt.show()
@@ -0,0 +1,2 @@
1
+ from .template import *
2
+ from .timeseries import *
@@ -0,0 +1,47 @@
1
+ import matplotlib.pyplot as plt
2
+ from matplotlib.pyplot import Figure, Axes
3
+ from pandas import DataFrame
4
+
5
+ from AeroViz.plot.timeseries.timeseries import timeseries
6
+
7
+
8
+ def timeseries_template(df: DataFrame) -> tuple[Figure, Axes]:
9
+ fig, ax = plt.subplots(5, 1, figsize=(len(df.index) * 0.01, 4))
10
+ (ax1, ax2, ax3, ax4, ax5) = ax
11
+
12
+ timeseries(df,
13
+ y=['Extinction', 'Scattering', 'Absorption'],
14
+ rolling=30,
15
+ ax=ax1,
16
+ ylabel='Coefficient',
17
+ ylim=[0., None],
18
+ set_xaxis_visible=False,
19
+ legend_ncol=3,
20
+ )
21
+
22
+ # Temp, RH
23
+ timeseries(df,
24
+ y='AT',
25
+ y2='RH',
26
+ rolling=30,
27
+ ax=ax2,
28
+ ax_plot_kws=dict(color='r'),
29
+ ax2_plot_kws=dict(color='b'),
30
+ ylim=[10, 30],
31
+ ylim2=[20, 100],
32
+ set_xaxis_visible=False,
33
+ legend_ncol=2,
34
+ )
35
+
36
+ timeseries(df, y='WS', color='WD', style='scatter', ax=ax3, scatter_kws=dict(cmap='hsv'),
37
+ cbar_kws=dict(ticks=[0, 90, 180, 270, 360]),
38
+ ylim=[0, None], set_xaxis_visible=False)
39
+
40
+ timeseries(df, y='VC', color='PBLH', style='bar', ax=ax4, bar_kws=dict(cmap='Blues'), set_xaxis_visible=False,
41
+ ylim=[0, 5000])
42
+
43
+ timeseries(df, y='PM2.5', color='PM1/PM25', style='scatter', ax=ax5, ylim=[0, None])
44
+
45
+ plt.show()
46
+
47
+ return fig, ax