Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to change date modes in time-series types #30

Merged
merged 12 commits into from
Oct 17, 2024
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2018 Andrew Martin
Copyright (c) 2018 - 2024 Andrew Martin
OSLO Integrator Copyright (c) Microsoft Research
Amoeba Module Copyright (c) 2014 Mathias Brandewinder

Expand Down
4 changes: 2 additions & 2 deletions citest.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ open RProvider
open RProvider.``base``
open RProvider.zoo

R.c(1.,2.,3)
R.c (1., 2., 3)

R.as_zoo(R.c(1,2,3))
R.as_zoo (R.c (1, 2, 3))
2 changes: 1 addition & 1 deletion docs/data.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ fun modelResults subjectIds hypothesisIds ->
|> Seq.map (fun (s, h, (a, b)) -> (s, h, a, b))
|> Bristlecone.Data.ModelSelection.save "/some/dir"

fun (hypothesisResults: ModelSelection.ResultSet.ResultSet<string, Hypotheses.Hypothesis> seq) ->
fun (hypothesisResults: ModelSelection.ResultSet.ResultSet<string, Hypotheses.Hypothesis<float>,_,_,_> seq) ->
hypothesisResults
|> ModelSelection.Akaike.akaikeWeightsForSet (fun h -> h.ReferenceCode)
|> Bristlecone.Data.ModelSelection.save "/some/dir"
Expand Down
6 changes: 3 additions & 3 deletions docs/dendro.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ location on any day of the year, we can use the `Sunrise.calculate` function.

open Bristlecone.Dendro

let latitude = 54.2
let longitude = -4.2
let latitude = 54.2<latitude>
let longitude = -4.2<longitude>

Sunrise.calculate 2024 04 20 latitude longitude "Europe/London"
(*** include-it ***)
Expand All @@ -61,5 +61,5 @@ Sunrise.calculate 2024 04 20 latitude longitude "Europe/London"
As an illustration, if we looked at Tromsø in January we would see far less available light:
*)

Sunrise.calculate 2024 01 10 69.64961 18.95702 "Europe/London"
Sunrise.calculate 2024 01 10 69.64961<latitude> 18.95702<longitude> "Europe/London"
(*** include-it ***)
12 changes: 6 additions & 6 deletions docs/examples/predator-prey.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ module Graphing =

open Plotly.NET

let pairedFits (series: Map<string, ModelSystem.FitSeries>) =
let pairedFits (series: Map<string, ModelSystem.FitSeries<_,_,_>>) =
match testResult with
| Ok r ->
series
Expand All @@ -177,17 +177,17 @@ module Graphing =
x
| Error e -> sprintf "Cannot display data, as model fit did not run successfully (%s)" e

let pairedFitsForTestResult (testResult: Result<Bristlecone.Test.TestResult, string>) =
let pairedFitsForTestResult (testResult: Result<Bristlecone.Test.TestResult<_,_,_>, string>) =
match testResult with
| Ok r -> pairedFits r.Series
| Error e -> sprintf "Cannot display data, as model fit did not run successfully (%s)" e

let pairedFitsForResult (testResult: Result<Bristlecone.ModelSystem.EstimationResult, string>) =
let pairedFitsForResult (testResult: Result<Bristlecone.ModelSystem.EstimationResult<_,_,_>, string>) =
match testResult with
| Ok r -> pairedFits (r.Series |> Seq.map (fun kv -> kv.Key.Value, kv.Value) |> Map.ofSeq)
| Error e -> sprintf "Cannot display data, as model fit did not run successfully (%s)" e

