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,93 @@
1
+ # SMPS-APS Merge Algorithms
2
+
3
+ 本資料夾包含 SMPS 與 APS 粒徑分布合併演算法的各版本實現。
4
+
5
+ ## 版本演進
6
+
7
+ | 版本 | 檔案 | 主要特點 |
8
+ |------|------|----------|
9
+ | v0 | `_merge_v0.py` | 最原始版本,無 `union_index` 索引對齊 |
10
+ | v0.1 | `_merge_v0_1.py` | 加入 `union_index` 索引對齊功能 |
11
+ | v1 | `_merge_v1.py` | 加入 `shift_mode` 參數 (mobility/aerodynamic) |
12
+ | v2 | `_merge_v2.py` | 簡化輸出,移除 qc_cond 過濾 |
13
+ | v3 | `_merge_v3.py` | 加入 multiprocessing 平行運算 + dN/dS/dV 演算法 |
14
+ | v4 | `_merge_v4.py` | 加入 PM2.5 fitness 函數 + SMPS times 校正 |
15
+
16
+ ## 版本詳細說明
17
+
18
+ ### v0 (原始版本)
19
+ - 基本的 power law fitting 密度估算
20
+ - 使用 UnivariateSpline 進行重疊區域平滑
21
+ - rho 閾值: 0.3 < ρ² < 2.0
22
+ - 輸出: `data_all`, `data_qc`, `density_all`, `density_qc`
23
+
24
+ ### v0.1
25
+ - 加入 `union_index` 確保 SMPS 與 APS 時間索引對齊
26
+ - 導出函數名稱改為 `merge_SMPS_APS`(無底線前綴)
27
+
28
+ ### v1
29
+ - 加入 `shift_mode` 參數,支援:
30
+ - `'mobility'`: 移動 APS 粒徑到 mobility diameter
31
+ - `'aerodynamic'`: 移動 SMPS 粒徑到 aerodynamic diameter
32
+ - rho 閾值放寬: 0.3 < ρ² < 2.6
33
+
34
+ ### v2
35
+ - 移除 `shift_mode` 參數(固定使用 mobility)
36
+ - 簡化輸出結構,移除 `_qc` 過濾版本
37
+ - 加入 APS 校正迭代(2次)
38
+ - 輸出: `data_all`, `data_all_aer`, `density_all`
39
+
40
+ ### v3
41
+ - 引入 multiprocessing 平行運算加速
42
+ - 新增 dN/dS/dV 相關性演算法 (`_corr_with_dNdSdV`)
43
+ - 同時計算多種演算法結果:
44
+ - `dn`: 純 power law fitting
45
+ - `dndsdv`: dN/dS/dV 相關性
46
+ - `cor_dn`: APS 校正後的 power law
47
+ - `cor_dndsdv`: APS 校正後的 dN/dS/dV
48
+ - 輸出: `data_dn`, `data_dndsdv`, `data_cor_dn`, `data_cor_dndsdv`, `density`
49
+
50
+ ### v4 (最新版本)
51
+ - 加入 PM2.5 質量約束的 fitness 函數
52
+ - 引入 SMPS times 校正因子(搜尋最佳倍率)
53
+ - 參數 `times_range`: SMPS 數據乘數範圍 (預設 0.8~1.25)
54
+ - 輸出新增 `times` 欄位記錄各時間點的最佳校正倍率
55
+
56
+ ## 使用方式
57
+
58
+ ```python
59
+ from pathlib import Path
60
+ from AeroViz.dataProcess import DataProcess
61
+
62
+ dp = DataProcess('SizeDistr', Path('./output'))
63
+
64
+ # v1 (基本版)
65
+ result = dp.merge_SMPS_APS(df_smps, df_aps)
66
+
67
+ # v2
68
+ result = dp.merge_SMPS_APS_v2(df_smps, df_aps)
69
+
70
+ # v3 (含 dN/dS/dV 演算法)
71
+ result = dp.merge_SMPS_APS_v3(df_smps, df_aps, dndsdv_alg=True)
72
+
73
+ # v4 (含 PM2.5 校正,推薦)
74
+ result = dp.merge_SMPS_APS_v4(df_smps, df_aps, df_pm25)
75
+ ```
76
+
77
+ ## 演算法核心概念
78
+
79
+ ### Power Law Fitting
80
+ 利用 SMPS 上端粒徑的 power law 擬合(y = Ax^B),計算 APS 粒徑的位移因子(shift factor),用以估算有效密度。
81
+
82
+ ### Shift Factor
83
+ 位移因子的平方即為有效密度估計值:
84
+ - ρ_eff = shift_factor²
85
+ - 合理範圍: 0.6 ~ 2.6 g/cm³
86
+
87
+ ### dN/dS/dV 相關性
88
+ v3+ 版本加入的演算法,同時考慮:
89
+ - dN/dlogDp (數量分布)
90
+ - dS/dlogDp (表面積分布)
91
+ - dV/dlogDp (體積分布)
92
+
93
+ 通過最大化三者的相關性來決定最佳位移因子。
@@ -0,0 +1,20 @@
1
+ """
2
+ SMPS-APS Merge Algorithms
3
+
4
+ Version history:
5
+ - v0: Original implementation
6
+ - v0.1: Added union_index alignment
7
+ - v1: Added shift_mode parameter
8
+ - v2: Simplified output, removed qc filtering
9
+ - v3: Multiprocessing + dN/dS/dV algorithm
10
+ - v4: PM2.5 fitness + SMPS times correction
11
+ """
12
+
13
+ from ._merge_v0 import _merge_SMPS_APS as merge_v0
14
+ from ._merge_v0_1 import merge_SMPS_APS as merge_v0_1
15
+ from ._merge_v1 import _merge_SMPS_APS as merge_v1
16
+ from ._merge_v2 import merge_SMPS_APS as merge_v2
17
+ from ._merge_v3 import merge_SMPS_APS as merge_v3
18
+ from ._merge_v4 import merge_SMPS_APS as merge_v4
19
+
20
+ __all__ = ['merge_v0', 'merge_v0_1', 'merge_v1', 'merge_v2', 'merge_v3', 'merge_v4']
@@ -0,0 +1,251 @@
1
+ from datetime import datetime as dtm
2
+
3
+ import numpy as np
4
+ from pandas import DataFrame, to_datetime
5
+ # from scipy.interpolate import interp1d
6
+ from scipy.interpolate import UnivariateSpline as unvpline, interp1d
7
+
8
+ __all__ = ['_merge_SMPS_APS']
9
+
10
+
11
+ def __test_plot(smpsx, smps, apsx, aps, mergex, merge, mergeox, mergeo, _sh):
12
+ from matplotlib.pyplot import subplots, close, show
13
+
14
+ ## parameter
15
+ # '''
16
+ ## plot
17
+ fig, ax = subplots()
18
+
19
+ ax.plot(smpsx, smps, c='#ff794c', label='smps', marker='o', lw=2)
20
+ ax.plot(apsx, aps, c='#4c79ff', label='aps', marker='o', lw=2)
21
+ ax.plot(mergex, merge, c='#79796a', label='merge')
22
+ # ax.plot(mergeox,mergeo,c='#111111',label='mergeo',marker='o',lw=.75)
23
+
24
+ ax.set(xscale='log', yscale='log', )
25
+
26
+ ax.legend(framealpha=0, )
27
+ ax.set_title((_sh ** 2)[0], fontsize=13)
28
+
29
+ show()
30
+ close()
31
+
32
+
33
+ # '''
34
+
35
+
36
+ ## Overlap fitting
37
+ ## Create a fitting func. by smps data
38
+ ## return : shift factor
39
+ def _overlap_fitting(_smps_ori, _aps_ori, _smps_lb, _aps_hb):
40
+ print(f"\t\t{dtm.now().strftime('%m/%d %X')} : \033[92moverlap range fitting\033[0m")
41
+
42
+ ## overlap fitting
43
+ ## parmeter
44
+ _dt_indx = _smps_ori.index
45
+
46
+ ## overlap diameter data
47
+ _aps = _aps_ori[_aps_ori.keys()[_aps_ori.keys() < _aps_hb]].copy()
48
+ _smps = _smps_ori[_smps_ori.keys()[_smps_ori.keys() > _smps_lb]].copy()
49
+
50
+ ## use SMPS data apply power law fitting
51
+ ## y = Ax^B, A = e**coefa, B = coefb, x = logx, y = logy
52
+ ## ref : http://mathworld.wolfram.com/LeastSquaresFittingPowerLaw.html
53
+ ## power law fit to SMPS num conc at upper bins to log curve
54
+
55
+ ## coefficient A, B
56
+ _smps_qc_cond = ((_smps != 0) & np.isfinite(_smps))
57
+ _smps_qc = _smps.where(_smps_qc_cond)
58
+
59
+ _size = _smps_qc_cond.sum(axis=1)
60
+ _size = _size.where(_size != 0.).copy()
61
+
62
+ _logx, _logy = np.log(_smps_qc.keys()._data.astype(float)), np.log(_smps_qc)
63
+ _x, _y, _xy, _xx = _logx.sum(), _logy.sum(axis=1), (_logx * _logy).sum(axis=1), (_logx ** 2).sum()
64
+
65
+ _coeB = ((_size * _xy - _x * _y) / (_size * _xx - _x ** 2.))
66
+ _coeA = np.exp((_y - _coeB * _x) / _size).values.reshape(-1, 1)
67
+ _coeB = _coeB.values.reshape(-1, 1)
68
+
69
+ ## rebuild shift smps data by coe. A, B
70
+ ## x_shift = (y_ori/A)**(1/B)
71
+ _aps_shift_x = (_aps / _coeA) ** (1 / _coeB)
72
+ _aps_shift_x = _aps_shift_x.where(np.isfinite(_aps_shift_x))
73
+
74
+ ## the least squares of diameter
75
+ ## the shift factor which the cklosest to 1
76
+ _shift_factor = (_aps_shift_x.keys()._data.astype(float) / _aps_shift_x)
77
+ _shift_factor.columns = range(len(_aps_shift_x.keys()))
78
+
79
+ _dropna_idx = _shift_factor.dropna(how='all').index.copy()
80
+
81
+ ## use the target function to get the similar aps and smps bin
82
+ ## S2 = sum( (smps_fit_line(dia) - aps(dia*shift_factor) )**2 )
83
+ ## assumption : the same diameter between smps and aps should get the same conc.
84
+
85
+ ## be sure they art in log value
86
+ _S2 = DataFrame(index=_aps_shift_x.index)
87
+ _dia_table = DataFrame(np.full(_aps_shift_x.shape, _aps_shift_x.keys()),
88
+ columns=_aps_shift_x.keys(), index=_aps_shift_x.index)
89
+ for _idx, _factor in _shift_factor.items():
90
+ _smps_fit_df = _coeA * (_dia_table / _factor.to_frame().values) ** _coeB
91
+ _S2[_idx] = ((_smps_fit_df - _aps) ** 2).sum(axis=1)
92
+
93
+ _least_squ_idx = _S2.idxmin(axis=1).loc[_dropna_idx]
94
+
95
+ _shift_factor_out = DataFrame(_shift_factor.loc[_dropna_idx].values[range(len(_dropna_idx)), _least_squ_idx.values],
96
+ index=_dropna_idx).reindex(_dt_indx)
97
+
98
+ return _shift_factor_out, (DataFrame(_coeA, index=_dt_indx), DataFrame(_coeB, index=_dt_indx))
99
+
100
+
101
+ ## Remove big shift data ()
102
+ ## Return : aps, smps, shift (without big shift data)
103
+ def _shift_data_process(_shift):
104
+ print(f"\t\t{dtm.now().strftime('%m/%d %X')} : \033[92mshift-data quality control\033[0m")
105
+
106
+ _rho = _shift ** 2
107
+ _shift = _shift.mask((~np.isfinite(_shift)) | (_rho > 2) | (_rho < 0.3))
108
+
109
+ _qc_index = _shift.mask((_rho < 0.6) | (_shift.isna())).dropna().index
110
+
111
+ return _qc_index, _shift
112
+
113
+
114
+ # return _smps.loc[~_big_shift], _aps.loc[~_big_shift], _shift[~_big_shift].reshape(-1,1)
115
+
116
+
117
+ ## Create merge data
118
+ ## shift all smps bin and remove the aps bin which smaller than the latest old smps bin
119
+ ## Return : merge bins, merge data, density
120
+ def _merge_data(_smps_ori, _aps_ori, _shift_ori, _shift_mode, _smps_lb, _aps_hb, _coe):
121
+ print(f"\t\t{dtm.now().strftime('%m/%d %X')} : \033[92mcreate merge data\033[0m")
122
+
123
+ _ori_idx = _smps_ori.index
124
+ _merge_idx = _smps_ori.loc[_aps_ori.dropna(how='all').index].dropna(how='all').index
125
+
126
+ _uni_idx, _count = np.unique(np.hstack((_smps_ori.dropna(how='all').index, _aps_ori.dropna(how='all').index,
127
+ _shift_ori.dropna(how='all').index)), return_counts=True)
128
+
129
+ _merge_idx = to_datetime(np.unique(_uni_idx[_count == 3]))
130
+
131
+ _smps, _aps, _shift = _smps_ori.loc[_merge_idx], _aps_ori.loc[_merge_idx], _shift_ori.loc[_merge_idx].values
132
+
133
+ ## parameter
134
+ _coeA, _coeB = _coe[0].loc[_merge_idx], _coe[1].loc[_merge_idx]
135
+ _smps_key, _aps_key = _smps.keys()._data.astype(float), _aps.keys()._data.astype(float)
136
+
137
+ _test = 1000
138
+
139
+ # _cntr = (_smps_lb+_aps_hb)/2
140
+ _cntr = _test
141
+ _bin_lb = _smps_key[-1]
142
+
143
+ ## make shift bins
144
+ _smps_bin = np.full(_smps.shape, _smps_key)
145
+ _aps_bin = np.full(_aps.shape, _aps_key)
146
+ # _std_bin = _smps_key.tolist()+_aps_key[_aps_key>_smps_key[-1]].tolist()
147
+ _std_bin = np.geomspace(_smps_key[0], _aps_key[-1], 230)
148
+ _std_bin_merge = _std_bin[(_std_bin < _cntr) & (_std_bin > _bin_lb)]
149
+ _std_bin_inte1 = _std_bin[_std_bin <= _bin_lb]
150
+ _std_bin_inte2 = _std_bin[_std_bin >= _cntr]
151
+
152
+ if _shift_mode == 'mobility':
153
+ _aps_bin /= _shift
154
+
155
+ elif _shift_mode == 'aerodynamic':
156
+ _smps_bin *= _shift
157
+
158
+ ## merge
159
+ _merge_lst = []
160
+ for _bin_smps, _bin_aps, _dt_smps, _dt_aps, _sh in zip(_smps_bin, _aps_bin, _smps.values, _aps.values, _shift):
161
+ ## remove
162
+
163
+ ## keep complete smps bins and data
164
+ ## remove the aps bin data lower than smps bin
165
+ _condi = _bin_aps >= _bin_smps[-1]
166
+
167
+ _merge_bin = np.hstack((_bin_smps, _bin_aps[_condi]))
168
+ _merge_dt = np.hstack((_dt_smps, _dt_aps[_condi]))
169
+
170
+ # _merge_fit_loc = (_merge_bin<_aps_hb)&(_merge_bin>_smps_lb)
171
+ _merge_fit_loc = (_merge_bin < 1500) & (_merge_bin > _smps_lb)
172
+
173
+ ## coeA and coeB
174
+ _unvpl_fc = unvpline(np.log(_merge_bin[_merge_fit_loc]), np.log(_merge_dt[_merge_fit_loc]), s=50)
175
+ # _unvpl_fc = unvpline(_merge_bin[_merge_fit_loc],_merge_dt[_merge_fit_loc],s=150)
176
+ # _inte_log_fc = interp1d(n.log10(_merge_bin[_merge_fit_loc]),n.log10(_merge_dt[_merge_fit_loc]),
177
+ # kind='linear',fill_value='extrapolate')
178
+ _inte_fc = interp1d(_merge_bin, _merge_dt, kind='linear', fill_value='extrapolate')
179
+
180
+ __merge = np.exp(_unvpl_fc(np.log(_std_bin_merge)))
181
+ # __merge = _unvpl_fc(_std_bin_merge)
182
+
183
+ _merge_dt_fit = np.hstack((_inte_fc(_std_bin_inte1), __merge, _inte_fc(_std_bin_inte2)))
184
+ # _merge_dt_fit = __merge
185
+ # __test_plot(_bin_smps,_dt_smps,_bin_aps,_dt_aps,_std_bin,_merge_dt_fit,_merge_bin,_merge_dt,_sh)
186
+
187
+ _merge_lst.append(_merge_dt_fit)
188
+
189
+ _df_merge = DataFrame(_merge_lst, columns=_std_bin, index=_merge_idx)
190
+ _df_merge = _df_merge.mask(_df_merge < 0)
191
+
192
+ ## process output df
193
+ ## average, align with index
194
+ def _out_df(*_df_arg, **_df_kwarg):
195
+ _df = DataFrame(*_df_arg, **_df_kwarg).reindex(_ori_idx)
196
+ _df.index.name = 'time'
197
+ return _df
198
+
199
+ return _out_df(_df_merge), _out_df(_shift_ori ** 2)
200
+
201
+
202
+ ## aps_fit_highbound : the diameter I choose randomly
203
+ def _merge_SMPS_APS(df_smps, df_aps, aps_unit, shift_mode, smps_overlap_lowbound, aps_fit_highbound):
204
+ # print(f'\nMerge data :')
205
+ # print(f' APS fittint higher diameter : {aps_fit_highbound:4d} nm')
206
+ # print(f' SMPS overlap lower diameter : {smps_overlap_lowbound:4d} nm')
207
+ # print(f' Average time : {self.data_freq:>4s}\n')
208
+
209
+ ## get data, remove 'total' and 'mode'
210
+ ## set to the same units
211
+ smps, aps = df_smps, df_aps
212
+ smps.columns = smps.keys().to_numpy(float)
213
+ aps.columns = aps.keys().to_numpy(float)
214
+
215
+ if aps_unit == 'um':
216
+ aps.columns = aps.keys() * 1e3
217
+
218
+ ## shift infomation, calculate by powerlaw fitting
219
+ shift, coe = _overlap_fitting(smps, aps, smps_overlap_lowbound, aps_fit_highbound)
220
+
221
+ ## process data by shift infomation, and average data
222
+ qc_cond, shift = _shift_data_process(shift)
223
+
224
+ ## merge aps and smps..
225
+ merge_data, density = _merge_data(smps, aps, shift, shift_mode, smps_overlap_lowbound, aps_fit_highbound, coe)
226
+ density.columns = ['density']
227
+
228
+ ## add total and mode
229
+ # merge_total = merge_data.sum(axis=1,min_count=1).copy()
230
+ # merge_mode = merge_data.idxmax(axis=1).astype(float).copy()
231
+
232
+ # merge_data['total'] = merge_total
233
+ # merge_data['mode'] = merge_mode
234
+
235
+ ## out
236
+ out_dic = {
237
+ 'data_all': merge_data,
238
+ 'data_qc': merge_data.loc[qc_cond],
239
+ 'density_all': density,
240
+ 'density_qc': density.loc[qc_cond],
241
+ }
242
+
243
+ ## process data
244
+
245
+ for _nam, _df in out_dic.items():
246
+ out_dic[_nam] = _df.reindex(df_aps.index).copy()
247
+
248
+ # merge_data = merge_data.reindex(df_aps.index)
249
+ # density = density.reindex(df_aps.index)
250
+
251
+ return out_dic
@@ -0,0 +1,246 @@
1
+ from datetime import datetime as dtm
2
+
3
+ import numpy as np
4
+ from pandas import DataFrame, to_datetime
5
+ # from scipy.interpolate import interp1d
6
+ from scipy.interpolate import UnivariateSpline as unvpline, interp1d
7
+
8
+ from AeroViz.dataProcess.core import union_index
9
+
10
+ __all__ = ['merge_SMPS_APS']
11
+
12
+
13
+ def __test_plot(smpsx, smps, apsx, aps, mergex, merge, mergeox, mergeo, _sh):
14
+ from matplotlib.pyplot import subplots, close, show
15
+
16
+ ## parameter
17
+ # '''
18
+ ## plot
19
+ fig, ax = subplots()
20
+
21
+ ax.plot(smpsx, smps, c='#ff794c', label='smps', marker='o', lw=2)
22
+ ax.plot(apsx, aps, c='#4c79ff', label='aps', marker='o', lw=2)
23
+ ax.plot(mergex, merge, c='#79796a', label='merge')
24
+ # ax.plot(mergeox,mergeo,c='#111111',label='mergeo',marker='o',lw=.75)
25
+
26
+ ax.set(xscale='log', yscale='log', )
27
+
28
+ ax.legend(framealpha=0, )
29
+ ax.set_title((_sh ** 2)[0], fontsize=13)
30
+
31
+ show()
32
+ close()
33
+
34
+
35
+ # '''
36
+
37
+
38
+ ## Overlap fitting
39
+ ## Create a fitting func. by smps data
40
+ ## return : shift factor
41
+ def _overlap_fitting(_smps_ori, _aps_ori, _smps_lb, _aps_hb):
42
+ print(f"\t\t{dtm.now().strftime('%m/%d %X')} : \033[92moverlap range fitting\033[0m")
43
+
44
+ ## overlap fitting
45
+ ## parmeter
46
+ _dt_indx = _smps_ori.index
47
+
48
+ ## overlap diameter data
49
+ _aps = _aps_ori[_aps_ori.keys()[_aps_ori.keys() < _aps_hb]].copy()
50
+ _smps = _smps_ori[_smps_ori.keys()[_smps_ori.keys() > _smps_lb]].copy()
51
+
52
+ ## use SMPS data apply power law fitting
53
+ ## y = Ax^B, A = e**coefa, B = coefb, x = logx, y = logy
54
+ ## ref : http://mathworld.wolfram.com/LeastSquaresFittingPowerLaw.html
55
+ ## power law fit to SMPS num conc at upper bins to log curve
56
+
57
+ ## coefficient A, B
58
+ _smps_qc_cond = ((_smps != 0) & np.isfinite(_smps))
59
+ _smps_qc = _smps.where(_smps_qc_cond)
60
+
61
+ _size = _smps_qc_cond.sum(axis=1)
62
+ _size = _size.where(_size != 0.).copy()
63
+
64
+ _logx, _logy = np.log(_smps_qc.keys()._data.astype(float)), np.log(_smps_qc)
65
+ _x, _y, _xy, _xx = _logx.sum(), _logy.sum(axis=1), (_logx * _logy).sum(axis=1), (_logx ** 2).sum()
66
+
67
+ _coeB = ((_size * _xy - _x * _y) / (_size * _xx - _x ** 2.))
68
+ _coeA = np.exp((_y - _coeB * _x) / _size).values.reshape(-1, 1)
69
+ _coeB = _coeB.values.reshape(-1, 1)
70
+
71
+ ## rebuild shift smps data by coe. A, B
72
+ ## x_shift = (y_ori/A)**(1/B)
73
+ _aps_shift_x = (_aps / _coeA) ** (1 / _coeB)
74
+ _aps_shift_x = _aps_shift_x.where(np.isfinite(_aps_shift_x))
75
+
76
+ ## the least squares of diameter
77
+ ## the shift factor which the cklosest to 1
78
+ _shift_factor = (_aps_shift_x.keys()._data.astype(float) / _aps_shift_x)
79
+ _shift_factor.columns = range(len(_aps_shift_x.keys()))
80
+
81
+ _dropna_idx = _shift_factor.dropna(how='all').index.copy()
82
+
83
+ ## use the target function to get the similar aps and smps bin
84
+ ## S2 = sum( (smps_fit_line(dia) - aps(dia*shift_factor) )**2 )
85
+ ## assumption : the same diameter between smps and aps should get the same conc.
86
+
87
+ ## be sure they art in log value
88
+ _S2 = DataFrame(index=_aps_shift_x.index)
89
+ _dia_table = DataFrame(np.full(_aps_shift_x.shape, _aps_shift_x.keys()),
90
+ columns=_aps_shift_x.keys(), index=_aps_shift_x.index)
91
+ for _idx, _factor in _shift_factor.items():
92
+ _smps_fit_df = _coeA * (_dia_table / _factor.to_frame().values) ** _coeB
93
+ _S2[_idx] = ((_smps_fit_df - _aps) ** 2).sum(axis=1)
94
+
95
+ _least_squ_idx = _S2.idxmin(axis=1).loc[_dropna_idx]
96
+
97
+ _shift_factor_out = DataFrame(_shift_factor.loc[_dropna_idx].values[range(len(_dropna_idx)), _least_squ_idx.values],
98
+ index=_dropna_idx).reindex(_dt_indx)
99
+
100
+ return _shift_factor_out, (DataFrame(_coeA, index=_dt_indx), DataFrame(_coeB, index=_dt_indx))
101
+
102
+
103
+ ## Remove big shift data ()
104
+ ## Return : aps, smps, shift (without big shift data)
105
+ def _shift_data_process(_shift):
106
+ print(f"\t\t{dtm.now().strftime('%m/%d %X')} : \033[92mshift-data quality control\033[0m")
107
+
108
+ _rho = _shift ** 2
109
+ _shift = _shift.mask((~np.isfinite(_shift)) | (_rho > 2) | (_rho < 0.3))
110
+
111
+ _qc_index = _shift.mask((_rho < 0.6) | (_shift.isna())).dropna().index
112
+
113
+ return _qc_index, _shift
114
+
115
+
116
+ # return _smps.loc[~_big_shift], _aps.loc[~_big_shift], _shift[~_big_shift].reshape(-1,1)
117
+
118
+
119
+ ## Create merge data
120
+ ## shift all smps bin and remove the aps bin which smaller than the latest old smps bin
121
+ ## Return : merge bins, merge data, density
122
+ def _merge_data(_smps_ori, _aps_ori, _shift_ori, _smps_lb, _aps_hb, _coe, _shift_mode):
123
+ print(f"\t\t{dtm.now().strftime('%m/%d %X')} : \033[92mcreate merge data : {_shift_mode}\033[0m")
124
+
125
+ _ori_idx = _smps_ori.index
126
+ _merge_idx = _smps_ori.loc[_aps_ori.dropna(how='all').index].dropna(how='all').index
127
+
128
+ _corr_aps_cond = _aps_ori.keys() < 700
129
+ _corr_aps_ky = _aps_ori.keys()[_corr_aps_cond]
130
+
131
+ _uni_idx, _count = np.unique(np.hstack((_smps_ori.dropna(how='all').index, _aps_ori.dropna(how='all').index,
132
+ _shift_ori.dropna(how='all').index)), return_counts=True)
133
+
134
+ _merge_idx = to_datetime(np.unique(_uni_idx[_count == 3]))
135
+
136
+ _smps, _aps, _shift = _smps_ori.loc[_merge_idx], _aps_ori.loc[_merge_idx], _shift_ori.loc[_merge_idx].values
137
+
138
+ ## parameter
139
+ _coeA, _coeB = _coe[0].loc[_merge_idx], _coe[1].loc[_merge_idx]
140
+ _smps_key, _aps_key = _smps.keys()._data.astype(float), _aps.keys()._data.astype(float)
141
+
142
+ _cntr = 1000
143
+ _bin_lb = _smps_key[-1]
144
+
145
+ ## make shift bins
146
+ _smps_bin = np.full(_smps.shape, _smps_key)
147
+ _aps_bin = np.full(_aps.shape, _aps_key)
148
+
149
+ _std_bin = np.geomspace(_smps_key[0], _aps_key[-1], 230)
150
+ _std_bin_merge = _std_bin[(_std_bin < _cntr) & (_std_bin > _bin_lb)]
151
+ _std_bin_inte1 = _std_bin[_std_bin <= _bin_lb]
152
+ _std_bin_inte2 = _std_bin[_std_bin >= _cntr]
153
+
154
+ if _shift_mode == 'mobility':
155
+ _aps_bin /= _shift
156
+
157
+ elif _shift_mode == 'aerodynamic':
158
+ _smps_bin *= _shift
159
+
160
+ ## merge
161
+ _merge_lst, _corr_lst = [], []
162
+ for _bin_smps, _bin_aps, _dt_smps, _dt_aps, _sh in zip(_smps_bin, _aps_bin, _smps.values, _aps.values, _shift):
163
+ ## keep complete smps bins and data
164
+ ## remove the aps bin data lower than smps bin
165
+ _condi = _bin_aps >= _bin_smps[-1]
166
+
167
+ _merge_bin = np.hstack((_bin_smps, _bin_aps[_condi]))
168
+ _merge_dt = np.hstack((_dt_smps, _dt_aps[_condi]))
169
+
170
+ _merge_fit_loc = (_merge_bin < 1500) & (_merge_bin > _smps_lb)
171
+
172
+ ## coeA and coeB
173
+ _unvpl_fc = unvpline(np.log(_merge_bin[_merge_fit_loc]), np.log(_merge_dt[_merge_fit_loc]), s=50)
174
+ _inte_fc = interp1d(_merge_bin, _merge_dt, kind='linear', fill_value='extrapolate')
175
+
176
+ _merge_dt_fit = np.hstack((_inte_fc(_std_bin_inte1), np.exp(_unvpl_fc(np.log(_std_bin_merge))),
177
+ _inte_fc(_std_bin_inte2)))
178
+
179
+ _merge_lst.append(_merge_dt_fit)
180
+ _corr_lst.append(interp1d(_std_bin, _merge_dt_fit)(_bin_aps[_corr_aps_cond]))
181
+
182
+ _df_merge = DataFrame(_merge_lst, columns=_std_bin, index=_merge_idx)
183
+ _df_merge = _df_merge.mask(_df_merge < 0)
184
+
185
+ _df_corr = DataFrame(_corr_lst, columns=_corr_aps_ky, index=_merge_idx) / _aps_ori.loc[_merge_idx, _corr_aps_ky]
186
+
187
+ ## process output df
188
+ ## average, align with index
189
+ def _out_df(*_df_arg, **_df_kwarg):
190
+ _df = DataFrame(*_df_arg, **_df_kwarg).reindex(_ori_idx)
191
+ _df.index.name = 'time'
192
+ return _df
193
+
194
+ return _out_df(_df_merge), _out_df(_shift_ori ** 2), _out_df(_df_corr)
195
+
196
+
197
+ def merge_SMPS_APS(df_smps, df_aps, aps_unit='um', smps_overlap_lowbound=500, aps_fit_highbound=1000):
198
+ df_smps, df_aps = union_index(df_smps, df_aps)
199
+
200
+ ## set to the same units
201
+ smps, aps_ori = df_smps.copy(), df_aps.copy()
202
+ smps.columns = smps.keys().to_numpy(float)
203
+ aps_ori.columns = aps_ori.keys().to_numpy(float)
204
+
205
+ if aps_unit == 'um':
206
+ aps_ori.columns = aps_ori.keys() * 1e3
207
+
208
+ den_lst, mer_lst = [], []
209
+ aps_input = aps_ori.loc[:, aps_ori.keys() > 700].copy()
210
+
211
+ for _count in range(2):
212
+
213
+ ## shift infomation, calculate by powerlaw fitting
214
+ shift, coe = _overlap_fitting(smps, aps_input, smps_overlap_lowbound, aps_fit_highbound)
215
+
216
+ ## process data by shift infomation, and average data
217
+ qc_cond, shift = _shift_data_process(shift)
218
+
219
+ ## merge aps and smps
220
+ merge_arg = (smps, aps_ori, shift, smps_overlap_lowbound, aps_fit_highbound, coe)
221
+ merge_data_mob, density, _corr = _merge_data(*merge_arg, 'mobility')
222
+ merge_data_aer, density, _ = _merge_data(*merge_arg, 'aerodynamic')
223
+ density.columns = ['density']
224
+
225
+ if _count == 0:
226
+ corr = _corr.resample('1d').mean().reindex(smps.index).ffill()
227
+ corr = corr.mask(corr < 1, 1)
228
+ aps_ori.loc[:, corr.keys()] *= corr
229
+
230
+ aps_input = aps_ori.copy()
231
+
232
+ ## out
233
+ out_dic = {
234
+ 'data_all': merge_data_mob,
235
+ 'data_qc': merge_data_mob.loc[qc_cond],
236
+ 'data_all_aer': merge_data_aer,
237
+ 'data_qc_aer': merge_data_aer.loc[qc_cond],
238
+ 'density_all': density,
239
+ 'density_qc': density.loc[qc_cond],
240
+ }
241
+
242
+ ## process data
243
+ for _nam, _df in out_dic.items():
244
+ out_dic[_nam] = _df.reindex(smps.index).copy()
245
+
246
+ return out_dic