Skip to content

Commit

Permalink
Merge pull request #18 from bankofcanada/dev
Browse files Browse the repository at this point in the history
Bugfixes, tests and documentation
  • Loading branch information
bbejanov authored Apr 6, 2022
2 parents 844ef13 + 3de7d40 commit 75c7979
Show file tree
Hide file tree
Showing 18 changed files with 1,014 additions and 864 deletions.
4 changes: 0 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
vscode_workspace*
*.db
*.jld2
*.svg
*.pdf
*.html
various.jl
Manifest.toml
archive
.vscode

docs/build
docs/Manifest.toml
tbd.jl
4 changes: 3 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
name = "TimeSeriesEcon"
uuid = "8b6756d2-c55c-11ea-2998-5f67ea17da60"
authors = ["Atai Akunov <[email protected]>", "Boyan Bejanov <[email protected]>", "Nicholas Labelle St-Pierre <[email protected]>"]
version = "0.4.0"
version = "0.4.1"

[deps]
Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b"
MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

[compat]
Expand Down
101 changes: 49 additions & 52 deletions src/TimeSeriesEcon.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,56 +2,45 @@
# All rights reserved.


# """
# TimeSeriesEcon

# This package is part of the StateSpaceEcon ecosystem.
# TimeSeriesEcon.jl provides functionality to work with
# low-Frequency discrete macroeconomic time-series data.

# ### Frequencies (abstract type):
# - Unit
# - Monthly
# - Quarterly
# - Yearly

# ### Types:

# - `MIT{Frequency}` (aka "Moment In Time")
# - a primitive type denoting monthly, quarterly, and yearly dates
# - `TSeries{Frequency}`
# - an `AbstractVector` that can be indexed using `MIT`

# ### Functions:

# - `MIT` Constructors/Functions
# - `mm(year::Int, period::Int)`: returns a monthly `MIT` type instance
# - `qq(year::Int, period::Int)`: returns a quarterly `MIT` type instance
# - `yy(year::Int)`: returns a yearly `MIT` type instance
# - `ii(x::Int)`: returns a unit `MIT` type instance
# - `year(x::MIT)`: returns a `Int64` year value associated with `x`
# - `period(x::MIT)`: returns a `Int64` period value associated with `x`
# - `frequencyof(x::MIT)`: returns `<: Frequency` assosicated wtih `x`


# - Functions operating on `TSeries`
# - `mitrange(x::TSeries)`: returns a `UnitRange{MIT{Frequency}}` for the given `x`
# - `firstdate(x::TSeries)`: returns `MIT{Frequency}` first date associated with `x`
# - `lastdate(x::TSeries)`: returns `MIT{Frequency}` last date associated with `x`
# - `ppy(x::TSeries)`: returns the number of periods per year for `x::TSeries`. (`ppy` also accepts `x::MIT` and `x::Frequency`)
# - `shift(x::TSeries, i::Int64)`: shifts the dates of `x` by `firstdate(x) - i`
# - `shift!`: in-place version of `shift`
# - `pct(x::TSeries, shift_value::Int64; islog::Bool = false)`: calculates percent rate of change of `x::TSeries`
# - `apct(x::TSeries, islog::Bool = false)`: calculates annualized percent rate of change of `x::TSeries`
# - `nanrm!(x::TSeries, type::Symbol=:both)`: removes `NaN` from `x::TSeries`
# """
"""
TimeSeriesEcon
This package is part of the StateSpaceEcon ecosystem. Provides the data types
and functionality necessary to work with macroeconomic discrete time models.
### Working with time
* Frequencies are represented by abstract type [`Frequency`](@ref).
* Concrete frequencies include [`Yearly`](@ref), [`Quarterly`](@ref) and
[`Monthly`](@ref).
* Moments in time are represented by data type [`MIT`](@ref).
* Lengths of time are represented by data type [`Duration`](@ref).
### Working with time series
* Data type [`TSeries`](@ref) represents a single time series.
* Data type [`MVTSeries`](@ref) represents a multivariate time series.
### Working with other data
* Data type [`Workspace`](@ref) is a general purpose dictionary-like collection
of "variable"-like objects.
### Tutorial
* [TimeSeriesEcon tutorial](https://bankofcanada.github.io/DocsEcon.jl/dev/Tutorials/TimeSeriesEcon/main/)
"""
module TimeSeriesEcon

