AeroViz 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of AeroViz might be problematic. Click here for more details.

Files changed (102) hide show
  1. AeroViz/__init__.py +15 -0
  2. AeroViz/dataProcess/Chemistry/__init__.py +63 -0
  3. AeroViz/dataProcess/Chemistry/_calculate.py +27 -0
  4. AeroViz/dataProcess/Chemistry/_isoropia.py +99 -0
  5. AeroViz/dataProcess/Chemistry/_mass_volume.py +175 -0
  6. AeroViz/dataProcess/Chemistry/_ocec.py +184 -0
  7. AeroViz/dataProcess/Chemistry/_partition.py +29 -0
  8. AeroViz/dataProcess/Chemistry/_teom.py +16 -0
  9. AeroViz/dataProcess/Optical/_IMPROVE.py +61 -0
  10. AeroViz/dataProcess/Optical/__init__.py +62 -0
  11. AeroViz/dataProcess/Optical/_absorption.py +54 -0
  12. AeroViz/dataProcess/Optical/_extinction.py +36 -0
  13. AeroViz/dataProcess/Optical/_mie.py +16 -0
  14. AeroViz/dataProcess/Optical/_mie_sd.py +143 -0
  15. AeroViz/dataProcess/Optical/_scattering.py +30 -0
  16. AeroViz/dataProcess/SizeDistr/__init__.py +61 -0
  17. AeroViz/dataProcess/SizeDistr/__merge.py +250 -0
  18. AeroViz/dataProcess/SizeDistr/_merge.py +245 -0
  19. AeroViz/dataProcess/SizeDistr/_merge_v1.py +254 -0
  20. AeroViz/dataProcess/SizeDistr/_merge_v2.py +243 -0
  21. AeroViz/dataProcess/SizeDistr/_merge_v3.py +518 -0
  22. AeroViz/dataProcess/SizeDistr/_merge_v4.py +424 -0
  23. AeroViz/dataProcess/SizeDistr/_size_distr.py +93 -0
  24. AeroViz/dataProcess/VOC/__init__.py +19 -0
  25. AeroViz/dataProcess/VOC/_potential_par.py +76 -0
  26. AeroViz/dataProcess/__init__.py +11 -0
  27. AeroViz/dataProcess/core/__init__.py +92 -0
  28. AeroViz/plot/__init__.py +7 -0
  29. AeroViz/plot/distribution/__init__.py +1 -0
  30. AeroViz/plot/distribution/distribution.py +582 -0
  31. AeroViz/plot/improve/__init__.py +1 -0
  32. AeroViz/plot/improve/improve.py +240 -0
  33. AeroViz/plot/meteorology/__init__.py +1 -0
  34. AeroViz/plot/meteorology/meteorology.py +317 -0
  35. AeroViz/plot/optical/__init__.py +2 -0
  36. AeroViz/plot/optical/aethalometer.py +77 -0
  37. AeroViz/plot/optical/optical.py +388 -0
  38. AeroViz/plot/templates/__init__.py +8 -0
  39. AeroViz/plot/templates/contour.py +47 -0
  40. AeroViz/plot/templates/corr_matrix.py +108 -0
  41. AeroViz/plot/templates/diurnal_pattern.py +42 -0
  42. AeroViz/plot/templates/event_evolution.py +65 -0
  43. AeroViz/plot/templates/koschmieder.py +156 -0
  44. AeroViz/plot/templates/metal_heatmap.py +57 -0
  45. AeroViz/plot/templates/regression.py +256 -0
  46. AeroViz/plot/templates/scatter.py +130 -0
  47. AeroViz/plot/templates/templates.py +398 -0
  48. AeroViz/plot/timeseries/__init__.py +1 -0
  49. AeroViz/plot/timeseries/timeseries.py +317 -0
  50. AeroViz/plot/utils/__init__.py +3 -0
  51. AeroViz/plot/utils/_color.py +71 -0
  52. AeroViz/plot/utils/_decorator.py +74 -0
  53. AeroViz/plot/utils/_unit.py +55 -0
  54. AeroViz/process/__init__.py +31 -0
  55. AeroViz/process/core/DataProc.py +19 -0
  56. AeroViz/process/core/SizeDist.py +90 -0
  57. AeroViz/process/core/__init__.py +4 -0
  58. AeroViz/process/method/PyMieScatt_update.py +567 -0
  59. AeroViz/process/method/__init__.py +2 -0
  60. AeroViz/process/method/mie_theory.py +258 -0
  61. AeroViz/process/method/prop.py +62 -0
  62. AeroViz/process/script/AbstractDistCalc.py +143 -0
  63. AeroViz/process/script/Chemical.py +176 -0
  64. AeroViz/process/script/IMPACT.py +49 -0
  65. AeroViz/process/script/IMPROVE.py +161 -0
  66. AeroViz/process/script/Others.py +65 -0
  67. AeroViz/process/script/PSD.py +103 -0
  68. AeroViz/process/script/PSD_dry.py +94 -0
  69. AeroViz/process/script/__init__.py +5 -0
  70. AeroViz/process/script/retrieve_RI.py +70 -0
  71. AeroViz/rawDataReader/__init__.py +68 -0
  72. AeroViz/rawDataReader/core/__init__.py +397 -0
  73. AeroViz/rawDataReader/script/AE33.py +31 -0
  74. AeroViz/rawDataReader/script/AE43.py +34 -0
  75. AeroViz/rawDataReader/script/APS_3321.py +47 -0
  76. AeroViz/rawDataReader/script/Aurora.py +38 -0
  77. AeroViz/rawDataReader/script/BC1054.py +46 -0
  78. AeroViz/rawDataReader/script/EPA_vertical.py +18 -0
  79. AeroViz/rawDataReader/script/GRIMM.py +35 -0
  80. AeroViz/rawDataReader/script/IGAC_TH.py +104 -0
  81. AeroViz/rawDataReader/script/IGAC_ZM.py +90 -0
  82. AeroViz/rawDataReader/script/MA350.py +45 -0
  83. AeroViz/rawDataReader/script/NEPH.py +57 -0
  84. AeroViz/rawDataReader/script/OCEC_LCRES.py +34 -0
  85. AeroViz/rawDataReader/script/OCEC_RES.py +28 -0
  86. AeroViz/rawDataReader/script/SMPS_TH.py +41 -0
  87. AeroViz/rawDataReader/script/SMPS_aim11.py +51 -0
  88. AeroViz/rawDataReader/script/SMPS_genr.py +51 -0
  89. AeroViz/rawDataReader/script/TEOM.py +46 -0
  90. AeroViz/rawDataReader/script/Table.py +28 -0
  91. AeroViz/rawDataReader/script/VOC_TH.py +30 -0
  92. AeroViz/rawDataReader/script/VOC_ZM.py +37 -0
  93. AeroViz/rawDataReader/script/__init__.py +22 -0
  94. AeroViz/tools/__init__.py +3 -0
  95. AeroViz/tools/database.py +94 -0
  96. AeroViz/tools/dataclassifier.py +117 -0
  97. AeroViz/tools/datareader.py +66 -0
  98. AeroViz-0.1.0.dist-info/LICENSE +21 -0
  99. AeroViz-0.1.0.dist-info/METADATA +117 -0
  100. AeroViz-0.1.0.dist-info/RECORD +102 -0
  101. AeroViz-0.1.0.dist-info/WHEEL +5 -0
  102. AeroViz-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,245 @@
