housemonitor / prepare_data.py
lyimo's picture
Upload 5 files
e5ef030 verified
Raw
History Blame Contribute Delete
5.17 kB
"""
One-time data prep: AMPds2 -> compact hourly parquet for the Space.
Usage
-----
python prepare_data.py /path/to/dataverse_files.zip
python prepare_data.py /path/to/AMPds2_folder
python prepare_data.py # uses the default Colab/Drive path below
Produces data/ampds2_hourly.parquet (small enough to commit to the HF Space).
Resamples the minutely AMPds2 active-power readings to hourly means.
"""
import io
import os
import sys
import zipfile
import tempfile
import numpy as np
import pandas as pd
DEFAULT_INPUT = "/content/drive/MyDrive/AMPds2/dataverse_files.zip"
OUT = "data/ampds2_hourly.parquet"
METER_RE = __import__("re").compile(r"^[A-Z][A-Z0-9]{1,2}E$") # WHE, FGE, HPE, B1E, B2E, ...
def _read_table(buf_or_path, name):
sep = "\t" if name.lower().endswith((".tab", ".tsv")) else ","
return pd.read_csv(buf_or_path, sep=sep, low_memory=False)
def _score(df):
"""How likely this table is the wide active-power file (meter-code columns incl. WHE)."""
cols = [str(c).strip().upper() for c in df.columns]
meters = [c for c in cols if METER_RE.match(c)]
return (len(meters), "WHE" in cols)
def _iter_tables(path):
"""Yield (name, dataframe) for every csv/tab/tsv found in a zip (incl. nested) or folder."""
if zipfile.is_zipfile(path):
with zipfile.ZipFile(path) as z:
for m in z.namelist():
low = m.lower()
if low.endswith(".zip"):
with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as tf:
tf.write(z.read(m)); nested = tf.name
try:
yield from _iter_tables(nested)
finally:
os.unlink(nested)
elif low.endswith((".csv", ".tab", ".tsv")):
try:
yield m, _read_table(io.BytesIO(z.read(m)), m)
except Exception as e:
print(" skip", m, "->", e)
elif os.path.isdir(path):
for root, _, files in os.walk(path):
for f in files:
if f.lower().endswith((".csv", ".tab", ".tsv")):
fp = os.path.join(root, f)
try:
yield f, _read_table(fp, f)
except Exception as e:
print(" skip", f, "->", e)
else: # single file
name = os.path.basename(path)
yield name, _read_table(path, name)
def find_power_table(path):
best, best_score, best_name = None, (-1, False), None
for name, df in _iter_tables(path):
# an active-power file is preferred (…_P… in AMPds2); compute a score regardless
s = _score(df)
bonus = (1, s[1]) if ("_p" in name.lower() or name.lower().startswith("electricity")) else (0, s[1])
score = (s[0] + bonus[0] * 100, s[1])
print(f" candidate {name:40s} meters={s[0]:2d} has_WHE={s[1]}")
if score > best_score:
best, best_score, best_name = df, score, name
if best is None or best_score[0] < 1:
raise SystemExit("No AMPds2 power table found (need a CSV with meter-code columns like WHE, FGE).")
print(" -> using:", best_name)
return best
def to_hourly(df):
df = df.copy()
df.columns = [str(c).strip() for c in df.columns]
upper = {c: c.upper() for c in df.columns}
# timestamp: prefer an explicit UNIX seconds column, else first datetime-like column
ts_col = next((c for c in df.columns if upper[c] in ("UNIX_TS", "TS", "TIMESTAMP", "TIME")), None)
if ts_col is not None and np.issubdtype(df[ts_col].dropna().dtype, np.number):
idx = pd.to_datetime(df[ts_col], unit="s")
elif ts_col is not None:
idx = pd.to_datetime(df[ts_col], errors="coerce")
else:
ts_col = df.columns[0]
idx = pd.to_datetime(df[ts_col], errors="coerce")
if idx.isna().mean() > 0.5: # maybe it's unix seconds in col 0
idx = pd.to_datetime(pd.to_numeric(df[ts_col], errors="coerce"), unit="s")
df.index = idx
meters = [c for c in df.columns if METER_RE.match(upper[c]) and c != ts_col]
out = df[meters].apply(pd.to_numeric, errors="coerce")
out.columns = [upper[c] for c in meters]
out = out[~out.index.isna()].sort_index()
hourly = out.resample("h").mean()
return hourly
def main():
inp = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_INPUT
if not os.path.exists(inp):
raise SystemExit(f"Input not found: {inp}\nPass the path to dataverse_files.zip or the AMPds2 folder.")
print("Reading AMPds2 from:", inp)
raw = find_power_table(inp)
print(f" raw shape: {raw.shape}")
hourly = to_hourly(raw)
os.makedirs(os.path.dirname(OUT), exist_ok=True)
hourly.index.name = "ts"
hourly.to_parquet(OUT)
mb = os.path.getsize(OUT) / 1e6
print(f"\nWrote {OUT} ({hourly.shape[0]} hours x {hourly.shape[1]} meters, {mb:.1f} MB)")
print("Columns:", list(hourly.columns))
print("Commit this parquet to your Space (or upload via the Files tab).")
if __name__ == "__main__":
main()