Guides
Transforms & feature engineering
Raw rows preserve exactly what the station reported. Transforms are opt-in — you call them when you want derived features. The raw fields stay in the row alongside the derived ones, so a downstream consumer can recompute or override.
Why opt-in
Section titled “Why opt-in”Raw-as-reported is a hard contract. If the SDK silently rounded temperatures or computed RH at fetch time, a backtest replayed against historical settlements would silently drift. So transforms live in a separate mostlyright.transforms (Python) / @mostlyrightmd/core/transforms (TS) namespace — explicit, traceable.
Unit conversions
Section titled “Unit conversions”The observation schema carries both temp_c and temp_f natively; same for wind_speed_kt / wind_speed_ms and pressure_inhg / pressure_hpa. Use whichever your model expects.
For ad-hoc conversions:
from mostlyright.transforms import ( c_to_f, f_to_c, kt_to_ms, ms_to_kt, inhg_to_hpa, hpa_to_inhg, m_to_ft, ft_to_m,)
print(c_to_f(20.0)) # 68.0print(kt_to_ms(10)) # 5.144import { cToF, fToC, ktToMs, msToKt } from "@mostlyrightmd/core/transforms";
console.log(cToF(20.0)); // 68.0console.log(ktToMs(10)); // 5.144Derived features
Section titled “Derived features”Relative humidity
Section titled “Relative humidity”Magnus-Tetens formula, returns 0–100 (clamped).
from mostlyright.transforms import relative_humidity
rh = relative_humidity(temp_c=20.0, dewpoint_c=10.0)print(rh) # 52.5import { relativeHumidity } from "@mostlyrightmd/core/transforms";const rh = relativeHumidity({ tempC: 20.0, dewpointC: 10.0 });Heat index
Section titled “Heat index”NWS-canonical formula. Returns the input temperature unchanged below 80 °F (heat index isn’t meaningful in cool conditions).
from mostlyright.transforms import heat_index_f
hi = heat_index_f(temp_f=95, rh_pct=70)print(hi) # 124.4Wind chill
Section titled “Wind chill”NWS-canonical (2001 revision). Returns input unchanged above 50 °F or below 3 mph.
from mostlyright.transforms import wind_chill_f
wc = wind_chill_f(temp_f=20, wind_speed_mph=15)print(wc) # 6Feels-like
Section titled “Feels-like”Auto-picks heat index OR wind chill based on temperature:
from mostlyright.transforms import feels_like_f
print(feels_like_f(temp_f=95, rh_pct=70, wind_speed_mph=0)) # 124.4 (heat index)print(feels_like_f(temp_f=20, rh_pct=30, wind_speed_mph=15)) # 6 (wind chill)print(feels_like_f(temp_f=65, rh_pct=50, wind_speed_mph=5)) # 65 (no transform)Pandas / DataFrame ergonomics (Python)
Section titled “Pandas / DataFrame ergonomics (Python)”Apply transforms across a DataFrame:
import pandas as pdfrom mostlyright import researchfrom mostlyright.transforms import relative_humidity, feels_like_f, c_to_f
df = research("KNYC", "2025-01-06", "2025-01-12")
df["obs_rh"] = relative_humidity( df["obs_mean_temp_c"], df["obs_mean_dewpoint_c"],)df["obs_feels_like_f"] = feels_like_f( temp_f=df["obs_mean_f"], rh_pct=df["obs_rh"], wind_speed_mph=df["obs_max_wind_kt"] * 1.15078, # kt to mph)Transforms are NumPy-friendly — they vectorize over Series automatically.
TypeScript per-row
Section titled “TypeScript per-row”Pure-function transforms work over single values; map across an array yourself:
import { relativeHumidity, feelsLikeF } from "@mostlyrightmd/core/transforms";
const enriched = rows.map((r) => ({ ...r, rh_pct: relativeHumidity({ tempC: r.temp_c, dewpointC: r.dewpoint_c }), feels_like_f: feelsLikeF({ tempF: r.temp_f, rhPct: relativeHumidity({ tempC: r.temp_c, dewpointC: r.dewpoint_c }), windSpeedMph: (r.wind_speed_kt ?? 0) * 1.15078, }),}));Preprocessing namespace
Section titled “Preprocessing namespace”mostlyright.preprocessing (Python) / mostlyright.preprocessing (TypeScript, lowercase per Phase 21 21-10) wraps the common transforms behind a single ergonomic surface:
from mostlyright import preprocessing
enriched = preprocessing.attach_derived_features(rows)# rh, feels_like_f, vapor_pressure, etc. attached per rowimport { preprocessing } from "mostlyright";
const enriched = preprocessing.attachDerivedFeatures(rows);Settlement-day aggregation
Section titled “Settlement-day aggregation”research() already does per-day rollups (high, low, mean, count) using LST bucketing. For custom aggregation windows:
from mostlyright.weather import obsimport pandas as pd
rows = obs("KNYC", "2025-01-06", "2025-01-12")df = pd.DataFrame(rows)df["observed_at"] = pd.to_datetime(df["observed_at"])df = df.set_index("observed_at")
# 6-hour bucketsbuckets = df.resample("6H").agg({ "temp_f": ["min", "max", "mean"], "wind_speed_kt": "max",})See also
Section titled “See also”- Quality control — physics-bound rules that flag outliers BEFORE you transform
- Research API — daily aggregates wired-in
- API reference:
mostlyright.transforms·@mostlyrightmd/core/transforms