let parameterTrace (result: Result<ModelSystem.EstimationResult, 'b>) =
let parameterTrace (result: Result<ModelSystem.EstimationResult<_,_,_>, 'b>) =
match result with
| Ok r ->
r.Trace
Expand Down Expand Up @@ -223,8 +223,8 @@ type PopulationData = FSharp.Data.CsvProvider<"data/lynx-hare.csv", ResolutionFo
let data =
let csv = PopulationData.Load(__SOURCE_DIRECTORY__ + "/data/lynx-hare.csv")

[ (code "hare").Value, Time.TimeSeries.fromObservations (csv.Rows |> Seq.map (fun r -> float r.Hare, r.Year))
(code "lynx").Value, Time.TimeSeries.fromObservations (csv.Rows |> Seq.map (fun r -> float r.Lynx, r.Year)) ]
[ (code "hare").Value, Time.TimeSeries.fromNeoObservations (csv.Rows |> Seq.map (fun r -> float r.Hare, r.Year))
(code "lynx").Value, Time.TimeSeries.fromNeoObservations (csv.Rows |> Seq.map (fun r -> float r.Lynx, r.Year)) ]
|> Map.ofList

(*** include-value: data ***)
Expand Down
2 changes: 1 addition & 1 deletion docs/index.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ let engine =
Bristlecone.mkContinuous
|> Bristlecone.withCustomOptimisation (Optimisation.Amoeba.swarm 5 20 Optimisation.Amoeba.Solver.Default)

let testSettings = Bristlecone.Test.TestSettings<float>.Default
let testSettings = Bristlecone.Test.TestSettings<_,_,_,_>.Default
let testResult = Bristlecone.testModel engine testSettings hypothesis

(*** include-fsi-output ***)
Expand Down
6 changes: 3 additions & 3 deletions docs/integration.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@ function to match the `EstimationEngine.Integration` type signature as follows:

open Bristlecone

let myCustomIntegrator: EstimationEngine.Integrate<'data, 'time> =
let myCustomIntegrator: EstimationEngine.Integrate<'data, 'time, _, _> =
fun writeOut tInitial tEnd tStep initialConditions externalEnvironment model ->
invalidOp "Doesn't actually do anything!"

let engine =
Bristlecone.mkContinuous |> Bristlecone.withContinuousTime myCustomIntegrator

Bristlecone.mkContinuous |> Bristlecone.withContinuousTime myCustomIntegrator

(**

Expand Down
2 changes: 1 addition & 1 deletion docs/model-selection.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ If you are working with `ResultSet`s, calculating and saving the weights is easi
be completed as below:
*)

fun (hypothesisResults: ModelSelection.ResultSet.ResultSet<string, Language.Hypotheses.Hypothesis> seq) ->
fun (hypothesisResults: ModelSelection.ResultSet.ResultSet<string, Language.Hypotheses.Hypothesis<float>, _, _, _> seq) ->
hypothesisResults
|> ModelSelection.Akaike.akaikeWeightsForSet (fun h -> h.ReferenceCode)
|> Bristlecone.Data.ModelSelection.save "/some/dir"
3 changes: 1 addition & 2 deletions docs/optimisation.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@ let myCustomOptimiser: EstimationEngine.Optimiser<float> =
EstimationEngine.InDetachedSpace
<| fun writeOut n domain f -> invalidOp "Doesn't actually do anything!"

let engine =
Bristlecone.mkContinuous |> Bristlecone.withCustomOptimisation myCustomOptimiser
Bristlecone.mkContinuous |> Bristlecone.withCustomOptimisation myCustomOptimiser

(**
## Included optimisation methods
Expand Down
78 changes: 78 additions & 0 deletions docs/time-series.fsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
(**
---
title: Time-Series and Time-Frames
category: Components
categoryindex: 4
index: 1
---

[![Script]({{root}}/img/badge-script.svg)]({{root}}/{{fsdocs-source-basename}}.fsx)&emsp;
[![Notebook]({{root}}/img/badge-notebook.svg)]({{root}}/{{fsdocs-source-basename}}.ipynb)
*)

(*** condition: prepare ***)
#nowarn "211"
#r "../src/Bristlecone/bin/Debug/netstandard2.0/Bristlecone.dll"
(*** condition: fsx ***)
#if FSX
#r "nuget: Bristlecone,{{fsdocs-package-version}}"
#endif // FSX
(*** condition: ipynb ***)
#if IPYNB
#r "nuget: Bristlecone,{{fsdocs-package-version}}"
#endif // IPYNB

(**
Time-series and time-frames
========================

Bristlecone includes core representations of time-series,, time-frames, and
time indexes.

A key concept is the date mode. Bristlecone supports using different modes of measuring
time, from simple `DateTime` representation (calendar time) to various dating methods
commonly used in long-term ecology and archaeology.

The time-related types are included in the `Bristlecone.Time` module:
*)

open System
open Bristlecone
open Bristlecone.Time

(***
## Time Series

A time-series is a representation of data ordered in time by the date
of observation.

### From calendar-date observations

Time-series may be created from date-time observations using built-in .NET
types and the `TimeSeries.fromNeoObservations` function:
*)

let someObservations = [
2.1, DateTime(2020, 03, 21)
5.4, DateTime(2020, 03, 22)
-54.2, DateTime(2020, 03, 23)
]

let ts = TimeSeries.fromNeoObservations someObservations
(*** include-value: ts ***)

(**
### From radiocarbon dates

As an example of an alternative date format, uncalibrated radiocarbon dates
may be used as follows:
*)

let someDatedValues = [
1654, DatingMethods.Radiocarbon 345<``BP (radiocarbon)``>
982, DatingMethods.Radiocarbon -2<``BP (radiocarbon)``>
5433, DatingMethods.Radiocarbon 1023<``BP (radiocarbon)``>
]

let tsRadiocarbon = TimeSeries.fromObservations DateMode.radiocarbonDateMode someDatedValues
(*** include-value: tsRadiocarbon ***)
2 changes: 1 addition & 1 deletion docs/workflow.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ the orchestration agent:
let orchestrator =
Orchestration.OrchestrationAgent(logger, System.Environment.ProcessorCount, false)

fun datasets hypotheses engine ->
fun datasets hypotheses (engine:EstimationEngine.EstimationEngine<float,System.DateTime,System.TimeSpan,System.TimeSpan>) ->

// Orchestrate the analyses
let work = workPackages datasets hypotheses engine
Expand Down
93 changes: 53 additions & 40 deletions src/Bristlecone.Dendro/Data.fs
Original file line number Diff line number Diff line change
@@ -1,45 +1,58 @@
namespace Bristlecone.Data

open Bristlecone
open Bristlecone.Time
open Bristlecone.Dendro

module PlantIndividual =

open FSharp.Data

type RingWidthData = CsvProvider<"data-types/ring-width.csv">
type EnvironmentVariableData = CsvProvider<"data-types/env-variable.csv">

let loadRingWidths (fileName: string) =
let data = RingWidthData.Load fileName

data.Rows
|> Seq.groupBy (fun row -> row.``Plant Code``)
|> Seq.map (fun (code, rows) ->
let growth =
open Bristlecone
open Bristlecone.Time
open Bristlecone.Dendro
open Bristlecone.Dendro.Units

/// Load plant-specific time-series from CSV files.
module Csv =

open FSharp.Data

type RingWidthData = CsvProvider<"data-types/ring-width.csv">
type EnvironmentVariableData = CsvProvider<"data-types/env-variable.csv">

/// <summary>Load ring widths from a CSV file that contains 'Year', 'Plant Code',
/// and 'Increment (mm)' columns.</summary>
/// <param name="csvFileName">The file to load</param>
/// <returns>A plant individual containing the growth series.</returns>
let loadRingWidths (csvFileName: string) =
let data = RingWidthData.Load csvFileName

data.Rows
|> Seq.groupBy (fun row -> row.``Plant Code``)
|> Seq.map (fun (code, rows) ->
let growth =
rows
|> Seq.sortBy (fun i -> i.Year)
|> Seq.map (fun i ->
(float i.``Increment (mm)`` * 1.<millimetre / year>, i.Year * 1<year> |> DatingMethods.Annual))
|> TimeSeries.fromObservations DateMode.annualDateMode
|> GrowthSeries.Absolute
|> PlantIndividual.PlantGrowth.RingWidth

{ Identifier = code |> ShortCode.create |> Option.get
Growth = growth
InternalControls = [] |> Map.ofList
Environment = [] |> Map.ofList }
: PlantIndividual.PlantIndividual<DatingMethods.Annual, int<year>, int<year>>)
|> Seq.toList

/// <summary>Load plant-specific environmental time-series from
/// a CSV file, where all time-series are specified in a single CSV.</summary>
/// <param name="fileName">The CSV file to load from.</param>
/// <returns>A map of time-series by their plant individual.</returns>
let loadPlantSpecificEnvironments (fileName: string) =
let data = EnvironmentVariableData.Load fileName

data.Rows
|> Seq.groupBy (fun row -> row.``Plant Code``)
|> Seq.map (fun (code, rows) ->
code,
rows
|> Seq.sortBy (fun i -> i.Date)
|> Seq.map (fun i -> (float i.``Increment (mm)`` * 1.<mm>, i.Date))
|> TimeSeries.fromObservations
|> GrowthSeries.Absolute
|> PlantIndividual.RingWidth

{ Identifier = code |> ShortCode.create |> Option.get
Growth = growth
InternalControls = [] |> Map.ofList
Environment = [] |> Map.ofList }
: PlantIndividual.PlantIndividual)
|> Seq.toList

let loadLocalEnvironmentVariable (fileName: string) =
let data = EnvironmentVariableData.Load fileName

data.Rows
|> Seq.groupBy (fun row -> row.``Plant Code``)
|> Seq.map (fun (code, rows) ->
code,
rows
|> Seq.map (fun i -> float i.Predictor, i.Date)
|> TimeSeries.fromObservations)
|> Seq.toList
|> Seq.map (fun i -> float i.Predictor, i.Year * 1<year> |> DatingMethods.Annual)
|> TimeSeries.fromObservations DateMode.annualDateMode)
|> Seq.toList
27 changes: 4 additions & 23 deletions src/Bristlecone.Dendro/Library.fs
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,14 @@ open Bristlecone.Dendro

module Bristlecone =

/// <summary>Lower time-series data from a plant individual type into
/// basic time-series that may be used for model-fitting</summary>
/// <param name="plant">A plant individual</param>
/// <returns>A coded map of time-series data for model-fitting</returns>
let fromDendro (plant: PlantIndividual.PlantIndividual) =
let g =
match plant.Growth |> PlantIndividual.growthSeries with
| GrowthSeries.Absolute g -> g
| GrowthSeries.Cumulative g -> g
| GrowthSeries.Relative g -> g

plant.Environment |> Map.add (ShortCode.create "x" |> Option.get) g

/// <summary>Fit a model to plant growth time-series. The plant individual's growth data is always labelled as `x`.</summary>
/// <param name="engine">A configured estimation engine</param>
/// <param name="endCondition">The end condition to apply to optimisation</param>
/// <param name="system">The compiled model system for analysis</param>
/// <param name="plant">A plant individual</param>
/// <returns>An estimation result for the given plant, model and fitting method (engine)</returns>
let fitDendro engine endCondition system (plant: PlantIndividual.PlantIndividual) =
Bristlecone.fit engine endCondition (fromDendro plant) system
let fitDendro engine endCondition system plant =
Bristlecone.fit engine endCondition (PlantIndividual.toTimeSeries plant) system

/// <summary> Perform n-step-ahead computation on the hypothesis and plant. The plant individual's growth data is always labelled as `x`.</summary>
/// <param name="engine"></param>
Expand All @@ -34,12 +21,6 @@ module Bristlecone =
/// <param name="preTransform"></param>
/// <param name="estimate"></param>
/// <returns></returns>
let oneStepAheadDendro
(engine: EstimationEngine.EstimationEngine<float, float>)
system
(plant: PlantIndividual.PlantIndividual)
preTransform
estimate
=
let predictors = fromDendro plant
let oneStepAheadDendro engine system plant preTransform estimate =
let predictors = PlantIndividual.toTimeSeries plant
Bristlecone.oneStepAhead engine system preTransform predictors estimate
Loading
Loading