# other packages
using MacroTools
using RecipesBase
using OrderedCollections

# standard library
using Statistics
using Serialization
using Distributed

include("momentintime.jl")
export MIT, Duration
export mm, qq, yy
# export mm, qq, yy
export Monthly, Quarterly, Yearly, Frequency, YPFrequency, Unit
export year, period, mit2yp, ppy
export frequencyof
Expand Down Expand Up @@ -80,6 +69,13 @@ export @rec

include("plotrecipes.jl")

include("workspaces.jl")

include("serialize.jl")

include("various.jl")


"""
rangeof(s; drop::Integer)
Expand All @@ -89,13 +85,16 @@ end. This adds convenience when using [`@rec`](@ref)
Example
```
julia> q = TSeries(20Q1:21Q4); rangeof(q; drop=1)
julia> q = TSeries(20Q1:21Q4);
julia> rangeof(q; drop=1)
20Q2:21Q4
julia> rangeof(q; drop=-4)
20Q1:20Q4
julia> q[begin:begin+1] .= 1; @rec rangeof(q; drop=2) q[t] = q[t-1] + q[t-2]; q
julia> q[begin:begin+1] .= 1;
julia> @rec rangeof(q; drop=2) q[t] = q[t-1] + q[t-2];
julia> q
8-element TSeries{Quarterly} with range 20Q1:21Q4:
20Q1 : 1.0
20Q2 : 1.0
Expand All @@ -107,11 +106,9 @@ julia> q[begin:begin+1] .= 1; @rec rangeof(q; drop=2) q[t] = q[t-1] + q[t-2]; q
21Q4 : 21.0
```
"""
@inline rangeof(x::Union{TSeries, MVTSeries}; drop::Integer) =
(rng = rangeof(x);
drop > 0 ? (first(rng) + drop:last(rng)) : (first(rng):last(rng)+drop))


include("workspaces.jl")
@inline function rangeof(x::Union{TSeries,MVTSeries,Workspace}; drop::Integer)
rng = rangeof(x)
return drop > 0 ? (first(rng)+drop:last(rng)) : (first(rng):last(rng)+drop)
end

end
100 changes: 45 additions & 55 deletions src/fconvert.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,46 +3,7 @@

import Statistics: mean


"""
overlay(t1, t2, ...)
Construct a TSeries in which each observation is taken from the first
non-missing observation in the list of arguments. A missing observation is one
for which [`istypenan`](@ref) returns `true`.
All TSeries in the argument list must be of the same frequency. The data type of
the resulting TSeries is computed by the standard promotion of numerical types
in Julia. Its range is the union of the ranges of the arguments.
"""
@inline overlay(ts::Vararg{<:TSeries}) = overlay(mapreduce(rangeof, union, ts), ts...)

"""
overlay(rng, t1, t2, ...)
If the first argument is a range (must be of the same frequency), that becomes
the range of the resulting TSeries.
"""
function overlay(rng::AbstractRange{<:MIT}, ts::Vararg{<:TSeries})
T = mapreduce(eltype, promote_type, ts)
ret = TSeries(rng, typenan(T))
# na = collection of periods where the entry of ret is missing (typenan(T))
na = collect(rng)
for t in ts
if isempty(na)
# if na is empty, then we've assigned all slots
break
end
# keep = periods that are not yet assigned and t has valid values in them
keep = intersect(na, rangeof(t)[values(@. !istypenan(t))])
# assign
ret[keep] = t[keep]
# update na by removing the periods we just assigned
na = setdiff(na, keep)
end
return ret
end
export overlay
#### strip and strip!

