Tabular Regression
Scikit-learn
Joblib
space-weather
geomagnetic-storms
kp-index
solar-wind
scikit-learn
Instructions to use aliza-ayaz/tormenta with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Scikit-learn
How to use aliza-ayaz/tormenta with Scikit-learn:
from huggingface_hub import hf_hub_download import joblib model = joblib.load( hf_hub_download("aliza-ayaz/tormenta", "sklearn_model.joblib") ) # only load pickle files from sources you trust # read more about it here https://skops.readthedocs.io/en/stable/persistence.html - Notebooks
- Google Colab
- Kaggle
File size: 3,359 Bytes
826c53e 31abf8d 826c53e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | """Merge OMNI (features) + GFZ (label) into the hourly modelling table.
Each hour h is labelled with the Kp of the 3-h block containing h. The table keeps
all hours (missing ones as NaN) and the GFZ definitive flag; the D=2 filter is applied
at training time so preliminary rows stay available for monitoring.
"""
import pandas as pd
from src import config
from src.ingest.fetch_gfz import kp_3hourly, load_gfz_daily
from src.ingest.fetch_omni import load_omni_lst
def build_hourly(omni: pd.DataFrame, gfz_daily: pd.DataFrame) -> pd.DataFrame:
# End at the last hour with any solar-wind data: SPDF's current-year file can carry trailing fill
# rows, and GFZ labels for those hours must not enter training with empty features
omni = omni.loc[: omni[config.SW_FEATURES].dropna(how="all").index.max()]
# Complete hourly grid so later .shift()/.rolling() never jump across gaps
grid = pd.date_range(omni.index.min(), omni.index.max(), freq="h", name="time")
df = omni.reindex(grid).drop(columns=["sunspot_r", "f107"])
kp3 = kp_3hourly(gfz_daily)
block = df.index.floor(f"{config.KP_BLOCK_HOURS}h")
df[config.TARGET] = kp3["kp"].reindex(block).to_numpy()
df["kp_definitive"] = kp3["definitive"].reindex(block).fillna(False).astype(bool).to_numpy()
# Daily observed F10.7 on its own calendar day; features.py applies the 1-day causal shift
df["f107_obs"] = gfz_daily["f107_obs"].reindex(df.index.normalize()).to_numpy()
return df
def build_f107_history(gfz_daily: pd.DataFrame, path=config.F107_HISTORY) -> pd.Series:
"""Daily observed F10.7 file read by the live app for the trailing 81-day mean (last year kept)."""
hist = gfz_daily["f107_obs"].dropna()
hist = hist[hist.index > hist.index.max() - pd.Timedelta(days=365)]
hist.to_csv(path, header=True)
return hist
def save_processed(df: pd.DataFrame, path=config.PROCESSED_CSV) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
df.to_csv(path)
def load_processed(path=config.PROCESSED_CSV) -> pd.DataFrame:
return pd.read_csv(path, index_col="time", parse_dates=["time"])
def summarize(df: pd.DataFrame) -> pd.DataFrame:
"""Missing-% per column, printed with row/column counts."""
usable = df[config.TARGET].notna() & df["kp_definitive"]
print(f"Rows (hours): {len(df):,} Columns: {df.shape[1]}")
print(f"Span: {df.index.min()} → {df.index.max()}")
print(f"Rows with definitive Kp label: {usable.sum():,} "
f"(~{usable.sum() // config.KP_BLOCK_HOURS:,} independent 3-h labels)")
return (df.isna().mean() * 100).round(2).rename("missing_%").to_frame()
def check(df: pd.DataFrame) -> None:
"""Phase 1 sanity checks — raise if the table is malformed."""
assert df.index.is_unique, "duplicate timestamps"
assert (df.index.to_series().diff().dropna() == pd.Timedelta("1h")).all(), "hourly grid has holes"
kp = df[config.TARGET].dropna()
assert kp.between(0, 9).all(), "Kp outside [0, 9]"
print("Sanity checks passed: unique timestamps, complete hourly grid, Kp in [0, 9].")
def main() -> pd.DataFrame:
omni = load_omni_lst()
gfz = load_gfz_daily()
df = build_hourly(omni, gfz)
check(df)
save_processed(df)
build_f107_history(gfz)
print(summarize(df).to_string())
return df
if __name__ == "__main__":
main()
|