Skip to content

Commit c852227

Browse files
committed
add ffill/! and bfill/! #41
1 parent 009b908 commit c852227

File tree

4 files changed

+68
-0
lines changed

4 files changed

+68
-0
lines changed

docs/src/man/missing.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ The `IMD.sum`, `IMD.minimum`, and `IMD.maximum` functions also support the `thre
107107

108108
The following functions are also exported by InMemoryDatasets:
109109

110+
* `bfill` : backward filling
111+
* `bfill!` : backward filling in-place
112+
* `ffill` : forward filling
113+
* `ffill!` : forward filling in-place
110114
* `lag` : Create a lag-k of the provided vector
111115
* `lag!` : Replace its input with a lag-k values
112116
* `lead` : Create a lead-k of the provided vector

src/InMemoryDatasets.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,10 @@ export
9696
cummax!,
9797
cummin,
9898
cummin!,
99+
ffill!,
100+
ffill,
101+
bfill!,
102+
bfill,
99103
# from join
100104
innerjoin,
101105
outerjoin,

src/stat/non_hp_stat.jl

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ same size as `x` (the input array).
4545
(lag, lag!)
4646

4747
function lag(x::AbstractVector, k; default = missing)
48+
@assert firstindex(x) == 1 "lag only supports 1-based indexing"
4849
res = Vector{Union{promote_type(typeof(default), eltype(x)), Missing}}(undef, length(x))
4950
for i in 1:k
5051
@inbounds res[i] = default
@@ -58,6 +59,7 @@ end
5859
lag(x::AbstractVector; default = missing) = lag(x,1; default = default)
5960

6061
function lag!(x::AbstractVector, k; default = missing)
62+
@assert firstindex(x) == 1 "lag! only supports 1-based indexing"
6163
@assert promote_type(typeof(default), eltype(x)) <: eltype(x) "`default` must be the same type as the element of the passed vector"
6264
for i in length(x):-1:(k+1)
6365
@inbounds x[i] = x[i-k]
@@ -83,6 +85,7 @@ same size as `x` (the input array).
8385
(lead, lead!)
8486

8587
function lead(x::AbstractVector, k; default = missing)
88+
@assert firstindex(x) == 1 "lead only supports 1-based indexing"
8689
res = Vector{Union{promote_type(typeof(default), eltype(x)), Missing}}(undef, length(x))
8790
for i in 1:length(x)-k
8891
@inbounds res[i] = x[i+k]
@@ -95,6 +98,7 @@ end
9598
lead(x::AbstractVector; default = missing) = lead(x, 1; default = default)
9699

97100
function lead!(x::AbstractVector, k; default = missing)
101+
@assert firstindex(x) == 1 "lead! only supports 1-based indexing"
98102
@assert promote_type(typeof(default), eltype(x)) <: eltype(x) "`default` must be the same type as the element of the passed vector"
99103
for i in 1:length(x)-k
100104
@inbounds x[i] = x[i+k]
@@ -447,9 +451,51 @@ end
447451
Return upto `k` largest nonmissing elements of `x`. When `rev = true` it returns upto `k` smallest nonmissing elements of `x`. When all elements are missing, the function returns `missing`
448452
"""
449453
function topk(x::AbstractVector, k::Int; rev = false)
454+
@assert firstindex(x) == 1 "topk only supports 1-based indexing"
450455
if rev
451456
k_smallest(x, k)
452457
else
453458
k_largest(x, k)
454459
end
455460
end
461+
462+
463+
"""
464+
ffill(x; [by = ismissing])
465+
ffill!(x; [by = ismissing])
466+
467+
Replace those elements of `x` which returns `true` when `by` is called on them with the last element which calling `by` on it returns `false`.
468+
469+
`ffill!` modifies the input vector in-place
470+
"""
471+
(ffill, ffill!)
472+
473+
function ffill!(x::AbstractVector; by = ismissing)
474+
@assert firstindex(x) == 1 "ffill!/ffill only support 1-based indexing"
475+
for i in 2:length(x)
476+
if by(x[i])
477+
x[i] = x[i-1]
478+
end
479+
end
480+
x
481+
end
482+
ffill(x; by = ismissing) = ffill!(copy(x), by = by)
483+
484+
"""
485+
bfill(x; [by = ismissing])
486+
bfill!(x; [by = ismissing])
487+
488+
Replace those elements of `x` which returns `true` when `by` is called on them with the next element which calling `by` on it returns `false`.
489+
490+
`bfill!` modifies the input vector in-place
491+
"""
492+
function bfill!(x::AbstractVector; by = ismissing)
493+
@assert firstindex(x) == 1 "bfill!/bfill only support 1-based indexing"
494+
for i in length(x)-1:-1:1
495+
if by(x[i])
496+
x[i] = x[i+1]
497+
end
498+
end
499+
x
500+
end
501+
bfill(x, by = ismissing) = bfill!(copy(x), by = by)

test/data.jl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,3 +366,17 @@ end
366366
@test byrow(mask(view(ds, nrow(ds):-1:1, ncol(ds):-1:1), [>(5), ==(10)], [2,1], threads = false), all, threads = false) == [trues(500);falses(500)]
367367
@test byrow(view(ds, nrow(ds):-1:1, ncol(ds):-1:1), all, [2,1], by = [>(5), ==(10)], threads = false) == [trues(500);falses(500)]
368368
end
369+
370+
@testset "ffill, ffill!, bfill, bfill!" begin
371+
x = [missing, 1, 2, missing, missing, missing, 10, missing]
372+
@test isequal(ffill(x) , [missing, 1, 2,2,2,2,10,10])
373+
@test isequal(bfill(x), [1,1,2,10,10,10,10, missing])
374+
@test isequal(bfill([missing]), [missing])
375+
@test isequal(ffill([missing]), [missing])
376+
377+
ds = Dataset(g = [1,1,1,1,2,2,2,2], x = [1.0,2.0,1.0,2.0,missing, missing,1.0,1.0])
378+
@test modify(groupby(ds, :g), :x=>ffill!) == ds
379+
@test modify(groupby(ds, :g), :x=>bfill!) == Dataset(g = [1,1,1,1,2,2,2,2], x = [1.0,2.0,1.0,2.0,1.0, 1.0,1.0,1.0])
380+
@test modify(groupby(ds, :g), :x=>x->ffill!(x, by = isequal(1.0))) == Dataset(g = [1,1,1,1,2,2,2,2], x = [1.0,2.0,2.0,2.0,missing, missing, missing, missing])
381+
@test modify(groupby(ds, :g), :x=>x->bfill!(x, by = isequal(1.0))) == Dataset(g = [1,1,1,1,2,2,2,2], x = [2.0,2.0,2.0,2.0,missing, missing,1.0,1.0])
382+
end

0 commit comments

Comments
 (0)