AeroViz 0.1.2__py3-none-any.whl → 0.1.3b0__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.
- AeroViz/__init__.py +4 -4
- AeroViz/config/DEFAULT_DATA.csv +1417 -0
- AeroViz/config/DEFAULT_PNSD_DATA.csv +1417 -0
- AeroViz/dataProcess/Chemistry/__init__.py +38 -38
- AeroViz/dataProcess/Chemistry/_calculate.py +15 -15
- AeroViz/dataProcess/Chemistry/_isoropia.py +69 -68
- AeroViz/dataProcess/Chemistry/_mass_volume.py +158 -158
- AeroViz/dataProcess/Chemistry/_ocec.py +109 -109
- AeroViz/dataProcess/Chemistry/_partition.py +19 -18
- AeroViz/dataProcess/Chemistry/_teom.py +8 -11
- AeroViz/dataProcess/Optical/_IMPROVE.py +40 -39
- AeroViz/dataProcess/Optical/__init__.py +35 -35
- AeroViz/dataProcess/Optical/_absorption.py +35 -35
- AeroViz/dataProcess/Optical/_extinction.py +25 -24
- AeroViz/dataProcess/Optical/_mie.py +5 -6
- AeroViz/dataProcess/Optical/_mie_sd.py +89 -90
- AeroViz/dataProcess/Optical/_scattering.py +16 -16
- AeroViz/dataProcess/SizeDistr/__init__.py +37 -37
- AeroViz/dataProcess/SizeDistr/__merge.py +159 -158
- AeroViz/dataProcess/SizeDistr/_merge.py +155 -154
- AeroViz/dataProcess/SizeDistr/_merge_v1.py +162 -161
- AeroViz/dataProcess/SizeDistr/_merge_v2.py +153 -152
- AeroViz/dataProcess/SizeDistr/_merge_v3.py +326 -326
- AeroViz/dataProcess/SizeDistr/_merge_v4.py +272 -274
- AeroViz/dataProcess/SizeDistr/_size_distr.py +51 -51
- AeroViz/dataProcess/VOC/__init__.py +7 -7
- AeroViz/dataProcess/VOC/_potential_par.py +53 -55
- AeroViz/dataProcess/VOC/voc_par.json +464 -0
- AeroViz/dataProcess/__init__.py +4 -4
- AeroViz/dataProcess/core/__init__.py +59 -58
- AeroViz/plot/__init__.py +6 -1
- AeroViz/plot/bar.py +126 -0
- AeroViz/plot/box.py +68 -0
- AeroViz/plot/distribution/distribution.py +421 -427
- AeroViz/plot/meteorology/meteorology.py +240 -292
- AeroViz/plot/optical/__init__.py +0 -1
- AeroViz/plot/optical/optical.py +230 -230
- AeroViz/plot/pie.py +198 -0
- AeroViz/plot/regression.py +210 -0
- AeroViz/plot/scatter.py +99 -0
- AeroViz/plot/templates/__init__.py +0 -3
- AeroViz/plot/templates/contour.py +25 -25
- AeroViz/plot/templates/corr_matrix.py +86 -93
- AeroViz/plot/templates/diurnal_pattern.py +24 -24
- AeroViz/plot/templates/koschmieder.py +106 -106
- AeroViz/plot/templates/metal_heatmap.py +34 -34
- AeroViz/plot/timeseries/timeseries.py +53 -60
- AeroViz/plot/utils/__init__.py +2 -1
- AeroViz/plot/utils/_color.py +57 -57
- AeroViz/plot/utils/_unit.py +48 -48
- AeroViz/plot/utils/fRH.json +390 -0
- AeroViz/plot/utils/plt_utils.py +92 -0
- AeroViz/plot/utils/sklearn_utils.py +49 -0
- AeroViz/plot/utils/units.json +84 -0
- AeroViz/plot/violin.py +79 -0
- AeroViz/process/__init__.py +15 -15
- AeroViz/process/core/DataProc.py +9 -9
- AeroViz/process/core/SizeDist.py +81 -81
- AeroViz/process/method/PyMieScatt_update.py +488 -488
- AeroViz/process/method/mie_theory.py +231 -229
- AeroViz/process/method/prop.py +40 -40
- AeroViz/process/script/AbstractDistCalc.py +103 -103
- AeroViz/process/script/Chemical.py +166 -166
- AeroViz/process/script/IMPACT.py +40 -40
- AeroViz/process/script/IMPROVE.py +152 -152
- AeroViz/process/script/Others.py +45 -45
- AeroViz/process/script/PSD.py +26 -26
- AeroViz/process/script/PSD_dry.py +69 -70
- AeroViz/process/script/retrieve_RI.py +50 -51
- AeroViz/rawDataReader/__init__.py +57 -57
- AeroViz/rawDataReader/core/__init__.py +328 -326
- AeroViz/rawDataReader/script/AE33.py +18 -18
- AeroViz/rawDataReader/script/AE43.py +20 -20
- AeroViz/rawDataReader/script/APS_3321.py +30 -30
- AeroViz/rawDataReader/script/Aurora.py +23 -23
- AeroViz/rawDataReader/script/BC1054.py +40 -40
- AeroViz/rawDataReader/script/EPA_vertical.py +9 -9
- AeroViz/rawDataReader/script/GRIMM.py +21 -21
- AeroViz/rawDataReader/script/IGAC_TH.py +67 -67
- AeroViz/rawDataReader/script/IGAC_ZM.py +59 -59
- AeroViz/rawDataReader/script/MA350.py +39 -39
- AeroViz/rawDataReader/script/NEPH.py +74 -74
- AeroViz/rawDataReader/script/OCEC_LCRES.py +21 -21
- AeroViz/rawDataReader/script/OCEC_RES.py +16 -16
- AeroViz/rawDataReader/script/SMPS_TH.py +25 -25
- AeroViz/rawDataReader/script/SMPS_aim11.py +32 -32
- AeroViz/rawDataReader/script/SMPS_genr.py +31 -31
- AeroViz/rawDataReader/script/TEOM.py +28 -28
- AeroViz/rawDataReader/script/Table.py +12 -12
- AeroViz/rawDataReader/script/VOC_TH.py +16 -16
- AeroViz/rawDataReader/script/VOC_ZM.py +28 -28
- AeroViz/rawDataReader/script/__init__.py +20 -20
- AeroViz/rawDataReader/utils/config.py +161 -161
- AeroViz/tools/database.py +65 -65
- AeroViz/tools/dataclassifier.py +106 -106
- AeroViz/tools/dataprinter.py +51 -51
- AeroViz/tools/datareader.py +38 -38
- {AeroViz-0.1.2.dist-info → AeroViz-0.1.3b0.dist-info}/METADATA +5 -4
- AeroViz-0.1.3b0.dist-info/RECORD +110 -0
- AeroViz/config/__init__.py +0 -0
- AeroViz/plot/improve/__init__.py +0 -1
- AeroViz/plot/improve/improve.py +0 -240
- AeroViz/plot/optical/aethalometer.py +0 -77
- AeroViz/plot/templates/event_evolution.py +0 -65
- AeroViz/plot/templates/regression.py +0 -256
- AeroViz/plot/templates/scatter.py +0 -130
- AeroViz/plot/templates/templates.py +0 -398
- AeroViz/plot/utils/_decorator.py +0 -74
- AeroViz-0.1.2.dist-info/RECORD +0 -106
- {AeroViz-0.1.2.dist-info → AeroViz-0.1.3b0.dist-info}/LICENSE +0 -0
- {AeroViz-0.1.2.dist-info → AeroViz-0.1.3b0.dist-info}/WHEEL +0 -0
- {AeroViz-0.1.2.dist-info → AeroViz-0.1.3b0.dist-info}/top_level.txt +0 -0
|
@@ -10,134 +10,134 @@ from AeroViz.process.method import properties, internal, external, core_shell, s
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class AbstractDistCalc(ABC):
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
@abstractmethod
|
|
14
|
+
def useApply(self) -> DataFrame:
|
|
15
|
+
pass
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class NumberDistCalc(AbstractDistCalc):
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
def __init__(self, psd: SizeDist):
|
|
20
|
+
self.psd = psd
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
def useApply(self) -> DataFrame:
|
|
23
|
+
""" Calculate number distribution """
|
|
24
|
+
return self.psd.data
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
class SurfaceDistCalc(AbstractDistCalc):
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
def __init__(self, psd: SizeDist):
|
|
29
|
+
self.psd = psd
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
def useApply(self) -> DataFrame:
|
|
32
|
+
""" Calculate surface distribution """
|
|
33
|
+
return self.psd.data.dropna().apply(lambda col: np.pi * self.psd.dp ** 2 * np.array(col),
|
|
34
|
+
axis=1, result_type='broadcast').reindex(self.psd.index)
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
class VolumeDistCalc(AbstractDistCalc):
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
def __init__(self, psd: SizeDist):
|
|
39
|
+
self.psd = psd
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
def useApply(self) -> DataFrame:
|
|
42
|
+
""" Calculate volume distribution """
|
|
43
|
+
return self.psd.data.dropna().apply(lambda col: np.pi / 6 * self.psd.dp ** 3 * np.array(col),
|
|
44
|
+
axis=1, result_type='broadcast').reindex(self.psd.index)
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
class PropertiesDistCalc(AbstractDistCalc):
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
def __init__(self, psd: SizeDist):
|
|
49
|
+
self.psd = psd
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
def useApply(self) -> DataFrame:
|
|
52
|
+
""" Calculate properties of distribution """
|
|
53
|
+
return self.psd.data.dropna().apply(partial(properties, dp=self.psd.dp, dlogdp=self.psd.dlogdp,
|
|
54
|
+
weighting=self.psd.weighting),
|
|
55
|
+
axis=1, result_type='expand').reindex(self.psd.index)
|
|
56
56
|
|
|
57
57
|
|
|
58
58
|
class ExtinctionDistCalc(AbstractDistCalc):
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
59
|
+
mapping = {'internal': internal,
|
|
60
|
+
'external': external,
|
|
61
|
+
'core_shell': core_shell,
|
|
62
|
+
'sensitivity': sensitivity}
|
|
63
|
+
|
|
64
|
+
def __init__(self,
|
|
65
|
+
psd: SizeDist,
|
|
66
|
+
RI: DataFrame,
|
|
67
|
+
method: Literal['internal', 'external', 'utils-shell', 'sensitivity'],
|
|
68
|
+
result_type: Literal['extinction', 'scattering', 'absorption'] = 'extinction'
|
|
69
|
+
):
|
|
70
|
+
self.psd = psd
|
|
71
|
+
self.RI = RI
|
|
72
|
+
if method not in ExtinctionDistCalc.mapping:
|
|
73
|
+
raise ValueError(f"Invalid method: {method}. Valid methods are: {list(ExtinctionDistCalc.mapping.keys())}")
|
|
74
|
+
self.method = ExtinctionDistCalc.mapping[method]
|
|
75
|
+
self.result_type = result_type
|
|
76
|
+
|
|
77
|
+
def useApply(self) -> DataFrame:
|
|
78
|
+
""" Calculate volume distribution """
|
|
79
|
+
combined_data = concat([self.psd.data, self.RI], axis=1).dropna()
|
|
80
|
+
return combined_data.apply(partial(self.method, dp=self.psd.dp, result_type=self.result_type),
|
|
81
|
+
axis=1, result_type='expand').reindex(self.psd.index).set_axis(self.psd.dp, axis=1)
|
|
82
82
|
|
|
83
83
|
|
|
84
84
|
# TODO:
|
|
85
85
|
class LungDepositsDistCalc(AbstractDistCalc):
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
def __init__(self, psd: SizeDist, lung_curve):
|
|
88
|
+
self.psd = psd
|
|
89
|
+
self.lung_curve = lung_curve
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
def useApply(self) -> DataFrame:
|
|
92
|
+
pass
|
|
93
93
|
|
|
94
94
|
|
|
95
95
|
class DistributionCalculator: # 策略模式 (Strategy Pattern)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
96
|
+
""" Interface for distribution calculator """
|
|
97
|
+
|
|
98
|
+
mapping = {'number': NumberDistCalc,
|
|
99
|
+
'surface': SurfaceDistCalc,
|
|
100
|
+
'volume': VolumeDistCalc,
|
|
101
|
+
'property': PropertiesDistCalc,
|
|
102
|
+
'extinction': ExtinctionDistCalc,
|
|
103
|
+
'lung_deposit': LungDepositsDistCalc}
|
|
104
|
+
|
|
105
|
+
def __init__(self,
|
|
106
|
+
calculator: Literal['number', 'surface', 'volume', 'property', 'extinction'],
|
|
107
|
+
psd: SizeDist,
|
|
108
|
+
RI: DataFrame = None,
|
|
109
|
+
method: str = None,
|
|
110
|
+
result_type: str = None
|
|
111
|
+
):
|
|
112
|
+
"""
|
|
113
|
+
Initialize the DistributionCalculator.
|
|
114
|
+
|
|
115
|
+
Parameters:
|
|
116
|
+
calculator (CalculatorType): The type of calculator.
|
|
117
|
+
psd (SizeDist): The particle size distribution data.
|
|
118
|
+
RI (Optional[DataFrame]): The refractive index data. Default is None.
|
|
119
|
+
method (Optional[str]): The method to use. Default is None.
|
|
120
|
+
result_type (Optional[str]): The result type. Default is None.
|
|
121
|
+
"""
|
|
122
|
+
if calculator not in DistributionCalculator.mapping.keys():
|
|
123
|
+
raise ValueError(
|
|
124
|
+
f"Invalid calculator: {calculator}. Valid calculators are: {list(DistributionCalculator.mapping.keys())}")
|
|
125
|
+
self.calculator = DistributionCalculator.mapping[calculator]
|
|
126
|
+
self.psd = psd
|
|
127
|
+
self.RI = RI
|
|
128
|
+
self.method = method
|
|
129
|
+
self.result_type = result_type
|
|
130
|
+
|
|
131
|
+
def useApply(self) -> DataFrame:
|
|
132
|
+
"""
|
|
133
|
+
Apply the calculator to the data.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
DataFrame: The calculated data.
|
|
137
|
+
"""
|
|
138
|
+
if self.RI is not None:
|
|
139
|
+
return self.calculator(self.psd, self.RI, self.method, self.result_type).useApply()
|
|
140
|
+
elif issubclass(self.calculator, (NumberDistCalc, SurfaceDistCalc, VolumeDistCalc, PropertiesDistCalc)):
|
|
141
|
+
return self.calculator(self.psd).useApply()
|
|
142
|
+
else:
|
|
143
|
+
raise ValueError("RI parameter is required for this calculator type")
|
|
@@ -8,169 +8,169 @@ from AeroViz.tools.datareader import DataReader
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class ChemicalProc(DataProc):
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
11
|
+
"""
|
|
12
|
+
A class for process chemical data.
|
|
13
|
+
|
|
14
|
+
Parameters:
|
|
15
|
+
-----------
|
|
16
|
+
reset : bool, optional
|
|
17
|
+
If True, resets the process. Default is False.
|
|
18
|
+
filename : str, optional
|
|
19
|
+
The name of the file to process. Default is None.
|
|
20
|
+
|
|
21
|
+
Methods:
|
|
22
|
+
--------
|
|
23
|
+
mass(_df):
|
|
24
|
+
Calculate mass-related parameters.
|
|
25
|
+
|
|
26
|
+
volume(_df):
|
|
27
|
+
Calculate volume-related parameters.
|
|
28
|
+
|
|
29
|
+
volume_average_mixing(_df):
|
|
30
|
+
Calculate volume average mixing parameters.
|
|
31
|
+
|
|
32
|
+
process_data():
|
|
33
|
+
Process data and save the result.
|
|
34
|
+
|
|
35
|
+
Attributes:
|
|
36
|
+
-----------
|
|
37
|
+
DEFAULT_PATH : Path
|
|
38
|
+
The default path for data files.
|
|
39
|
+
|
|
40
|
+
Examples:
|
|
41
|
+
---------
|
|
42
|
+
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(self, file_paths: list[Path | str] = None):
|
|
46
|
+
super().__init__()
|
|
47
|
+
self.file_paths = [Path(fp) for fp in file_paths]
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
def mass(_df): # Series like
|
|
51
|
+
Ammonium, Sulfate, Nitrate, OC, Soil, SS, EC, PM25 = _df
|
|
52
|
+
status = (Ammonium / 18) / (2 * (Sulfate / 96) + (Nitrate / 62))
|
|
53
|
+
|
|
54
|
+
if status >= 1:
|
|
55
|
+
_df['NH4_status'] = 'Enough'
|
|
56
|
+
_df['AS'] = 1.375 * Sulfate
|
|
57
|
+
_df['AN'] = 1.29 * Nitrate
|
|
58
|
+
|
|
59
|
+
if status < 1:
|
|
60
|
+
_df['NH4_status'] = 'Deficiency'
|
|
61
|
+
mol_A = Ammonium / 18
|
|
62
|
+
mol_S = Sulfate / 96
|
|
63
|
+
mol_N = Nitrate / 62
|
|
64
|
+
residual = mol_A - 2 * mol_S
|
|
65
|
+
|
|
66
|
+
if residual > 0:
|
|
67
|
+
_df['AS'] = 1.375 * Sulfate
|
|
68
|
+
_df['AN'] = residual * 80 if residual <= mol_N else mol_N * 80
|
|
69
|
+
|
|
70
|
+
else:
|
|
71
|
+
_df['AS'] = mol_A / 2 * 132 if mol_A <= 2 * mol_S else mol_S * 132
|
|
72
|
+
_df['AN'] = 0
|
|
73
|
+
|
|
74
|
+
_df['OM'] = 1.8 * OC
|
|
75
|
+
_df['Soil'] = 28.57 * Soil
|
|
76
|
+
_df['SS'] = 2.54 * SS
|
|
77
|
+
_df['EC'] = EC
|
|
78
|
+
_df['SIA'] = _df['AS'] + _df['AN']
|
|
79
|
+
_df['total_mass'] = _df[['AS', 'AN', 'OM', 'Soil', 'SS', 'EC']].sum()
|
|
80
|
+
species_lst = ['AS', 'AN', 'OM', 'Soil', 'SS', 'EC', 'SIA', 'unknown_mass']
|
|
81
|
+
|
|
82
|
+
_df['unknown_mass'] = PM25 - _df['total_mass'] if PM25 >= _df['total_mass'] else 0
|
|
83
|
+
for _species, _val in _df[species_lst].items():
|
|
84
|
+
_df[f'{_species}_ratio'] = _val / PM25 if PM25 >= _df['total_mass'] else _val / _df['total_mass']
|
|
85
|
+
|
|
86
|
+
return _df['NH4_status':]
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def volume(_df):
|
|
90
|
+
_df['AS_volume'] = (_df['AS'] / 1.76)
|
|
91
|
+
_df['AN_volume'] = (_df['AN'] / 1.73)
|
|
92
|
+
_df['OM_volume'] = (_df['OM'] / 1.4)
|
|
93
|
+
_df['Soil_volume'] = (_df['Soil'] / 2.6)
|
|
94
|
+
_df['SS_volume'] = (_df['SS'] / 2.16)
|
|
95
|
+
_df['EC_volume'] = (_df['EC'] / 1.5)
|
|
96
|
+
_df['ALWC_volume'] = _df['ALWC']
|
|
97
|
+
_df['total_volume'] = sum(_df['AS_volume':'EC_volume'])
|
|
98
|
+
|
|
99
|
+
for _species, _val in _df['AS_volume':'ALWC_volume'].items():
|
|
100
|
+
_df[f'{_species}_ratio'] = _val / _df['total_volume']
|
|
101
|
+
|
|
102
|
+
_df['density'] = _df['total_mass'] / _df['total_volume']
|
|
103
|
+
return _df['AS_volume':]
|
|
104
|
+
|
|
105
|
+
@staticmethod
|
|
106
|
+
def volume_average_mixing(_df):
|
|
107
|
+
_df['n_dry'] = (1.53 * _df['AS_volume_ratio'] +
|
|
108
|
+
1.55 * _df['AN_volume_ratio'] +
|
|
109
|
+
1.55 * _df['OM_volume_ratio'] +
|
|
110
|
+
1.56 * _df['Soil_volume_ratio'] +
|
|
111
|
+
1.54 * _df['SS_volume_ratio'] +
|
|
112
|
+
1.80 * _df['EC_volume_ratio'])
|
|
113
|
+
|
|
114
|
+
_df['k_dry'] = (0.00 * _df['OM_volume_ratio'] +
|
|
115
|
+
0.01 * _df['Soil_volume_ratio'] +
|
|
116
|
+
0.54 * _df["EC_volume_ratio"])
|
|
117
|
+
|
|
118
|
+
# 檢查_df['ALWC']是否缺失 -> 有值才計算ambient的折射率
|
|
119
|
+
if notna(_df['ALWC']):
|
|
120
|
+
v_dry = _df['total_volume']
|
|
121
|
+
v_wet = _df['total_volume'] + _df['ALWC']
|
|
122
|
+
|
|
123
|
+
multiplier = v_dry / v_wet
|
|
124
|
+
_df['ALWC_volume_ratio'] = (1 - multiplier)
|
|
125
|
+
|
|
126
|
+
_df['n_amb'] = (1.53 * _df['AS_volume_ratio'] +
|
|
127
|
+
1.55 * _df['AN_volume_ratio'] +
|
|
128
|
+
1.55 * _df['OM_volume_ratio'] +
|
|
129
|
+
1.56 * _df['Soil_volume_ratio'] +
|
|
130
|
+
1.54 * _df['SS_volume_ratio'] +
|
|
131
|
+
1.80 * _df['EC_volume_ratio']) * multiplier + \
|
|
132
|
+
(1.33 * _df['ALWC_volume_ratio'])
|
|
133
|
+
|
|
134
|
+
_df['k_amb'] = (0.00 * _df['OM_volume_ratio'] +
|
|
135
|
+
0.01 * _df['Soil_volume_ratio'] +
|
|
136
|
+
0.54 * _df['EC_volume_ratio']) * multiplier
|
|
137
|
+
|
|
138
|
+
_df['gRH'] = (v_wet / v_dry) ** (1 / 3)
|
|
139
|
+
|
|
140
|
+
return _df[['n_dry', 'k_dry', 'n_amb', 'k_amb', 'gRH']]
|
|
141
|
+
|
|
142
|
+
@staticmethod
|
|
143
|
+
def kappa(_df, diameter=0.5):
|
|
144
|
+
surface_tension, Mw, density, universal_gas_constant = 0.072, 18, 1, 8.314 # J/mole*K
|
|
145
|
+
|
|
146
|
+
A = 4 * (surface_tension * Mw) / (density * universal_gas_constant * (_df['AT'] + 273))
|
|
147
|
+
power = A / diameter
|
|
148
|
+
a_w = (_df['RH'] / 100) * (np.exp(-power))
|
|
149
|
+
|
|
150
|
+
_df['kappa_chem'] = (_df['gRH'] ** 3 - 1) * (1 - a_w) / a_w
|
|
151
|
+
_df['kappa_vam'] = np.nan
|
|
152
|
+
|
|
153
|
+
@staticmethod
|
|
154
|
+
def ISORROPIA():
|
|
155
|
+
pass
|
|
156
|
+
|
|
157
|
+
def process_data(self, reset: bool = False, save_file: Path | str = None) -> DataFrame:
|
|
158
|
+
save_file = Path(save_file)
|
|
159
|
+
if save_file.exists() and not reset:
|
|
160
|
+
return read_csv(save_file, parse_dates=['Time'], index_col='Time')
|
|
161
|
+
else:
|
|
162
|
+
df = concat([DataReader(file) for file in self.file_paths], axis=1)
|
|
163
|
+
|
|
164
|
+
df_mass = df[['NH4+', 'SO42-', 'NO3-', 'O_OC', 'Fe', 'Na+', 'O_EC', 'PM25']].dropna().apply(self.mass,
|
|
165
|
+
axis=1)
|
|
166
|
+
df_mass['ALWC'] = df['ALWC']
|
|
167
|
+
df_volume = df_mass[['AS', 'AN', 'OM', 'Soil', 'SS', 'EC', 'total_mass', 'ALWC']].dropna().apply(
|
|
168
|
+
self.volume,
|
|
169
|
+
axis=1)
|
|
170
|
+
df_volume['ALWC'] = df['ALWC']
|
|
171
|
+
df_vam = df_volume.dropna().apply(self.volume_average_mixing, axis=1)
|
|
172
|
+
|
|
173
|
+
_df = concat([df_mass, df_volume.drop(['ALWC'], axis=1), df_vam], axis=1).reindex(df.index.copy())
|
|
174
|
+
_df.to_csv(save_file)
|
|
175
|
+
|
|
176
|
+
return _df
|