diff --git a/src/011-R-from-Julia.jl b/src/011-R-from-Julia.jl index 1524375..f75e5a5 100644 --- a/src/011-R-from-Julia.jl +++ b/src/011-R-from-Julia.jl @@ -2,67 +2,43 @@ using DataFrames using OrderedCollections """ -# R from Julia +# `R` from `Julia` -A collection of functions that facilitate the translation of inputs from `Julia` into `R`. +A collection of internal functions that facilitate the translation of `Julia` objects into `R`. # Details -* [`r_get_states`] translates a State matrix into a `DataFrame` that can be passed to `R`. In the input matrix, each row is a particle and each column is a time step. -* [`r_get_dataset`] translates a Dictionary of observations into a Vector of DataFrames that can be passed to `R`. + +* [`Patter.r_get_dataset()`](@ref) translates a Dictionary of observations into a `Vector` of `DataFrame`s that can be passed to `R`. +* [`Patter.r_get_states()`](@ref) translates a `Matrix` of [`State`](@ref)s into a `DataFrame` that can be passed to `R`. In the input `Matrix`, each row is a particle and each column is a time step. +* [`Patter.r_get_particles()`](@ref) wraps [`Patter.r_get_states()`](@ref) and translates particle outputs (from [`particle_filter()`](@ref) and [`two_filter_smoother()`](@ref)) into a `NamedTuple` for `R`. + +These functions are [`State`](@ref) and model agnostic; that is, they work irrespective of the input [`State`](@ref) and model sub-types. Custom methods are not required to handle novel sub-types. # Returns -A long-format `DataFrame`, with columns for `path_id`, `timestep` and each state dimension. -""" -function r_get_states(state::Matrix, timesteps::Vector = collect(1:size(state, 2))) - # Initialise empty matrix - fields = fieldnames(typeof(state[1])) - values = Matrix{Float64}(undef, prod(size(state)), length(fields) + 2) - # Define path ID & time step columns - np = size(state, 1) - nt = size(state, 2) - values[:, 1] = repeat(1:np, inner = nt) - values[:, 2] = repeat(1:nt, outer = np) - # Populate matrix - for i in 1:size(values, 1) - for j in eachindex(fields) - values[i, j + 2] = getfield(state[Int(values[i, 1]), Int(values[i, 2])], fields[j]) - end - end - # Replace column indices with timesteps - values[:, 2] = repeat(minimum(timesteps):maximum(timesteps), outer = np) - # Coerce to dataframe - fields = (:path_id, :timestep, fields...) - df = DataFrame(values, collect(fields)) - df.path_id = Int.(df.path_id) - df.timestep = Int.(df.timestep) - df -end -# Examples: +* [`Patter.r_get_dataset()`](@ref) returns a `Vector` of `DataFrame`s, with columns for `timestamp`, `obs` and the observation model parameters; +* [`Patter.r_get_states()`](@ref) returns a long-format `DataFrame`, with columns for `path_id`, `timestep` and each state dimension; +* [`Patter.r_get_particles()`](@ref) returns a `NamedTuple` of particle information, including: + - `states`: A `DataFrame` of [`State`](@ref) dimensions (from [`Patter.r_get_states()`](@ref)); + - `diagnostics`: A `DataFrame` of algorithm diagnostics, including `timestep`, `timestamp`, `ess` and `maxlp` columns; + - `convergence`: A `Boolian` that defines algorithm convergence; -# Define state matrix: -# * Two rows: two particles -# * Three columns: three time steps -# state = [StateXY(0.0, 1.0, 2.0) StateXY(0.0, 3.0, 4.0) StateXY(0.0, 5.0, 6.0); -# StateXY(0.0, 7.0, 8.0) StateXY(0.0, 9.0, 10.0) StateXY(0.0, 11.0, 2.0)] -# r_get_states(state) +""" +function r_get end -# Create a big matrix of StateXY objects -# np = 1000 -# nt = 20000 -# state = [StateXY(rand(), rand(), rand()) for _ in 1:np, _ in 1:nt] -# r_get_states(state) -#### Convert struct to dict +#### Convert a struct to an Ordered Dict function struct_to_dict(s) return OrderedCollections.OrderedDict(key => getfield(s, key) for key in propertynames(s)) end + #### Formulate dictionaries to hold the observation for a selected time stamp function dict_obs(timestamp, obs, sensor) OrderedCollections.OrderedDict(:timestamp => timestamp, :obs => obs, struct_to_dict(sensor)...) end + #### Extract dataset(s) from yobs for a selected model type function r_get_dataset(yobs::Dict, model_type::Type{<: ModelObs}) @@ -93,7 +69,51 @@ function r_get_dataset(yobs::Dict, model_type::Type{<: ModelObs}) output end +@doc (@doc r_get) r_get_dataset + +#### Get a DataFrame of States +function r_get_states(state::Matrix, timesteps::Vector = collect(1:size(state, 2))) + # Initialise empty matrix + fields = fieldnames(typeof(state[1])) + values = Matrix{Float64}(undef, prod(size(state)), length(fields) + 2) + # Define path ID & time step columns + np = size(state, 1) + nt = size(state, 2) + values[:, 1] = repeat(1:np, inner = nt) + values[:, 2] = repeat(1:nt, outer = np) + # Populate matrix + for i in 1:size(values, 1) + for j in eachindex(fields) + values[i, j + 2] = getfield(state[Int(values[i, 1]), Int(values[i, 2])], fields[j]) + end + end + # Replace column indices with timesteps + values[:, 2] = repeat(minimum(timesteps):maximum(timesteps), outer = np) + # Coerce to dataframe + fields = (:path_id, :timestep, fields...) + df = DataFrame(values, collect(fields)) + df.path_id = Int.(df.path_id) + df.timestep = Int.(df.timestep) + df +end +@doc (@doc r_get) r_get_states + +# Examples: +# Define state matrix: +# * Two rows: two particles +# * Three columns: three time steps +# state = [StateXY(0.0, 1.0, 2.0) StateXY(0.0, 3.0, 4.0) StateXY(0.0, 5.0, 6.0); +# StateXY(0.0, 7.0, 8.0) StateXY(0.0, 9.0, 10.0) StateXY(0.0, 11.0, 2.0)] +# r_get_states(state) +# Create a big matrix of StateXY objects +# np = 1000 +# nt = 20000 +# state = [StateXY(rand(), rand(), rand()) for _ in 1:np, _ in 1:nt] +# r_get_states(state) + + +#### Get a Tuple of particle information (states, diagnostics, convergence) function r_get_particles(particles::NamedTuple) # Collate information states = r_get_states(particles.state, particles.timesteps) @@ -107,4 +127,5 @@ function r_get_particles(particles::NamedTuple) diagnostics = diagnostics, convergence = particles.convergence ) -end \ No newline at end of file +end +@doc (@doc r_get) r_get_particles \ No newline at end of file