Quick start¶
One call¶
import pandas as pd
import pynrpf
frame = pd.read_parquet("readings.parquet") # substation_id, timestamp, net_load_MW, solar_MW
result = pynrpf.run(frame)
result.site_days.head() # one row per site and day
result.intervals.head() # one row per reading
result.summary() # days per outcome, MWh flipped
result.provenance # the version and settings that produced the tables; store it with them
The frame needs four columns: a site identifier, a timestamp on the fifteen-minute grid, the recorded net load in MW (positive = import as stored) and a solar generation estimate in MW.
The control¶
result = pynrpf.run(frame, c=0.9)
c is the only control. A day is corrected automatically when its probability reaches c,
kept automatically when the probability is at most 1 - c, and sent to review in between.
The release default is 0.7. Raw readings are never altered: the corrected series is a
separate column.
Show details
Column names and incomplete days¶
Other column names are mapped with columns:
result = pynrpf.run(frame, columns=dict(site="site_id", timestamp="ts", net_load="mw", solar="pv"))
A day is scored when all 96 readings are present; missing values inside a complete day are allowed and only disqualify the windows that touch them. Timestamps with a time zone are converted to UTC before the calendar day is taken; naive timestamps are taken as they are.
The site-day table¶
| column | type | meaning |
|---|---|---|
site |
string | site identifier as given |
date |
string | calendar day, YYYY-MM-DD |
n_slots |
int64 | readings found for the day; 96 is complete |
input_ok |
bool | the day was complete and had at least one admissible window |
n_admissible |
int64 | candidate windows that could be scored |
evidence |
float64 | r*: evidence of the best window; NaN when not scored |
p |
float64 | calibrated probability that the day carries a wrong sign |
outcome |
string | AUTO_CORRECT, AUTO_KEEP or UNCERTAIN |
window_start |
int64 | first slot of the best window (0 = 00:00), -1 when none |
window_end |
int64 | last slot of the best window, inclusive, -1 when none |
runner_start |
int64 | first slot of the runner-up window, -1 when none |
runner_end |
int64 | last slot of the runner-up window, -1 when none |
runner_evidence |
float64 | evidence of the runner-up window |
proposed_mwh |
float64 | energy the best window would flip, MWh |
recorded_min_mw |
float64 | minimum recorded net load of the day, MW |
corrected_min_mw |
float64 | minimum after flipping the best window, MW |
min_change_mw |
float64 | corrected minus recorded minimum, MW |
Slots count fifteen-minute intervals from midnight: slot 24 is 06:00, slot 71 is 17:45. The window and the energy are reported for every scored day, whatever the outcome, so a reviewer sees what the tool would have done on an UNCERTAIN day.
The interval table¶
| column | type | meaning |
|---|---|---|
site |
string | site identifier as given |
timestamp |
string | the reading's timestamp as given, ISO 8601 |
net_load_mw |
float64 | recorded net load, MW |
in_window |
bool | the slot lies inside the best window |
corrected |
bool | the sign was flipped: in the window and the day is AUTO_CORRECT |
net_load_corrected_mw |
float64 | recorded net load with the sign flipped where corrected, MW |
A new population¶
The release calibration was fitted on eight Ausgrid substations with real, reviewed errors. For a population whose ordinary days look different, refit the intercept from about 27 reviewed days and keep the slope; see Calibration.
from pynrpf import RELEASE_CALIBRATION, fit_calibration
pair = fit_calibration(reviewed["evidence"], reviewed["label"], slope=RELEASE_CALIBRATION.slope)
result = pynrpf.run(frame, calibration=pair)
Every public name is listed in the API.