1
+ from AeroViz.dataProcess.core import _union_index
2
+
3
+ from datetime import datetime as dtm
4
+ from pandas import DataFrame, to_datetime
5
+ # from scipy.interpolate import interp1d
6
+ from scipy.interpolate import UnivariateSpline as unvpline, interp1d
7
+ import numpy as np
8
+
9
+ __all__ = ['merge_SMPS_APS']
10
+
11
+
12
+ def __test_plot(smpsx, smps, apsx, aps, mergex, merge, mergeox, mergeo, _sh):
13
+ from matplotlib.pyplot import subplots, close, show, rcParams
14
+
15
+ ## parameter
16
+ # '''
17
+ ## plot
18
+ fig, ax = subplots()
19
+
20
+ ax.plot(smpsx, smps, c='#ff794c', label='smps', marker='o', lw=2)
21
+ ax.plot(apsx, aps, c='#4c79ff', label='aps', marker='o', lw=2)
22
+ ax.plot(mergex, merge, c='#79796a', label='merge')
23
+ # ax.plot(mergeox,mergeo,c='#111111',label='mergeo',marker='o',lw=.75)
24
+
25
+ ax.set(xscale='log', yscale='log', )
26
+
27
+ ax.legend(framealpha=0, )
28
+ ax.set_title((_sh ** 2)[0], fontsize=13)
29
+
30
+ show()
31
+ close()
32
+
33
+
34
+ # '''
35
+
36
+
37
+ ## Overlap fitting
38
+ ## Create a fitting func. by smps data
39
+ ## return : shift factor
40
+ def _overlap_fitting(_smps_ori, _aps_ori, _smps_lb, _aps_hb):
41
+ print(f"\t\t{dtm.now().strftime('%m/%d %X')} : \033[92moverlap range fitting\033[0m")
42
+
43
+ ## overlap fitting
44
+ ## parmeter
45
+ _dt_indx = _smps_ori.index
46
+
47
+ ## overlap diameter data
48
+ _aps = _aps_ori[_aps_ori.keys()[_aps_ori.keys() < _aps_hb]].copy()
49
+ _smps = _smps_ori[_smps_ori.keys()[_smps_ori.keys() > _smps_lb]].copy()
50
+
51
+ ## use SMPS data apply power law fitting
52
+ ## y = Ax^B, A = e**coefa, B = coefb, x = logx, y = logy
53
+ ## ref : http://mathworld.wolfram.com/LeastSquaresFittingPowerLaw.html
54
+ ## power law fit to SMPS num conc at upper bins to log curve
55
+
56
+ ## coefficient A, B
57
+ _smps_qc_cond = ((_smps != 0) & np.isfinite(_smps))
58
+ _smps_qc = _smps.where(_smps_qc_cond)
59
+
60
+ _size = _smps_qc_cond.sum(axis=1)
61
+ _size = _size.where(_size != 0.).copy()
62
+
63
+ _logx, _logy = np.log(_smps_qc.keys()._data.astype(float)), np.log(_smps_qc)
64
+ _x, _y, _xy, _xx = _logx.sum(), _logy.sum(axis=1), (_logx * _logy).sum(axis=1), (_logx ** 2).sum()
65
+
66
+ _coeB = ((_size * _xy - _x * _y) / (_size * _xx - _x ** 2.))
67
+ _coeA = np.exp((_y - _coeB * _x) / _size).values.reshape(-1, 1)
68
+ _coeB = _coeB.values.reshape(-1, 1)
69
+
70
+ ## rebuild shift smps data by coe. A, B
71
+ ## x_shift = (y_ori/A)**(1/B)
72
+ _aps_shift_x = (_aps / _coeA) ** (1 / _coeB)
73
+ _aps_shift_x = _aps_shift_x.where(np.isfinite(_aps_shift_x))
74
+
75
+ ## the least squares of diameter
76
+ ## the shift factor which the cklosest to 1
77
+ _shift_factor = (_aps_shift_x.keys()._data.astype(float) / _aps_shift_x)
78
+ _shift_factor.columns = range(len(_aps_shift_x.keys()))
79
+
80
+ _dropna_idx = _shift_factor.dropna(how='all').index.copy()
81
+
82
+ ## use the target function to get the similar aps and smps bin
83
+ ## S2 = sum( (smps_fit_line(dia) - aps(dia*shift_factor) )**2 )
84
+ ## assumption : the same diameter between smps and aps should get the same conc.
85
+
86
+ ## be sure they art in log value
87
+ _S2 = DataFrame(index=_aps_shift_x.index)
88
+ _dia_table = DataFrame(np.full(_aps_shift_x.shape, _aps_shift_x.keys()),
89
+ columns=_aps_shift_x.keys(), index=_aps_shift_x.index)
90
+ for _idx, _factor in _shift_factor.items():
91
+ _smps_fit_df = _coeA * (_dia_table / _factor.to_frame().values) ** _coeB
92
+ _S2[_idx] = ((_smps_fit_df - _aps) ** 2).sum(axis=1)
93
+
94
+ _least_squ_idx = _S2.idxmin(axis=1).loc[_dropna_idx]
95
+
96
+ _shift_factor_out = DataFrame(_shift_factor.loc[_dropna_idx].values[range(len(_dropna_idx)), _least_squ_idx.values],
97
+ index=_dropna_idx).reindex(_dt_indx)
98
+
99
+ return _shift_factor_out, (DataFrame(_coeA, index=_dt_indx), DataFrame(_coeB, index=_dt_indx))
100
+
101
+
102
+ ## Remove big shift data ()
103
+ ## Return : aps, smps, shift (without big shift data)
104
+ def _shift_data_process(_shift):
105
+ print(f"\t\t{dtm.now().strftime('%m/%d %X')} : \033[92mshift-data quality control\033[0m")
106
+
107
+ _rho = _shift ** 2
108
+ _shift = _shift.mask((~np.isfinite(_shift)) | (_rho > 2) | (_rho < 0.3))
109
+
110
+ _qc_index = _shift.mask((_rho < 0.6) | (_shift.isna())).dropna().index
111
+
112
+ return _qc_index, _shift
113
+
114
+
115
+ # return _smps.loc[~_big_shift], _aps.loc[~_big_shift], _shift[~_big_shift].reshape(-1,1)
116
+
117
+
118
+ ## Create merge data
119
+ ## shift all smps bin and remove the aps bin which smaller than the latest old smps bin
120
+ ## Return : merge bins, merge data, density
121
+ def _merge_data(_smps_ori, _aps_ori, _shift_ori, _smps_lb, _aps_hb, _coe, _shift_mode):
122
+ print(f"\t\t{dtm.now().strftime('%m/%d %X')} : \033[92mcreate merge data : {_shift_mode}\033[0m")
123
+
124
+ _ori_idx = _smps_ori.index
125
+ _merge_idx = _smps_ori.loc[_aps_ori.dropna(how='all').index].dropna(how='all').index
126
+
127
+ _corr_aps_cond = _aps_ori.keys() < 700
128
+ _corr_aps_ky = _aps_ori.keys()[_corr_aps_cond]
129
+
130
+ _uni_idx, _count = np.unique(np.hstack((_smps_ori.dropna(how='all').index, _aps_ori.dropna(how='all').index,
131
+ _shift_ori.dropna(how='all').index)), return_counts=True)
132
+
133
+ _merge_idx = to_datetime(np.unique(_uni_idx[_count == 3]))
134
+
135
+ _smps, _aps, _shift = _smps_ori.loc[_merge_idx], _aps_ori.loc[_merge_idx], _shift_ori.loc[_merge_idx].values
136
+
137
+ ## parameter
138
+ _coeA, _coeB = _coe[0].loc[_merge_idx], _coe[1].loc[_merge_idx]
139
+ _smps_key, _aps_key = _smps.keys()._data.astype(float), _aps.keys()._data.astype(float)
140
+
141
+ _cntr = 1000
142
+ _bin_lb = _smps_key[-1]
143
+
144
+ ## make shift bins
145
+ _smps_bin = np.full(_smps.shape, _smps_key)
146
+ _aps_bin = np.full(_aps.shape, _aps_key)
147
+
148
+ _std_bin = np.geomspace(_smps_key[0], _aps_key[-1], 230)
149
+ _std_bin_merge = _std_bin[(_std_bin < _cntr) & (_std_bin > _bin_lb)]
150
+ _std_bin_inte1 = _std_bin[_std_bin <= _bin_lb]
151
+ _std_bin_inte2 = _std_bin[_std_bin >= _cntr]
152
+
153
+ if _shift_mode == 'mobility':
154
+ _aps_bin /= _shift
155
+
156
+ elif _shift_mode == 'aerodynamic':
157
+ _smps_bin *= _shift
158
+
159
+ ## merge
160
+ _merge_lst, _corr_lst = [], []
161
+ for _bin_smps, _bin_aps, _dt_smps, _dt_aps, _sh in zip(_smps_bin, _aps_bin, _smps.values, _aps.values, _shift):
162
+ ## keep complete smps bins and data
163
+ ## remove the aps bin data lower than smps bin
164
+ _condi = _bin_aps >= _bin_smps[-1]
165
+
166
+ _merge_bin = np.hstack((_bin_smps, _bin_aps[_condi]))
167
+ _merge_dt = np.hstack((_dt_smps, _dt_aps[_condi]))
168
+
169
+ _merge_fit_loc = (_merge_bin < 1500) & (_merge_bin > _smps_lb)
170
+
171
+ ## coeA and coeB
172
+ _unvpl_fc = unvpline(np.log(_merge_bin[_merge_fit_loc]), np.log(_merge_dt[_merge_fit_loc]), s=50)
173
+ _inte_fc = interp1d(_merge_bin, _merge_dt, kind='linear', fill_value='extrapolate')
174
+
175
+ _merge_dt_fit = np.hstack((_inte_fc(_std_bin_inte1), np.exp(_unvpl_fc(np.log(_std_bin_merge))),
176
+ _inte_fc(_std_bin_inte2)))
177
+
178
+ _merge_lst.append(_merge_dt_fit)
179
+ _corr_lst.append(interp1d(_std_bin, _merge_dt_fit)(_bin_aps[_corr_aps_cond]))
180
+
181
+ _df_merge = DataFrame(_merge_lst, columns=_std_bin, index=_merge_idx)
182
+ _df_merge = _df_merge.mask(_df_merge < 0)
183
+
184
+ _df_corr = DataFrame(_corr_lst, columns=_corr_aps_ky, index=_merge_idx) / _aps_ori.loc[_merge_idx, _corr_aps_ky]
185
+
186
+ ## process output df
187
+ ## average, align with index
188
+ def _out_df(*_df_arg, **_df_kwarg):
189
+ _df = DataFrame(*_df_arg, **_df_kwarg).reindex(_ori_idx)
190
+ _df.index.name = 'time'
191
+ return _df
192
+
193
+ return _out_df(_df_merge), _out_df(_shift_ori ** 2), _out_df(_df_corr)
194
+
195
+
196
+ def merge_SMPS_APS(df_smps, df_aps, aps_unit='um', smps_overlap_lowbound=500, aps_fit_highbound=1000):
197
+ df_smps, df_aps = _union_index(df_smps, df_aps)
198
+
199
+ ## set to the same units
200
+ smps, aps_ori = df_smps.copy(), df_aps.copy()
201
+ smps.columns = smps.keys().to_numpy(float)
202
+ aps_ori.columns = aps_ori.keys().to_numpy(float)
203
+
204
+ if aps_unit == 'um':
205
+ aps_ori.columns = aps_ori.keys() * 1e3
206
+
207
+ den_lst, mer_lst = [], []
208
+ aps_input = aps_ori.loc[:, aps_ori.keys() > 700].copy()
209
+
210
+ for _count in range(2):
211
+
212
+ ## shift infomation, calculate by powerlaw fitting
213
+ shift, coe = _overlap_fitting(smps, aps_input, smps_overlap_lowbound, aps_fit_highbound)
214
+
215
+ ## process data by shift infomation, and average data
216
+ qc_cond, shift = _shift_data_process(shift)
217
+
218
+ ## merge aps and smps
219
+ merge_arg = (smps, aps_ori, shift, smps_overlap_lowbound, aps_fit_highbound, coe)
220
+ merge_data_mob, density, _corr = _merge_data(*merge_arg, 'mobility')
221
+ merge_data_aer, density, _ = _merge_data(*merge_arg, 'aerodynamic')
222
+ density.columns = ['density']
223
+
224
+ if _count == 0:
225
+ corr = _corr.resample('1d').mean().reindex(smps.index).ffill()
226
+ corr = corr.mask(corr < 1, 1)
227
+ aps_ori.loc[:, corr.keys()] *= corr
228
+
229
+ aps_input = aps_ori.copy()
230
+
231
+ ## out
232
+ out_dic = {
233
+ 'data_all': merge_data_mob,
234
+ 'data_qc': merge_data_mob.loc[qc_cond],
235
+ 'data_all_aer': merge_data_aer,
236
+ 'data_qc_aer': merge_data_aer.loc[qc_cond],
237
+ 'density_all': density,
238
+ 'density_qc': density.loc[qc_cond],
239
+ }
240
+
241
+ ## process data
242
+ for _nam, _df in out_dic.items():
243
+ out_dic[_nam] = _df.reindex(smps.index).copy()
244
+
245
+ return out_dic
@@ -0,0 +1,254 @@
1
+ from AeroViz.dataProcess.core import _union_index
2
+
3
+ from datetime import datetime as dtm
4
+ from pandas import DataFrame, to_datetime
5
+ # from scipy.interpolate import interp1d
6
+ from scipy.interpolate import UnivariateSpline as unvpline, interp1d
7
+ import numpy as np
8
+
9
+ __all__ = ['_merge_SMPS_APS']
10
+
11
+
12
+ def __test_plot(smpsx, smps, apsx, aps, mergex, merge, mergeox, mergeo, _sh):
13
+ from matplotlib.pyplot import subplots, close, show, rcParams
14
+
15
+ ## parameter
16
+ # '''
17
+ ## plot
18
+ fig, ax = subplots()
19
+
20
+ ax.plot(smpsx, smps, c='#ff794c', label='smps', marker='o', lw=2)
21
+ ax.plot(apsx, aps, c='#4c79ff', label='aps', marker='o', lw=2)
22
+ ax.plot(mergex, merge, c='#79796a', label='merge')
23
+ # ax.plot(mergeox,mergeo,c='#111111',label='mergeo',marker='o',lw=.75)
24
+
25
+ ax.set(xscale='log', yscale='log', )
26
+
27
+ ax.legend(framealpha=0, )
28
+ ax.set_title((_sh ** 2)[0], fontsize=13)
29
+
30
+ show()
31
+ close()
32
+
33
+
34
+ # '''
35
+
36
+
37
+ ## Overlap fitting
38
+ ## Create a fitting func. by smps data
39
+ ## return : shift factor
40
+ def _overlap_fitting(_smps_ori, _aps_ori, _smps_lb, _aps_hb):
41
+ print(f"\t\t{dtm.now().strftime('%m/%d %X')} : \033[92moverlap range fitting\033[0m")
42
+
43
+ ## overlap fitting
44
+ ## parmeter
45
+ _dt_indx = _smps_ori.index
46
+
47
+ ## overlap diameter data
48
+ _aps = _aps_ori[_aps_ori.keys()[_aps_ori.keys() < _aps_hb]].copy()
49
+ _smps = _smps_ori[_smps_ori.keys()[_smps_ori.keys() > _smps_lb]].copy()
50
+
51
+ ## use SMPS data apply power law fitting
52
+ ## y = Ax^B, A = e**coefa, B = coefb, x = logx, y = logy
53
+ ## ref : http://mathworld.wolfram.com/LeastSquaresFittingPowerLaw.html
54
+ ## power law fit to SMPS num conc at upper bins to log curve
55
+
56
+ ## coefficient A, B
57
+ _smps_qc_cond = ((_smps != 0) & np.isfinite(_smps))
58
+ _smps_qc = _smps.where(_smps_qc_cond)
59
+
60
+ _size = _smps_qc_cond.sum(axis=1)
61
+ _size = _size.where(_size != 0.).copy()
62
+
63
+ _logx, _logy = np.log(_smps_qc.keys()._data.astype(float)), np.log(_smps_qc)
64
+ _x, _y, _xy, _xx = _logx.sum(), _logy.sum(axis=1), (_logx * _logy).sum(axis=1), (_logx ** 2).sum()
65
+
66
+ _coeB = ((_size * _xy - _x * _y) / (_size * _xx - _x ** 2.))
67
+ _coeA = np.exp((_y - _coeB * _x) / _size).values.reshape(-1, 1)
68
+ _coeB = _coeB.values.reshape(-1, 1)
69
+
70
+ ## rebuild shift smps data by coe. A, B
71
+ ## x_shift = (y_ori/A)**(1/B)
72
+ _aps_shift_x = (_aps / _coeA) ** (1 / _coeB)
73
+ _aps_shift_x = _aps_shift_x.where(np.isfinite(_aps_shift_x))
74
+
75
+ ## the least squares of diameter
76
+ ## the shift factor which the cklosest to 1
77
+ _shift_factor = (_aps_shift_x.keys()._data.astype(float) / _aps_shift_x)
78
+ _shift_factor.columns = range(len(_aps_shift_x.keys()))
79
+
80
+ _dropna_idx = _shift_factor.dropna(how='all').index.copy()
81
+
82
+ ## use the target function to get the similar aps and smps bin
83
+ ## S2 = sum( (smps_fit_line(dia) - aps(dia*shift_factor) )**2 )
84
+ ## assumption : the same diameter between smps and aps should get the same conc.
85
+
86
+ ## be sure they art in log value
87
+ _S2 = DataFrame(index=_aps_shift_x.index)
88
+ _dia_table = DataFrame(np.full(_aps_shift_x.shape, _aps_shift_x.keys()),
89
+ columns=_aps_shift_x.keys(), index=_aps_shift_x.index)
90
+ for _idx, _factor in _shift_factor.items():
91
+ _smps_fit_df = _coeA * (_dia_table / _factor.to_frame().values) ** _coeB
92
+ _S2[_idx] = ((_smps_fit_df - _aps) ** 2).sum(axis=1)
93
+
94
+ _least_squ_idx = _S2.idxmin(axis=1).loc[_dropna_idx]
95
+
96
+ _shift_factor_out = DataFrame(_shift_factor.loc[_dropna_idx].values[range(len(_dropna_idx)), _least_squ_idx.values],
97
+ index=_dropna_idx).reindex(_dt_indx)
98
+
99
+ return _shift_factor_out, (DataFrame(_coeA, index=_dt_indx), DataFrame(_coeB, index=_dt_indx))
100
+
101
+
102
+ ## Remove big shift data ()
103
+ ## Return : aps, smps, shift (without big shift data)
104
+ def _shift_data_process(_shift):
105
+ print(f"\t\t{dtm.now().strftime('%m/%d %X')} : \033[92mshift-data quality control\033[0m")
106
+
107
+ _rho = _shift ** 2
108
+ _shift = _shift.mask((~np.isfinite(_shift)) | (_rho > 2.6) | (_rho < 0.3))
109
+
110
+ _qc_index = _shift.mask((_rho < 0.6) | (_shift.isna())).dropna().index
111
+
112
+ return _qc_index, _shift
113
+
114
+
115
+ # return _smps.loc[~_big_shift], _aps.loc[~_big_shift], _shift[~_big_shift].reshape(-1,1)
116
+
117
+
118
+ ## Create merge data
119
+ ## shift all smps bin and remove the aps bin which smaller than the latest old smps bin
120
+ ## Return : merge bins, merge data, density
121
+ def _merge_data(_smps_ori, _aps_ori, _shift_ori, _shift_mode, _smps_lb, _aps_hb, _coe):
122
+ print(f"\t\t{dtm.now().strftime('%m/%d %X')} : \033[92mcreate merge data\033[0m")
123
+
124
+ _ori_idx = _smps_ori.index
125
+ _merge_idx = _smps_ori.loc[_aps_ori.dropna(how='all').index].dropna(how='all').index
126
+
127
+ _uni_idx, _count = np.unique(np.hstack((_smps_ori.dropna(how='all').index, _aps_ori.dropna(how='all').index,
128
+ _shift_ori.dropna(how='all').index)), return_counts=True)
129
+
130
+ _merge_idx = to_datetime(np.unique(_uni_idx[_count == 3]))
131
+
132
+ _smps, _aps, _shift = _smps_ori.loc[_merge_idx], _aps_ori.loc[_merge_idx], _shift_ori.loc[_merge_idx].values
133
+
134
+ ## parameter
135
+ _coeA, _coeB = _coe[0].loc[_merge_idx], _coe[1].loc[_merge_idx]
136
+ _smps_key, _aps_key = _smps.keys()._data.astype(float), _aps.keys()._data.astype(float)
137
+
138
+ _test = 1000
139
+
140
+ # _cntr = (_smps_lb+_aps_hb)/2
141
+ _cntr = _test
142
+ _bin_lb = _smps_key[-1]
143
+
144
+ ## make shift bins
145
+ _smps_bin = np.full(_smps.shape, _smps_key)
146
+ _aps_bin = np.full(_aps.shape, _aps_key)
147
+ # _std_bin = _smps_key.tolist()+_aps_key[_aps_key>_smps_key[-1]].tolist()
148
+ _std_bin = np.geomspace(_smps_key[0], _aps_key[-1], 230)
149
+ _std_bin_merge = _std_bin[(_std_bin < _cntr) & (_std_bin > _bin_lb)]
150
+ _std_bin_inte1 = _std_bin[_std_bin <= _bin_lb]
151
+ _std_bin_inte2 = _std_bin[_std_bin >= _cntr]
152
+
153
+ if _shift_mode == 'mobility':
154
+ _aps_bin /= _shift
155
+
156
+ elif _shift_mode == 'aerodynamic':
157
+ _smps_bin *= _shift
158
+
159
+ ## merge
160
+ _merge_lst = []
161
+ for _bin_smps, _bin_aps, _dt_smps, _dt_aps, _sh in zip(_smps_bin, _aps_bin, _smps.values, _aps.values, _shift):
162
+ ## remove
163
+
164
+ ## keep complete smps bins and data
165
+ ## remove the aps bin data lower than smps bin
166
+ _condi = _bin_aps >= _bin_smps[-1]
167
+
168
+ _merge_bin = np.hstack((_bin_smps, _bin_aps[_condi]))
169
+ _merge_dt = np.hstack((_dt_smps, _dt_aps[_condi]))
170
+
171
+ # _merge_fit_loc = (_merge_bin<_aps_hb)&(_merge_bin>_smps_lb)
172
+ _merge_fit_loc = (_merge_bin < 1500) & (_merge_bin > _smps_lb)
173
+
174
+ ## coeA and coeB
175
+ _unvpl_fc = unvpline(np.log(_merge_bin[_merge_fit_loc]), np.log(_merge_dt[_merge_fit_loc]), s=50)
176
+ # _unvpl_fc = unvpline(_merge_bin[_merge_fit_loc],_merge_dt[_merge_fit_loc],s=150)
177
+ # _inte_log_fc = interp1d(n.log10(_merge_bin[_merge_fit_loc]),n.log10(_merge_dt[_merge_fit_loc]),
178
+ # kind='linear',fill_value='extrapolate')
179
+ _inte_fc = interp1d(_merge_bin, _merge_dt, kind='linear', fill_value='extrapolate')
180
+
181
+ __merge = np.exp(_unvpl_fc(np.log(_std_bin_merge)))
182
+ # __merge = _unvpl_fc(_std_bin_merge)
183
+
184
+ _merge_dt_fit = np.hstack((_inte_fc(_std_bin_inte1), __merge, _inte_fc(_std_bin_inte2)))
185
+ # _merge_dt_fit = __merge
186
+ # __test_plot(_bin_smps,_dt_smps,_bin_aps,_dt_aps,_std_bin,_merge_dt_fit,_merge_bin,_merge_dt,_sh)
187
+
188
+ _merge_lst.append(_merge_dt_fit)
189
+
190
+ _df_merge = DataFrame(_merge_lst, columns=_std_bin, index=_merge_idx)
191
+ _df_merge = _df_merge.mask(_df_merge < 0)
192
+
193
+ ## process output df
194
+ ## average, align with index
195
+ def _out_df(*_df_arg, **_df_kwarg):
196
+ _df = DataFrame(*_df_arg, **_df_kwarg).reindex(_ori_idx)
197
+ _df.index.name = 'time'
198
+ return _df
199
+
200
+ return _out_df(_df_merge), _out_df(_shift_ori ** 2)
201
+
202
+
203
+ ## aps_fit_highbound : the diameter I choose randomly
204
+ def _merge_SMPS_APS(df_smps, df_aps, aps_unit, shift_mode, smps_overlap_lowbound, aps_fit_highbound):
205
+ df_smps, df_aps = _union_index(df_smps, df_aps)
206
+
207
+ # print(f'\nMerge data :')
208
+ # print(f' APS fittint higher diameter : {aps_fit_highbound:4d} nm')
209
+ # print(f' SMPS overlap lower diameter : {smps_overlap_lowbound:4d} nm')
210
+ # print(f' Average time : {self.data_freq:>4s}\n')
211
+
212
+ ## get data, remove 'total' and 'mode'
213
+ ## set to the same units
214
+ smps, aps = df_smps, df_aps
215
+ smps.columns = smps.keys().to_numpy(float)
216
+ aps.columns = aps.keys().to_numpy(float)
217
+
218
+ if aps_unit == 'um':
219
+ aps.columns = aps.keys() * 1e3
220
+
221
+ ## shift infomation, calculate by powerlaw fitting
222
+ shift, coe = _overlap_fitting(smps, aps, smps_overlap_lowbound, aps_fit_highbound)
223
+
224
+ ## process data by shift infomation, and average data
225
+ qc_cond, shift = _shift_data_process(shift)
226
+
227
+ ## merge aps and smps..
228
+ merge_data, density = _merge_data(smps, aps, shift, shift_mode, smps_overlap_lowbound, aps_fit_highbound, coe)
229
+ density.columns = ['density']
230
+
231
+ ## add total and mode
232
+ # merge_total = merge_data.sum(axis=1,min_count=1).copy()
233
+ # merge_mode = merge_data.idxmax(axis=1).astype(float).copy()
234
+
235
+ # merge_data['total'] = merge_total
236
+ # merge_data['mode'] = merge_mode
237
+
238
+ ## out
239
+ out_dic = {
240
+ 'data_all': merge_data,
241
+ 'data_qc': merge_data.loc[qc_cond],
242
+ 'density_all': density,
243
+ 'density_qc': density.loc[qc_cond],
244
+ }
245
+
246
+ ## process data
247
+
248
+ for _nam, _df in out_dic.items():
249
+ out_dic[_nam] = _df.reindex(df_aps.index).copy()
250
+
251
+ # merge_data = merge_data.reindex(df_aps.index)
252
+ # density = density.reindex(df_aps.index)
253
+
254
+ return out_dic