function _valid_range(t::TSeries)
fd = firstdate(t)
Expand All @@ -56,7 +17,19 @@ function _valid_range(t::TSeries)
return fd:ld
end

"""
strip(t:TSeries)
Remove leading and trailing `NaN` from the given time series. This version
creates a new [`TSeries`](@ref) instance.
"""
Base.strip(t::TSeries) = getindex(t, _valid_range(t))
"""
strip!(t::TSeries)
Remove leading and training `NaN` from the given time series. This is
done in-place.
"""
strip!(t::TSeries) = resize!(t, _valid_range(t))
export strip!

Expand All @@ -70,16 +43,34 @@ Conversion from $(frequencyof(t)) to $F not implemented.
""")

# do nothing when the source and target frequencies are the same.
fconvert(::Type{F}, t::TSeries{F}) where {F <: Frequency} = t
fconvert(::Type{F}, t::TSeries{F}) where {F<:Frequency} = t

"""
fconvert(F1, t::TSeries{F2}; method) where {F1 <: YPFrequency, F2 <: YPFrequency}
Convert between frequencies of the [`YPFrequency`](@ref) variety.
TODO: describe `method` when converting to a higher frequency (interpolation)
TODO: describe `method` when converting to a lower frequency (aggregation)
fconvert(F1, x::TSeries{F2}; method) where {F1 <: YPFrequency, F2 <: YPFrequency}
Convert between frequencies derived from [`YPFrequency`](@ref).
Currently this works only when the periods per year of the higher frequency is
an exact multiple of the periods per year of the lower frequency.
### Converting to Higher Frequency
The only method available is `method=:const`, where the value at each period of
the higher frequency is the value of the period of the lower frequency it
belongs to.
```
x = TSeries(2000Q1:2000Q3, collect(Float64, 1:3))
fconvert(Monthly, x)
```
### Converting to Lower Frequency
The range of the result includes periods that are fully included in the range of
the input. For each period of the lower frequency we aggregate all periods of
the higher frequency within it. We have 4 methods currently available: `:mean`,
`:sum`, `:begin`, and `:end`. The default is `:mean`.
```
x = TSeries(2000M1:2000M7, collect(Float64, 1:7))
fconvert(Quarterly, x; method = :sum)
```
"""
function fconvert(F::Type{<:YPFrequency{N1}}, t::TSeries{<:YPFrequency{N2}}; method=nothing) where {N1,N2}
args = Dict()
Expand Down Expand Up @@ -120,20 +111,19 @@ function _to_lower(F::Type{<:YPFrequency{N1}}, t::TSeries{<:YPFrequency{N2}}; me
(d2, r2) = divrem(p2 - 1, np)
li = MIT{F}(y2, d2 + 1) - (r2 < np - 1)
# println("y2 = $y2, p2 = $p2, d2 = $d2, r2 = $r2, li = $li")
ret = TSeries(eltype(t), fi:li)
vals = t[begin + (r1 > 0) * (np - r1):end - (r2 < np-1)*(1+r2)].values
vals = t[begin+(r1>0)*(np-r1):end-(r2<np-1)*(1+r2)].values
# println("vals = $vals")
if method == :mean
copyto!(ret, mean(reshape(vals, np, :); dims=1))
ret = mean(reshape(vals, np, :); dims=1)
elseif method == :sum
copyto!(ret, sum(reshape(vals, np, :); dims=1))
ret = sum(reshape(vals, np, :); dims=1)
elseif method == :begin
copyto!(ret, reshape(vals, np, :)[begin, :])
ret = reshape(vals, np, :)[begin, :]
elseif method == :end
copyto!(ret, reshape(vals, np, :)[end, :])
ret = reshape(vals, np, :)[end, :]
else
throw(ArgumentError("Conversion method not available: $(method)."))
end
return ret
return copyto!(TSeries(eltype(ret), fi:li), ret)
end

Loading

0 comments on commit 75c7979

Please sign in to comment.