Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions src/basemisc.jl
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,10 @@ macro _mapbase(sig::Expr, imp::Expr)
doc = " $sig"
fdef = Expr(:function, sig, fbody)

return esc(
quote
$doc
$fdef
end,
)
return esc(quote
$doc
$fdef
end)
end

# Cumulative functions
Expand Down
2 changes: 1 addition & 1 deletion src/broadcast.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Base.broadcastable(x::AbstractTimeSeries) = x

Base.Broadcast.instantiate(bc::Broadcasted{<:TimeArrayStyle}) =
# skip the default axes checking
Broadcast.flatten(bc)
Broadcast.flatten(bc)

function Base.copy(bc′::Broadcasted{<:TimeArrayStyle})
tas = find_ta(bc′)
Expand Down
6 changes: 4 additions & 2 deletions src/combine.jl
Original file line number Diff line number Diff line change
Expand Up @@ -206,9 +206,11 @@ collapse(ta, month, last)
collapse(ta, month, last, mean)
```
"""
collapse(
function collapse(
ta::TimeArray, period::Period, timestamp::Function, value::Function=timestamp; kw...
) = collapse(ta, x -> floor(x, period), timestamp, value; kw...)
)
collapse(ta, x -> floor(x, period), timestamp, value; kw...)
end

# vcat ######################

Expand Down
15 changes: 9 additions & 6 deletions src/modify.jl
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,15 @@ for f in [:rename, :rename!]
@eval begin
$f(ta::TimeArray, colnames::Symbol) = $f(ta, [colnames])
$f(ta::TimeArray) = throw(MethodError($f, (ta,)))
$f(ta::TimeArray, pairs::Pair{Symbol,Symbol}...) =
$f(ta, _mapcol(colnames(ta), pairs))
$f(f::Base.Callable, ta::TimeArray, colnametyp::Type{Symbol}=Symbol) =
$f(ta, map(f, colnames(ta)))
$f(f::Base.Callable, ta::TimeArray, colnametyp::Type{String}) =
$f(Symbol ∘ f ∘ string, ta)
$f(ta::TimeArray, pairs::Pair{Symbol,Symbol}...) = $f(
ta, _mapcol(colnames(ta), pairs)
)
$f(f::Base.Callable, ta::TimeArray, colnametyp::Type{Symbol}=Symbol) = $f(
ta, map(f, colnames(ta))
)
$f(f::Base.Callable, ta::TimeArray, colnametyp::Type{String}) = $f(
Symbol ∘ f ∘ string, ta
)
end
end

Expand Down
2 changes: 1 addition & 1 deletion src/retime.jl
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ _toAggregationMethod(::Val{:sum}) = Sum()
_toAggregationMethod(::Val{:median}) = Median()
_toAggregationMethod(::Val{:first}) = First()
_toAggregationMethod(::Val{:last}) = Last()
_toAggregationMethof(f::Function) = AggregationFunction(f)
_toAggregationMethod(f::Function) = AggregationFunction(f)
_toAggregationMethod(x::AggregationMethod) = x

_toExtrapolationMethod(x::Symbol) = _toExtrapolationMethod(Val(x))
Expand Down
3 changes: 2 additions & 1 deletion src/split.jl
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,9 @@ Split `data` by `period` function, returns a vector of `TimeSeries.TimeArray`.
- `data::TimeSeries.TimeArray`: Data to split
- `period::Function`: Function, e.g. `Dates.day` that is used to split the `data`.
"""
Base.split(data::TimeSeries.TimeArray, period::Function) =
function Base.split(data::TimeSeries.TimeArray, period::Function)
Iterators.map(i -> data[i], _split(TimeSeries.timestamp(data), period))
end

function _split(ts::AbstractVector{D}, period::Function) where {D<:TimeType}
m = length(ts)
Expand Down
62 changes: 34 additions & 28 deletions src/timearray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,8 @@ end
io::IO,
::MIME"text/plain",
ta::TimeArray;
allrows=!get(io, :limit, false),
allcols=!get(io, :limit, false),
allrows=(!get(io, :limit, false)),
allcols=(!get(io, :limit, false)),
)
nrow = size(values(ta), 1)
ncol = size(values(ta), 2)
Expand Down Expand Up @@ -278,8 +278,8 @@ else
io::IO,
::MIME"text/plain",
ta::TimeArray;
allrows=!get(io, :limit, false),
allcols=!get(io, :limit, false),
allrows=(!get(io, :limit, false)),
allcols=(!get(io, :limit, false)),
)
nrow = size(values(ta), 1)
ncol = size(values(ta), 2)
Expand All @@ -301,8 +301,8 @@ else
row_label_column_alignment=:r,
column_label_alignment=:l,
continuation_row_alignment=:c,
fit_table_in_display_horizontally=!allcols, # replaced crop keyword for v3 of PrettyTables
fit_table_in_display_vertically=!allrows, # replaced crop keyword for v3 of PrettyTables
fit_table_in_display_horizontally=(!allcols), # replaced crop keyword for v3 of PrettyTables
fit_table_in_display_vertically=(!allrows), # replaced crop keyword for v3 of PrettyTables
vertical_crop_mode=:middle,
)
end
Expand All @@ -318,27 +318,34 @@ getindex(ta::TimeArray) = throw(BoundsError(typeof(ta), []))
# single row
@propagate_inbounds getindex(ta::TimeArray, n::Integer) =
# avoid conversion to column vector
TimeArray(timestamp(ta)[n], values(ta)[n:n, :], colnames(ta), meta(ta))
TimeArray(
timestamp(ta)[n], values(ta)[n:n, :], colnames(ta), meta(ta)
)

# single row 1d
@propagate_inbounds getindex(ta::TimeArray{T,1}, n::Integer) where {T} =
TimeArray(timestamp(ta)[n], values(ta)[[n]], colnames(ta), meta(ta))
@propagate_inbounds getindex(ta::TimeArray{T,1}, n::Integer) where {T} = TimeArray(
timestamp(ta)[n], values(ta)[[n]], colnames(ta), meta(ta)
)

# range of rows
@propagate_inbounds getindex(ta::TimeArray, r::UnitRange{<:Integer}) =
TimeArray(timestamp(ta)[r], values(ta)[r, :], colnames(ta), meta(ta))
@propagate_inbounds getindex(ta::TimeArray, r::UnitRange{<:Integer}) = TimeArray(
timestamp(ta)[r], values(ta)[r, :], colnames(ta), meta(ta)
)

# range of 1d rows
@propagate_inbounds getindex(ta::TimeArray{T,1}, r::UnitRange{<:Integer}) where {T} =
TimeArray(timestamp(ta)[r], values(ta)[r], colnames(ta), meta(ta))
@propagate_inbounds getindex(ta::TimeArray{T,1}, r::UnitRange{<:Integer}) where {T} = TimeArray(
timestamp(ta)[r], values(ta)[r], colnames(ta), meta(ta)
)

# array of rows
@propagate_inbounds getindex(ta::TimeArray, a::AbstractVector{<:Integer}) =
TimeArray(timestamp(ta)[a], values(ta)[a, :], colnames(ta), meta(ta))
@propagate_inbounds getindex(ta::TimeArray, a::AbstractVector{<:Integer}) = TimeArray(
timestamp(ta)[a], values(ta)[a, :], colnames(ta), meta(ta)
)

# array of 1d rows
@propagate_inbounds getindex(ta::TimeArray{T,1}, a::AbstractVector{<:Integer}) where {T} =
TimeArray(timestamp(ta)[a], values(ta)[a], colnames(ta), meta(ta))
@propagate_inbounds getindex(ta::TimeArray{T,1}, a::AbstractVector{<:Integer}) where {T} = TimeArray(
timestamp(ta)[a], values(ta)[a], colnames(ta), meta(ta)
)

# single column by name
@propagate_inbounds function getindex(ta::TimeArray, s::Symbol)
Expand All @@ -348,15 +355,12 @@ end

# array of columns by name
@propagate_inbounds getindex(ta::TimeArray, ss::Symbol...) = getindex(ta, collect(ss))
@propagate_inbounds getindex(ta::TimeArray, ss::Vector{Symbol}) =
TimeArray(ta; values=values(ta)[:, map(s -> findcol(ta, s), ss)], colnames=ss)
@propagate_inbounds getindex(ta::TimeArray, ss::Vector{Symbol}) = TimeArray(
ta; values=values(ta)[:, map(s -> findcol(ta, s), ss)], colnames=ss
)

# ta[rows, cols]
@propagate_inbounds getindex(
ta::TimeArray,
rows::Union{AbstractVector{<:Integer},Colon},
cols::AbstractVector{Symbol},
) = TimeArray(
@propagate_inbounds getindex(ta::TimeArray, rows::Union{AbstractVector{<:Integer},Colon}, cols::AbstractVector{Symbol}) = TimeArray(
ta;
timestamp=timestamp(ta)[rows],
values=values(ta)[rows, map(s -> findcol(ta, s), cols)],
Expand All @@ -371,8 +375,9 @@ end
@propagate_inbounds getindex(ta::TimeArray, rows, col::Symbol) = getindex(ta, rows, [col])

# ta[n, col]
@propagate_inbounds getindex(ta::TimeArray, n::Integer, col::Symbol) =
getindex(ta, [n], [col])
@propagate_inbounds getindex(ta::TimeArray, n::Integer, col::Symbol) = getindex(
ta, [n], [col]
)

# single date
@propagate_inbounds function getindex(ta::TimeArray{T,N,D}, d::D) where {T,N,D}
Expand All @@ -388,8 +393,9 @@ end
end

# StepRange{Date,...}
@propagate_inbounds getindex(ta::TimeArray{T,N,D}, r::StepRange{D}) where {T,N,D} =
ta[collect(r)]
@propagate_inbounds getindex(ta::TimeArray{T,N,D}, r::StepRange{D}) where {T,N,D} = ta[collect(
r
)]

@propagate_inbounds getindex(ta::TimeArray, k::TimeArray{Bool,1}) = ta[findwhen(k)]

Expand Down
81 changes: 81 additions & 0 deletions test/retime.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ using Statistics
@test TimeSeries._toAggregationMethod(:first) == TimeSeries.First()
@test TimeSeries._toAggregationMethod(:last) == TimeSeries.Last()

# Test function conversion to AggregationFunction
custom_func = x -> sum(x) / length(x)
agg_method = TimeSeries._toAggregationMethod(custom_func)
@test agg_method isa TimeSeries.AggregationFunction
@test agg_method.func === custom_func

@test_throws MethodError TimeSeries._toAggregationMethod(:foo)
end

Expand All @@ -34,6 +40,10 @@ using Statistics
@test TimeSeries._toExtrapolationMethod(:missing) == TimeSeries.MissingExtrapolate()
@test TimeSeries._toExtrapolationMethod(:nan) == TimeSeries.NaNExtrapolate()

# Test custom FillConstant values
@test TimeSeries.FillConstant(42.0).value == 42.0
@test TimeSeries.FillConstant(-1).value == -1

@test_throws MethodError TimeSeries._toExtrapolationMethod(:foo)
end

Expand Down Expand Up @@ -112,6 +122,11 @@ using Statistics
@test timestamp(cl_new) == new_timestamps
@test values(cl_new[:Close][1])[1] == 0.0

# Test custom fill value
cl_new = retime(cl, new_timestamps; extrapolate=TimeSeries.FillConstant(99.9))
@test timestamp(cl_new) == new_timestamps
@test values(cl_new[:Close][1])[1] == 99.9

cl_new = retime(cl, new_timestamps; extrapolate=TimeSeries.NearestExtrapolate())
@test timestamp(cl_new) == new_timestamps
@test values(cl_new[:Close][1])[1] == values(cl[:Close][1])[1]
Expand Down Expand Up @@ -277,4 +292,70 @@ using Statistics

ta_new = retime(ta, Hour(1); upsample=:linear)
end

@testset "AggregationFunction with custom function" begin
new_timestamps = collect(Dates.Date(2000):Dates.Week(1):Dates.Date(2001))

# Test with custom function: weighted average favouring first value
custom_agg = x -> 0.7 * first(x) + 0.3 * last(x)
cl_new = retime(cl, new_timestamps; downsample=custom_agg)

@test timestamp(cl_new) == new_timestamps

# Verify custom function is applied
idx = new_timestamps[2] .<= timestamp(cl) .< new_timestamps[3]
cl_values = values(cl[:Close][idx])
expected = 0.7 * first(cl_values) + 0.3 * last(cl_values)
@test expected == values(cl_new[:Close][2])[1]

# Test with standard deviation function
cl_new_std = retime(cl, new_timestamps; downsample=std)
@test timestamp(cl_new_std) == new_timestamps

# Test with custom function on multi-column
ohlc_new = retime(ohlc, new_timestamps; downsample=x -> median(x))
@test timestamp(ohlc_new) == new_timestamps
end

@testset "Count aggregation" begin
new_timestamps = collect(Dates.Date(2000):Dates.Week(1):Dates.Date(2001))

cl_new = retime(cl, new_timestamps; downsample=TimeSeries.Count())
@test timestamp(cl_new) == new_timestamps

# Verify count is correct
idx = new_timestamps[2] .<= timestamp(cl) .< new_timestamps[3]
expected_count = count(!ismissing, values(cl[:Close][idx]))
@test expected_count == values(cl_new[:Close][2])[1]

# Test count with symbol
cl_new_sym = retime(cl, new_timestamps; downsample=:count)
@test values(cl_new) == values(cl_new_sym)
end

@testset "First and Last aggregation" begin
new_timestamps = collect(Dates.Date(2000):Dates.Week(1):Dates.Date(2001))

cl_first = retime(cl, new_timestamps; downsample=TimeSeries.First())
cl_last = retime(cl, new_timestamps; downsample=TimeSeries.Last())

@test timestamp(cl_first) == new_timestamps
@test timestamp(cl_last) == new_timestamps

# Verify first and last are correct
idx = new_timestamps[2] .<= timestamp(cl) .< new_timestamps[3]
@test first(values(cl[:Close][idx])) == values(cl_first[:Close][2])[1]
@test last(values(cl[:Close][idx])) == values(cl_last[:Close][2])[1]
end

@testset "Median aggregation" begin
new_timestamps = collect(Dates.Date(2000):Dates.Week(1):Dates.Date(2001))

cl_new = retime(cl, new_timestamps; downsample=:median)
@test timestamp(cl_new) == new_timestamps

# Verify median is correct
idx = new_timestamps[2] .<= timestamp(cl) .< new_timestamps[3]
@test median(values(cl[:Close][idx])) == values(cl_new[:Close][2])[1]
end
end
Loading