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,62 @@
1
+ from ..core import _writter, _run_process
2
+
3
+ __all__ = [
4
+
5
+ 'Optical',
6
+
7
+ ]
8
+
9
+
10
+ class Optical(_writter):
11
+
12
+ ## scatter
13
+ @_run_process('Optical - SAE', 'SAE')
14
+ def SAE(self, df_sca):
15
+ from ._scattering import _SAE
16
+
17
+ out = _SAE(df_sca)
18
+
19
+ return self, out
20
+
21
+ ## absorption
22
+ @_run_process('Optical - absCoe', 'absCoe')
23
+ def absCoe(self, df_ae33, abs_band=[550]):
24
+ from ._absorption import _absCoe
25
+
26
+ out = _absCoe(df_ae33, abs_band)
27
+
28
+ return self, out
29
+
30
+ @_run_process('Optical - AAE', 'AAE')
31
+ def AAE(self, df_abs):
32
+ from ._absorption import _AAE
33
+
34
+ out = _AAE(df_abs)
35
+
36
+ return self, out
37
+
38
+ ## extinction
39
+ @_run_process('Optical - basic', 'opt_basic')
40
+ def basic(self, df_abs, df_sca, df_ec=None, df_mass=None, df_no2=None):
41
+ from ._extinction import _basic
42
+
43
+ out = _basic(df_abs, df_sca, df_ec, df_mass, df_no2)
44
+
45
+ return self, out
46
+
47
+ @_run_process('Optical - Mie', 'Mie')
48
+ def Mie(self, df_psd, df_m, wave_length=550):
49
+ from ._mie import _mie
50
+
51
+ out = _mie(df_psd, df_m, wave_length)
52
+
53
+ return self, out
54
+
55
+ @_run_process('Optical - IMPROVE', 'IMPROVE')
56
+ def IMPROVE(self, df_mass, df_RH, method='revised'):
57
+ # _fc = __import__(f'_IMPROVE._{method}')
58
+ from ._IMPROVE import _revised
59
+
60
+ out = _revised(df_mass, df_RH)
61
+
62
+ return self, out
@@ -0,0 +1,54 @@
1
+ def _absCoe(df, abs_band):
2
+ import numpy as n
3
+ from scipy.optimize import curve_fit
4
+
5
+ band = n.array([370, 470, 520, 590, 660, 880, 950])
6
+
7
+ df_out = {}
8
+
9
+ def _get_slope(__df):
10
+ func = lambda _x, _sl, _int: _sl * _x + _int
11
+ popt, pcov = curve_fit(func, band, __df.values)
12
+
13
+ return func(n.array(abs_band), *popt)
14
+
15
+ MAE = n.array([18.47, 14.54, 13.14, 11.58, 10.35, 7.77, 7.19]) * 1e-3
16
+ df_abs = (df.copy() * MAE).dropna().copy()
17
+
18
+ df_out = df_abs.apply(_get_slope, axis=1, result_type='expand').reindex(df.index)
19
+ df_out.columns = [f'abs_{_band}' for _band in abs_band]
20
+
21
+ df_out['eBC'] = df['BC6']
22
+
23
+ return df_out
24
+
25
+
26
+ def _AAE(df):
27
+ import numpy as n
28
+ from scipy.optimize import curve_fit
29
+
30
+ def _AAEcalc(_df):
31
+ ## parameter
32
+ MAE = n.array([18.47, 14.54, 13.14, 11.58, 10.35, 7.77, 7.19]) * 1e-3
33
+ band = n.array([370, 470, 520, 590, 660, 880, 950])
34
+ _df *= MAE
35
+
36
+ ## 7 pts fitting
37
+ ## function
38
+ def _get_slope(__df):
39
+ func = lambda _x, _sl, _int: _sl * _x + _int
40
+ popt, pcov = curve_fit(func, n.log(band), n.log(__df))
41
+
42
+ return popt
43
+
44
+ ## calculate
45
+ _AAE = _df.apply(_get_slope, axis=1, result_type='expand')
46
+ _AAE.columns = ['slope', 'intercept']
47
+
48
+ return _AAE
49
+
50
+ df_out = _AAEcalc(df[['BC1', 'BC2', 'BC3', 'BC4', 'BC5', 'BC6', 'BC7']].dropna())
51
+ df_out = df_out.mask((-df_out.slope < 0.8) | (-df_out.slope > 2.)).copy()
52
+
53
+ df_out['eBC'] = df['BC6']
54
+ return df_out.reindex(df.index)
@@ -0,0 +1,36 @@
1
+ from AeroViz.dataProcess.core import _union_index
2
+ from pandas import DataFrame
3
+
4
+
5
+ def _basic(df_abs, df_sca, df_ec, df_mass, df_no2):
6
+ df_abs, df_sca, df_ec, df_mass, df_no2 = _union_index(df_abs, df_sca, df_ec, df_mass, df_no2)
7
+
8
+ df_out = DataFrame()
9
+
10
+ ## abs and sca coe
11
+ df_out['abs'] = df_abs.copy()
12
+ df_out['sca'] = df_sca.copy()
13
+
14
+ ## extinction coe.
15
+ df_out['ext'] = df_out['abs'] + df_out['sca']
16
+
17
+ ## SSA
18
+ df_out['SSA'] = df_out['sca'] / df_out['ext']
19
+
20
+ ## MAE, MSE, MEE
21
+ if df_mass is not None:
22
+ df_out['MAE'] = df_out['abs'] / df_mass
23
+ df_out['MSE'] = df_out['sca'] / df_mass
24
+ df_out['MEE'] = df_out['MSE'] + df_out['MAE']
25
+
26
+ ## gas absorbtion
27
+ if df_no2 is not None:
28
+ df_out['abs_gas'] = df_no2 * .33
29
+ df_out['sca_gas'] = 10
30
+ df_out['ext_all'] = df_out['ext'] + df_out['abs_gas'] + df_out['sca_gas']
31
+
32
+ ## other
33
+ if df_ec is not None:
34
+ df_out['eBC'] = df_ec / 1e3
35
+
36
+ return df_out
@@ -0,0 +1,16 @@
1
+ # from PyMieScatt import Mie_SD
2
+ # from PyMieScatt import Mie_SD
3
+
4
+ from ._mie_sd import Mie_SD
5
+ from pandas import date_range, concat, DataFrame, to_numeric
6
+
7
+
8
+ def _mie(_psd_ori, _RI_ori, _wave):
9
+ _ori_idx = _psd_ori.index.copy()
10
+ _cal_idx = _psd_ori.loc[_RI_ori.dropna().index].dropna(how='all').index
11
+
12
+ _psd, _RI = _psd_ori.loc[_cal_idx], _RI_ori.loc[_cal_idx]
13
+
14
+ _out = Mie_SD(_RI.values, 550, _psd)
15
+
16
+ return _out.reindex(_ori_idx)
@@ -0,0 +1,143 @@
1
+ # -*- coding: utf-8 -*-
2
+ # http://pymiescatt.readthedocs.io/en/latest/forward.html
3
+ import numpy as np
4
+ from scipy.integrate import trapezoid
5
+ from scipy.special import jv, yv
6
+ import warnings
7
+ from pandas import date_range, concat, DataFrame, to_numeric, to_datetime, Series
8
+
9
+
10
+ def coerceDType(d):
11
+ if type(d) is not np.ndarray:
12
+ return np.array(d)
13
+ else:
14
+ return d
15
+
16
+
17
+ def Mie_ab(m, x, nmax, df_n):
18
+ nu = df_n.copy() + 0.5
19
+ n1 = 2 * df_n.copy() + 1
20
+
21
+ sx = np.sqrt(0.5 * np.pi * x)
22
+ px = sx.reshape(-1, 1) * jv(nu, x.reshape(-1, 1))
23
+ chx = -sx.reshape(-1, 1) * yv(nu, x.reshape(-1, 1))
24
+
25
+ p1x = concat([DataFrame(np.sin(x)), px.mask(df_n == nmax.reshape(-1, 1))], axis=1)
26
+ p1x.columns = np.arange(len(p1x.keys()))
27
+ p1x = p1x[df_n.keys()]
28
+
29
+ ch1x = concat([DataFrame(np.cos(x)), chx.mask(df_n == nmax.reshape(-1, 1))], axis=1)
30
+ ch1x.columns = np.arange(len(ch1x.keys()))
31
+ ch1x = ch1x[df_n.keys()]
32
+
33
+ gsx = px - (0 + 1j) * chx
34
+ gs1x = p1x - (0 + 1j) * ch1x
35
+
36
+ mx = m.reshape(-1, 1) * x
37
+ nmx = np.round(np.max(np.hstack([[nmax] * m.size, np.abs(mx)]).reshape(m.size, 2, -1), axis=1) + 16)
38
+
39
+ df_qext = DataFrame(columns=m, index=df_n.index)
40
+ df_qsca = DataFrame(columns=m, index=df_n.index)
41
+
42
+ df_n /= x.reshape(-1, 1)
43
+ for _bin_idx, (_nmx_ary, _mx, _nmax) in enumerate(zip(nmx.T, mx.T, nmax)):
44
+
45
+ df_D = DataFrame(np.nan, index=np.arange(m.size), columns=df_n.keys())
46
+
47
+ Dn_lst = []
48
+ for _nmx, _uni_idx in DataFrame(_nmx_ary).groupby(0).groups.items():
49
+
50
+ _inv_mx = 1 / _mx[_uni_idx]
51
+
52
+ Dn = np.zeros((_uni_idx.size, int(_nmx)), dtype=complex)
53
+ for _idx in range(int(_nmx) - 1, 1, -1):
54
+ Dn[:, _idx - 1] = (_idx * _inv_mx) - (1 / (Dn[:, _idx] + _idx * _inv_mx))
55
+
56
+ Dn_lst.append(Dn[:, 1: int(_nmax) + 1])
57
+ df_D.loc[_uni_idx, 0: int(_nmax) - 1] = Dn[:, 1: int(_nmax) + 1]
58
+
59
+ ## other parameter
60
+ _df_n, _px, _p1x, _gsx, _gs1x, _n1 = df_n.loc[_bin_idx], px.loc[_bin_idx], p1x.loc[_bin_idx], gsx.loc[_bin_idx], \
61
+ gs1x.loc[_bin_idx], n1.loc[_bin_idx].values
62
+
63
+ _da = df_D / m.reshape(-1, 1) + _df_n
64
+ _db = df_D * m.reshape(-1, 1) + _df_n
65
+
66
+ _an = (_da * _px - _p1x) / (_da * _gsx - _gs1x)
67
+ _bn = (_db * _px - _p1x) / (_db * _gsx - _gs1x)
68
+
69
+ _real_an, _real_bn = np.real(_an), np.real(_bn)
70
+ _imag_an, _imag_bn = np.imag(_an), np.imag(_bn)
71
+
72
+ _pr_qext = np.nansum(_n1 * (_real_an + _real_bn), axis=1)
73
+ _pr_qsca = np.nansum(_n1 * (_real_an ** 2 + _real_bn ** 2 + _imag_an ** 2 + _imag_bn ** 2), axis=1)
74
+
75
+ df_qext.loc[_bin_idx] = _pr_qext
76
+ df_qsca.loc[_bin_idx] = _pr_qsca
77
+
78
+ return df_qext, df_qsca
79
+
80
+
81
+ def MieQ(m_ary, wavelength, diameter):
82
+ # http://pymiescatt.readthedocs.io/en/latest/forward.html#MieQ
83
+
84
+ x = np.pi * diameter / wavelength
85
+
86
+ nmax = np.round(2 + x + 4 * (x ** (1 / 3)))
87
+
88
+ df_n = DataFrame([np.arange(1, nmax.max() + 1)] * nmax.size)
89
+ df_n = df_n.mask(df_n > nmax.reshape(-1, 1))
90
+
91
+ n1 = 2 * df_n + 1
92
+ n2 = df_n * (df_n + 2) / (df_n + 1)
93
+ n3 = n1 / (df_n * (df_n + 1))
94
+ x2 = x ** 2
95
+
96
+ _qext, _qsca = Mie_ab(m_ary, x, nmax, df_n)
97
+
98
+ qext = (2 / x2).reshape(-1, 1) * _qext
99
+ qsca = (2 / x2).reshape(-1, 1) * _qsca
100
+
101
+ # return qext.astype(float).values.T, qsca.astype(float).values.T,
102
+ return qext.values.T.astype(float), qsca.values.T.astype(float)
103
+
104
+
105
+ def Mie_SD(m_ary, wavelength, psd, multp_m_in1psd=False, dt_chunk_size=10, q_table=False):
106
+ m_ary = coerceDType(m_ary)
107
+ if type(psd) is not DataFrame:
108
+ psd = DataFrame(psd).T
109
+
110
+ if (len(m_ary) != len(psd)) & ~multp_m_in1psd:
111
+ raise ValueError('"m array" size should be same as "psd" size')
112
+
113
+ dp = psd.keys().values
114
+ ndp = psd.values
115
+ aSDn = np.pi * ((dp / 2) ** 2) * ndp * 1e-6
116
+
117
+ if q_table:
118
+ qext, qsca = q_table
119
+ else:
120
+ qext, qsca = MieQ(m_ary, wavelength, dp)
121
+
122
+ if multp_m_in1psd:
123
+ # print('\tcalculate ext')
124
+
125
+ aSDn_all = np.repeat(aSDn, m_ary.size, axis=0).reshape(len(aSDn), m_ary.size, -1)
126
+
127
+ qext_all = np.repeat(qext[np.newaxis, :, :], len(aSDn), axis=0).reshape(*aSDn_all.shape)
128
+ qsca_all = np.repeat(qsca[np.newaxis, :, :], len(aSDn), axis=0).reshape(*aSDn_all.shape)
129
+
130
+ df_ext = DataFrame(trapezoid(aSDn_all * qext_all), columns=m_ary, index=psd.index).astype(float)
131
+ df_sca = DataFrame(trapezoid(aSDn_all * qsca_all), columns=m_ary, index=psd.index).astype(float)
132
+ df_abs = df_ext - df_sca
133
+ # print('\tdone')
134
+
135
+ return dict(ext=df_ext, sca=df_sca, abs=df_abs)
136
+
137
+ else:
138
+ df_out = DataFrame(index=psd.index)
139
+ df_out['ext'] = trapezoid(qext * aSDn).astype(float)
140
+ df_out['sca'] = trapezoid(qsca * aSDn).astype(float)
141
+ df_out['abs'] = df_out['ext'] - df_out['sca']
142
+
143
+ return df_out
@@ -0,0 +1,30 @@
1
+ import numpy as np
2
+ from scipy.optimize import curve_fit
3
+
4
+ __all__ = [
5
+ '_SAE',
6
+ ]
7
+
8
+
9
+ def _SAE(df):
10
+ def _SAEcalc(_df):
11
+ ## parameter
12
+ band = np.array([450, 550, 700]) * 1e-3
13
+
14
+ ## 3 pts fitting
15
+ ## function
16
+ def _get_slope(__df):
17
+ func = lambda _x, _sl, _int: _sl * _x + _int
18
+ popt, pcov = curve_fit(func, np.log(band), np.log(__df))
19
+
20
+ return popt
21
+
22
+ ## calculate
23
+ _SAE = _df.apply(_get_slope, axis=1, result_type='expand')
24
+ _SAE.columns = ['slope', 'intercept']
25
+
26
+ return _SAE
27
+
28
+ df_out = _SAEcalc(df[['B', 'G', 'R']].dropna())
29
+
30
+ return df_out.reindex(df.index)
@@ -0,0 +1,61 @@
1
+ from ..core import _writter, _run_process
2
+
3
+ __all__ = [
4
+
5
+ 'SizeDistr',
6
+
7
+ ]
8
+
9
+
10
+ class SizeDistr(_writter):
11
+
12
+ ## basic
13
+ @_run_process('SizeDistr - basic', 'distr_basic')
14
+ def basic(self, df, hybrid_bin_start_loc=None, unit='nm', bin_range=(0, 20000), input_type='norm'):
15
+ from ._size_distr import _basic
16
+
17
+ out = _basic(df, hybrid_bin_start_loc, unit, bin_range, input_type)
18
+
19
+ return self, out
20
+
21
+ ## merge
22
+ @_run_process('SizeDistr - merge_SMPS_APS_v4', 'distr_merge')
23
+ def merge_SMPS_APS_v4(self, df_smps, df_aps, df_pm25, aps_unit='um',
24
+ smps_overlap_lowbound=500, aps_fit_highbound=1000, dndsdv_alg=True,
25
+ times_range=(0.8, 1.25, .05)):
26
+ from ._merge_v4 import merge_SMPS_APS
27
+
28
+ out = merge_SMPS_APS(df_smps, df_aps, df_pm25, aps_unit, smps_overlap_lowbound, aps_fit_highbound, dndsdv_alg,
29
+ times_range)
30
+
31
+ return self, out
32
+
33
+ ## merge
34
+ @_run_process('SizeDistr - merge_SMPS_APS_v3', 'distr_merge')
35
+ def merge_SMPS_APS_v3(self, df_smps, df_aps, aps_unit='um',
36
+ smps_overlap_lowbound=500, aps_fit_highbound=1000, dndsdv_alg=True):
37
+ from ._merge_v3 import merge_SMPS_APS
38
+
39
+ out = merge_SMPS_APS(df_smps, df_aps, aps_unit, smps_overlap_lowbound, aps_fit_highbound, dndsdv_alg)
40
+
41
+ return self, out
42
+
43
+ ## merge
44
+ @_run_process('SizeDistr - merge_SMPS_APS_v2', 'distr_merge')
45
+ def merge_SMPS_APS_v2(self, df_smps, df_aps, aps_unit='um',
46
+ smps_overlap_lowbound=500, aps_fit_highbound=1000):
47
+ from ._merge_v2 import merge_SMPS_APS
48
+
49
+ out = merge_SMPS_APS(df_smps, df_aps, aps_unit, smps_overlap_lowbound, aps_fit_highbound)
50
+
51
+ return self, out
52
+
53
+ ## merge
54
+ @_run_process('SizeDistr - merge_SMPS_APS_v1', 'distr_merge')
55
+ def merge_SMPS_APS(self, df_smps, df_aps, aps_unit='um', shift_mode='mobility',
56
+ smps_overlap_lowbound=523, aps_fit_highbound=800):
57
+ from ._merge_v1 import _merge_SMPS_APS
58
+
59
+ out = _merge_SMPS_APS(df_smps, df_aps, aps_unit, shift_mode, smps_overlap_lowbound, aps_fit_highbound)
60
+
61
+ return self, out
@@ -0,0 +1,250 @@
1
+ from datetime import datetime as dtm
2
+ from pandas import DataFrame, to_datetime
3
+ # from scipy.interpolate import interp1d
4
+ from scipy.interpolate import UnivariateSpline as unvpline, interp1d
5
+ import numpy as np
6
+
7
+ __all__ = ['_merge_SMPS_APS']
8
+
9
+
10
+ def __test_plot(smpsx, smps, apsx, aps, mergex, merge, mergeox, mergeo, _sh):
11
+ from matplotlib.pyplot import subplots, close, show, rcParams
12
+
13
+ ## parameter
14
+ # '''
15
+ ## plot
16
+ fig, ax = subplots()
17
+
18
+ ax.plot(smpsx, smps, c='#ff794c', label='smps', marker='o', lw=2)
19
+ ax.plot(apsx, aps, c='#4c79ff', label='aps', marker='o', lw=2)
20
+ ax.plot(mergex, merge, c='#79796a', label='merge')
21
+ # ax.plot(mergeox,mergeo,c='#111111',label='mergeo',marker='o',lw=.75)
22
+
23
+ ax.set(xscale='log', yscale='log', )
24
+
25
+ ax.legend(framealpha=0, )
26
+ ax.set_title((_sh ** 2)[0], fontsize=13)
27
+
28
+ show()
29
+ close()
30
+
31
+
32
+ # '''
33
+
34
+
35
+ ## Overlap fitting
36
+ ## Create a fitting func. by smps data
37
+ ## return : shift factor
38
+ def _overlap_fitting(_smps_ori, _aps_ori, _smps_lb, _aps_hb):
39
+ print(f"\t\t{dtm.now().strftime('%m/%d %X')} : \033[92moverlap range fitting\033[0m")
40
+
41
+ ## overlap fitting
42
+ ## parmeter
43
+ _dt_indx = _smps_ori.index
44
+
45
+ ## overlap diameter data
46
+ _aps = _aps_ori[_aps_ori.keys()[_aps_ori.keys() < _aps_hb]].copy()
47
+ _smps = _smps_ori[_smps_ori.keys()[_smps_ori.keys() > _smps_lb]].copy()
48
+
49
+ ## use SMPS data apply power law fitting
50
+ ## y = Ax^B, A = e**coefa, B = coefb, x = logx, y = logy
51
+ ## ref : http://mathworld.wolfram.com/LeastSquaresFittingPowerLaw.html
52
+ ## power law fit to SMPS num conc at upper bins to log curve
53
+
54
+ ## coefficient A, B
55
+ _smps_qc_cond = ((_smps != 0) & np.isfinite(_smps))
56
+ _smps_qc = _smps.where(_smps_qc_cond)
57
+
58
+ _size = _smps_qc_cond.sum(axis=1)
59
+ _size = _size.where(_size != 0.).copy()
60
+
61
+ _logx, _logy = np.log(_smps_qc.keys()._data.astype(float)), np.log(_smps_qc)
62
+ _x, _y, _xy, _xx = _logx.sum(), _logy.sum(axis=1), (_logx * _logy).sum(axis=1), (_logx ** 2).sum()
63
+
64
+ _coeB = ((_size * _xy - _x * _y) / (_size * _xx - _x ** 2.))
65
+ _coeA = np.exp((_y - _coeB * _x) / _size).values.reshape(-1, 1)
66
+ _coeB = _coeB.values.reshape(-1, 1)
67
+
68
+ ## rebuild shift smps data by coe. A, B
69
+ ## x_shift = (y_ori/A)**(1/B)
70
+ _aps_shift_x = (_aps / _coeA) ** (1 / _coeB)
71
+ _aps_shift_x = _aps_shift_x.where(np.isfinite(_aps_shift_x))
72
+
73
+ ## the least squares of diameter
74
+ ## the shift factor which the cklosest to 1
75
+ _shift_factor = (_aps_shift_x.keys()._data.astype(float) / _aps_shift_x)
76
+ _shift_factor.columns = range(len(_aps_shift_x.keys()))
77
+
78
+ _dropna_idx = _shift_factor.dropna(how='all').index.copy()
79
+
80
+ ## use the target function to get the similar aps and smps bin
81
+ ## S2 = sum( (smps_fit_line(dia) - aps(dia*shift_factor) )**2 )
82
+ ## assumption : the same diameter between smps and aps should get the same conc.
83
+
84
+ ## be sure they art in log value
85
+ _S2 = DataFrame(index=_aps_shift_x.index)
86
+ _dia_table = DataFrame(np.full(_aps_shift_x.shape, _aps_shift_x.keys()),
87
+ columns=_aps_shift_x.keys(), index=_aps_shift_x.index)
88
+ for _idx, _factor in _shift_factor.items():
89
+ _smps_fit_df = _coeA * (_dia_table / _factor.to_frame().values) ** _coeB
90
+ _S2[_idx] = ((_smps_fit_df - _aps) ** 2).sum(axis=1)
91
+
92
+ _least_squ_idx = _S2.idxmin(axis=1).loc[_dropna_idx]
93
+
94
+ _shift_factor_out = DataFrame(_shift_factor.loc[_dropna_idx].values[range(len(_dropna_idx)), _least_squ_idx.values],
95
+ index=_dropna_idx).reindex(_dt_indx)
96
+
97
+ return _shift_factor_out, (DataFrame(_coeA, index=_dt_indx), DataFrame(_coeB, index=_dt_indx))
98
+
99
+
100
+ ## Remove big shift data ()
101
+ ## Return : aps, smps, shift (without big shift data)
102
+ def _shift_data_process(_shift):
103
+ print(f"\t\t{dtm.now().strftime('%m/%d %X')} : \033[92mshift-data quality control\033[0m")
104
+
105
+ _rho = _shift ** 2
106
+ _shift = _shift.mask((~np.isfinite(_shift)) | (_rho > 2) | (_rho < 0.3))
107
+
108
+ _qc_index = _shift.mask((_rho < 0.6) | (_shift.isna())).dropna().index
109
+
110
+ return _qc_index, _shift
111
+
112
+
113
+ # return _smps.loc[~_big_shift], _aps.loc[~_big_shift], _shift[~_big_shift].reshape(-1,1)
114
+
115
+
116
+ ## Create merge data
117
+ ## shift all smps bin and remove the aps bin which smaller than the latest old smps bin
118
+ ## Return : merge bins, merge data, density
119
+ def _merge_data(_smps_ori, _aps_ori, _shift_ori, _shift_mode, _smps_lb, _aps_hb, _coe):
120
+ print(f"\t\t{dtm.now().strftime('%m/%d %X')} : \033[92mcreate merge data\033[0m")
121
+
122
+ _ori_idx = _smps_ori.index
123
+ _merge_idx = _smps_ori.loc[_aps_ori.dropna(how='all').index].dropna(how='all').index
124
+
125
+ _uni_idx, _count = np.unique(np.hstack((_smps_ori.dropna(how='all').index, _aps_ori.dropna(how='all').index,
126
+ _shift_ori.dropna(how='all').index)), return_counts=True)
127
+
128
+ _merge_idx = to_datetime(np.unique(_uni_idx[_count == 3]))
129
+
130
+ _smps, _aps, _shift = _smps_ori.loc[_merge_idx], _aps_ori.loc[_merge_idx], _shift_ori.loc[_merge_idx].values
131
+
132
+ ## parameter
133
+ _coeA, _coeB = _coe[0].loc[_merge_idx], _coe[1].loc[_merge_idx]
134
+ _smps_key, _aps_key = _smps.keys()._data.astype(float), _aps.keys()._data.astype(float)
135
+
136
+ _test = 1000
137
+
138
+ # _cntr = (_smps_lb+_aps_hb)/2
139
+ _cntr = _test
140
+ _bin_lb = _smps_key[-1]
141
+
142
+ ## make shift bins
143
+ _smps_bin = np.full(_smps.shape, _smps_key)
144
+ _aps_bin = np.full(_aps.shape, _aps_key)
145
+ # _std_bin = _smps_key.tolist()+_aps_key[_aps_key>_smps_key[-1]].tolist()
146
+ _std_bin = np.geomspace(_smps_key[0], _aps_key[-1], 230)
147
+ _std_bin_merge = _std_bin[(_std_bin < _cntr) & (_std_bin > _bin_lb)]
148
+ _std_bin_inte1 = _std_bin[_std_bin <= _bin_lb]
149
+ _std_bin_inte2 = _std_bin[_std_bin >= _cntr]
150
+
151
+ if _shift_mode == 'mobility':
152
+ _aps_bin /= _shift
153
+
154
+ elif _shift_mode == 'aerodynamic':
155
+ _smps_bin *= _shift
156
+
157
+ ## merge
158
+ _merge_lst = []
159
+ for _bin_smps, _bin_aps, _dt_smps, _dt_aps, _sh in zip(_smps_bin, _aps_bin, _smps.values, _aps.values, _shift):
160
+ ## remove
161
+
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<_aps_hb)&(_merge_bin>_smps_lb)
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
+ # _unvpl_fc = unvpline(_merge_bin[_merge_fit_loc],_merge_dt[_merge_fit_loc],s=150)
175
+ # _inte_log_fc = interp1d(n.log10(_merge_bin[_merge_fit_loc]),n.log10(_merge_dt[_merge_fit_loc]),
176
+ # kind='linear',fill_value='extrapolate')
177
+ _inte_fc = interp1d(_merge_bin, _merge_dt, kind='linear', fill_value='extrapolate')
178
+
179
+ __merge = np.exp(_unvpl_fc(np.log(_std_bin_merge)))
180
+ # __merge = _unvpl_fc(_std_bin_merge)
181
+
182
+ _merge_dt_fit = np.hstack((_inte_fc(_std_bin_inte1), __merge, _inte_fc(_std_bin_inte2)))
183
+ # _merge_dt_fit = __merge
184
+ # __test_plot(_bin_smps,_dt_smps,_bin_aps,_dt_aps,_std_bin,_merge_dt_fit,_merge_bin,_merge_dt,_sh)
185
+
186
+ _merge_lst.append(_merge_dt_fit)
187
+
188
+ _df_merge = DataFrame(_merge_lst, columns=_std_bin, index=_merge_idx)
189
+ _df_merge = _df_merge.mask(_df_merge < 0)
190
+
191
+ ## process output df
192
+ ## average, align with index
193
+ def _out_df(*_df_arg, **_df_kwarg):
194
+ _df = DataFrame(*_df_arg, **_df_kwarg).reindex(_ori_idx)
195
+ _df.index.name = 'time'
196
+ return _df
197
+
198
+ return _out_df(_df_merge), _out_df(_shift_ori ** 2)
199
+
200
+
201
+ ## aps_fit_highbound : the diameter I choose randomly
202
+ def _merge_SMPS_APS(df_smps, df_aps, aps_unit, shift_mode, smps_overlap_lowbound, aps_fit_highbound):
203
+ # print(f'\nMerge data :')
204
+ # print(f' APS fittint higher diameter : {aps_fit_highbound:4d} nm')
205
+ # print(f' SMPS overlap lower diameter : {smps_overlap_lowbound:4d} nm')
206
+ # print(f' Average time : {self.data_freq:>4s}\n')
207
+
208
+ ## get data, remove 'total' and 'mode'
209
+ ## set to the same units
210
+ smps, aps = df_smps, df_aps
211
+ smps.columns = smps.keys().to_numpy(float)
212
+ aps.columns = aps.keys().to_numpy(float)
213
+
214
+ if aps_unit == 'um':
215
+ aps.columns = aps.keys() * 1e3
216
+
217
+ ## shift infomation, calculate by powerlaw fitting
218
+ shift, coe = _overlap_fitting(smps, aps, smps_overlap_lowbound, aps_fit_highbound)
219
+
220
+ ## process data by shift infomation, and average data
221
+ qc_cond, shift = _shift_data_process(shift)
222
+
223
+ ## merge aps and smps..
224
+ merge_data, density = _merge_data(smps, aps, shift, shift_mode, smps_overlap_lowbound, aps_fit_highbound, coe)
225
+ density.columns = ['density']
226
+
227
+ ## add total and mode
228
+ # merge_total = merge_data.sum(axis=1,min_count=1).copy()
229
+ # merge_mode = merge_data.idxmax(axis=1).astype(float).copy()
230
+
231
+ # merge_data['total'] = merge_total
232
+ # merge_data['mode'] = merge_mode
233
+
234
+ ## out
235
+ out_dic = {
236
+ 'data_all': merge_data,
237
+ 'data_qc': merge_data.loc[qc_cond],
238
+ 'density_all': density,
239
+ 'density_qc': density.loc[qc_cond],
240
+ }
241
+
242
+ ## process data
243
+
244
+ for _nam, _df in out_dic.items():
245
+ out_dic[_nam] = _df.reindex(df_aps.index).copy()
246
+
247
+ # merge_data = merge_data.reindex(df_aps.index)
248
+ # density = density.reindex(df_aps.index)
249
+
250
+ return out_dic