@@ -224,12 +224,20 @@ <h1>Source code for vtools.functions.transition</h1><div class="highlight"><pre>
224224< span class ="sd "> ts1 : pandas.Series or pandas.DataFrame</ span >
225225< span class ="sd "> The final time series segment. Must share the same frequency and type as `ts0`.</ span >
226226
227- < span class ="sd "> method : {"linear", "pchip"}, default="linear"</ span >
228- < span class ="sd "> The interpolation method to use for generating the transition.</ span >
227+ < span class ="sd "> method : {"linear", "pchip", "blend"}, default="linear"</ span >
228+ < span class ="sd "> The interpolation strategy:</ span >
229+ < span class ="sd "> - "linear": interpolate across a gap using endpoints from ts0/ts1.</ span >
230+ < span class ="sd "> - "pchip": shape-preserving interpolation using nearby points (see `overlap`).</ span >
231+ < span class ="sd "> - "blend": requires an explicit `window=(start, end)` where both ts0 and ts1</ span >
232+ < span class ="sd "> have values on every timestamp; returns a linear combination</ span >
233+ < span class ="sd "> (1 - w(t)) * ts0(t) + w(t) * ts1(t) with w(start)=0 → w(end)=1.</ span >
229234
230235< span class ="sd "> window : [start, end] or None</ span >
231- < span class ="sd "> If None and there's a natural gap (ts0.last < ts1.first), that full gap is used.</ span >
232- < span class ="sd "> If provided, start<end, ts0 must have samples at/before start, ts1 at/after end.</ span >
236+ < span class ="sd "> - For "linear"/"pchip": If None and there's a natural gap (ts0.last < ts1.first),</ span >
237+ < span class ="sd "> that full gap is used. If provided, start<end, ts0 must have a sample at/before</ span >
238+ < span class ="sd "> start and ts1 at/after end; optional widening to a natural gap via `max_snap`.</ span >
239+ < span class ="sd "> - For "blend": **Required.** Both series must cover every timestamp in</ span >
240+ < span class ="sd "> [start, end] with non-missing values; no widening or gap logic is applied.</ span >
233241
234242< span class ="sd "> names : None, str, or iterable of str, optional</ span >
235243< span class ="sd "> - If `None` (default), inputs must share compatible column names.</ span >
@@ -268,6 +276,49 @@ <h1>Source code for vtools.functions.transition</h1><div class="highlight"><pre>
268276
269277 < span class ="n "> freq</ span > < span class ="o "> =</ span > < span class ="n "> ts0</ span > < span class ="o "> .</ span > < span class ="n "> index</ span > < span class ="o "> .</ span > < span class ="n "> freq</ span >
270278
279+
280+ < span class ="c1 "> # --- BLEND mode: explicit overlap with non-missing values in both series ---</ span >
281+ < span class ="k "> if</ span > < span class ="n "> method</ span > < span class ="o "> ==</ span > < span class ="s2 "> "blend"</ span > < span class ="p "> :</ span >
282+ < span class ="k "> if</ span > < span class ="n "> window</ span > < span class ="ow "> is</ span > < span class ="kc "> None</ span > < span class ="p "> :</ span >
283+ < span class ="k "> raise</ span > < span class ="ne "> ValueError</ span > < span class ="p "> (</ span > < span class ="s2 "> "method='blend' requires window=(start, end)."</ span > < span class ="p "> )</ span >
284+ < span class ="n "> start</ span > < span class ="o "> =</ span > < span class ="n "> pd</ span > < span class ="o "> .</ span > < span class ="n "> Timestamp</ span > < span class ="p "> (</ span > < span class ="n "> window</ span > < span class ="p "> [</ span > < span class ="mi "> 0</ span > < span class ="p "> ])</ span >
285+ < span class ="n "> end</ span > < span class ="o "> =</ span > < span class ="n "> pd</ span > < span class ="o "> .</ span > < span class ="n "> Timestamp</ span > < span class ="p "> (</ span > < span class ="n "> window</ span > < span class ="p "> [</ span > < span class ="mi "> 1</ span > < span class ="p "> ])</ span >
286+ < span class ="k "> if</ span > < span class ="n "> start</ span > < span class ="o "> >=</ span > < span class ="n "> end</ span > < span class ="p "> :</ span >
287+ < span class ="k "> raise</ span > < span class ="ne "> ValueError</ span > < span class ="p "> (</ span > < span class ="s2 "> "blend window start must be strictly before end."</ span > < span class ="p "> )</ span >
288+ < span class ="c1 "> # exact inclusive grid for the blend interval</ span >
289+ < span class ="n "> trans_index</ span > < span class ="o "> =</ span > < span class ="n "> pd</ span > < span class ="o "> .</ span > < span class ="n "> date_range</ span > < span class ="p "> (</ span > < span class ="n "> start</ span > < span class ="o "> =</ span > < span class ="n "> start</ span > < span class ="p "> ,</ span > < span class ="n "> end</ span > < span class ="o "> =</ span > < span class ="n "> end</ span > < span class ="p "> ,</ span > < span class ="n "> freq</ span > < span class ="o "> =</ span > < span class ="n "> freq</ span > < span class ="p "> )</ span >
290+ < span class ="k "> if</ span > < span class ="nb "> len</ span > < span class ="p "> (</ span > < span class ="n "> trans_index</ span > < span class ="p "> )</ span > < span class ="o "> <</ span > < span class ="mi "> 2</ span > < span class ="p "> :</ span >
291+ < span class ="k "> raise</ span > < span class ="ne "> ValueError</ span > < span class ="p "> (</ span > < span class ="s2 "> "blend window must contain at least two timestamps."</ span > < span class ="p "> )</ span >
292+ < span class ="c1 "> # require full coverage with no NaNs</ span >
293+ < span class ="k "> try</ span > < span class ="p "> :</ span >
294+ < span class ="n "> seg0</ span > < span class ="o "> =</ span > < span class ="n "> ts0</ span > < span class ="o "> .</ span > < span class ="n "> loc</ span > < span class ="p "> [</ span > < span class ="n "> trans_index</ span > < span class ="p "> ]</ span >
295+ < span class ="n "> seg1</ span > < span class ="o "> =</ span > < span class ="n "> ts1</ span > < span class ="o "> .</ span > < span class ="n "> loc</ span > < span class ="p "> [</ span > < span class ="n "> trans_index</ span > < span class ="p "> ]</ span >
296+ < span class ="k "> except</ span > < span class ="ne "> KeyError</ span > < span class ="p "> :</ span >
297+ < span class ="k "> raise</ span > < span class ="ne "> ValueError</ span > < span class ="p "> (</ span > < span class ="s2 "> "Both series must cover every timestamp in the blend window."</ span > < span class ="p "> )</ span >
298+ < span class ="k "> if</ span > < span class ="nb "> isinstance</ span > < span class ="p "> (</ span > < span class ="n "> ts0</ span > < span class ="p "> ,</ span > < span class ="n "> pd</ span > < span class ="o "> .</ span > < span class ="n "> DataFrame</ span > < span class ="p "> ):</ span >
299+ < span class ="k "> if</ span > < span class ="n "> seg0</ span > < span class ="o "> .</ span > < span class ="n "> isna</ span > < span class ="p "> ()</ span > < span class ="o "> .</ span > < span class ="n "> any</ span > < span class ="p "> ()</ span > < span class ="o "> .</ span > < span class ="n "> any</ span > < span class ="p "> ()</ span > < span class ="ow "> or</ span > < span class ="n "> seg1</ span > < span class ="o "> .</ span > < span class ="n "> isna</ span > < span class ="p "> ()</ span > < span class ="o "> .</ span > < span class ="n "> any</ span > < span class ="p "> ()</ span > < span class ="o "> .</ span > < span class ="n "> any</ span > < span class ="p "> ():</ span >
300+ < span class ="k "> raise</ span > < span class ="ne "> ValueError</ span > < span class ="p "> (</ span > < span class ="s2 "> "NaNs found within blend window in ts0/ts1."</ span > < span class ="p "> )</ span >
301+ < span class ="n "> w</ span > < span class ="o "> =</ span > < span class ="n "> np</ span > < span class ="o "> .</ span > < span class ="n "> linspace</ span > < span class ="p "> (</ span > < span class ="mf "> 0.0</ span > < span class ="p "> ,</ span > < span class ="mf "> 1.0</ span > < span class ="p "> ,</ span > < span class ="nb "> len</ span > < span class ="p "> (</ span > < span class ="n "> trans_index</ span > < span class ="p "> ))[:,</ span > < span class ="kc "> None</ span > < span class ="p "> ]</ span >
302+ < span class ="n "> blended_vals</ span > < span class ="o "> =</ span > < span class ="p "> (</ span > < span class ="mf "> 1.0</ span > < span class ="o "> -</ span > < span class ="n "> w</ span > < span class ="p "> )</ span > < span class ="o "> *</ span > < span class ="n "> seg0</ span > < span class ="o "> .</ span > < span class ="n "> to_numpy</ span > < span class ="p "> (</ span > < span class ="n "> dtype</ span > < span class ="o "> =</ span > < span class ="nb "> float</ span > < span class ="p "> )</ span > < span class ="o "> +</ span > < span class ="n "> w</ span > < span class ="o "> *</ span > < span class ="n "> seg1</ span > < span class ="o "> .</ span > < span class ="n "> to_numpy</ span > < span class ="p "> (</ span > < span class ="n "> dtype</ span > < span class ="o "> =</ span > < span class ="nb "> float</ span > < span class ="p "> )</ span >
303+ < span class ="n "> blended</ span > < span class ="o "> =</ span > < span class ="n "> pd</ span > < span class ="o "> .</ span > < span class ="n "> DataFrame</ span > < span class ="p "> (</ span > < span class ="n "> blended_vals</ span > < span class ="p "> ,</ span > < span class ="n "> index</ span > < span class ="o "> =</ span > < span class ="n "> trans_index</ span > < span class ="p "> ,</ span > < span class ="n "> columns</ span > < span class ="o "> =</ span > < span class ="n "> ts0</ span > < span class ="o "> .</ span > < span class ="n "> columns</ span > < span class ="p "> )</ span >
304+ < span class ="k "> else</ span > < span class ="p "> :</ span >
305+ < span class ="k "> if</ span > < span class ="n "> seg0</ span > < span class ="o "> .</ span > < span class ="n "> isna</ span > < span class ="p "> ()</ span > < span class ="o "> .</ span > < span class ="n "> any</ span > < span class ="p "> ()</ span > < span class ="ow "> or</ span > < span class ="n "> seg1</ span > < span class ="o "> .</ span > < span class ="n "> isna</ span > < span class ="p "> ()</ span > < span class ="o "> .</ span > < span class ="n "> any</ span > < span class ="p "> ():</ span >
306+ < span class ="k "> raise</ span > < span class ="ne "> ValueError</ span > < span class ="p "> (</ span > < span class ="s2 "> "NaNs found within blend window in ts0/ts1."</ span > < span class ="p "> )</ span >
307+ < span class ="n "> w</ span > < span class ="o "> =</ span > < span class ="n "> np</ span > < span class ="o "> .</ span > < span class ="n "> linspace</ span > < span class ="p "> (</ span > < span class ="mf "> 0.0</ span > < span class ="p "> ,</ span > < span class ="mf "> 1.0</ span > < span class ="p "> ,</ span > < span class ="nb "> len</ span > < span class ="p "> (</ span > < span class ="n "> trans_index</ span > < span class ="p "> ))</ span >
308+ < span class ="n "> blended_vals</ span > < span class ="o "> =</ span > < span class ="p "> (</ span > < span class ="mf "> 1.0</ span > < span class ="o "> -</ span > < span class ="n "> w</ span > < span class ="p "> )</ span > < span class ="o "> *</ span > < span class ="n "> seg0</ span > < span class ="o "> .</ span > < span class ="n "> to_numpy</ span > < span class ="p "> (</ span > < span class ="n "> dtype</ span > < span class ="o "> =</ span > < span class ="nb "> float</ span > < span class ="p "> )</ span > < span class ="o "> +</ span > < span class ="n "> w</ span > < span class ="o "> *</ span > < span class ="n "> seg1</ span > < span class ="o "> .</ span > < span class ="n "> to_numpy</ span > < span class ="p "> (</ span > < span class ="n "> dtype</ span > < span class ="o "> =</ span > < span class ="nb "> float</ span > < span class ="p "> )</ span >
309+ < span class ="n "> blended</ span > < span class ="o "> =</ span > < span class ="n "> pd</ span > < span class ="o "> .</ span > < span class ="n "> Series</ span > < span class ="p "> (</ span > < span class ="n "> blended_vals</ span > < span class ="p "> ,</ span > < span class ="n "> index</ span > < span class ="o "> =</ span > < span class ="n "> trans_index</ span > < span class ="p "> ,</ span > < span class ="n "> name</ span > < span class ="o "> =</ span > < span class ="n "> ts0</ span > < span class ="o "> .</ span > < span class ="n "> name</ span > < span class ="p "> )</ span >
310+ < span class ="c1 "> # splice: ts0 before start, blend in [start,end], ts1 after end</ span >
311+ < span class ="k "> if</ span > < span class ="n "> return_type</ span > < span class ="o "> ==</ span > < span class ="s2 "> "glue"</ span > < span class ="p "> :</ span >
312+ < span class ="k "> return</ span > < span class ="n "> blended</ span >
313+ < span class ="k "> elif</ span > < span class ="n "> return_type</ span > < span class ="o "> ==</ span > < span class ="s2 "> "series"</ span > < span class ="p "> :</ span >
314+ < span class ="n "> left</ span > < span class ="o "> =</ span > < span class ="n "> ts0</ span > < span class ="o "> .</ span > < span class ="n "> loc</ span > < span class ="p "> [</ span > < span class ="n "> ts0</ span > < span class ="o "> .</ span > < span class ="n "> index</ span > < span class ="o "> <</ span > < span class ="n "> start</ span > < span class ="p "> ]</ span >
315+ < span class ="n "> right</ span > < span class ="o "> =</ span > < span class ="n "> ts1</ span > < span class ="o "> .</ span > < span class ="n "> loc</ span > < span class ="p "> [</ span > < span class ="n "> ts1</ span > < span class ="o "> .</ span > < span class ="n "> index</ span > < span class ="o "> ></ span > < span class ="n "> end</ span > < span class ="p "> ]</ span >
316+ < span class ="k "> return</ span > < span class ="n "> pd</ span > < span class ="o "> .</ span > < span class ="n "> concat</ span > < span class ="p "> ([</ span > < span class ="n "> left</ span > < span class ="p "> ,</ span > < span class ="n "> blended</ span > < span class ="p "> ,</ span > < span class ="n "> right</ span > < span class ="p "> ])</ span >
317+ < span class ="k "> else</ span > < span class ="p "> :</ span >
318+ < span class ="k "> raise</ span > < span class ="ne "> ValueError</ span > < span class ="p "> (</ span > < span class ="s2 "> "return_type must be either 'glue' or 'series'."</ span > < span class ="p "> )</ span >
319+
320+
321+
271322 < span class ="c1 "> # `resolved` is either:</ span >
272323 < span class ="c1 "> # • (start_time, end_time): data-aligned gap anchors computed from `window`</ span >
273324 < span class ="c1 "> # (and, if applicable, widened inside the natural gap by `max_snap`), where</ span >
@@ -349,7 +400,7 @@ <h1>Source code for vtools.functions.transition</h1><div class="highlight"><pre>
349400 < span class ="p "> )</ span >
350401 < span class ="n "> interpolated</ span > < span class ="p "> [</ span > < span class ="n "> col</ span > < span class ="p "> ]</ span > < span class ="o "> =</ span > < span class ="n "> interp</ span > < span class ="p "> (</ span > < span class ="n "> trans_index</ span > < span class ="o "> .</ span > < span class ="n "> astype</ span > < span class ="p "> (</ span > < span class ="n "> np</ span > < span class ="o "> .</ span > < span class ="n "> int64</ span > < span class ="p "> ))</ span >
351402 < span class ="k "> else</ span > < span class ="p "> :</ span >
352- < span class ="k "> raise</ span > < span class ="ne "> ValueError</ span > < span class ="p "> (</ span > < span class ="s2 "> "Only 'linear' and 'pchip' methods are supported."</ span > < span class ="p "> )</ span >
403+ < span class ="k "> raise</ span > < span class ="ne "> ValueError</ span > < span class ="p "> (</ span > < span class ="s2 "> "Only 'linear' and 'pchip' and 'blend' methods are supported."</ span > < span class ="p "> )</ span >
353404
354405 < span class ="c1 "> # Final output</ span >
355406 < span class ="k "> if</ span > < span class ="n "> return_type</ span > < span class ="o "> ==</ span > < span class ="s2 "> "glue"</ span > < span class ="p "> :</ span >
0 commit comments