Repository for NIST-EPA Legionella study Python tools. Includes scripts for aerosol particle size distribution analysis, emission-rate calculations, data validation, visualization, and statistical modeling of shower-generated aerosols under varying temperature, humidity, and exhaust-fan conditions.
This project analyzes aerosol characteristics from shower-generated aerosols with a focus on potential Legionella transmission risks from shower fixtures. The research investigates:
- Aerosol particle size distribution analysis
- Emission-rate calculations using numerical approaches
- CO2 decay analysis for air change rate determination
- Temperature and relative humidity monitoring
- data validation, visualization, and statistical modeling
Certain commercial equipment, instruments, software, or materials are identified in this repository in order to specify the experimental and analytical procedures adequately. Such identification is not intended to imply recommendation or endorsement of any product or service by NIST, nor is it intended to imply that the materials or equipment identified are necessarily the best available for the purpose.
The analysis procedures in this repository involve multiple stages of data processing and mathematical modeling, each contributing to the overall uncertainty in the final results. Key sources of uncertainty include:
CO₂-derived Air Change Rate (λ) Uncertainty:
- Statistical uncertainty from linear regression of log-transformed decay data (reported as standard error of the slope)
- Systematic uncertainty from assuming a well-mixed zone and constant infiltration fractions (α, β)
- Uncertainty in source concentration characterization evaluated through three methods: weighted average (α·C_outside + β·C_entry), outside-only, and entry-only
- Data quality filters including minimum concentration difference requirements (50 ppm) and sufficient data point thresholds
- Temporal alignment uncertainty between CO₂ injection timing and decay window selection
Particle Analysis Uncertainty:
- Penetration factor (p) uncertainty from temporal variability in indoor/outdoor concentration ratios during before/after windows
- Other process rate (β) uncertainty from numerical differentiation of concentration data, mitigated by 5th–95th percentile trimming and R²-based selection criteria
- Emission rate (E) uncertainty from propagation of errors in the mass balance equation and concentration time derivatives
- Forward Euler simulation uncertainty from accumulation of integration errors over the 2-hour prediction window
- Size-bin dependent uncertainties due to varying signal-to-noise ratios across the 0.35–10.0 µm range
General Measurement Uncertainties:
- Sensor-specific uncertainties: Aranet4 CO₂ sensors (±50 ppm + 3% of reading), QuantAQ MODULAIR-PM particle counters, HOBO UX100 temperature/relative humidity sensors
- Temporal synchronization uncertainty between different sensor systems sampling at 1-minute intervals
- Spatial representativeness limitations of point measurements in heterogeneous indoor environments
- Model structural uncertainty from assuming first-order decay processes and uniform mixing in the mass balance equations
Uncertainty quantification is implemented throughout the analysis pipeline, with standard deviations, standard errors, and confidence intervals reported where applicable. The three-source-method approach for λ calculation provides explicit assessment of structural uncertainty in ventilation rate determination. Where measurements fall below detection thresholds or fail quality criteria, results are appropriately flagged or excluded to prevent overinterpretation.
NIST_EPA_Legionella/
├── .vscode/ # VS Code configuration
│ └── settings.json
├── .env # Environment variables (API keys for QuantAQ, gitignored)
├── .gitignore
├── CODEMETA.yaml # NIST Software Portal metadata
├── CODEOWNERS # Repository ownership for PR reviews
├── LICENSE.md # NIST software licensing statement
├── README.md
├── fair-software.md # FAIR software principles compliance checklist
├── data_config.json # Active configuration (gitignored)
├── data_config.template.json # Configuration template
├── epa_mh.yml # Conda environment specification
├── run_analysis_workflow.py # Convenience script to run the full analysis pipeline
│
├── src/ # Core analysis modules (imported by scripts)
│ ├── __init__.py # Package initialization (re-exports data_paths)
│ ├── data_paths.py # Portable data access via data_config.json
│ ├── env_data_loader.py # Unified environmental sensor data loader
│ ├── event_manager.py # Event filtering, naming (MMDD_Temp_ToD_RNN), logging
│ ├── event_matching.py # Match CO2 injections to shower events by timing
│ ├── particle_data_loader.py # QuantAQ particle data loading and preparation
│ ├── particle_calculations.py # Penetration, deposition & emission calculations
│ ├── plot_co2.py # CO2 decay visualization functions
│ ├── plot_environmental.py # RH, temperature, wind visualization
│ ├── plot_particle.py # Particle decay visualization (re-exports boxplot/comparison functions)
│ ├── plot_particle_boxplots.py # Temperature-axis and shower-head boxplot functions
│ ├── plot_comparison.py # Categorical comparison boxplot families (door, fan, spray pattern, etc.)
│ ├── plot_style.py # Consistent matplotlib styling constants
│ ├── plot_utils.py # Central plotting re-exports (imports all plot_* modules)
│ ├── quantaq_utils.py # QuantAQ API client utilities
│ ├── sig_figs.py # Significant figures rounding and formatting
│ └── deprecated/ # Deprecated/archived code
│ └── co2_decay_analysis.py # Previous version of CO2 analysis
│
├── scripts/ # Executable analysis scripts (run directly)
│ ├── __init__.py
│ │
│ │ # Data Download & Processing
│ ├── download_quantaq_data.py # Download QuantAQ sensor data from API
│ ├── process_quantaq_data.py # Combine weekly chunks and process raw/final QuantAQ data
│ │
│ │ # Log Processing
│ ├── process_co2_log.py # Consolidate daily CO2 injection logs → state-change log
│ ├── process_shower_log.py # Consolidate daily shower logs → state-change log
│ ├── fix_log_files.py # Repair corrupted/empty log files
│ ├── analyze_log_files.py # Diagnose log file issues (unmatched events, etc.)
│ │
│ │ # Event Management
│ ├── event_registry.py # Unified event registry with synthetic event creation
│ │
│ │ # Analysis
│ ├── co2_decay_analysis.py # CO2 decay & air change rate (λ) analysis
│ ├── particle_decay_analysis.py # Particle penetration, deposition & emission analysis
│ ├── rh_temp_other_analysis.py # RH, temperature & wind condition analysis
│ ├── export_event_timeseries.py # Export per-event predicted Ct for all size bins to CSV (--event N)
│ └── export_config_timeseries.py # Export 1-min avg env/particle time series per config group to Excel
│
└── docs/ # Documentation & instrument references
├── Data Analysis.docx # Data analysis planning notes
└── pdf_links.md # Public download links for instrument manuals and data sheets (not included due to copyright)
- Python 3.14+
- Conda (recommended for environment management)
-
Clone the repository:
git clone https://github.com/usnistgov/NIST_EPA_Legionella.git cd NIST_EPA_Legionella -
Create the conda environment:
conda env create -f epa_mh.yml conda activate epa_mh
-
Configure data paths:
cp data_config.template.json data_config.json
Edit
data_config.jsonto set the correctdata_rootpath for your machine. -
Set up API credentials (for QuantAQ data): Create a
.envfile with your QuantAQ API key:QUANTAQ_API_KEY=your_api_key_here
The src/data_paths.py module provides portable data access functions:
from src import get_instrument_path, get_instrument_file, get_instrument_files_for_date_range
# Get instrument data directory
path = get_instrument_path("Aranet4")
# Get data file for a specific date
file = get_instrument_file("Aranet4", "2026-01-15")
# Get files for a date range
files = get_instrument_files_for_date_range("QuantAQ", "2026-01-05", "2026-01-15")-
Download data from the QuantAQ API:
python scripts/download_quantaq_data.py
-
Process data (parse nested structures, merge raw/final data):
python scripts/process_quantaq_data.py
Plotting modules in src/ generate publication-quality figures used by the analysis scripts:
src/plot_co2.py- CO2 decay curves and air change rate analysissrc/plot_particle.py- Particle concentration decay and emission rates; re-exports boxplot/comparison functionssrc/plot_particle_boxplots.py- Temperature-axis and shower-head E_total/β/E/p boxplotssrc/plot_comparison.py- Categorical comparison boxplot families (door, fan, spray pattern, mannequin, head type)src/plot_environmental.py- RH, temperature, and wind time seriessrc/plot_style.py- Consistent matplotlib styling constants and helperssrc/plot_utils.py- Re-exports all plot_* functions for backward compatibility
All plot modules are imported by analysis scripts through src/plot_utils.py.
| Instrument | Model | Purpose | Data Location |
|---|---|---|---|
| AIO2 | Met One AIO 2 | Outdoor weather (10m tower) | MH DAQ/weather_station |
| Aranet4 | Aranet4 PRO | CO2 monitoring (Bedroom, Bathroom, Entry, Outside) | CO2 |
| HOBO_UX100 | Onset HOBO UX100 | RH/Temperature (6 locations: Bathroom1×2, Bathroom2, Bath/Bed, Bedroom1–3) | HOBOs |
| QuantAQ | MODULAIR-PM | PM monitoring (indoor: MOD-PM-00195, outdoor: MOD-PM-00785) | QuantAQ |
| Setra_264 | Setra 264 | Differential pressure | MH DAQ/indoor_daq |
| Vaisala_HMP155 | Vaisala HMP155 | RH/Temperature | MH DAQ/indoor_daq |
| Vaisala_HMP45A | Vaisala HMP45A | RH/Temperature | MH DAQ/indoor_daq |
- Chassis: NI cDAQ-9178 (8-slot USB)
- Modules: 5x NI 9201 (8-channel analog input, +/-10V)
The complete data analysis pipeline follows this sequence. To run all steps automatically:
python run_analysis_workflow.pyEach step's output is logged to output/logs/<script_name>.log. Or run steps individually:
-
Data Collection: Download sensor data from the QuantAQ API
python scripts/download_quantaq_data.py
-
Data Processing: Process and merge raw QuantAQ sensor data
python scripts/process_quantaq_data.py
-
Log Processing: Consolidate daily 1-second log files into state-change logs
python scripts/process_co2_log.py python scripts/process_shower_log.py
-
Event Management: Run the event manager to match, name, and register events
python scripts/event_registry.py [--force] [--no-co2] [--output-dir DIR]
Flag Description --forceRegenerate registry even if it already exists --no-co2Skip CO2 analysis (registry will have no lambda values or PM exclusions) --output-dir DIROutput directory (default: data_root/output)This must be run before the analysis scripts so that events have consistent names (see Event Naming Convention below). Produces
event_log.csv. -
CO2 Analysis: Determine air change rates (λ) from CO2 decay
python scripts/co2_decay_analysis.py [--alpha FLOAT] [--beta FLOAT] [--output-dir DIR] [--no-plot] [--no-sig-figs]
Flag Description --alpha FLOATFraction of infiltration from outside (default: 0.5) --beta FLOATFraction of infiltration from entry zone (default: 0.5) --output-dir DIROutput directory (default: data_root/output)--no-plotDisable plot generation --no-sig-figsDisable significant figure rounding (default: 3 sig figs for files, 2 for figures) --entry-stopTruncate decay window when C_bedroom ≤ 100 + (C_entry + C_outside) / 2 — stops the fit once the remaining bedroom decay signal is within 100 ppm of the entry/outside background mean -
Environmental Analysis: Characterize RH, temperature, and wind conditions
python scripts/rh_temp_other_analysis.py [--output-dir DIR] [--no-plots] [--max-plot-events N] [--events LIST] [--no-sig-figs]
Flag Description --output-dir DIROutput directory (default: data_root/output)--no-plotsSkip plot generation --max-plot-events NLimit time series plots to first N events (default: all) --events LISTComma-separated event numbers to plot, e.g. 1,3,5(overrides--max-plot-events)--no-sig-figsDisable significant figure rounding (default: 3 sig figs for files, 2 for figures) -
Particle Analysis: Calculate penetration, deposition, and emission rates (requires step 5 results)
python scripts/particle_decay_analysis.py [--output-dir DIR] [--no-plot] [--no-sig-figs]
Flag Description --output-dir DIROutput directory (default: data_root/output)--no-plotDisable plot generation --no-sig-figsDisable significant figure rounding (default: 3 sig figs for files, 2 for figures) -
Export Event Time Series (optional utility): Export predicted Ct for all size bins for a single event to CSV:
python scripts/export_event_timeseries.py --event N [--output PATH] [--output-dir DIR] [--no-sig-figs]
Flag Description --event INTEvent number to export (required) --output PATHOutput CSV file path (default: output/event_N_timeseries.csv)--output-dir DIRAnalysis output directory (default: data_root/output)--no-sig-figsDisable significant figure rounding -
Export Config Time Series (optional utility): Export 1-minute averaged environmental and particle time series per unique test configuration to Excel:
python scripts/export_config_timeseries.py [--no-sig-figs]
Flag Description --no-sig-figsDisable significant figure rounding Produces
output/event_config_timeseries.xlsxwith one sheet per configuration group. Each sheet contains the mean, standard deviation, maximum, and minimum (across replicate events) of bathroom RH/Temp, bath/bed hallway RH/Temp, bedroom RH/Temp, and all 12 OPC-N3 particle bins (0.35–10.0 µm) at 1-minute resolution from shower-on through the 2-hour deposition window. Flow rates in the 4.1–5.6 LPM range (standard and tagged variants) are grouped together; 1.4 LPM and 2.2 LPM events remain separate. A hyperlinked index sheet lists every configuration group with event counts and event numbers.
Each analysis module produces CSV/Excel summaries and optional visualizations in the output/ directory.
Events are named using a structured format generated by scripts/event_registry.py. This naming convention must be applied (step 4 in the workflow above) before running the analysis scripts.
Format: MMDD_W##[_ShowerHead[_SprayPattern]][_Mannequin]_DoorPos[_Fan]_RNN
| Component | Description | Values |
|---|---|---|
MMDD |
Month and day of the event | e.g., 0115 for January 15 |
W## |
Water temperature code (°C) | W11, W14, W22, W23, W25, W30, W37, W38, W40, W43, W48, W49, W52, W53 |
ShowerHead |
Shower head type; omitted for Standard head | Pepco, FilterWand, Used |
SprayPattern |
Spray pattern; varies by head type | Wide, Narrow, Mid, rainfall, 12Nozzle, SingleWide1, SingleWide2 |
Mannequin |
Appended only when a mannequin was present | Mannequin |
DoorPos |
Bathroom-to-bedroom (bath) door position | Open, Closed, Ajar |
Fan |
Appended only when the bath fan ran during the test | Fan |
RNN |
Replicate number | R01, R02, R03, etc. |
Examples:
0115_W48_Open_R01- January 15, 48 °C, standard head, bath door open, replicate 10122_W11_Open_R03- January 22, 11 °C, standard head, replicate 30224_W52_Pepco_Wide_Open_R01- February 24, 52 °C, Pepco head, wide spray0309_W40_Pepco_Narrow_Open_R01- March 9, 40 °C, Pepco head, narrow spray0311_W40_Pepco_Narrow_Mannequin_Open_R01- March 11, mannequin present0408_W38_Pepco_Wide_Closed_R01- April 8, 38 °C, Pepco head, wide spray, bath door closed
Note on config_key: The config_key stored in event_log.csv and spreadsheet outputs is a separate, more detailed identifier used for grouping in boxplots. It includes both door positions, fan status, and measured flow rate: W##[_ShowerHead[_SprayPattern]][_Mannequin]_BathDoorXxx_BdrmDoorXxx_FanXxx[_FlowRateX.XLPM] (e.g., W48_BathDoorOpen_BdrmDoorClosed_FanOff_FlowRate4.6LPM).
Test Parameter Timeline (Standard shower head):
- W48 (48 °C): From experiment start (2026-01-14); bath door open, bedroom door closed
- W11 (11 °C): Starting 2026-01-22 14:00
- W25 (25 °C): Starting 2026-02-02 17:00
- W30 (30 °C): Starting 2026-02-05 10:00
- W37 (37 °C): Starting 2026-02-09 10:00
- W23 (23 °C): Starting 2026-02-11 08:00 (data excluded; kept for documentation)
- W22 (22 °C): Starting 2026-02-13 11:00
- W43 (43 °C): Starting 2026-02-16 11:00
- W14 (14 °C): Starting 2026-02-18 10:23
- W53 (53 °C): Starting 2026-02-20 08:00
Test Parameter Timeline (Pepco shower head, first phase, installed 2026-02-24):
- W52 (52 °C), Wide spray: Starting 2026-02-24 08:00
- W49 (49 °C), Wide spray: Starting 2026-02-26 10:23
- W40 (40 °C), Narrow spray: Starting 2026-03-02 09:23
- W40 (40 °C), Wide spray: Starting 2026-03-04 10:23
- W40 (40 °C), Mid spray: Starting 2026-03-06 08:47
- W40 (40 °C), Narrow spray: Starting 2026-03-09 08:47
- W40 (40 °C), Narrow spray + Mannequin: Starting 2026-03-11 09:47
- W38 (38 °C), Narrow spray: Starting 2026-03-12 10:47
- W38 (38 °C), Narrow spray + Mannequin: Starting 2026-03-13 08:45
- W38 (38 °C), Narrow spray (no mannequin): Starting 2026-03-17 08:25
- W38 (38 °C), Narrow spray + Mannequin: Starting 2026-03-18 09:45
- W38 (38 °C), Wide spray: Starting 2026-03-19 08:25
- W38 (38 °C), Wide spray + Mannequin: Starting 2026-03-22 09:25
- W38 (38 °C), Wide spray (no mannequin): Starting 2026-03-27 08:25
Test Parameter Timeline (FilterWand shower head, installed 2026-04-10):
- W38 (38 °C), FilterWand (no spray pattern variable): Starting 2026-04-10 08:55
Test Parameter Timeline (Used shower heads, starting 2026-04-13):
- W38 (38 °C), Used-rainfall: Starting 2026-04-13 11:35
- W38 (38 °C), Used-12Nozzle: Starting 2026-04-15 08:25
- W38 (38 °C), Used-SingleWide1: Starting 2026-04-17 08:35
- W38 (38 °C), Used-SingleWide2: Starting 2026-04-20 08:35
Test Parameter Timeline (Pepco shower head, second phase, reinstalled 2026-04-23):
- W38 (38 °C), Narrow spray + Mannequin: Starting 2026-04-23 14:15
- W38 (38 °C), Narrow spray (no mannequin): Starting 2026-04-29 08:35
Bath fan testing:
- Fan off: From experiment start through 2026-03-30
- Fan on (12 min per shower): Starting 2026-03-31 12:45
- Fan off: Starting ~2026-04-07 (confirmed off from 2026-04-10 08:55)
Bath door position changes:
- Bath door open (BathDoorOpen): From experiment start through 2026-04-07
- Bath door closed (BathDoorClosed): Starting 2026-04-08 09:15
- Bath door open (BathDoorOpen): Starting 2026-04-10 08:55
- Bath door ajar (BathDoorAjar): Starting 2026-05-04 08:30
- Bath door open (BathDoorOpen): Starting 2026-05-07 08:00
Bedroom door position changes:
- Bedroom door closed (BdrmDoorClosed): From experiment start (2026-01-14)
- Bedroom door ajar (BdrmDoorAjar): Starting 2026-05-01 17:10 (door blown ajar between May 1–4; intentionally kept ajar May 4–7)
- Bedroom door closed (BdrmDoorClosed): Starting 2026-05-07 08:00
Run the CO2 decay analysis to calculate air change rates (λ):
# Basic analysis (plots enabled by default)
python scripts/co2_decay_analysis.py
# Custom source fractions
python scripts/co2_decay_analysis.py --alpha 0.6 --beta 0.4 # default is 0.5/0.5
# Skip plots, write to a custom directory
python scripts/co2_decay_analysis.py --no-plot --output-dir /path/to/outputMethodology:
- Combines three Aranet4 sensor files: Bedroom, Entry, and Outside
- Applies 6-minute rolling average to reduce sensor noise
- Fixed 2-hour decay analysis window starting at :50 (10 minutes before the shower hour)
- Three source concentration methods for uncertainty: λ_average (mean of outside + entry), λ_outside, λ_entry
- Calculates λ via linear regression of the log-transformed decay: y = −ln[(C(t) − C_avg) / (C₀ − C_avg)], λ = slope of y vs. time
Output Files:
output/co2_lambda_summary.csv- Per-event lambda resultsoutput/co2_lambda_overall_summary.csv- Overall statisticsoutput/plots/event_figures/co2_decay/event_NN-testname_co2_decay.png- Individual event decay plots with shower ON/OFF markersoutput/plots/lambda_summary.png- Summary bar chart of λ valuesoutput/plots/air_change_rate_boxplot.png- Box-and-whisker of λ by water temperature
Run the particle decay analysis to calculate penetration factors, deposition rates, and emission rates across 12 OPC-N3 size bins (0.35–10.0 µm):
# Run particle analysis (requires CO2 analysis results; plots enabled by default)
python scripts/particle_decay_analysis.py
# Skip plots
python scripts/particle_decay_analysis.py --no-plot
# Write to a custom directory without significant figure rounding
python scripts/particle_decay_analysis.py --output-dir /path/to/output --no-sig-figsMethodology:
The mass balance equation for indoor particle concentration:
V·dC/dt = p·Q·C_out - Q·C - β·C·V + E
dC/dt = p·λ·C_out - λ·C - β·C + E/V
- Penetration factor (p): Ratio of indoor to outdoor concentration averaged over before/after windows relative to the shower event; zeros excluded; capped at 1.
- QA: Minimum 10 valid points per window (
MIN_POINTS_PENETRATION); windows where this is not met are skipped. - Night event windows: before = 9 pm (day before) → 2 am; after = 9 am → 2 pm
- Day event windows: before = 9 am → 2 pm; after = 9 pm → 2 am (next day)
- QA: Minimum 10 valid points per window (
- Air change rate (λ): Loaded from CO2 decay analysis results (h⁻¹).
- Other process rate (β, numerical): Step-by-step solution during the 2-hour (
DEPOSITION_WINDOW_HOURS) post-shower decay window; beta solved at each time step from the discrete mass balance:All estimates ≤β = 1/dt - λ - C_{t+1}/(C_t·dt) + p·λ·(C_out,t / C_t)MAX_OTHER_PROCESS_RATE(15 h⁻¹) are collected (no lower bound, to avoid upward bias). A 5th–95th percentile trim removes extreme outliers symmetrically. Beta is then selected via an R²-based four-step procedure (threshold R² = 0.80): (a) use unclamped trimmed mean — if R² ≥ 0.80 keep it; (b) clamp to ≥ 0 — if R² ≥ 0.80 keep it; (c) set β = 0 — if R² ≥ 0.80 keep it; (d) if β = 0 also fails → β = NaN (bin invalid, no Ct prediction plotted).- QA: Minimum 10 valid points in the decay window and after peak (
MIN_POINTS_OTHER_PROCESS);MAX_OTHER_PROCESS_RATE= 15 h⁻¹ upper cap (above this, values are noise spikes for sub-3 µm particles).
- QA: Minimum 10 valid points in the decay window and after peak (
- Emission rate (E): Mass balance rearranged and solved numerically at each time step during the shower-on-to-peak window; mean and std reported from positive values.
- QA: Minimum 3 valid consecutive steps (
MIN_POINTS_EMISSION); E_total clips negative per-step contributions to 0 before trapezoid integration.
- QA: Minimum 3 valid consecutive steps (
- Predicted concentration (Ct): Single continuous forward Euler simulation from shower ON to 2 hours after shower OFF. E = E_mean before peak_time, E = 0 after. Returned as two connected segments forming one continuous predicted Ct curve on figures. Decay phase starts from the predicted peak (end of emission simulation), not the measured peak.
- Emission R² (E_r_squared): R² of the emission-phase simulation vs. measured indoor concentration from shower ON to peak.
- Total emission (E_total): Trapezoidal rule area under the E_t vs. time curve from shower ON to peak; negative per-step contributions clipped to 0.
Output Files:
-
output/particle_analysis_summary.xlsx- Multi-sheet Excel workbook:all_results— Full results table (all metrics per event and bin)p_penetration— Penetration factors per event and bin (includes test_name)beta_other— Other process rates per event and bin (includes test_name)beta_r_squared— R² of forward Euler decay fit per event and bin (includes test_name)E_emission— Emission rates per event and bin (includes test_name)E_total_particles— Total emitted particle counts (E_total) per bin (includes test_name)E_r_squared— R² of forward Euler emission-phase simulation per bin (includes test_name)peak_comparison— Measured vs. predicted concentration at peak_time and deposition_end (wide format, one row per event)
-
output/plots/event_figures/pm_decay/event_NN-testname_pm_decay.png- Individual event decay curves (four-panel)- Top panel: Measured concentration (solid) and continuous predicted Ct (dashed); shower ON/OFF dotted markers; optional outdoor PM overlay for events in
OUTDOOR_PM_EVENTS - Emission panels (×3): Bins 0–2, 3–6, and 7–11; per-step E_t as faint lines; E_mean as dashed horizontal line; emission R² annotation; x-axes match concentration panel
- Top panel: Measured concentration (solid) and continuous predicted Ct (dashed); shower ON/OFF dotted markers; optional outdoor PM overlay for events in
-
output/plots/event_figures/excluded_events/- Figures for duration-excluded (water-temperature-testing) events -
output/plots/penetration_summary.png- Bar chart summary of penetration factors by size -
output/plots/deposition_summary.png- Bar chart summary of other process rates by size -
output/plots/emission_summary.png- Bar chart summary of emission rates by size -
output/plots/emission_etotal_boxplot_{bin0-2,bin3-6,bin7-11}.png- E_total by water temperature (fixed temp axis, one figure per bin group) -
output/plots/emission_etotal_boxplot.md- Event membership for the E_total boxplot (lists event numbers and config keys per box) -
output/plots/other_process_rate_boxplot_{bin0-2,bin3-6,bin7-11}.png- β by water temperature (fixed temp axis) -
output/plots/other_process_rate_boxplot.md- Event membership for the β boxplot -
output/plots/emission_rate_boxplot_{bin0-2,bin3-6,bin7-11}.png- E_mean by water temperature (fixed temp axis) -
output/plots/emission_rate_boxplot.md- Event membership for the E_mean boxplot -
output/plots/penetration_factor_boxplot_{bin0-2,bin3-6,bin7-11}.png- p by water temperature (fixed temp axis) -
output/plots/penetration_factor_boxplot.md- Event membership for the penetration factor boxplot -
output/plots/emission_etotal_by_bedroom_rh_boxplot_{bin0-2,bin3-6,bin7-11}.png- E_total vs. bedroom RH (metric axis) -
output/plots/emission_etotal_by_bedroom_rh_boxplot.md- Event membership for the bedroom RH boxplot -
output/plots/emission_etotal_by_bedroom_temp_boxplot_{bin0-2,bin3-6,bin7-11}.png- E_total vs. bedroom temperature (metric axis) -
output/plots/emission_etotal_by_bedroom_temp_boxplot.md- Event membership for the bedroom temp boxplot -
output/plots/emission_etotal_by_acr_boxplot_{bin0-2,bin3-6,bin7-11}.png- E_total vs. air change rate (metric axis) -
output/plots/emission_etotal_by_acr_boxplot.md- Event membership for the ACR boxplot -
output/plots/emission_etotal_by_beta_boxplot_{bin0-2,bin3-6,bin7-11}.png- E_total vs. other process rate (metric axis) -
output/plots/emission_etotal_by_beta_boxplot.md- Event membership for the β boxplot -
output/plots/emission_etotal_by_p_boxplot_{bin0-2,bin3-6,bin7-11}.png- E_total vs. penetration factor (metric axis) -
output/plots/emission_etotal_by_p_boxplot.md- Event membership for the p boxplot -
output/plots/emission_etotal_by_showerhead_boxplot_{bin0-2,bin3-6,bin7-11}.png- E_total by shower head type -
output/plots/emission_etotal_by_showerhead_boxplot.md- Event membership for the shower head boxplot -
output/plots/comparison/spray_pattern_{metric}_{bin0-2,bin3-6,bin7-11}.png- Comparison boxplots by spray pattern (5 metric families × 3 bin groups each) -
output/plots/comparison/head_type_{metric}_{bin0-2,bin3-6,bin7-11}.png- Comparison boxplots by shower head type -
output/plots/comparison/mannequin_{metric}_{bin0-2,bin3-6,bin7-11}.png- Comparison boxplots by mannequin presence -
output/plots/comparison/bath_door_{metric}_{bin0-2,bin3-6,bin7-11}.png- Comparison boxplots by bathroom door position -
output/plots/comparison/bedroom_door_{metric}_{bin0-2,bin3-6,bin7-11}.png- Comparison boxplots by bedroom door position -
output/plots/comparison/fan_status_{metric}_{bin0-2,bin3-6,bin7-11}.png- Comparison boxplots by fan status
Run the environmental analysis to characterize conditions before and after shower events:
# Run RH/temperature/wind analysis (plots enabled by default)
python scripts/rh_temp_other_analysis.py
# Skip plots
python scripts/rh_temp_other_analysis.py --no-plots
# Plot only specific events
python scripts/rh_temp_other_analysis.py --events 1,3,5
# Limit time series plots to first 10 events
python scripts/rh_temp_other_analysis.py --max-plot-events 10Methodology:
- Monitors RH and temperature at multiple locations:
- Entry, Bedroom, Bathroom, Outside (Aranet4)
- Bedroom, Bathroom, Living Room (DAQ/Vaisala)
- Bathroom1, Bathroom2, Bath/Bed, Bedroom1, Bedroom2, Bedroom3 (HOBO UX100)
- Note: QuantAQ
met_rh/met_tempmeasure inside the flow cell and are excluded from RH/temp analysis
- Analyzes windspeed and direction from outdoor weather station (AIO2)
- Compares pre-shower baseline (30 min) to post-shower response (2 hours)
- Calculates mean, standard deviation, and min-max ranges
- Generates time series and box plot comparisons
Output Files:
output/rh_temp_wind_summary.xlsx- Multi-sheet Excel workbook:RH_Summary,Temp_Summary,WindSpeed_Summary,WindDir_Summary— per-sensor summary statisticsRH_Details,Temp_Details,WindSpeed_Details,WindDir_Details— per-event detail statisticsEvent_Log— event metadata (test_name, config_key, shower times, door position, fan status)Bedroom_Conditions— per-event bedroom RH and temperature (30-min pre-shower window); used by particle analysis for boxplot annotations; not rounded by sig figs
output/plots/event_figures/rh_timeseries/event_NN-testname_rh_timeseries.png- RH time series per eventoutput/plots/event_figures/temperature_timeseries/event_NN-testname_temperature_timeseries.png- Temperature per eventoutput/plots/event_figures/wind_timeseries/event_NN-testname_wind_timeseries.png- Wind data per eventoutput/plots/event_figures/excluded_events/- Time series figures for duration-excluded eventsoutput/plots/rh_pre_post_boxplot.png- Pre/post RH comparison across all eventsoutput/plots/temperature_pre_post_boxplot.png- Pre/post temperature comparisonoutput/plots/wind_speed_pre_post_boxplot.png- Pre/post wind speed comparisonoutput/plots/wind_direction_pre_post_boxplot.png- Pre/post wind direction comparison
Key packages (see epa_mh.yml for complete list):
- pandas, numpy - Data manipulation
- scipy - Numerical integration (trapezoidal rule for E_total calculation)
- matplotlib - Publication-quality figure generation
- bokeh - Interactive visualization
- requests - API communication
- python-dotenv - Environment variable management (.env file loading)
- pyyaml - Configuration management
- openpyxl - Excel file I/O
Instrument manuals and data sheets are not included in this repository due to copyright. Public download links are listed in docs/pdf_links.md.
- PI: Dustin G. Poppendieck — dustin.poppendieck@nist.gov
- Maintainer: Nathan Lima — nathan.lima@nist.gov
- NIST Organizational Unit: Engineering Laboratory
- Division: Building Energy and Environment Division
- Group: Indoor Air Quality and Ventilation Group
The experimental data associated with this project are not included in this repository. Data can be made available upon request by contacting the PI.
If you use this software, please cite it as:
@software{nist_epa_legionella,
author = {Lima, Nathan M. and Poppendieck, Dustin G.},
title = {NIST-EPA Legionella Study Analysis Tools},
year = {2026},
publisher = {National Institute of Standards and Technology},
doi = {10.18434/mds2-4153},
url = {https://github.com/usnistgov/NIST_EPA_Legionella}
}A peer-reviewed publication and/or NIST Technical Note associated with this study is in preparation. This section will be updated with a citable BibTeX entry and DOI when available.
This software is in the public domain and provided "as is" according to the LICENSE.md file. It was developed by employees of the National Institute of Standards and Technology (NIST), an agency of the Federal Government and is being made available as a public service. Pursuant to Title 17 United States Code Section 105, works of NIST employees are not subject to copyright protection in the United States. This software may be subject to foreign copyright. Permission in the United States and in foreign countries, to the extent that NIST may hold copyright, to use, copy, modify, create derivative works, and distribute this software and its documentation without fee is hereby granted on a non-exclusive basis, provided that this notice and disclaimer of warranty appears in all copies.
See LICENSE.md for the full NIST licensing statement.