AeroViz 0.1.5__py3-none-any.whl → 0.1.7__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/dataProcess/Chemistry/_mass_volume.py +4 -3
- AeroViz/dataProcess/Chemistry/_ocec.py +20 -7
- AeroViz/dataProcess/Optical/_IMPROVE.py +2 -3
- AeroViz/dataProcess/SizeDistr/__init__.py +6 -10
- AeroViz/plot/__init__.py +1 -0
- AeroViz/plot/meteorology/meteorology.py +2 -0
- AeroViz/plot/optical/optical.py +1 -1
- AeroViz/plot/pie.py +14 -2
- AeroViz/plot/radar.py +184 -0
- AeroViz/plot/scatter.py +16 -7
- AeroViz/plot/templates/koschmieder.py +11 -8
- AeroViz/plot/timeseries/timeseries.py +0 -1
- AeroViz/rawDataReader/__init__.py +75 -70
- AeroViz/rawDataReader/config/supported_instruments.py +70 -38
- AeroViz/rawDataReader/core/__init__.py +208 -178
- AeroViz/rawDataReader/script/AE33.py +1 -1
- AeroViz/rawDataReader/script/AE43.py +1 -1
- AeroViz/rawDataReader/script/APS_3321.py +2 -2
- AeroViz/rawDataReader/script/Aurora.py +1 -1
- AeroViz/rawDataReader/script/BC1054.py +1 -1
- AeroViz/rawDataReader/script/EPA.py +39 -0
- AeroViz/rawDataReader/script/GRIMM.py +1 -1
- AeroViz/rawDataReader/script/IGAC.py +6 -23
- AeroViz/rawDataReader/script/MA350.py +1 -1
- AeroViz/rawDataReader/script/Minion.py +102 -30
- AeroViz/rawDataReader/script/NEPH.py +1 -1
- AeroViz/rawDataReader/script/{Sunset_OCEC.py → OCEC.py} +2 -2
- AeroViz/rawDataReader/script/SMPS.py +77 -0
- AeroViz/rawDataReader/script/TEOM.py +2 -2
- AeroViz/rawDataReader/script/VOC.py +2 -2
- AeroViz/rawDataReader/script/XRF.py +11 -0
- AeroViz/rawDataReader/script/__init__.py +4 -6
- {AeroViz-0.1.5.dist-info → AeroViz-0.1.7.dist-info}/METADATA +57 -32
- {AeroViz-0.1.5.dist-info → AeroViz-0.1.7.dist-info}/RECORD +37 -55
- AeroViz/process/__init__.py +0 -31
- AeroViz/process/core/DataProc.py +0 -19
- AeroViz/process/core/SizeDist.py +0 -90
- AeroViz/process/core/__init__.py +0 -4
- AeroViz/process/method/PyMieScatt_update.py +0 -567
- AeroViz/process/method/__init__.py +0 -2
- AeroViz/process/method/mie_theory.py +0 -260
- AeroViz/process/method/prop.py +0 -62
- AeroViz/process/script/AbstractDistCalc.py +0 -143
- AeroViz/process/script/Chemical.py +0 -177
- AeroViz/process/script/IMPACT.py +0 -49
- AeroViz/process/script/IMPROVE.py +0 -161
- AeroViz/process/script/Others.py +0 -65
- AeroViz/process/script/PSD.py +0 -103
- AeroViz/process/script/PSD_dry.py +0 -93
- AeroViz/process/script/__init__.py +0 -5
- AeroViz/process/script/retrieve_RI.py +0 -69
- AeroViz/rawDataReader/script/EPA_vertical.py +0 -46
- AeroViz/rawDataReader/script/SMPS_TH.py +0 -41
- AeroViz/rawDataReader/script/SMPS_aim11.py +0 -51
- AeroViz/rawDataReader/script/SMPS_genr.py +0 -51
- AeroViz/rawDataReader/script/Table.py +0 -27
- {AeroViz-0.1.5.dist-info → AeroViz-0.1.7.dist-info}/LICENSE +0 -0
- {AeroViz-0.1.5.dist-info → AeroViz-0.1.7.dist-info}/WHEEL +0 -0
- {AeroViz-0.1.5.dist-info → AeroViz-0.1.7.dist-info}/top_level.txt +0 -0
|
@@ -1,38 +1,78 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
1
3
|
import numpy as np
|
|
2
|
-
|
|
4
|
+
import pandas
|
|
5
|
+
from pandas import read_excel, to_numeric
|
|
3
6
|
|
|
4
7
|
from AeroViz.rawDataReader.core import AbstractReader
|
|
5
8
|
|
|
9
|
+
pandas.set_option("future.no_silent_downcasting", True)
|
|
10
|
+
|
|
11
|
+
desired_order1 = ['SO2', 'NO', 'NOx', 'NO2', 'CO', 'O3', 'THC', 'NMHC',
|
|
12
|
+
'CH4', 'PM10', 'PM2.5', 'WS', 'WD', 'AT', 'RH']
|
|
13
|
+
|
|
14
|
+
desired_order2 = ['Benzene', 'Toluene', 'EthylBenzene', 'm/p-Xylene', 'o-Xylene']
|
|
15
|
+
|
|
16
|
+
desired_order3 = ['Al', 'Si', 'P', 'S', 'Cl', 'K', 'Ca', 'Ti', 'V', 'Cr', 'Mn', 'Fe',
|
|
17
|
+
'Co', 'Ni', 'Cu', 'Zn', 'Ga', 'Ge', 'As', 'Se', 'Br', 'Rb', 'Sr',
|
|
18
|
+
'Y', 'Zr', 'Nb', 'Mo', 'Pd', 'Ag', 'Cd', 'In', 'Sn', 'Sb', 'Te',
|
|
19
|
+
'Cs', 'Ba', 'La', 'Ce', 'W', 'Pt', 'Au', 'Hg', 'Tl', 'Pb', 'Bi']
|
|
20
|
+
|
|
21
|
+
desired_order4 = ['NH3', 'HF', 'HCl', 'HNO2', 'HNO3', 'G-SO2',
|
|
22
|
+
'Na+', 'NH4+', 'K+', 'Mg2+', 'Ca2+',
|
|
23
|
+
'F-', 'Cl-', 'NO2-', 'NO3-', 'PO43-', 'SO42-']
|
|
24
|
+
|
|
6
25
|
|
|
7
26
|
class Reader(AbstractReader):
|
|
8
27
|
nam = 'Minion'
|
|
9
28
|
|
|
10
29
|
def _raw_reader(self, file):
|
|
11
|
-
|
|
12
|
-
|
|
30
|
+
# 讀取 Excel 文件
|
|
31
|
+
df = read_excel(file, index_col=0, parse_dates=True)
|
|
32
|
+
|
|
33
|
+
# 重命名列,去除空白
|
|
34
|
+
df = df.rename(columns=lambda x: x.strip())
|
|
35
|
+
|
|
36
|
+
# 保存單位行並給它一個名稱
|
|
37
|
+
units = df.iloc[0].copy()
|
|
13
38
|
|
|
14
|
-
|
|
15
|
-
|
|
39
|
+
# 刪除原始數據中的單位行
|
|
40
|
+
df = df.iloc[1:]
|
|
16
41
|
|
|
17
|
-
|
|
42
|
+
# 替換特定值
|
|
43
|
+
df = df.replace({'維護校正': '*', np.nan: '-', '0L': '_', 'Nodata': '-'}, inplace=False)
|
|
44
|
+
df = df.replace(to_replace=r'\d*[#]\b', value='#', regex=True)
|
|
45
|
+
df = df.replace(to_replace=r'\d*[L]\b', value='_', regex=True)
|
|
18
46
|
|
|
19
|
-
|
|
47
|
+
# 處理除了'WD'列的 0 值
|
|
48
|
+
non_wd_columns = [col for col in df.columns if col != 'WD']
|
|
49
|
+
df.loc[:, non_wd_columns] = df.loc[:, non_wd_columns].replace({0: '_'})
|
|
50
|
+
|
|
51
|
+
# 重新排序列
|
|
52
|
+
df = self.reorder_dataframe_columns(df, [desired_order1, desired_order2, desired_order3, desired_order4])
|
|
53
|
+
|
|
54
|
+
# 將單位行添加回 DataFrame
|
|
55
|
+
# df = concat([units.to_frame().T, df])
|
|
56
|
+
|
|
57
|
+
df.index.name = 'Time'
|
|
58
|
+
|
|
59
|
+
return df.loc[~df.index.duplicated() & df.index.notna()]
|
|
20
60
|
|
|
21
61
|
def _QC(self, _df):
|
|
62
|
+
# remove negative value
|
|
63
|
+
_df = _df.mask((_df < 0).copy())
|
|
64
|
+
|
|
22
65
|
# XRF QAQC
|
|
23
66
|
_df = self.XRF_QAQC(_df)
|
|
24
67
|
|
|
25
68
|
# ions balance
|
|
26
|
-
_df = self.
|
|
27
|
-
|
|
28
|
-
# remove negative value
|
|
29
|
-
_df = _df.mask((_df < 0).copy())
|
|
69
|
+
_df = self.IGAC_QAQC(_df)
|
|
30
70
|
|
|
31
71
|
# QC data in 6h
|
|
32
|
-
return _df.resample('6h').apply(self.
|
|
72
|
+
return _df.resample('6h').apply(self.n_sigma_QC).resample(self.meta.get("freq")).mean()
|
|
33
73
|
|
|
34
74
|
# base on Xact 625i Minimum Decision Limit (MDL) for XRF in ng/m3, 60 min sample time
|
|
35
|
-
def XRF_QAQC(self, df):
|
|
75
|
+
def XRF_QAQC(self, df, MDL_replace: Literal['nan', '0.5 * MDL'] = 'nan'):
|
|
36
76
|
MDL = {
|
|
37
77
|
'Al': 100, 'Si': 18, 'P': 5.2, 'S': 3.2,
|
|
38
78
|
'Cl': 1.7, 'K': 1.2, 'Ca': 0.3, 'Ti': 1.6,
|
|
@@ -40,34 +80,68 @@ class Reader(AbstractReader):
|
|
|
40
80
|
'Co': 0.14, 'Ni': 0.096, 'Cu': 0.079, 'Zn': 0.067,
|
|
41
81
|
'Ga': 0.059, 'Ge': 0.056, 'As': 0.063, 'Se': 0.081,
|
|
42
82
|
'Br': 0.1, 'Rb': 0.19, 'Sr': 0.22, 'Y': 0.28,
|
|
43
|
-
'Zr': 0.33, 'Nb': 0.41, 'Mo': 0.48, '
|
|
44
|
-
'Cd': 2.5, 'In': 3.1, 'Sn': 4.1,
|
|
45
|
-
'
|
|
46
|
-
'La': 0.36, 'Ce': 0.3, '
|
|
47
|
-
'
|
|
83
|
+
'Zr': 0.33, 'Nb': 0.41, 'Mo': 0.48, 'Pd': 2.2,
|
|
84
|
+
'Ag': 1.9, 'Cd': 2.5, 'In': 3.1, 'Sn': 4.1,
|
|
85
|
+
'Sb': 5.2, 'Te': 0.6, 'Cs': 0.37, 'Ba': 0.39,
|
|
86
|
+
'La': 0.36, 'Ce': 0.3, 'W': 0.0001, 'Pt': 0.12,
|
|
87
|
+
'Au': 0.1, 'Hg': 0.12, 'Tl': 0.12, 'Pb': 0.13,
|
|
88
|
+
'Bi': 0.13
|
|
48
89
|
}
|
|
49
|
-
# 將小於 MDL 值的數據替換為
|
|
90
|
+
# 將小於 MDL 值的數據替換為 nan or 5/6 MDL
|
|
50
91
|
for element, threshold in MDL.items():
|
|
51
92
|
if element in df.columns:
|
|
52
|
-
|
|
93
|
+
rep = np.nan if MDL_replace == 'nan' else 0.5 * threshold
|
|
94
|
+
df[element] = df[element].where(df[element] >= threshold, rep)
|
|
53
95
|
|
|
54
96
|
self.logger.info(f"{'=' * 60}")
|
|
55
97
|
self.logger.info(f"XRF QAQC summary:")
|
|
56
98
|
self.logger.info("\t\ttransform values below MDL to NaN")
|
|
57
99
|
self.logger.info(f"{'=' * 60}")
|
|
58
100
|
|
|
101
|
+
# 轉換單位 ng/m3 -> ug/m3
|
|
102
|
+
if df.Al.max() > 10 and df.Fe.max() > 10:
|
|
103
|
+
# 確保 MDL.keys() 中的所有列都存在於 _df 中
|
|
104
|
+
columns_to_convert = [col for col in MDL.keys() if col in df.columns]
|
|
105
|
+
|
|
106
|
+
df[columns_to_convert] = df[columns_to_convert].div(1000)
|
|
107
|
+
|
|
59
108
|
return df
|
|
60
109
|
|
|
61
|
-
def
|
|
110
|
+
def IGAC_QAQC(self, df, tolerance=1):
|
|
62
111
|
"""
|
|
63
112
|
Calculate the balance of ions in the system
|
|
64
113
|
"""
|
|
114
|
+
# https://www.yangyao-env.com/web/product/product_in2.jsp?pd_id=PD1640151884502
|
|
115
|
+
MDL = {
|
|
116
|
+
'HF': 0.08, 'HCl': 0.05, 'HNO2': 0.01, 'HNO3': 0.05, 'G-SO2': 0.05, 'NH3': 0.1,
|
|
117
|
+
'Na+': 0.05, 'NH4+': 0.08, 'K+': 0.08, 'Mg2+': 0.05, 'Ca2+': 0.05,
|
|
118
|
+
'F-': 0.08, 'Cl-': 0.05, 'NO2-': 0.05, 'NO3-': 0.01, 'PO43-': None, 'SO42-': 0.05,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
MR = {
|
|
122
|
+
'HF': 200, 'HCl': 200, 'HNO2': 200, 'HNO3': 200, 'G-SO2': 200, 'NH3': 300,
|
|
123
|
+
'Na+': 300, 'NH4+': 300, 'K+': 300, 'Mg2+': 300, 'Ca2+': 300,
|
|
124
|
+
'F-': 300, 'Cl-': 300, 'NO2-': 300, 'NO3-': 300, 'PO43-': None, 'SO42-': 300,
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
_cation, _anion, _main = (['Na+', 'NH4+', 'K+', 'Mg2+', 'Ca2+'],
|
|
128
|
+
['Cl-', 'NO2-', 'NO3-', 'SO42-'],
|
|
129
|
+
['SO42-', 'NO3-', 'NH4+'])
|
|
130
|
+
# QC: replace values below MDL with 0.5 * MDL -> ions balance -> PM2.5 > main salt
|
|
131
|
+
# mass tolerance = 0.3, ions balance tolerance = 0.3
|
|
132
|
+
|
|
133
|
+
# # conc. of main salt should be present at the same time (NH4+, SO42-, NO3-)
|
|
134
|
+
# _df_salt = df.mask(df.sum(axis=1, min_count=1) > df.PM25).dropna(subset=_main).copy()
|
|
135
|
+
|
|
65
136
|
# Define the ions
|
|
66
|
-
item = ['Na+', 'NH4+', 'K+', 'Mg2+', 'Ca2+', '
|
|
137
|
+
item = ['Na+', 'NH4+', 'K+', 'Mg2+', 'Ca2+', 'Cl-', 'NO2-', 'NO3-', 'SO42-']
|
|
67
138
|
|
|
68
139
|
# Calculate the balance
|
|
69
|
-
_df = df[item].
|
|
70
|
-
|
|
140
|
+
_df = df[item].apply(lambda x: to_numeric(x, errors='coerce'))
|
|
141
|
+
|
|
142
|
+
# for (_key, _df_col) in _df.items():
|
|
143
|
+
# _df[_key] = _df_col.mask(_df_col < MDL[_key], MDL[_key] / 2)
|
|
144
|
+
|
|
71
145
|
_df['+_mole'] = _df[['Na+', 'NH4+', 'K+', 'Mg2+', 'Ca2+']].div([23, 18, 39, (24 / 2), (40 / 2)]).sum(axis=1,
|
|
72
146
|
skipna=True)
|
|
73
147
|
_df['-_mole'] = _df[['Cl-', 'NO2-', 'NO3-', 'SO42-']].div([35.5, 46, 62, (96 / 2)]).sum(axis=1, skipna=True)
|
|
@@ -79,12 +153,8 @@ class Reader(AbstractReader):
|
|
|
79
153
|
lower_bound, upper_bound = 1 - tolerance, 1 + tolerance
|
|
80
154
|
|
|
81
155
|
# 根据ratio决定是否保留原始数据
|
|
82
|
-
valid_mask = (
|
|
83
|
-
|
|
84
|
-
(_df['ratio'] >= lower_bound) &
|
|
85
|
-
~np.isnan(_df['+_mole']) &
|
|
86
|
-
~np.isnan(_df['-_mole'])
|
|
87
|
-
)
|
|
156
|
+
valid_mask = ((_df['ratio'] <= upper_bound) & (_df['ratio'] >= lower_bound) &
|
|
157
|
+
~np.isnan(_df['+_mole']) & ~np.isnan(_df['-_mole']))
|
|
88
158
|
|
|
89
159
|
# 保留数据或将不符合条件的行设为NaN
|
|
90
160
|
df.loc[~valid_mask, item] = np.nan
|
|
@@ -100,4 +170,6 @@ class Reader(AbstractReader):
|
|
|
100
170
|
if retained_percentage < 70:
|
|
101
171
|
self.logger.warning("Warning: The percentage of retained data is less than 70%")
|
|
102
172
|
|
|
173
|
+
# print(f"\tretain {retained_percentage.__round__(0)}% data within tolerance {tolerance}")
|
|
174
|
+
|
|
103
175
|
return df
|
|
@@ -67,4 +67,4 @@ class Reader(AbstractReader):
|
|
|
67
67
|
_df = _df[(_df['BB'] < _df['B']) & (_df['BG'] < _df['G']) & (_df['BR'] < _df['R'])]
|
|
68
68
|
|
|
69
69
|
# QC data in 1h
|
|
70
|
-
return _df.resample('1h').apply(self.
|
|
70
|
+
return _df.resample('1h').apply(self.n_sigma_QC).resample(self.meta.get("freq")).mean()
|
|
@@ -4,7 +4,7 @@ from AeroViz.rawDataReader.core import AbstractReader
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class Reader(AbstractReader):
|
|
7
|
-
nam = '
|
|
7
|
+
nam = 'OCEC'
|
|
8
8
|
|
|
9
9
|
def _raw_reader(self, file):
|
|
10
10
|
with open(file, 'r', encoding='utf-8', errors='ignore') as f:
|
|
@@ -51,7 +51,7 @@ class Reader(AbstractReader):
|
|
|
51
51
|
def _QC(self, _df):
|
|
52
52
|
import numpy as np
|
|
53
53
|
|
|
54
|
-
_df = _df.
|
|
54
|
+
_df = _df.mask((_df <= 0) | (_df > 100)).copy()
|
|
55
55
|
|
|
56
56
|
thresholds = {
|
|
57
57
|
'Thermal_OC': 0.3,
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import csv
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from pandas import to_datetime, to_numeric, read_csv, isna
|
|
5
|
+
|
|
6
|
+
from AeroViz.rawDataReader.core import AbstractReader
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def find_header_row(file_obj, delimiter):
|
|
10
|
+
csv_reader = csv.reader(file_obj, delimiter=delimiter)
|
|
11
|
+
for skip, row in enumerate(csv_reader):
|
|
12
|
+
if row and (row[0] in ['Sample #', 'Scan Number']):
|
|
13
|
+
return skip
|
|
14
|
+
raise ValueError("Header row not found")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def parse_date(df, date_format):
|
|
18
|
+
if 'Date' in df.columns and 'Start Time' in df.columns:
|
|
19
|
+
return to_datetime(df['Date'] + ' ' + df['Start Time'], format=date_format, errors='coerce')
|
|
20
|
+
elif 'DateTime Sample Start' in df.columns:
|
|
21
|
+
return to_datetime(df['DateTime Sample Start'], format=date_format, errors='coerce')
|
|
22
|
+
else:
|
|
23
|
+
raise ValueError("Expected date columns not found")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Reader(AbstractReader):
|
|
27
|
+
nam = 'SMPS'
|
|
28
|
+
|
|
29
|
+
def _raw_reader(self, file):
|
|
30
|
+
with open(file, 'r', encoding='utf-8', errors='ignore') as f:
|
|
31
|
+
if file.suffix.lower() == '.txt':
|
|
32
|
+
delimiter, date_formats = '\t', ['%m/%d/%y %X', '%m/%d/%Y %X']
|
|
33
|
+
else: # csv
|
|
34
|
+
delimiter, date_formats = ',', ['%d/%m/%Y %X']
|
|
35
|
+
|
|
36
|
+
skip = find_header_row(f, delimiter)
|
|
37
|
+
f.seek(0)
|
|
38
|
+
|
|
39
|
+
_df = read_csv(f, sep=delimiter, skiprows=skip)
|
|
40
|
+
|
|
41
|
+
for date_format in date_formats:
|
|
42
|
+
_time_index = parse_date(_df, date_format)
|
|
43
|
+
if not isna(_time_index).all():
|
|
44
|
+
break
|
|
45
|
+
else:
|
|
46
|
+
raise ValueError("Unable to parse dates with given formats")
|
|
47
|
+
|
|
48
|
+
# sequence the data
|
|
49
|
+
numeric_cols = [col for col in _df.columns if col.strip().replace('.', '').isdigit()]
|
|
50
|
+
numeric_cols.sort(key=lambda x: float(x.strip()))
|
|
51
|
+
|
|
52
|
+
_df.index = _time_index
|
|
53
|
+
_df.index.name = 'time'
|
|
54
|
+
|
|
55
|
+
_df_smps = _df[numeric_cols]
|
|
56
|
+
_df_smps.columns = _df_smps.columns.astype(float)
|
|
57
|
+
_df_smps = _df_smps.loc[_df_smps.index.dropna().copy()]
|
|
58
|
+
|
|
59
|
+
return _df_smps.apply(to_numeric, errors='coerce')
|
|
60
|
+
|
|
61
|
+
# QC data
|
|
62
|
+
def _QC(self, _df):
|
|
63
|
+
|
|
64
|
+
# mask out the data size lower than 7
|
|
65
|
+
_df['total'] = _df.sum(axis=1, min_count=1) * (np.diff(np.log(_df.keys().to_numpy(float)))).mean()
|
|
66
|
+
_df_size = _df['total'].dropna().resample('1h').size().resample(_df.index.freq).ffill()
|
|
67
|
+
_df = _df.mask(_df_size < 7)
|
|
68
|
+
|
|
69
|
+
# remove total conc. lower than 2000
|
|
70
|
+
_df = _df.mask(_df['total'] < 2000)
|
|
71
|
+
|
|
72
|
+
# remove the bin over 400 nm which num. conc. larger than 4000
|
|
73
|
+
_df_remv_ky = _df.keys()[:-2][_df.keys()[:-2] >= 400.]
|
|
74
|
+
|
|
75
|
+
_df[_df_remv_ky] = _df[_df_remv_ky].copy().mask(_df[_df_remv_ky] > 4000.)
|
|
76
|
+
|
|
77
|
+
return _df[_df.keys()[:-1]]
|
|
@@ -25,9 +25,9 @@ class Reader(AbstractReader):
|
|
|
25
25
|
|
|
26
26
|
_df = _df.set_index(to_datetime(_tm_idx, errors='coerce', format='%d - %m - %Y %X'))
|
|
27
27
|
|
|
28
|
-
_df = _df.where(_df['status'] <
|
|
28
|
+
_df = _df.where(_df['status'] < 1)
|
|
29
29
|
|
|
30
|
-
_df = _df[['PM_NV', 'PM_Total', 'noise'
|
|
30
|
+
_df = _df[['PM_NV', 'PM_Total', 'noise']]
|
|
31
31
|
|
|
32
32
|
return _df.loc[~_df.index.duplicated() & _df.index.notna()]
|
|
33
33
|
|
|
@@ -20,8 +20,8 @@ class Reader(AbstractReader):
|
|
|
20
20
|
|
|
21
21
|
if invalid_keys:
|
|
22
22
|
self.logger.warning(f'{invalid_keys} are not supported keys.')
|
|
23
|
-
print(f'\n\t
|
|
24
|
-
f'\n\
|
|
23
|
+
print(f'\n\t{invalid_keys} are not supported keys.'
|
|
24
|
+
f'\n\tPlease check the\033[91m support_voc.md\033[0m file to use the correct name.')
|
|
25
25
|
|
|
26
26
|
if valid_keys:
|
|
27
27
|
return _df[valid_keys].loc[~_df.index.duplicated() & _df.index.notna()]
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
__all__ = [
|
|
2
2
|
'NEPH',
|
|
3
3
|
'Aurora',
|
|
4
|
-
'
|
|
5
|
-
'SMPS_genr',
|
|
6
|
-
'SMPS_aim11',
|
|
4
|
+
'SMPS',
|
|
7
5
|
'APS_3321',
|
|
8
6
|
'GRIMM',
|
|
9
7
|
'AE33',
|
|
@@ -11,10 +9,10 @@ __all__ = [
|
|
|
11
9
|
'BC1054',
|
|
12
10
|
'MA350',
|
|
13
11
|
'TEOM',
|
|
14
|
-
'
|
|
12
|
+
'OCEC',
|
|
15
13
|
'IGAC',
|
|
14
|
+
'XRF',
|
|
16
15
|
'VOC',
|
|
17
|
-
'
|
|
18
|
-
'EPA_vertical',
|
|
16
|
+
'EPA',
|
|
19
17
|
'Minion'
|
|
20
18
|
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: AeroViz
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.7
|
|
4
4
|
Summary: Aerosol science
|
|
5
5
|
Home-page: https://github.com/Alex870521/AeroViz
|
|
6
6
|
Author: alex
|
|
@@ -11,14 +11,15 @@ Classifier: Operating System :: OS Independent
|
|
|
11
11
|
Requires-Python: >=3.12
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
|
-
Requires-Dist: pandas
|
|
15
|
-
Requires-Dist: numpy
|
|
16
|
-
Requires-Dist: matplotlib
|
|
17
|
-
Requires-Dist:
|
|
18
|
-
Requires-Dist:
|
|
19
|
-
Requires-Dist: scikit-learn
|
|
20
|
-
Requires-Dist: windrose
|
|
21
|
-
Requires-Dist: tabulate
|
|
14
|
+
Requires-Dist: pandas ==2.2.2
|
|
15
|
+
Requires-Dist: numpy ==1.26.4
|
|
16
|
+
Requires-Dist: matplotlib ==3.8.4
|
|
17
|
+
Requires-Dist: scipy ==1.14.0
|
|
18
|
+
Requires-Dist: seaborn ==0.13.2
|
|
19
|
+
Requires-Dist: scikit-learn ==1.5.1
|
|
20
|
+
Requires-Dist: windrose ==1.9.2
|
|
21
|
+
Requires-Dist: tabulate ==0.9.0
|
|
22
|
+
Requires-Dist: rich ~=13.7.1
|
|
22
23
|
|
|
23
24
|
## <div align="center">AeroViz for Aerosol Science Visualization</div>
|
|
24
25
|
|
|
@@ -27,7 +28,7 @@ Requires-Dist: tabulate
|
|
|
27
28
|
<img alt="Static Badge" src="https://img.shields.io/badge/python-3.12-blue?logo=python">
|
|
28
29
|
<img alt="Static Badge" src="https://img.shields.io/badge/License-MIT-yellow">
|
|
29
30
|
<img alt="Static Badge" src="https://img.shields.io/badge/github-updating-red?logo=github">
|
|
30
|
-
<img src="https://img.shields.io/badge/testing-green?logo=Pytest&logoColor=blue">
|
|
31
|
+
<img alt="Static Badge" src="https://img.shields.io/badge/testing-green?logo=Pytest&logoColor=blue">
|
|
31
32
|
|
|
32
33
|
</p>
|
|
33
34
|
|
|
@@ -52,42 +53,66 @@ Requires-Dist: tabulate
|
|
|
52
53
|
## <div align="center">Installation</div>
|
|
53
54
|
|
|
54
55
|
```bash
|
|
55
|
-
pip install AeroViz
|
|
56
|
+
pip install AeroViz
|
|
56
57
|
```
|
|
57
58
|
|
|
58
|
-
## <div align="center">
|
|
59
|
+
## <div align="center">Quick Start</div>
|
|
59
60
|
|
|
60
61
|
```python
|
|
61
62
|
import AeroViz
|
|
63
|
+
from AeroViz import RawDataReader, DataProcess, plot
|
|
64
|
+
|
|
65
|
+
# Read data from a supported instrument
|
|
66
|
+
data = RawDataReader('NEPH', '/path/to/data', start='2024-01-01', end='2024-01-31')
|
|
67
|
+
|
|
68
|
+
# Create a visualization
|
|
69
|
+
plot.timeseries(data, y='scattering_coefficient')
|
|
62
70
|
```
|
|
63
71
|
|
|
64
|
-
|
|
72
|
+
For more detailed usage instructions, please refer to our [User Guide]().
|
|
73
|
+
|
|
74
|
+
## RawDataReader
|
|
75
|
+
|
|
76
|
+
RawDataReader supports a wide range of aerosol instruments, including NEPH, SMPS, AE33, and many more. It handles
|
|
77
|
+
various file types and time resolutions, making data processing efficient and standardized.
|
|
78
|
+
|
|
79
|
+
For a detailed list of supported instruments, file types, and data columns, please refer to
|
|
80
|
+
our [RawDataReader Usage Guide](docs/RawDataReader_Usage_Guide.md) in the `docs` folder.
|
|
81
|
+
|
|
82
|
+
### Key Features:
|
|
83
|
+
|
|
84
|
+
- Supports multiple aerosol instruments
|
|
85
|
+
- Applies customizable quality control measures
|
|
86
|
+
- Offers flexible data filtering and resampling options
|
|
87
|
+
- Enables easy data export to CSV format
|
|
65
88
|
|
|
66
|
-
|
|
67
|
-
> We are continuously working to support more instruments. Please check back for updates or contribute to our project on
|
|
68
|
-
> GitHub.
|
|
89
|
+
### Supported Instruments
|
|
69
90
|
|
|
70
91
|
The AeroViz project currently supports data from the following instruments:
|
|
71
92
|
|
|
72
|
-
-
|
|
73
|
-
-
|
|
74
|
-
-
|
|
75
|
-
-
|
|
76
|
-
-
|
|
77
|
-
-
|
|
78
|
-
-
|
|
79
|
-
-
|
|
80
|
-
-
|
|
81
|
-
-
|
|
82
|
-
-
|
|
83
|
-
-
|
|
84
|
-
-
|
|
93
|
+
- SMPS (Scanning Mobility Particle Sizer)
|
|
94
|
+
- APS (Aerodynamic Particle Sizer)
|
|
95
|
+
- GRIMM (GRIMM Aerosol Technik)
|
|
96
|
+
- TEOM (Continuous Ambient Particulate Monitor)
|
|
97
|
+
- NEPH (Nephelometer)
|
|
98
|
+
- Aurora (Nephelometer)
|
|
99
|
+
- AE33 (Aethalometer Model 33)
|
|
100
|
+
- AE43 (Aethalometer Model 43)
|
|
101
|
+
- BC1054 (Black Carbon Monitor 1054)
|
|
102
|
+
- MA350 (MicroAeth MA350)
|
|
103
|
+
- OCEC (Organic Carbon Elemental Carbon Analyzer)
|
|
104
|
+
- IGAC (In-situ Gas and Aerosol Compositions monitor)
|
|
105
|
+
- XRF (X-ray Fluorescence Spectrometer)
|
|
106
|
+
- VOC (Volatile Organic Compounds Monitor)
|
|
107
|
+
|
|
108
|
+
> **Note:** We are continuously working to support more instruments. Please check back for updates or contribute to our
|
|
109
|
+
> project on GitHub.
|
|
85
110
|
|
|
86
111
|
## <div align="center">DataProcess Supported Method</div>
|
|
87
112
|
|
|
88
113
|
The AeroViz project currently supports the following processing methods:
|
|
89
114
|
|
|
90
|
-
- **Chemistry
|
|
115
|
+
- **Chemistry**:
|
|
91
116
|
- **Optical**
|
|
92
117
|
- **SizeDistr**
|
|
93
118
|
- **VOC**
|
|
@@ -100,7 +125,7 @@ For detailed documentation, please refer to the `docs` folder, which includes:
|
|
|
100
125
|
|
|
101
126
|
| Documentation | Description |
|
|
102
127
|
|--------------------------------------------|----------------------------|
|
|
103
|
-
| [User Guide](docs/user_guide
|
|
128
|
+
| [User Guide](docs/user_guide) | Basic usage instructions |
|
|
104
129
|
| [Developer Guide](docs/developer_guide.md) | Developer guidelines |
|
|
105
130
|
| [API Reference](docs/api_reference.md) | API documentation |
|
|
106
131
|
| [FAQ](docs/faq.md) | Frequently Asked Questions |
|
|
@@ -108,7 +133,7 @@ For detailed documentation, please refer to the `docs` folder, which includes:
|
|
|
108
133
|
|
|
109
134
|
</div>
|
|
110
135
|
|
|
111
|
-
## <div align="center">Related
|
|
136
|
+
## <div align="center">Related Source</div>
|
|
112
137
|
|
|
113
138
|
* #### [PyMieScatt](https://github.com/bsumlin/PyMieScatt.git)
|
|
114
139
|
* #### [py-smps](https://github.com/quant-aq/py-smps.git)
|