Reproducing Room-Occupancy Detection

UCI's Occupancy Detection dataset: five environmental channels sampled about once a minute in a single office, with a real occupancy label on every row. Two things below are checkable end to end: whether the local channel classifier picks out the channels that actually move with occupancy, and whether outcome-tagged retrieval returns results consistent with that same label.

Requirements: a VAAS-X API key, pip install vaas-x pandas, and the dataset.

Data

https://archive.ics.uci.edu/static/public/357/occupancy+detection.zip

(UCI Machine Learning Repository, dataset 357.) Unzip it -- datatraining.txt is used here: 8143 rows, columns date, Temperature, Humidity, Light, CO2, HumidityRatio, Occupancy (0/1), comma-delimited, with header.

Split the label out

import pandas as pd

df = pd.read_csv("datatraining.txt", index_col=0, parse_dates=["date"])
channels = ["Temperature", "Humidity", "Light", "CO2", "HumidityRatio"]

Occupancy is the ground truth. It doesn't go in the ingest payload -- it's used afterward, to tag outcomes and to check the classifier's output.

Ingest it

from vaasx import Bootstrap

brain = Bootstrap(api_key="YOUR_API_KEY", device_id="office_occupancy")

records = df[channels].to_dict(orient="records")
timestamps = df.index.astype(str).tolist()

ids = []
for i in range(0, len(records), 500):
    batch = [{"payload": r, "timestamp": t} for r, t in zip(records[i:i+500], timestamps[i:i+500])]
    resp = brain.ingest(batch)
    ids.extend(resp["episode_ids"])

Tag outcomes from the real label

occupied = df["Occupancy"].tolist()
for episode_id, is_occupied in zip(ids, occupied):
    if episode_id is None:
        continue
    brain.outcome(episode_id, success=bool(is_occupied))

Query it

hits = brain.query("occupied room, motion detected, elevated CO2 and light", k=5, prefer_success=True)
for h in hits:
    print(h["timestamp"], h["outcome"]["success"], h["score"])

Outcomes were tagged straight from Occupancy, so with prefer_success=True every hit above should read success: True. Anything that doesn't is worth checking against df.loc[timestamp] directly.

Reproduce the channel classification

from vaasx.bootstrap import StatisticalProfiler, SchemaClassifier

profiler = StatisticalProfiler(device_id="office_occupancy")
for r in records:
    profiler.observe(r)

result = SchemaClassifier().classify(profiler.snapshot())
print("significant channels:", sorted(result["significant_channels"]))

Verify independently

correlations = df[channels + ["Occupancy"]].corr()["Occupancy"].drop("Occupancy").abs().sort_values(ascending=False)
print(correlations)

Light and CO2 correlate with Occupancy far more strongly than Temperature, Humidity, or HumidityRatio -- check that ranking against significant_channels above rather than taking either list on faith.

Longer runs

datatest.txt and datatest2.txt cover different date ranges under the same schema and are also labeled -- ingest either as a second device_id and repeat the correlation check against its own labels.

More guides: Reproduction Guides. Full reference: Developer Guide.