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.
- AeroViz/__init__.py +13 -0
- AeroViz/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/data/DEFAULT_DATA.csv +1417 -0
- AeroViz/data/DEFAULT_PNSD_DATA.csv +1417 -0
- AeroViz/data/hysplit_example_data.txt +101 -0
- AeroViz/dataProcess/Chemistry/__init__.py +149 -0
- AeroViz/dataProcess/Chemistry/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/dataProcess/Chemistry/_calculate.py +557 -0
- AeroViz/dataProcess/Chemistry/_isoropia.py +150 -0
- AeroViz/dataProcess/Chemistry/_mass_volume.py +487 -0
- AeroViz/dataProcess/Chemistry/_ocec.py +172 -0
- AeroViz/dataProcess/Chemistry/isrpia.cnf +21 -0
- AeroViz/dataProcess/Chemistry/isrpia2.exe +0 -0
- AeroViz/dataProcess/Optical/PyMieScatt_update.py +577 -0
- AeroViz/dataProcess/Optical/_IMPROVE.py +452 -0
- AeroViz/dataProcess/Optical/__init__.py +281 -0
- AeroViz/dataProcess/Optical/__pycache__/PyMieScatt_update.cpython-312.pyc +0 -0
- AeroViz/dataProcess/Optical/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/dataProcess/Optical/__pycache__/mie_theory.cpython-312.pyc +0 -0
- AeroViz/dataProcess/Optical/_derived.py +518 -0
- AeroViz/dataProcess/Optical/_extinction.py +123 -0
- AeroViz/dataProcess/Optical/_mie_sd.py +912 -0
- AeroViz/dataProcess/Optical/_retrieve_RI.py +243 -0
- AeroViz/dataProcess/Optical/coefficient.py +72 -0
- AeroViz/dataProcess/Optical/fRH.pkl +0 -0
- AeroViz/dataProcess/Optical/mie_theory.py +260 -0
- AeroViz/dataProcess/README.md +271 -0
- AeroViz/dataProcess/SizeDistr/__init__.py +245 -0
- AeroViz/dataProcess/SizeDistr/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/dataProcess/SizeDistr/__pycache__/_size_dist.cpython-312.pyc +0 -0
- AeroViz/dataProcess/SizeDistr/_size_dist.py +810 -0
- AeroViz/dataProcess/SizeDistr/merge/README.md +93 -0
- AeroViz/dataProcess/SizeDistr/merge/__init__.py +20 -0
- AeroViz/dataProcess/SizeDistr/merge/_merge_v0.py +251 -0
- AeroViz/dataProcess/SizeDistr/merge/_merge_v0_1.py +246 -0
- AeroViz/dataProcess/SizeDistr/merge/_merge_v1.py +255 -0
- AeroViz/dataProcess/SizeDistr/merge/_merge_v2.py +244 -0
- AeroViz/dataProcess/SizeDistr/merge/_merge_v3.py +518 -0
- AeroViz/dataProcess/SizeDistr/merge/_merge_v4.py +422 -0
- AeroViz/dataProcess/SizeDistr/prop.py +62 -0
- AeroViz/dataProcess/VOC/__init__.py +14 -0
- AeroViz/dataProcess/VOC/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/dataProcess/VOC/_potential_par.py +108 -0
- AeroViz/dataProcess/VOC/support_voc.json +446 -0
- AeroViz/dataProcess/__init__.py +66 -0
- AeroViz/dataProcess/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/dataProcess/core/__init__.py +272 -0
- AeroViz/dataProcess/core/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/mcp_server.py +352 -0
- AeroViz/plot/__init__.py +13 -0
- AeroViz/plot/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/plot/__pycache__/bar.cpython-312.pyc +0 -0
- AeroViz/plot/__pycache__/box.cpython-312.pyc +0 -0
- AeroViz/plot/__pycache__/pie.cpython-312.pyc +0 -0
- AeroViz/plot/__pycache__/radar.cpython-312.pyc +0 -0
- AeroViz/plot/__pycache__/regression.cpython-312.pyc +0 -0
- AeroViz/plot/__pycache__/scatter.cpython-312.pyc +0 -0
- AeroViz/plot/__pycache__/violin.cpython-312.pyc +0 -0
- AeroViz/plot/bar.py +126 -0
- AeroViz/plot/box.py +69 -0
- AeroViz/plot/distribution/__init__.py +1 -0
- AeroViz/plot/distribution/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/plot/distribution/__pycache__/distribution.cpython-312.pyc +0 -0
- AeroViz/plot/distribution/distribution.py +576 -0
- AeroViz/plot/meteorology/CBPF.py +295 -0
- AeroViz/plot/meteorology/__init__.py +3 -0
- AeroViz/plot/meteorology/__pycache__/CBPF.cpython-312.pyc +0 -0
- AeroViz/plot/meteorology/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/plot/meteorology/__pycache__/hysplit.cpython-312.pyc +0 -0
- AeroViz/plot/meteorology/__pycache__/wind_rose.cpython-312.pyc +0 -0
- AeroViz/plot/meteorology/hysplit.py +93 -0
- AeroViz/plot/meteorology/wind_rose.py +77 -0
- AeroViz/plot/optical/__init__.py +1 -0
- AeroViz/plot/optical/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/plot/optical/__pycache__/optical.cpython-312.pyc +0 -0
- AeroViz/plot/optical/optical.py +388 -0
- AeroViz/plot/pie.py +210 -0
- AeroViz/plot/radar.py +184 -0
- AeroViz/plot/regression.py +200 -0
- AeroViz/plot/scatter.py +174 -0
- AeroViz/plot/templates/__init__.py +6 -0
- AeroViz/plot/templates/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/plot/templates/__pycache__/ammonium_rich.cpython-312.pyc +0 -0
- AeroViz/plot/templates/__pycache__/contour.cpython-312.pyc +0 -0
- AeroViz/plot/templates/__pycache__/corr_matrix.cpython-312.pyc +0 -0
- AeroViz/plot/templates/__pycache__/diurnal_pattern.cpython-312.pyc +0 -0
- AeroViz/plot/templates/__pycache__/koschmieder.cpython-312.pyc +0 -0
- AeroViz/plot/templates/__pycache__/metal_heatmap.cpython-312.pyc +0 -0
- AeroViz/plot/templates/ammonium_rich.py +34 -0
- AeroViz/plot/templates/contour.py +47 -0
- AeroViz/plot/templates/corr_matrix.py +267 -0
- AeroViz/plot/templates/diurnal_pattern.py +61 -0
- AeroViz/plot/templates/koschmieder.py +95 -0
- AeroViz/plot/templates/metal_heatmap.py +164 -0
- AeroViz/plot/timeseries/__init__.py +2 -0
- AeroViz/plot/timeseries/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/plot/timeseries/__pycache__/template.cpython-312.pyc +0 -0
- AeroViz/plot/timeseries/__pycache__/timeseries.cpython-312.pyc +0 -0
- AeroViz/plot/timeseries/template.py +47 -0
- AeroViz/plot/timeseries/timeseries.py +446 -0
- AeroViz/plot/utils/__init__.py +4 -0
- AeroViz/plot/utils/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/plot/utils/__pycache__/_color.cpython-312.pyc +0 -0
- AeroViz/plot/utils/__pycache__/_unit.cpython-312.pyc +0 -0
- AeroViz/plot/utils/__pycache__/plt_utils.cpython-312.pyc +0 -0
- AeroViz/plot/utils/__pycache__/sklearn_utils.cpython-312.pyc +0 -0
- AeroViz/plot/utils/_color.py +71 -0
- AeroViz/plot/utils/_unit.py +55 -0
- 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 +89 -0
- AeroViz/plot/violin.py +80 -0
- AeroViz/rawDataReader/FLOW.md +138 -0
- AeroViz/rawDataReader/__init__.py +220 -0
- AeroViz/rawDataReader/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/config/__init__.py +0 -0
- AeroViz/rawDataReader/config/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/config/__pycache__/supported_instruments.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/config/supported_instruments.py +135 -0
- AeroViz/rawDataReader/core/__init__.py +658 -0
- AeroViz/rawDataReader/core/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/core/__pycache__/logger.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/core/__pycache__/pre_process.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/core/__pycache__/qc.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/core/__pycache__/report.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/core/logger.py +171 -0
- AeroViz/rawDataReader/core/pre_process.py +308 -0
- AeroViz/rawDataReader/core/qc.py +961 -0
- AeroViz/rawDataReader/core/report.py +579 -0
- AeroViz/rawDataReader/script/AE33.py +173 -0
- AeroViz/rawDataReader/script/AE43.py +151 -0
- AeroViz/rawDataReader/script/APS.py +339 -0
- AeroViz/rawDataReader/script/Aurora.py +191 -0
- AeroViz/rawDataReader/script/BAM1020.py +90 -0
- AeroViz/rawDataReader/script/BC1054.py +161 -0
- AeroViz/rawDataReader/script/EPA.py +79 -0
- AeroViz/rawDataReader/script/GRIMM.py +68 -0
- AeroViz/rawDataReader/script/IGAC.py +140 -0
- AeroViz/rawDataReader/script/MA350.py +179 -0
- AeroViz/rawDataReader/script/Minion.py +218 -0
- AeroViz/rawDataReader/script/NEPH.py +199 -0
- AeroViz/rawDataReader/script/OCEC.py +173 -0
- AeroViz/rawDataReader/script/Q-ACSM.py +12 -0
- AeroViz/rawDataReader/script/SMPS.py +389 -0
- AeroViz/rawDataReader/script/TEOM.py +181 -0
- AeroViz/rawDataReader/script/VOC.py +106 -0
- AeroViz/rawDataReader/script/Xact.py +244 -0
- AeroViz/rawDataReader/script/__init__.py +28 -0
- AeroViz/rawDataReader/script/__pycache__/AE33.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/AE43.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/APS.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/Aurora.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/BAM1020.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/BC1054.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/EPA.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/GRIMM.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/IGAC.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/MA350.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/Minion.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/NEPH.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/OCEC.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/Q-ACSM.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/SMPS.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/TEOM.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/VOC.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/Xact.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/tools/__init__.py +2 -0
- AeroViz/tools/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/tools/__pycache__/database.cpython-312.pyc +0 -0
- AeroViz/tools/__pycache__/dataclassifier.cpython-312.pyc +0 -0
- AeroViz/tools/database.py +95 -0
- AeroViz/tools/dataclassifier.py +117 -0
- AeroViz/tools/dataprinter.py +58 -0
- aeroviz-0.1.21.dist-info/METADATA +294 -0
- aeroviz-0.1.21.dist-info/RECORD +180 -0
- aeroviz-0.1.21.dist-info/WHEEL +5 -0
- aeroviz-0.1.21.dist-info/licenses/LICENSE +21 -0
- aeroviz-0.1.21.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Derived optical and atmospheric parameters.
|
|
3
|
+
|
|
4
|
+
This module provides functions for calculating various derived parameters
|
|
5
|
+
from optical and chemical measurements.
|
|
6
|
+
|
|
7
|
+
Available Functions
|
|
8
|
+
-------------------
|
|
9
|
+
- derived_parameters: Calculate multiple derived parameters at once
|
|
10
|
+
- calculate_visibility: Calculate visibility from extinction
|
|
11
|
+
- calculate_MAC: Mass Absorption Cross-section
|
|
12
|
+
- calculate_Ox: Total oxidant (NO2 + O3)
|
|
13
|
+
- calculate_fRH: Hygroscopic growth factor for extinction
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import numpy as np
|
|
17
|
+
from pandas import DataFrame, concat
|
|
18
|
+
|
|
19
|
+
from AeroViz.dataProcess.core import validate_inputs
|
|
20
|
+
|
|
21
|
+
__all__ = ['derived_parameters', 'calculate_visibility', 'calculate_MAC',
|
|
22
|
+
'calculate_Ox', 'calculate_fRH', 'get_required_columns',
|
|
23
|
+
'calculate_BrC_absorption']
|
|
24
|
+
|
|
25
|
+
# Column descriptions for validation
|
|
26
|
+
COLUMN_DESCRIPTIONS = {
|
|
27
|
+
'Scattering': 'Scattering coefficient 散射係數 (Mm-1)',
|
|
28
|
+
'Absorption': 'Absorption coefficient 吸收係數 (Mm-1)',
|
|
29
|
+
'Extinction': 'Extinction coefficient 消光係數 (Mm-1)',
|
|
30
|
+
'NO2': 'Nitrogen dioxide 二氧化氮 (ppb)',
|
|
31
|
+
'O3': 'Ozone 臭氧 (ppb)',
|
|
32
|
+
'EC': 'Elemental carbon 元素碳 (ug/m3)',
|
|
33
|
+
'OC': 'Organic carbon 有機碳 (ug/m3)',
|
|
34
|
+
'PM1': 'PM1 (ug/m3)',
|
|
35
|
+
'PM2.5': 'PM2.5 (ug/m3)',
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def derived_parameters(df_sca=None,
|
|
40
|
+
df_abs=None,
|
|
41
|
+
df_ext=None,
|
|
42
|
+
df_no2=None,
|
|
43
|
+
df_o3=None,
|
|
44
|
+
df_ec=None,
|
|
45
|
+
df_oc=None,
|
|
46
|
+
df_pm1=None,
|
|
47
|
+
df_pm25=None,
|
|
48
|
+
df_improve=None
|
|
49
|
+
) -> DataFrame:
|
|
50
|
+
"""
|
|
51
|
+
Calculate various derived atmospheric parameters.
|
|
52
|
+
|
|
53
|
+
Parameters
|
|
54
|
+
----------
|
|
55
|
+
df_sca : DataFrame, optional
|
|
56
|
+
Scattering coefficient data (Mm-1).
|
|
57
|
+
df_abs : DataFrame, optional
|
|
58
|
+
Absorption coefficient data (Mm-1).
|
|
59
|
+
df_ext : DataFrame, optional
|
|
60
|
+
Extinction coefficient data (Mm-1).
|
|
61
|
+
df_no2 : DataFrame, optional
|
|
62
|
+
NO2 concentration (ppb).
|
|
63
|
+
df_o3 : DataFrame, optional
|
|
64
|
+
O3 concentration (ppb).
|
|
65
|
+
df_ec : DataFrame, optional
|
|
66
|
+
Elemental carbon concentration (ug/m3).
|
|
67
|
+
df_oc : DataFrame, optional
|
|
68
|
+
Organic carbon concentration (ug/m3).
|
|
69
|
+
df_pm1 : DataFrame, optional
|
|
70
|
+
PM1 concentration (ug/m3).
|
|
71
|
+
df_pm25 : DataFrame, optional
|
|
72
|
+
PM2.5 concentration (ug/m3).
|
|
73
|
+
df_improve : DataFrame, optional
|
|
74
|
+
IMPROVE extinction data with 'total_ext' and 'total_ext_dry' columns.
|
|
75
|
+
|
|
76
|
+
Returns
|
|
77
|
+
-------
|
|
78
|
+
DataFrame
|
|
79
|
+
Derived parameters including:
|
|
80
|
+
- PG: Total extinction (particle + gas)
|
|
81
|
+
- MAC: Mass Absorption Cross-section
|
|
82
|
+
- Ox: Oxidant (NO2 + O3)
|
|
83
|
+
- N2O5_tracer: NO2 * O3 indicator
|
|
84
|
+
- Vis_cal: Calculated visibility (km)
|
|
85
|
+
- fRH_IMPR: Hygroscopic growth factor from IMPROVE
|
|
86
|
+
- OCEC_ratio: OC/EC ratio
|
|
87
|
+
- PM1_PM25_ratio: PM1/PM2.5 ratio
|
|
88
|
+
"""
|
|
89
|
+
# Combine all inputs to get common index
|
|
90
|
+
all_dfs = [df for df in [df_sca, df_abs, df_ext, df_no2, df_o3, df_ec,
|
|
91
|
+
df_oc, df_pm1, df_pm25, df_improve] if df is not None]
|
|
92
|
+
|
|
93
|
+
if not all_dfs:
|
|
94
|
+
return DataFrame()
|
|
95
|
+
|
|
96
|
+
common_index = concat(all_dfs, axis=1).index
|
|
97
|
+
result = DataFrame(index=common_index)
|
|
98
|
+
|
|
99
|
+
# Total extinction (particle + gas)
|
|
100
|
+
if df_sca is not None and df_abs is not None:
|
|
101
|
+
result['Bsp'] = df_sca.iloc[:, 0] if isinstance(df_sca, DataFrame) else df_sca
|
|
102
|
+
result['Bap'] = df_abs.iloc[:, 0] if isinstance(df_abs, DataFrame) else df_abs
|
|
103
|
+
result['PG'] = result['Bsp'] + result['Bap']
|
|
104
|
+
|
|
105
|
+
# Mass Absorption Cross-section (MAC)
|
|
106
|
+
if df_abs is not None and df_ec is not None:
|
|
107
|
+
abs_val = df_abs.iloc[:, 0] if isinstance(df_abs, DataFrame) else df_abs
|
|
108
|
+
ec_val = df_ec.iloc[:, 0] if isinstance(df_ec, DataFrame) else df_ec
|
|
109
|
+
result['MAC'] = abs_val / ec_val
|
|
110
|
+
|
|
111
|
+
# Oxidant (Ox = NO2 + O3)
|
|
112
|
+
if df_no2 is not None and df_o3 is not None:
|
|
113
|
+
no2_val = df_no2.iloc[:, 0] if isinstance(df_no2, DataFrame) else df_no2
|
|
114
|
+
o3_val = df_o3.iloc[:, 0] if isinstance(df_o3, DataFrame) else df_o3
|
|
115
|
+
result['Ox'] = no2_val + o3_val
|
|
116
|
+
|
|
117
|
+
# N2O5 tracer (NO2 * O3)
|
|
118
|
+
result['N2O5_tracer'] = no2_val * o3_val
|
|
119
|
+
|
|
120
|
+
# Visibility calculation
|
|
121
|
+
if df_ext is not None:
|
|
122
|
+
ext_val = df_ext.iloc[:, 0] if isinstance(df_ext, DataFrame) else df_ext
|
|
123
|
+
result['Vis_cal'] = 1096 / ext_val # Koschmieder equation, visibility in km
|
|
124
|
+
|
|
125
|
+
# fRH from IMPROVE
|
|
126
|
+
if df_improve is not None and 'total_ext' in df_improve.columns and 'total_ext_dry' in df_improve.columns:
|
|
127
|
+
result['fRH_IMPR'] = df_improve['total_ext'] / df_improve['total_ext_dry']
|
|
128
|
+
|
|
129
|
+
# OC/EC ratio
|
|
130
|
+
if df_oc is not None and df_ec is not None:
|
|
131
|
+
oc_val = df_oc.iloc[:, 0] if isinstance(df_oc, DataFrame) else df_oc
|
|
132
|
+
ec_val = df_ec.iloc[:, 0] if isinstance(df_ec, DataFrame) else df_ec
|
|
133
|
+
result['OCEC_ratio'] = oc_val / ec_val
|
|
134
|
+
|
|
135
|
+
# PM1/PM2.5 ratio
|
|
136
|
+
if df_pm1 is not None and df_pm25 is not None:
|
|
137
|
+
pm1_val = df_pm1.iloc[:, 0] if isinstance(df_pm1, DataFrame) else df_pm1
|
|
138
|
+
pm25_val = df_pm25.iloc[:, 0] if isinstance(df_pm25, DataFrame) else df_pm25
|
|
139
|
+
ratio = pm1_val / pm25_val
|
|
140
|
+
result['PM1_PM25_ratio'] = np.where(ratio < 1, ratio, np.nan)
|
|
141
|
+
|
|
142
|
+
return result
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def calculate_visibility(df_ext: DataFrame) -> DataFrame:
|
|
146
|
+
"""
|
|
147
|
+
Calculate visibility from extinction coefficient.
|
|
148
|
+
|
|
149
|
+
Uses the Koschmieder equation: Visibility = 3.912 / Bext
|
|
150
|
+
For Bext in Mm-1, Visibility in km: Visibility = 1096 / Bext
|
|
151
|
+
|
|
152
|
+
Parameters
|
|
153
|
+
----------
|
|
154
|
+
df_ext : DataFrame
|
|
155
|
+
Extinction coefficient data (Mm-1). Any column name accepted.
|
|
156
|
+
|
|
157
|
+
Returns
|
|
158
|
+
-------
|
|
159
|
+
DataFrame
|
|
160
|
+
Visibility in kilometers.
|
|
161
|
+
|
|
162
|
+
Raises
|
|
163
|
+
------
|
|
164
|
+
ValueError
|
|
165
|
+
If df_ext is None or empty.
|
|
166
|
+
"""
|
|
167
|
+
if df_ext is None or df_ext.empty:
|
|
168
|
+
raise ValueError(
|
|
169
|
+
"\ncalculate_visibility() 需要消光係數資料!\n"
|
|
170
|
+
" 必要輸入: df_ext (Extinction coefficient, Mm-1)"
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
result = DataFrame(index=df_ext.index)
|
|
174
|
+
ext_val = df_ext.iloc[:, 0] if isinstance(df_ext, DataFrame) else df_ext
|
|
175
|
+
result['Visibility'] = 1096 / ext_val
|
|
176
|
+
return result
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def calculate_MAC(df_abs: DataFrame, df_ec: DataFrame) -> DataFrame:
|
|
180
|
+
"""
|
|
181
|
+
Calculate Mass Absorption Cross-section (MAC).
|
|
182
|
+
|
|
183
|
+
MAC = Babs / EC_mass
|
|
184
|
+
|
|
185
|
+
Parameters
|
|
186
|
+
----------
|
|
187
|
+
df_abs : DataFrame
|
|
188
|
+
Absorption coefficient data (Mm-1). Any column name accepted.
|
|
189
|
+
df_ec : DataFrame
|
|
190
|
+
Elemental carbon concentration (ug/m3). Any column name accepted.
|
|
191
|
+
|
|
192
|
+
Returns
|
|
193
|
+
-------
|
|
194
|
+
DataFrame
|
|
195
|
+
MAC values (m2/g).
|
|
196
|
+
|
|
197
|
+
Raises
|
|
198
|
+
------
|
|
199
|
+
ValueError
|
|
200
|
+
If df_abs or df_ec is None or empty.
|
|
201
|
+
"""
|
|
202
|
+
if df_abs is None or (hasattr(df_abs, 'empty') and df_abs.empty):
|
|
203
|
+
raise ValueError(
|
|
204
|
+
"\ncalculate_MAC() 需要吸收係數資料!\n"
|
|
205
|
+
" 必要輸入: df_abs (Absorption coefficient, Mm-1)"
|
|
206
|
+
)
|
|
207
|
+
if df_ec is None or (hasattr(df_ec, 'empty') and df_ec.empty):
|
|
208
|
+
raise ValueError(
|
|
209
|
+
"\ncalculate_MAC() 需要元素碳資料!\n"
|
|
210
|
+
" 必要輸入: df_ec (Elemental carbon, ug/m3)"
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
result = DataFrame(index=df_abs.index)
|
|
214
|
+
abs_val = df_abs.iloc[:, 0] if isinstance(df_abs, DataFrame) else df_abs
|
|
215
|
+
ec_val = df_ec.iloc[:, 0] if isinstance(df_ec, DataFrame) else df_ec
|
|
216
|
+
result['MAC'] = abs_val / ec_val
|
|
217
|
+
return result
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def calculate_Ox(df_no2: DataFrame, df_o3: DataFrame) -> DataFrame:
|
|
221
|
+
"""
|
|
222
|
+
Calculate total oxidant (Ox = NO2 + O3).
|
|
223
|
+
|
|
224
|
+
Parameters
|
|
225
|
+
----------
|
|
226
|
+
df_no2 : DataFrame
|
|
227
|
+
NO2 concentration (ppb). Any column name accepted.
|
|
228
|
+
df_o3 : DataFrame
|
|
229
|
+
O3 concentration (ppb). Any column name accepted.
|
|
230
|
+
|
|
231
|
+
Returns
|
|
232
|
+
-------
|
|
233
|
+
DataFrame
|
|
234
|
+
Ox values (ppb).
|
|
235
|
+
|
|
236
|
+
Raises
|
|
237
|
+
------
|
|
238
|
+
ValueError
|
|
239
|
+
If df_no2 or df_o3 is None or empty.
|
|
240
|
+
"""
|
|
241
|
+
if df_no2 is None or (hasattr(df_no2, 'empty') and df_no2.empty):
|
|
242
|
+
raise ValueError(
|
|
243
|
+
"\ncalculate_Ox() 需要 NO2 資料!\n"
|
|
244
|
+
" 必要輸入: df_no2 (NO2 concentration, ppb)"
|
|
245
|
+
)
|
|
246
|
+
if df_o3 is None or (hasattr(df_o3, 'empty') and df_o3.empty):
|
|
247
|
+
raise ValueError(
|
|
248
|
+
"\ncalculate_Ox() 需要 O3 資料!\n"
|
|
249
|
+
" 必要輸入: df_o3 (O3 concentration, ppb)"
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
result = DataFrame(index=df_no2.index)
|
|
253
|
+
no2_val = df_no2.iloc[:, 0] if isinstance(df_no2, DataFrame) else df_no2
|
|
254
|
+
o3_val = df_o3.iloc[:, 0] if isinstance(df_o3, DataFrame) else df_o3
|
|
255
|
+
result['Ox'] = no2_val + o3_val
|
|
256
|
+
return result
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def calculate_fRH(df_ext_wet: DataFrame, df_ext_dry: DataFrame) -> DataFrame:
|
|
260
|
+
"""
|
|
261
|
+
Calculate the hygroscopic growth factor for extinction (fRH).
|
|
262
|
+
|
|
263
|
+
fRH = Bext(wet) / Bext(dry)
|
|
264
|
+
|
|
265
|
+
Parameters
|
|
266
|
+
----------
|
|
267
|
+
df_ext_wet : DataFrame
|
|
268
|
+
Wet extinction coefficient. Any column name accepted.
|
|
269
|
+
df_ext_dry : DataFrame
|
|
270
|
+
Dry extinction coefficient. Any column name accepted.
|
|
271
|
+
|
|
272
|
+
Returns
|
|
273
|
+
-------
|
|
274
|
+
DataFrame
|
|
275
|
+
fRH values.
|
|
276
|
+
|
|
277
|
+
Raises
|
|
278
|
+
------
|
|
279
|
+
ValueError
|
|
280
|
+
If df_ext_wet or df_ext_dry is None or empty.
|
|
281
|
+
"""
|
|
282
|
+
if df_ext_wet is None or (hasattr(df_ext_wet, 'empty') and df_ext_wet.empty):
|
|
283
|
+
raise ValueError(
|
|
284
|
+
"\ncalculate_fRH() 需要濕消光係數資料!\n"
|
|
285
|
+
" 必要輸入: df_ext_wet (Wet extinction coefficient)"
|
|
286
|
+
)
|
|
287
|
+
if df_ext_dry is None or (hasattr(df_ext_dry, 'empty') and df_ext_dry.empty):
|
|
288
|
+
raise ValueError(
|
|
289
|
+
"\ncalculate_fRH() 需要乾消光係數資料!\n"
|
|
290
|
+
" 必要輸入: df_ext_dry (Dry extinction coefficient)"
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
result = DataFrame(index=df_ext_wet.index)
|
|
294
|
+
wet_val = df_ext_wet.iloc[:, 0] if isinstance(df_ext_wet, DataFrame) else df_ext_wet
|
|
295
|
+
dry_val = df_ext_dry.iloc[:, 0] if isinstance(df_ext_dry, DataFrame) else df_ext_dry
|
|
296
|
+
result['fRH'] = wet_val / dry_val
|
|
297
|
+
return result
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def get_required_columns():
|
|
301
|
+
"""
|
|
302
|
+
Get required inputs for derived parameter functions.
|
|
303
|
+
|
|
304
|
+
Returns
|
|
305
|
+
-------
|
|
306
|
+
dict
|
|
307
|
+
Dictionary with function names as keys and required inputs as values.
|
|
308
|
+
|
|
309
|
+
Examples
|
|
310
|
+
--------
|
|
311
|
+
>>> cols = get_required_columns()
|
|
312
|
+
>>> print(cols['calculate_MAC'])
|
|
313
|
+
"""
|
|
314
|
+
return {
|
|
315
|
+
'derived_parameters': {
|
|
316
|
+
'description': '所有輸入皆為可選,根據提供的資料計算相應的衍生參數',
|
|
317
|
+
'PG': 'df_sca + df_abs',
|
|
318
|
+
'MAC': 'df_abs + df_ec',
|
|
319
|
+
'Ox': 'df_no2 + df_o3',
|
|
320
|
+
'Vis_cal': 'df_ext',
|
|
321
|
+
'fRH_IMPR': "df_improve['total_ext', 'total_ext_dry']",
|
|
322
|
+
'OCEC_ratio': 'df_oc + df_ec',
|
|
323
|
+
'PM1_PM25_ratio': 'df_pm1 + df_pm25'
|
|
324
|
+
},
|
|
325
|
+
'calculate_visibility': ['Extinction coefficient (any column)'],
|
|
326
|
+
'calculate_MAC': ['Absorption coefficient (any column)', 'EC (any column)'],
|
|
327
|
+
'calculate_Ox': ['NO2 (any column)', 'O3 (any column)'],
|
|
328
|
+
'calculate_fRH': ['Wet extinction (any column)', 'Dry extinction (any column)'],
|
|
329
|
+
'calculate_BrC_absorption': ['Absorption coefficients at multiple wavelengths (DataFrame with abs_370, abs_470, etc.)']
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def calculate_BrC_absorption(df_abs: DataFrame,
|
|
334
|
+
wavelengths: list[int] = None,
|
|
335
|
+
ref_wavelength: int = 880,
|
|
336
|
+
aae_bc: float = 1.0) -> DataFrame:
|
|
337
|
+
"""
|
|
338
|
+
Calculate Brown Carbon (BrC) absorption by separating BC and BrC contributions.
|
|
339
|
+
|
|
340
|
+
This method assumes:
|
|
341
|
+
1. Black Carbon (BC) has a wavelength-independent AAE (default: 1.0)
|
|
342
|
+
2. Absorption at the reference wavelength (880nm) is entirely from BC
|
|
343
|
+
3. BrC absorption = Total absorption - BC absorption
|
|
344
|
+
|
|
345
|
+
The BC absorption at any wavelength λ is calculated as:
|
|
346
|
+
abs_BC(λ) = abs_ref * (ref_wavelength / λ)^AAE_BC
|
|
347
|
+
|
|
348
|
+
Parameters
|
|
349
|
+
----------
|
|
350
|
+
df_abs : DataFrame
|
|
351
|
+
Absorption coefficient data with columns like 'abs_370', 'abs_470', etc.
|
|
352
|
+
Units should be Mm-1.
|
|
353
|
+
wavelengths : list[int], optional
|
|
354
|
+
List of wavelengths to calculate BrC absorption for.
|
|
355
|
+
Default: [370, 470, 520, 590, 660] (all wavelengths shorter than reference)
|
|
356
|
+
ref_wavelength : int, default=880
|
|
357
|
+
Reference wavelength (nm) where absorption is assumed to be purely BC.
|
|
358
|
+
aae_bc : float, default=1.0
|
|
359
|
+
Absorption Ångström Exponent for Black Carbon.
|
|
360
|
+
Literature values typically range from 0.8 to 1.1 for fresh BC.
|
|
361
|
+
|
|
362
|
+
Returns
|
|
363
|
+
-------
|
|
364
|
+
DataFrame
|
|
365
|
+
DataFrame with columns:
|
|
366
|
+
- abs_BC_{wl}: BC absorption at each wavelength (Mm-1)
|
|
367
|
+
- abs_BrC_{wl}: BrC absorption at each wavelength (Mm-1, NaN if invalid)
|
|
368
|
+
- BrC_fraction_{wl}: BrC contribution fraction (0-1, NaN if invalid)
|
|
369
|
+
- AAE_BrC: Absorption Ångström Exponent of BrC (NaN if invalid)
|
|
370
|
+
|
|
371
|
+
Notes
|
|
372
|
+
-----
|
|
373
|
+
This separation method is based on the assumption that BC has a constant AAE
|
|
374
|
+
of approximately 1.0 across all wavelengths, while BrC exhibits stronger
|
|
375
|
+
absorption at shorter wavelengths (higher AAE).
|
|
376
|
+
|
|
377
|
+
The AAE_BC = 1.0 assumption comes from Mie theory calculations for pure
|
|
378
|
+
graphitic carbon particles. However, this value can vary depending on
|
|
379
|
+
particle size and mixing state.
|
|
380
|
+
|
|
381
|
+
**Validity check**: If calculated BC absorption exceeds total absorption
|
|
382
|
+
at ANY wavelength, the entire row is marked as invalid (NaN for all BrC values).
|
|
383
|
+
This indicates the separation assumption is not valid for that data point.
|
|
384
|
+
|
|
385
|
+
References
|
|
386
|
+
----------
|
|
387
|
+
- Lack, D.A. and Langridge, J.M. (2013). Atmos. Chem. Phys., 13, 8321-8341.
|
|
388
|
+
- Kirchstetter, T.W. et al. (2004). J. Geophys. Res., 109, D21208.
|
|
389
|
+
|
|
390
|
+
Examples
|
|
391
|
+
--------
|
|
392
|
+
>>> from AeroViz import DataProcess
|
|
393
|
+
>>> optical = DataProcess(method='Optical')
|
|
394
|
+
>>> brc_result = optical.BrC(df_abs, aae_bc=1.0)
|
|
395
|
+
"""
|
|
396
|
+
if df_abs is None or df_abs.empty:
|
|
397
|
+
raise ValueError(
|
|
398
|
+
"\ncalculate_BrC_absorption() 需要多波長吸收係數資料!\n"
|
|
399
|
+
" 必要輸入: df_abs (含有 abs_370, abs_470, ... 等欄位的 DataFrame)"
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
# Default wavelengths for BrC calculation (shorter than reference)
|
|
403
|
+
if wavelengths is None:
|
|
404
|
+
wavelengths = [370, 470, 520, 590, 660]
|
|
405
|
+
|
|
406
|
+
# Find available absorption columns
|
|
407
|
+
abs_cols = [col for col in df_abs.columns if col.startswith('abs_')]
|
|
408
|
+
available_wl = []
|
|
409
|
+
for col in abs_cols:
|
|
410
|
+
try:
|
|
411
|
+
wl = int(col.split('_')[1])
|
|
412
|
+
available_wl.append(wl)
|
|
413
|
+
except (ValueError, IndexError):
|
|
414
|
+
continue
|
|
415
|
+
|
|
416
|
+
# Check if reference wavelength is available
|
|
417
|
+
ref_col = f'abs_{ref_wavelength}'
|
|
418
|
+
if ref_col not in df_abs.columns:
|
|
419
|
+
raise ValueError(
|
|
420
|
+
f"\n找不到參考波長 {ref_wavelength}nm 的吸收資料!\n"
|
|
421
|
+
f" 可用的波長: {sorted(available_wl)}\n"
|
|
422
|
+
f" 請確保 df_abs 包含 '{ref_col}' 欄位"
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
# Filter wavelengths to those available and shorter than reference
|
|
426
|
+
calc_wavelengths = [wl for wl in wavelengths
|
|
427
|
+
if wl in available_wl and wl < ref_wavelength]
|
|
428
|
+
|
|
429
|
+
if not calc_wavelengths:
|
|
430
|
+
raise ValueError(
|
|
431
|
+
f"\n沒有可用的短波長資料用於 BrC 計算!\n"
|
|
432
|
+
f" 請求的波長: {wavelengths}\n"
|
|
433
|
+
f" 可用的波長: {sorted(available_wl)}\n"
|
|
434
|
+
f" 參考波長: {ref_wavelength}nm"
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
result = DataFrame(index=df_abs.index)
|
|
438
|
+
|
|
439
|
+
# Reference absorption (assumed to be pure BC at 880nm)
|
|
440
|
+
abs_ref = df_abs[ref_col]
|
|
441
|
+
|
|
442
|
+
# Track if BC > total at any wavelength (invalid separation)
|
|
443
|
+
bc_exceeds_total = np.zeros(len(df_abs), dtype=bool)
|
|
444
|
+
|
|
445
|
+
# Calculate BC and BrC absorption at each wavelength
|
|
446
|
+
brc_abs_data = {}
|
|
447
|
+
bc_abs_data = {}
|
|
448
|
+
|
|
449
|
+
for wl in calc_wavelengths:
|
|
450
|
+
abs_col = f'abs_{wl}'
|
|
451
|
+
abs_total = df_abs[abs_col]
|
|
452
|
+
|
|
453
|
+
# BC absorption at this wavelength using power law
|
|
454
|
+
# abs_BC(λ) = abs_ref * (λ_ref / λ)^AAE_BC
|
|
455
|
+
abs_bc = abs_ref * (ref_wavelength / wl) ** aae_bc
|
|
456
|
+
|
|
457
|
+
# Check if BC exceeds total at this wavelength
|
|
458
|
+
bc_exceeds_total |= (abs_bc > abs_total).values
|
|
459
|
+
|
|
460
|
+
# BrC absorption = Total - BC
|
|
461
|
+
abs_brc = abs_total - abs_bc
|
|
462
|
+
|
|
463
|
+
# BrC fraction (before clipping)
|
|
464
|
+
brc_fraction = np.where(abs_total > 0, abs_brc / abs_total, np.nan)
|
|
465
|
+
|
|
466
|
+
# Store raw values
|
|
467
|
+
bc_abs_data[wl] = abs_bc
|
|
468
|
+
brc_abs_data[wl] = abs_brc.clip(lower=0) # Clip for AAE calculation
|
|
469
|
+
|
|
470
|
+
result[f'abs_BC_{wl}'] = abs_bc
|
|
471
|
+
result[f'abs_BrC_{wl}'] = abs_brc.clip(lower=0)
|
|
472
|
+
result[f'BrC_fraction_{wl}'] = np.where(brc_fraction >= 0, brc_fraction, np.nan)
|
|
473
|
+
|
|
474
|
+
# Calculate BrC AAE using linear regression on log-log scale
|
|
475
|
+
brc_wavelengths = np.array(sorted(brc_abs_data.keys()))
|
|
476
|
+
|
|
477
|
+
def calc_brc_aae(row_data):
|
|
478
|
+
"""Calculate AAE for a single row of BrC absorption data."""
|
|
479
|
+
brc_values = np.array([row_data.get(wl, np.nan) for wl in brc_wavelengths])
|
|
480
|
+
|
|
481
|
+
# Need at least 2 valid points for AAE calculation
|
|
482
|
+
valid_mask = (brc_values > 0) & np.isfinite(brc_values)
|
|
483
|
+
if valid_mask.sum() < 2:
|
|
484
|
+
return np.nan
|
|
485
|
+
|
|
486
|
+
valid_wl = brc_wavelengths[valid_mask]
|
|
487
|
+
valid_brc = brc_values[valid_mask]
|
|
488
|
+
|
|
489
|
+
# Linear fit on log-log scale: log(abs) = -AAE * log(λ) + intercept
|
|
490
|
+
try:
|
|
491
|
+
log_wl = np.log(valid_wl)
|
|
492
|
+
log_brc = np.log(valid_brc)
|
|
493
|
+
slope, _ = np.polyfit(log_wl, log_brc, 1)
|
|
494
|
+
return -slope # AAE is negative of slope
|
|
495
|
+
except (np.linalg.LinAlgError, ValueError):
|
|
496
|
+
return np.nan
|
|
497
|
+
|
|
498
|
+
# Calculate AAE_BrC for each row
|
|
499
|
+
aae_brc_values = []
|
|
500
|
+
for idx in df_abs.index:
|
|
501
|
+
row_data = {wl: brc_abs_data[wl].loc[idx] for wl in brc_wavelengths}
|
|
502
|
+
aae_brc_values.append(calc_brc_aae(row_data))
|
|
503
|
+
|
|
504
|
+
aae_raw = np.array(aae_brc_values)
|
|
505
|
+
|
|
506
|
+
# Validity check: BC must not exceed total absorption at any wavelength
|
|
507
|
+
# If BC > total at any wavelength, the entire row is invalid
|
|
508
|
+
valid_separation = ~bc_exceeds_total
|
|
509
|
+
|
|
510
|
+
# Set all values to NaN for invalid rows (including BC)
|
|
511
|
+
for wl in calc_wavelengths:
|
|
512
|
+
result.loc[~valid_separation, f'abs_BC_{wl}'] = np.nan
|
|
513
|
+
result.loc[~valid_separation, f'abs_BrC_{wl}'] = np.nan
|
|
514
|
+
result.loc[~valid_separation, f'BrC_fraction_{wl}'] = np.nan
|
|
515
|
+
|
|
516
|
+
result['AAE_BrC'] = np.where(valid_separation, aae_raw, np.nan)
|
|
517
|
+
|
|
518
|
+
return result
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Basic extinction and optical property calculations.
|
|
3
|
+
|
|
4
|
+
Required Columns
|
|
5
|
+
----------------
|
|
6
|
+
df_sca:
|
|
7
|
+
- sca_550 : Scattering coefficient at 550nm (Mm-1)
|
|
8
|
+
- SAE : Scattering Angstrom Exponent
|
|
9
|
+
df_abs:
|
|
10
|
+
- abs_550 : Absorption coefficient at 550nm (Mm-1)
|
|
11
|
+
- AAE : Absorption Angstrom Exponent
|
|
12
|
+
- eBC : Equivalent Black Carbon (ng/m3)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from pandas import DataFrame
|
|
16
|
+
|
|
17
|
+
from AeroViz.dataProcess.core import union_index, validate_inputs
|
|
18
|
+
|
|
19
|
+
# Required columns
|
|
20
|
+
REQUIRED_SCA_COLUMNS = ['sca_550', 'SAE']
|
|
21
|
+
REQUIRED_ABS_COLUMNS = ['abs_550', 'AAE', 'eBC']
|
|
22
|
+
|
|
23
|
+
COLUMN_DESCRIPTIONS = {
|
|
24
|
+
'sca_550': 'Scattering coefficient at 550nm 散射係數 (Mm-1)',
|
|
25
|
+
'SAE': 'Scattering Angstrom Exponent 散射埃指數',
|
|
26
|
+
'abs_550': 'Absorption coefficient at 550nm 吸收係數 (Mm-1)',
|
|
27
|
+
'AAE': 'Absorption Angstrom Exponent 吸收埃指數',
|
|
28
|
+
'eBC': 'Equivalent Black Carbon 等效黑碳 (ng/m3)',
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _basic(df_sca, df_abs, df_mass=None, df_no2=None, df_temp=None):
|
|
33
|
+
"""
|
|
34
|
+
Calculate basic optical properties and extinction.
|
|
35
|
+
|
|
36
|
+
Parameters
|
|
37
|
+
----------
|
|
38
|
+
df_sca : DataFrame
|
|
39
|
+
Scattering data with columns: sca_550, SAE
|
|
40
|
+
df_abs : DataFrame
|
|
41
|
+
Absorption data with columns: abs_550, AAE, eBC
|
|
42
|
+
df_mass : DataFrame, optional
|
|
43
|
+
PM mass concentration (ug/m3) for MAE/MSE/MEE calculation
|
|
44
|
+
df_no2 : DataFrame, optional
|
|
45
|
+
NO2 concentration (ppb) for gas absorption
|
|
46
|
+
df_temp : DataFrame, optional
|
|
47
|
+
Temperature (Celsius) for Rayleigh scattering
|
|
48
|
+
|
|
49
|
+
Returns
|
|
50
|
+
-------
|
|
51
|
+
DataFrame
|
|
52
|
+
Optical properties: abs, sca, ext, SSA, SAE, AAE, eBC,
|
|
53
|
+
and optionally MAE, MSE, MEE, abs_gas, sca_gas, ext_all
|
|
54
|
+
|
|
55
|
+
Raises
|
|
56
|
+
------
|
|
57
|
+
ValueError
|
|
58
|
+
If required columns are missing from df_sca or df_abs
|
|
59
|
+
"""
|
|
60
|
+
# Validate required columns
|
|
61
|
+
validate_inputs(df_sca, REQUIRED_SCA_COLUMNS, '_basic (df_sca)', COLUMN_DESCRIPTIONS)
|
|
62
|
+
validate_inputs(df_abs, REQUIRED_ABS_COLUMNS, '_basic (df_abs)', COLUMN_DESCRIPTIONS)
|
|
63
|
+
|
|
64
|
+
df_sca, df_abs, df_mass, df_no2, df_temp = union_index(df_sca, df_abs, df_mass, df_no2, df_temp)
|
|
65
|
+
|
|
66
|
+
df_out = DataFrame()
|
|
67
|
+
|
|
68
|
+
# abs and sca coe
|
|
69
|
+
df_out['abs'] = df_abs['abs_550'].copy()
|
|
70
|
+
df_out['sca'] = df_sca['sca_550'].copy()
|
|
71
|
+
|
|
72
|
+
# extinction coe.
|
|
73
|
+
df_out['ext'] = df_out['abs'] + df_out['sca']
|
|
74
|
+
|
|
75
|
+
# SSA
|
|
76
|
+
df_out['SSA'] = df_out['sca'] / df_out['ext']
|
|
77
|
+
|
|
78
|
+
# SAE, AAE, eBC
|
|
79
|
+
df_out['SAE'] = df_sca['SAE'].copy()
|
|
80
|
+
df_out['AAE'] = df_abs['AAE'].copy()
|
|
81
|
+
df_out['eBC'] = df_abs['eBC'].copy() / 1e3
|
|
82
|
+
|
|
83
|
+
# MAE, MSE, MEE
|
|
84
|
+
if df_mass is not None:
|
|
85
|
+
df_out['MAE'] = df_out['abs'] / df_mass
|
|
86
|
+
df_out['MSE'] = df_out['sca'] / df_mass
|
|
87
|
+
df_out['MEE'] = df_out['MSE'] + df_out['MAE']
|
|
88
|
+
|
|
89
|
+
# gas absorbtion
|
|
90
|
+
if df_no2 is not None:
|
|
91
|
+
df_out['abs_gas'] = df_no2 * .33
|
|
92
|
+
|
|
93
|
+
if df_temp is not None:
|
|
94
|
+
df_out['sca_gas'] = (11.4 * 293 / (273 + df_temp))
|
|
95
|
+
|
|
96
|
+
if df_no2 is not None and df_temp is not None:
|
|
97
|
+
df_out['ext_all'] = df_out['ext'] + df_out['abs_gas'] + df_out['sca_gas']
|
|
98
|
+
|
|
99
|
+
return df_out
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def get_required_columns():
|
|
103
|
+
"""
|
|
104
|
+
Get required column names for basic extinction calculation.
|
|
105
|
+
|
|
106
|
+
Returns
|
|
107
|
+
-------
|
|
108
|
+
dict
|
|
109
|
+
Dictionary with input names as keys and required columns as values.
|
|
110
|
+
|
|
111
|
+
Examples
|
|
112
|
+
--------
|
|
113
|
+
>>> cols = get_required_columns()
|
|
114
|
+
>>> print(cols['df_sca'])
|
|
115
|
+
['sca_550', 'SAE']
|
|
116
|
+
"""
|
|
117
|
+
return {
|
|
118
|
+
'df_sca': REQUIRED_SCA_COLUMNS.copy(),
|
|
119
|
+
'df_abs': REQUIRED_ABS_COLUMNS.copy(),
|
|
120
|
+
'df_mass': 'PM mass (any column) - optional',
|
|
121
|
+
'df_no2': 'NO2 concentration (any column) - optional',
|
|
122
|
+
'df_temp': 'Temperature in Celsius (any column) - optional'
|
|
123
|
+
}
|