025 — Cross-chamber bias comparison (diagnostic)

025 — Cross-chamber bias comparison (diagnostic)#

Compare CO2 / H2O / temperature time series between chambers C1 and C2 to detect systematic between-chamber bias (sensor calibration drift, analyzer differences, mechanical seal differences). Output is diagnostic only — no corrected data is exported. Bias correction cannot distinguish drift from biology.

Runs on the bundled synthetic sample.

import pandas as pd
import matplotlib.pyplot as plt

from palmwtc.config import DataPaths
from palmwtc.viz import set_style

set_style()
paths = DataPaths.resolve()
print(paths.describe())
DataPaths (source=sample (bundled synthetic), site=libz):
  raw_dir       = /home/runner/work/palmwtc/palmwtc/src/palmwtc/data/sample/synthetic
  processed_dir = /home/runner/work/palmwtc/palmwtc/src/palmwtc/data/sample/Data/Integrated_QC_Data
  exports_dir   = /home/runner/work/palmwtc/palmwtc/src/palmwtc/data/sample/exports
  config_dir    = /home/runner/work/palmwtc/palmwtc/src/palmwtc/data/sample/config
  extras        = <none>
qc_path = paths.raw_dir / "QC_Flagged_Data_synthetic.parquet"
df = pd.read_parquet(qc_path, columns=["TIMESTAMP", "CO2_C1", "CO2_C2", "H2O_C1", "H2O_C2"])
df = df.dropna(subset=["CO2_C1", "CO2_C2"]).iloc[::60]  # sample to 30-min cadence
print(f"{len(df)} comparison rows")
335 comparison rows
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
axes[0].scatter(df["CO2_C1"], df["CO2_C2"], s=5, alpha=0.4)
axes[0].plot([df["CO2_C1"].min(), df["CO2_C1"].max()], [df["CO2_C1"].min(), df["CO2_C1"].max()], "r--", lw=0.8)
axes[0].set_xlabel("CO2 Chamber 1 (ppm)")
axes[0].set_ylabel("CO2 Chamber 2 (ppm)")
axes[0].set_title(f"CO2 cross-chamber (slope={(df['CO2_C2'] / df['CO2_C1']).median():.3f})")

axes[1].scatter(df["H2O_C1"], df["H2O_C2"], s=5, alpha=0.4)
axes[1].plot([df["H2O_C1"].min(), df["H2O_C1"].max()], [df["H2O_C1"].min(), df["H2O_C1"].max()], "r--", lw=0.8)
axes[1].set_xlabel("H2O Chamber 1 (mmol/mol)")
axes[1].set_ylabel("H2O Chamber 2 (mmol/mol)")
axes[1].set_title("H2O cross-chamber")

plt.tight_layout()
plt.show()
../_images/2f122a4af5960bf2dfa50181bc9deea3de2607bd64a01d2d361395c5b6b657d0.png

On real data, persistent off-diagonal scatter (slope != 1, large RMSE) flags between-chamber bias. A flagged window can then be added to config/sensor_exclusions.yaml for follow-up investigation.