Chemical Composition Analysis
Chemical composition analysis workflow, including mass reconstruction, ion balance, hygroscopicity calculation, and gas-particle partitioning ratios.
Data Preparation
from datetime import datetime
from pathlib import Path
import pandas as pd
from AeroViz import RawDataReader
from AeroViz.dataProcess import DataProcess
# Read ion data
igac = RawDataReader(
instrument='IGAC',
path=Path('./data/igac'),
start=datetime(2024, 1, 1),
end=datetime(2024, 3, 31)
)
# Read carbon component data
ocec = RawDataReader(
instrument='OCEC',
path=Path('./data/ocec'),
start=datetime(2024, 1, 1),
end=datetime(2024, 3, 31)
)
# Read elemental data (Xact 625i)
xact = RawDataReader(
instrument='Xact',
path=Path('./data/xrf'),
start=datetime(2024, 1, 1),
end=datetime(2024, 3, 31)
)
# Merge data
df_chem = pd.concat([igac, ocec, xact], axis=1)
Mass Reconstruction
Basic Reconstruction
dp = DataProcess('Chemistry', Path('./output'))
# Perform mass reconstruction
result = dp.reconstruction_basic(df_chem)
# Main component masses
df_mass = result['mass']
print(df_mass.columns)
# ['AS', 'AN', 'OM', 'EC', 'Soil', 'SS', 'PM25_rc']
# Ammonium status
nh4_status = result['NH4_status']
print(nh4_status.value_counts())
# Balance 45
# Excess 30
# Deficiency 15
Closure Check
# Calculate closure
closure = df_mass['PM25_rc'] / df_chem['PM25'] * 100
print(f"Mean closure: {closure.mean():.1f}%")
print(f"Std closure: {closure.std():.1f}%")
# Closure distribution
import matplotlib.pyplot as plt
plt.hist(closure, bins=20, edgecolor='black')
plt.xlabel('Closure (%)')
plt.ylabel('Frequency')
plt.axvline(100, color='r', linestyle='--', label='100%')
plt.legend()
plt.show()
Component Contributions
# Calculate contribution ratios for each component
components = ['AS', 'AN', 'OM', 'EC', 'Soil', 'SS']
contributions = df_mass[components].div(df_mass['PM25_rc'], axis=0) * 100
print("Average contribution (%):")
print(contributions.mean())
# AS 25.3
# AN 18.2
# OM 35.1
# EC 8.5
# Soil 7.2
# SS 5.7
Volume and Refractive Index Calculation
# Calculate volume fraction and refractive index
vol_ri = dp.volume_RI(df_chem)
# Volume fraction
df_volume = vol_ri['volume']
print(df_volume.columns)
# ['AS_volume', 'AN_volume', 'OM_volume', 'EC_volume', 'Soil_volume', 'SS_volume']
# Refractive index
df_RI = vol_ri['RI']
print(f"Mean n: {df_RI['n'].mean():.3f}")
print(f"Mean k: {df_RI['k'].mean():.4f}")
Hygroscopicity (kappa) Calculation
# Requires RH data
df_RH = met_data[['RH']]
# Calculate kappa and growth factor
kappa_result = dp.kappa(df_chem, df_RH)
# kappa value
df_kappa = kappa_result['kappa']
print(f"Mean kappa: {df_kappa.mean():.3f}")
# Growth factor
df_gRH = kappa_result['gRH']
print(f"Mean gRH at RH=80%: {df_gRH.mean():.2f}")
kappa vs Composition Relationship
# Analyze relationship between kappa and chemical composition
fig, axes = plt.subplots(1, 3, figsize=(12, 4))
# kappa vs SIA ratio
sia_ratio = (df_mass['AS'] + df_mass['AN']) / df_mass['PM25_rc']
axes[0].scatter(sia_ratio, df_kappa, alpha=0.5)
axes[0].set_xlabel('SIA / PM2.5')
axes[0].set_ylabel('kappa')
# kappa vs OM ratio
om_ratio = df_mass['OM'] / df_mass['PM25_rc']
axes[1].scatter(om_ratio, df_kappa, alpha=0.5)
axes[1].set_xlabel('OM / PM2.5')
axes[1].set_ylabel('kappa')
# kappa vs RH
axes[2].scatter(df_RH['RH'], df_kappa, alpha=0.5)
axes[2].set_xlabel('RH (%)')
axes[2].set_ylabel('kappa')
plt.tight_layout()
plt.show()
Gas-Particle Partitioning Ratios
# Requires gas data
df_gas = gas_data[['SO2', 'NO2', 'HNO3', 'NH3']]
df_combined = pd.concat([df_chem, df_gas], axis=1)
# Calculate partitioning ratios
partition = dp.partition_ratios(df_combined)
print(partition.columns)
# ['SOR', 'NOR', 'NTR', 'epsilon_ite', 'epsilon_ss']
Partitioning Ratio Description
| Indicator | Formula | Meaning |
|---|---|---|
| SOR | SO4^2-/(SO4^2-+SO2) | Sulfate conversion degree |
| NOR | NO3-/(NO3-+NO2) | Nitrate conversion degree |
| NTR | NO3-/(NO3-+HNO3) | Nitrate partitioning |
| epsilon_ite | NO3-/(NO3-+Cl-) | Nitrate vs chloride |
# Analyze diurnal variation of SOR and NOR
hourly_sor = partition['SOR'].groupby(partition.index.hour).mean()
hourly_nor = partition['NOR'].groupby(partition.index.hour).mean()
fig, ax = plt.subplots()
ax.plot(hourly_sor.index, hourly_sor.values, 'b-o', label='SOR')
ax.plot(hourly_nor.index, hourly_nor.values, 'r-o', label='NOR')
ax.set_xlabel('Hour')
ax.set_ylabel('Ratio')
ax.legend()
plt.show()
OC/EC Ratio Analysis
# OC/EC analysis
ocec_result = dp.ocec_ratio(df_chem[['OC', 'EC']])
# OC/EC ratio
ratio = ocec_result['ratio']
print(f"Mean OC/EC: {ratio.mean():.2f}")
# SOC estimation
soc = ocec_result['SOC']
print(f"Mean SOC: {soc.mean():.2f} ug/m3")
Complete Analysis Script
from datetime import datetime
from pathlib import Path
import pandas as pd
import matplotlib.pyplot as plt
from AeroViz import RawDataReader
from AeroViz.dataProcess import DataProcess
# 1. Read data
igac = RawDataReader('IGAC', Path('./data'), ...)
ocec = RawDataReader('OCEC', Path('./data'), ...)
df_chem = pd.concat([igac, ocec], axis=1)
df_RH = pd.read_csv('met.csv', index_col='time', parse_dates=True)[['RH']]
# 2. Initialize processor
dp = DataProcess('Chemistry', Path('./output'))
# 3. Mass reconstruction
mass_result = dp.reconstruction_basic(df_chem)
df_mass = mass_result['mass']
# 4. Volume and refractive index
vol_ri = dp.volume_RI(df_chem)
df_RI = vol_ri['RI']
# 5. Hygroscopicity
kappa_result = dp.kappa(df_chem, df_RH)
df_kappa = kappa_result['kappa']
df_gRH = kappa_result['gRH']
# 6. Output results summary
print("=== Chemical Analysis Summary ===")
print(f"PM2.5: {df_chem['PM25'].mean():.1f} +/- {df_chem['PM25'].std():.1f} ug/m3")
print(f"Closure: {(df_mass['PM25_rc']/df_chem['PM25']*100).mean():.1f}%")
print(f"RI: {df_RI['n'].mean():.3f} + {df_RI['k'].mean():.4f}i")
print(f"kappa: {df_kappa.mean():.3f} +/- {df_kappa.std():.3f}")