Smoothing data with the Savitzky-Golay filter

Back to the index

BinderScriptNotebook

Smoothing data with the Savitzky-Golay filter

Summary: This tutorial demonstrates how to access a public dataset for temperature data with FSharp.Data, how to smooth the data points with the Savitzky-Golay filter from FSharp.Stats and finally how to visualize the results with Plotly.NET.

Introduction:

The Savitzky-Golay is a type of low-pass filter, particularly suited for smoothing noisy data. The main idea behind this approach is to make for each point a least-square fit with a polynomial of high order over a odd-sized window centered at the point. One advantage of the Savitzky-Golay filter is that portions of high frequencies are not simply cut off, but are preserved due to the polynomial regression. This allows the filter to preserve properties of the distribution such as relative maxima, minima, and dispersion, which are usually distorted by flattening or shifting by conventional methods such as moving average.

This is useful when trying to identify general trends in highly fluctuating data sets, or to smooth out noise to improve the ability to find minima and maxima of the data trend. To showcase this we will plot a temperature dataset from the "Deutscher Wetterdienst", a german organization for climate data. We will do this for both the original data points and a smoothed version.

windowed polynomial regression

The image shows the moving window for polynomial regression used in the Savitzky-Golay filter @wikipedia

Referencing packages

// Packages hosted by the Fslab community
#r "nuget: FSharp.Stats"
// third party .net packages 
#r "nuget: FSharp.Data"
#r "nuget: Plotly.NET, 2.0.0-preview.6"
#r "nuget: Plotly.NET.Interactive, 2.0.0-preview.6"

Loading data

We will start by retrieving the data. This is done with the FSharp.Data package and will return a single string in the original format.

// Get data from Deutscher Wetterdienst
// Explanation for Abbreviations: https://www.dwd.de/DE/leistungen/klimadatendeutschland/beschreibung_tagesmonatswerte.html
let rawData = FSharp.Data.Http.RequestString @"https://raw.githubusercontent.com/fslaborg/datasets/main/data/WeatherDataAachen-Orsbach_daily_1year.txt"

// print first 1000 characters to console.
rawData.[..1000] |> printfn "%s"
<pre>
 
                Tageswerte der Station 10505 Aachen-Orsbach                                               
 STAT JJJJMMDD QN     TG     TN     TM     TX    RFM     FM     FX     SO     NM     RR     PM
----- -------- -- ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------
10505 20210728  1   13.5   14.4   17.7   21.5   73.3    3.0   13.9    4.9    6.4    5.5  983.8
10505 20210727  1   14.3   15.2   17.9   23.4   81.6    3.0   10.1    3.8    6.1    1.2  984.8
10505 20210726  1   14.8   16.0   18.5   22.6   79.3    3.0    9.8    5.2    6.7    1.4  983.7
10505 20210725  1   11.5   14.2   18.7   25.2   79.4    2.0   11.5    7.9           6.1  981.7
10505 20210724  1   12.4   13.8   17.9   21.8   83.8    2.0   10.2    0.3    7.5    2.8  982.4
10505 20210723  1    9.1   11.8   18.5   24.8   68.7    2.0    7.4   13.8    1.3    0.0  989.3
10505 20210722  1   10.1   12.9   19.1   24.1   67.9    2.0    5.8   11.0    1.1    0.0  994.2
10505 20210721  1    8.4   11.1

Currently the data set is not in a format, that is easily parsable. Normally you would try to use the Deedle package to read in the data into a Deedle data frame. As this is not possible here, we will do some ugly formatting.

Data Formatting/Parsing

open System
open System.Text.RegularExpressions

/// Tuple of 4 data arrays representing the measured temperature for over a year.
let processedData = 
    // First separate the huge string in lines
    rawData.Split([|'\n'|], StringSplitOptions.RemoveEmptyEntries)
    // Skip the first 5 rows until the real data starts, also skip the last row (length-2) to remove a "</pre>" at the end
    |> fun arr -> arr.[5..arr.Length-2]
    |> Array.map (fun data -> 
        // Regex pattern that will match groups of whitespace
        let whitespacePattern = @"\s+"
        // This is needed to tell regex to replace hits with a tabulator
        let matchEval = MatchEvaluator(fun _ -> @"\t" )
        // The original data columns are separated by different amounts of whitespace.
        // Therefore, we need a flexible string parsing option to replace any amount of whitespace with a single tabulator.
        // This is done with the regex pattern above and the fsharp core library "System.Text.RegularExpressions" 
        let tabSeparated = Regex.Replace(data, whitespacePattern, matchEval)
        tabSeparated
        // Split each row by tabulator will return rows with an equal amount of values, which we can access.
        |> fun dataStr -> dataStr.Split([|@"\t"|], StringSplitOptions.RemoveEmptyEntries)
        |> fun dataArr -> 
            // Second value is the date of measurement, which we will parse to the DateTime type
            DateTime.ParseExact(dataArr.[1], "yyyyMMdd", Globalization.CultureInfo.InvariantCulture),
            // 5th value is minimal temperature at that date.
            float dataArr.[4],
            // 6th value is average temperature over 24 timepoints at that date.
            float dataArr.[5],
            // 7th value is maximal temperature at that date.
            float dataArr.[6]
    )
    // Sort by date
    |> Array.sortBy (fun (day,tn,tm,tx) -> day)
    // Unzip the array of value tuples, to make the different values easier accessible
    |> fun arr -> 
        arr |> Array.map (fun (day,tn,tm,tx) -> day.ToShortDateString()),
        arr |> Array.map (fun (day,tn,tm,tx) -> tm),
        arr |> Array.map (fun (day,tn,tm,tx) -> tx),
        arr |> Array.map (fun (day,tn,tm,tx) -> tn)
([|"03/16/2020"; "03/17/2020"; "03/18/2020"; "03/19/2020"; "03/20/2020";
   "03/21/2020"; "03/22/2020"; "03/23/2020"; "03/24/2020"; "03/25/2020";
   "03/26/2020"; "03/27/2020"; "03/28/2020"; "03/29/2020"; "03/30/2020";
   "03/31/2020"; "04/01/2020"; "04/02/2020"; "04/03/2020"; "04/04/2020";
   "04/05/2020"; "04/06/2020"; "04/07/2020"; "04/08/2020"; "04/09/2020";
   "04/10/2020"; "04/11/2020"; "04/12/2020"; "04/13/2020"; "04/14/2020";
   "04/15/2020"; "04/16/2020"; "04/17/2020"; "04/18/2020"; "04/19/2020";
   "04/20/2020"; "04/21/2020"; "04/22/2020"; "04/23/2020"; "04/24/2020";
   "04/25/2020"; "04/26/2020"; "04/27/2020"; "04/28/2020"; "04/29/2020";
   "04/30/2020"; "05/01/2020"; "05/02/2020"; "05/03/2020"; "05/04/2020";
   "05/05/2020"; "05/06/2020"; "05/07/2020"; "05/08/2020"; "05/09/2020";
   "05/10/2020"; "05/11/2020"; "05/12/2020"; "05/13/2020"; "05/14/2020";
   "05/15/2020"; "05/16/2020"; "05/17/2020"; "05/18/2020"; "05/19/2020";
   "05/20/2020"; "05/21/2020"; "05/22/2020"; "05/23/2020"; "05/24/2020";
   "05/25/2020"; "05/26/2020"; "05/27/2020"; "05/28/2020"; "05/29/2020";
   "05/30/2020"; "05/31/2020"; "06/01/2020"; "06/02/2020"; "06/03/2020";
   "06/04/2020"; "06/05/2020"; "06/06/2020"; "06/07/2020"; "06/08/2020";
   "06/09/2020"; "06/10/2020"; "06/11/2020"; "06/12/2020"; "06/13/2020";
   "06/14/2020"; "06/15/2020"; "06/16/2020"; "06/17/2020"; "06/18/2020";
   "06/19/2020"; "06/20/2020"; "06/21/2020"; "06/22/2020"; "06/23/2020"; ...|],
 [|10.3; 9.0; 10.5; 11.1; 6.6; 4.8; 3.7; 4.0; 4.8; 4.6; 4.2; 7.2; 7.7; 2.1; 2.2;
   4.0; 4.3; 4.5; 6.6; 7.7; 13.0; 15.6; 13.8; 17.4; 15.7; 13.0; 14.2; 15.7; 7.4;
   5.2; 9.5; 15.9; 13.6; 11.9; 10.8; 11.9; 13.0; 13.9; 15.2; 13.5; 9.0; 11.2;
   14.2; 13.8; 12.1; 9.8; 9.3; 8.5; 10.6; 10.6; 8.5; 10.7; 13.9; 15.8; 16.1;
   14.0; 7.1; 6.4; 8.1; 7.5; 8.7; 11.3; 11.8; 15.2; 16.0; 15.2; 18.8; 19.1; 13.3;
   12.4; 15.2; 15.6; 16.9; 14.3; 14.9; 15.8; 15.1; 17.9; 20.5; 18.0; 11.9; 9.3;
   11.1; 12.4; 12.9; 12.6; 14.0; 15.1; 17.7; 19.3; 17.6; 18.2; 17.6; 17.3; 17.0;
   17.1; 16.6; 18.7; 16.1; 20.0; ...|],
 [|14.9; 13.8; 15.8; 15.2; 8.7; 8.9; 8.8; 9.6; 10.4; 10.6; 9.4; 13.3; 13.9; 4.6;
   6.7; 7.9; 9.4; 9.4; 10.1; 13.3; 20.4; 21.3; 19.1; 23.1; 20.8; 19.2; 22.0;
   21.9; 12.8; 9.5; 17.1; 23.1; 19.7; 16.6; 15.4; 17.8; 19.3; 21.0; 21.5; 18.9;
   13.9; 17.3; 20.7; 18.8; 16.3; 14.7; 13.8; 11.7; 16.1; 14.4; 13.4; 15.7; 19.9;
   20.6; 22.5; 18.3; 12.0; 11.2; 13.2; 12.9; 14.5; 16.5; 18.0; 21.6; 22.8; 20.3;
   26.7; 25.5; 18.2; 16.4; 20.1; 21.6; 23.1; 19.8; 21.0; 22.1; 20.7; 24.5; 27.1;
   23.0; 14.4; 12.8; 16.3; 17.0; 17.9; 16.1; 17.1; 18.4; 25.6; 24.5; 22.7; 22.7;
   20.8; 22.5; 22.0; 21.3; 20.9; 24.6; 21.8; 25.1; ...|],
 [|6.0; 4.7; 6.7; 7.1; 3.1; 1.1; -0.3; -1.1; -2.0; -2.5; -2.2; 0.1; 2.4; -1.3;
   -3.9; 0.1; -1.6; -1.6; 2.6; 0.1; 4.0; 11.2; 8.7; 9.7; 8.3; 6.2; 4.9; 10.2;
   1.3; 0.3; 1.1; 8.2; 7.0; 7.3; 8.2; 4.5; 6.7; 7.0; 5.9; 6.8; 5.3; 2.7; 6.0;
   9.9; 8.7; 7.6; 6.0; 4.7; 4.5; 6.5; 4.2; 2.2; 4.9; 9.3; 9.1; 6.1; 3.0; 0.8;
   1.9; 1.1; 2.3; 3.9; 3.8; 8.1; 8.3; 10.1; 9.6; 13.5; 9.5; 8.0; 11.5; 8.3; 10.3;
   8.0; 5.8; 7.3; 9.2; 9.3; 11.9; 12.1; 8.5; 5.9; 7.1; 8.8; 9.6; 10.1; 11.4;
   11.5; 10.3; 13.9; 14.3; 13.9; 13.6; 13.5; 12.9; 12.9; 12.2; 11.4; 11.3; 12.1;
   ...|])

Exploring the data set with Plotly.NET

Next we create a create chart function with Plotly.NET to produce a visual representation of our data set.

open Plotly.NET

// Because our data set is already rather wide we want to move the legend from the right side of the plot
// to the right center. As this function is not defined for fsharp we will use the underlying js bindings (https://plotly.com/javascript/legend/#positioning-the-legend-inside-the-plot).
// Declarative style in F# using underlying DynamicObj
// https://plotly.net/#Declarative-style-in-F-using-the-underlying
let legend = 
    let tmp = Legend()
    tmp?yanchor <- "top"
    tmp?y <- 0.99
    tmp?xanchor <- "left"
    tmp?x <- 0.5
    tmp

/// This function will take 'processedData' as input and return a range chart with a line for the average temperature
/// and a different colored area for the range between minimal and maximal temperature at that date.
let createTempChart (days,tm,tmUpper,tmLower) =
    Chart.Range(
        // data arrays
        days, tm, tmUpper, tmLower,
        StyleParam.Mode.Lines_Markers,
        Color="#3D1244",
        RangeColor="#F99BDE",
        // Name for line in legend
        Name="Average temperature over 24 timepoints each day",
        // Name for lower point when hovering over chart
        LowerName="Min temp",
        // Name for upper point when hovering over chart
        UpperName="Max temp"
    )
    // Configure the chart with the legend from above
    |> Chart.withLegend legend
    // Add name to y axis
    |> Chart.withY_AxisStyle("daily temperature [°C]")
    |> Chart.withSize (1000.,600.)

/// Chart for original data set 
let rawChart =
    processedData 
    |> createTempChart

As you can see the data looks chaotic and is difficult to analyze. Trends are hidden in daily temperature fluctuations and correlating events with temperature can get difficult. So next we want to smooth the data to clearly see temperature trends.

Savitzky-Golay filter

We will use the Signal.Filtering.savitzkyGolay function from FSharp.Stats.

Parameters:

  • windowSize (int) the length of the window. Must be an odd integer number.
  • order (int) the order of the polynomial used in the filtering. Must be less then windowSize - 1.
  • deriv (int) the order of the derivative to compute (default = 0 means only smoothing)
  • rate (int) this factor will influence amplitude when using Savitzky-Golay for derivation
  • data (float array) the values of the time history of the signal.
open FSharp.Stats

let smootheTemp ws order (days,tm,tmUpper,tmLower) =
    let tm' = Signal.Filtering.savitzkyGolay ws order 0 1 tm
    let tmUpper' = Signal.Filtering.savitzkyGolay ws order 0 1 tmUpper
    let tmLower' = Signal.Filtering.savitzkyGolay ws order 0 1 tmLower
    days,tm',tmUpper',tmLower'

let smoothedChart =
    processedData
    |> smootheTemp 31 4
    |> createTempChart 
val rawData : string
Multiple items
namespace FSharp

--------------------
namespace Microsoft.FSharp
Multiple items
namespace FSharp.Data

--------------------
namespace Microsoft.FSharp.Data
type Http = private new : unit -> Http static member private AppendQueryToUrl : url:string * query:(string * string) list -> string static member AsyncRequest : url:string * ?query:(string * string) list * ?headers:seq<string * string> * ?httpMethod:string * ?body:HttpRequestBody * ?cookies:seq<string * string> * ?cookieContainer:CookieContainer * ?silentHttpErrors:bool * ?silentCookieErrors:bool * ?responseEncodingOverride:string * ?customizeHttpRequest:(HttpWebRequest -> HttpWebRequest) * ?timeout:int -> Async<HttpResponse> static member AsyncRequestStream : url:string * ?query:(string * string) list * ?headers:seq<string * string> * ?httpMethod:string * ?body:HttpRequestBody * ?cookies:seq<string * string> * ?cookieContainer:CookieContainer * ?silentHttpErrors:bool * ?silentCookieErrors:bool * ?customizeHttpRequest:(HttpWebRequest -> HttpWebRequest) * ?timeout:int -> Async<HttpResponseWithStream> static member AsyncRequestString : url:string * ?query:(string * string) list * ?headers:seq<string * string> * ?httpMethod:string * ?body:HttpRequestBody * ?cookies:seq<string * string> * ?cookieContainer:CookieContainer * ?silentHttpErrors:bool * ?silentCookieErrors:bool * ?responseEncodingOverride:string * ?customizeHttpRequest:(HttpWebRequest -> HttpWebRequest) * ?timeout:int -> Async<string> static member private EncodeFormData : query:string -> string static member private InnerRequest : url:string * toHttpResponse:(string -> int -> string -> string -> 'a0 option -> Map<string,string> -> Map<string,string> -> Stream -> Async<'a1>) * ?query:(string * string) list * ?headers:seq<string * string> * ?httpMethod:string * ?body:HttpRequestBody * ?cookies:seq<string * string> * ?cookieContainer:CookieContainer * ?silentHttpErrors:bool * ?silentCookieErrors:bool * ?responseEncodingOverride:'a0 * ?customizeHttpRequest:(HttpWebRequest -> HttpWebRequest) * ?timeout:int -> Async<'a1> static member Request : url:string * ?query:(string * string) list * ?headers:seq<string * string> * ?httpMethod:string * ?body:HttpRequestBody * ?cookies:seq<string * string> * ?cookieContainer:CookieContainer * ?silentHttpErrors:bool * ?silentCookieErrors:bool * ?responseEncodingOverride:string * ?customizeHttpRequest:(HttpWebRequest -> HttpWebRequest) * ?timeout:int -> HttpResponse static member RequestStream : url:string * ?query:(string * string) list * ?headers:seq<string * string> * ?httpMethod:string * ?body:HttpRequestBody * ?cookies:seq<string * string> * ?cookieContainer:CookieContainer * ?silentHttpErrors:bool * ?silentCookieErrors:bool * ?customizeHttpRequest:(HttpWebRequest -> HttpWebRequest) * ?timeout:int -> HttpResponseWithStream static member RequestString : url:string * ?query:(string * string) list * ?headers:seq<string * string> * ?httpMethod:string * ?body:HttpRequestBody * ?cookies:seq<string * string> * ?cookieContainer:CookieContainer * ?silentHttpErrors:bool * ?silentCookieErrors:bool * ?responseEncodingOverride:string * ?customizeHttpRequest:(HttpWebRequest -> HttpWebRequest) * ?timeout:int -> string
<summary> Utilities for working with network via HTTP. Includes methods for downloading resources with specified headers, query parameters and HTTP body </summary>
static member FSharp.Data.Http.RequestString : url:string * ?query:(string * string) list * ?headers:seq<string * string> * ?httpMethod:string * ?body:FSharp.Data.HttpRequestBody * ?cookies:seq<string * string> * ?cookieContainer:System.Net.CookieContainer * ?silentHttpErrors:bool * ?silentCookieErrors:bool * ?responseEncodingOverride:string * ?customizeHttpRequest:(System.Net.HttpWebRequest -> System.Net.HttpWebRequest) * ?timeout:int -> string
val printfn : format:Printf.TextWriterFormat<'T> -> 'T
<summary>Print to <c>stdout</c> using the given format, and add a newline.</summary>
<param name="format">The formatter.</param>
<returns>The formatted result.</returns>
namespace System
namespace System.Text
namespace System.Text.RegularExpressions
val processedData : string [] * float [] * float [] * float []
 Tuple of 4 data arrays representing the measured temperature for over a year.
String.Split([<ParamArray>] separator: char []) : string []
String.Split(separator: string [], options: StringSplitOptions) : string []
String.Split(separator: string,?options: StringSplitOptions) : string []
String.Split(separator: char [], options: StringSplitOptions) : string []
String.Split(separator: char [], count: int) : string []
String.Split(separator: char,?options: StringSplitOptions) : string []
String.Split(separator: string [], count: int, options: StringSplitOptions) : string []
String.Split(separator: string, count: int,?options: StringSplitOptions) : string []
String.Split(separator: char [], count: int, options: StringSplitOptions) : string []
String.Split(separator: char, count: int,?options: StringSplitOptions) : string []
type StringSplitOptions = | None = 0 | RemoveEmptyEntries = 1 | TrimEntries = 2
<summary>Specifies whether applicable <see cref="Overload:System.String.Split" /> method overloads include or omit empty substrings from the return value.</summary>
field StringSplitOptions.RemoveEmptyEntries: StringSplitOptions = 1
<summary>The return value does not include array elements that contain an empty string.</summary>
val arr : string []
property Array.Length: int with get
<summary>Gets the total number of elements in all the dimensions of the <see cref="T:System.Array" />.</summary>
<exception cref="T:System.OverflowException">The array is multidimensional and contains more than <see cref="F:System.Int32.MaxValue" /> elements.</exception>
<returns>The total number of elements in all the dimensions of the <see cref="T:System.Array" />; zero if there are no elements in the array.</returns>
type Array = interface ICollection interface IEnumerable interface IList interface IStructuralComparable interface IStructuralEquatable interface ICloneable new : unit -> unit member Clone : unit -> obj member CopyTo : array: Array * index: int -> unit + 1 overload member GetEnumerator : unit -> IEnumerator ...
<summary>Provides methods for creating, manipulating, searching, and sorting arrays, thereby serving as the base class for all arrays in the common language runtime.</summary>
val map : mapping:('T -> 'U) -> array:'T [] -> 'U []
<summary>Builds a new array whose elements are the results of applying the given function to each of the elements of the array.</summary>
<param name="mapping">The function to transform elements of the array.</param>
<param name="array">The input array.</param>
<returns>The array of transformed elements.</returns>
<exception cref="T:System.ArgumentNullException">Thrown when the input array is null.</exception>
val data : string
val whitespacePattern : string
val matchEval : MatchEvaluator
type MatchEvaluator = new : object: obj * method: nativeint -> unit member BeginInvoke : match: Match * callback: AsyncCallback * object: obj -> IAsyncResult member EndInvoke : result: IAsyncResult -> string member Invoke : match: Match -> string
<summary>Represents the method that is called each time a regular expression match is found during a <see cref="Overload:System.Text.RegularExpressions.Regex.Replace" /> method operation.</summary>
<param name="match">The <see cref="T:System.Text.RegularExpressions.Match" /> object that represents a single regular expression match during a <see cref="Overload:System.Text.RegularExpressions.Regex.Replace" /> method operation.</param>
<returns>A string returned by the method that is represented by the <see cref="T:System.Text.RegularExpressions.MatchEvaluator" /> delegate.</returns>
val tabSeparated : string
Multiple items
type Regex = interface ISerializable new : unit -> unit + 4 overloads member GetGroupNames : unit -> string [] member GetGroupNumbers : unit -> int [] member GroupNameFromNumber : i: int -> string member GroupNumberFromName : name: string -> int member InitializeReferences : unit -> unit member IsMatch : input: string -> bool + 4 overloads member Match : input: string -> Match + 5 overloads member Matches : input: string -> MatchCollection + 4 overloads ...
<summary>Represents an immutable regular expression.</summary>

--------------------
Regex(pattern: string) : Regex
Regex(pattern: string, options: RegexOptions) : Regex
Regex(pattern: string, options: RegexOptions, matchTimeout: TimeSpan) : Regex
Regex.Replace(input: string, pattern: string, evaluator: MatchEvaluator) : string
Regex.Replace(input: string, pattern: string, replacement: string) : string
Regex.Replace(input: string, pattern: string, evaluator: MatchEvaluator, options: RegexOptions) : string
Regex.Replace(input: string, pattern: string, replacement: string, options: RegexOptions) : string
Regex.Replace(input: string, pattern: string, evaluator: MatchEvaluator, options: RegexOptions, matchTimeout: TimeSpan) : string
Regex.Replace(input: string, pattern: string, replacement: string, options: RegexOptions, matchTimeout: TimeSpan) : string
val dataStr : string
val dataArr : string []
Multiple items
[<Struct>] type DateTime = new : year: int * month: int * day: int -> unit + 10 overloads member Add : value: TimeSpan -> DateTime member AddDays : value: float -> DateTime member AddHours : value: float -> DateTime member AddMilliseconds : value: float -> DateTime member AddMinutes : value: float -> DateTime member AddMonths : months: int -> DateTime member AddSeconds : value: float -> DateTime member AddTicks : value: int64 -> DateTime member AddYears : value: int -> DateTime ...
<summary>Represents an instant in time, typically expressed as a date and time of day.</summary>

--------------------
DateTime ()
   (+0 other overloads)
DateTime(ticks: int64) : DateTime
   (+0 other overloads)
DateTime(ticks: int64, kind: DateTimeKind) : DateTime
   (+0 other overloads)
DateTime(year: int, month: int, day: int) : DateTime
   (+0 other overloads)
DateTime(year: int, month: int, day: int, calendar: Globalization.Calendar) : DateTime
   (+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int) : DateTime
   (+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, kind: DateTimeKind) : DateTime
   (+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, calendar: Globalization.Calendar) : DateTime
   (+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int) : DateTime
   (+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int, kind: DateTimeKind) : DateTime
   (+0 other overloads)
DateTime.ParseExact(s: string, format: string, provider: IFormatProvider) : DateTime
DateTime.ParseExact(s: string, formats: string [], provider: IFormatProvider, style: Globalization.DateTimeStyles) : DateTime
DateTime.ParseExact(s: string, format: string, provider: IFormatProvider, style: Globalization.DateTimeStyles) : DateTime
DateTime.ParseExact(s: ReadOnlySpan<char>, formats: string [], provider: IFormatProvider,?style: Globalization.DateTimeStyles) : DateTime
DateTime.ParseExact(s: ReadOnlySpan<char>, format: ReadOnlySpan<char>, provider: IFormatProvider,?style: Globalization.DateTimeStyles) : DateTime
namespace System.Globalization
Multiple items
type CultureInfo = interface ICloneable interface IFormatProvider new : culture: int -> unit + 3 overloads member ClearCachedData : unit -> unit member Clone : unit -> obj member Equals : value: obj -> bool member GetConsoleFallbackUICulture : unit -> CultureInfo member GetFormat : formatType: Type -> obj member GetHashCode : unit -> int member ToString : unit -> string ...
<summary>Provides information about a specific culture (called a locale for unmanaged code development). The information includes the names for the culture, the writing system, the calendar used, the sort order of strings, and formatting for dates and numbers.</summary>

--------------------
Globalization.CultureInfo(culture: int) : Globalization.CultureInfo
Globalization.CultureInfo(name: string) : Globalization.CultureInfo
Globalization.CultureInfo(culture: int, useUserOverride: bool) : Globalization.CultureInfo
Globalization.CultureInfo(name: string, useUserOverride: bool) : Globalization.CultureInfo
property Globalization.CultureInfo.InvariantCulture: Globalization.CultureInfo with get
<summary>Gets the <see cref="T:System.Globalization.CultureInfo" /> object that is culture-independent (invariant).</summary>
<returns>The object that is culture-independent (invariant).</returns>
Multiple items
val float : value:'T -> float (requires member op_Explicit)
<summary>Converts the argument to 64-bit float. This is a direct conversion for all primitive numeric types. For strings, the input is converted using <c>Double.Parse()</c> with InvariantCulture settings. Otherwise the operation requires an appropriate static conversion method on the input type.</summary>
<param name="value">The input value.</param>
<returns>The converted float</returns>


--------------------
[<Struct>] type float = Double
<summary>An abbreviation for the CLI type <see cref="T:System.Double" />.</summary>
<category>Basic Types</category>


--------------------
type float<'Measure> = float
<summary>The type of double-precision floating point numbers, annotated with a unit of measure. The unit of measure is erased in compiled code and when values of this type are analyzed using reflection. The type is representationally equivalent to <see cref="T:System.Double" />.</summary>
<category index="6">Basic Types with Units of Measure</category>
val sortBy : projection:('T -> 'Key) -> array:'T [] -> 'T [] (requires comparison)
<summary>Sorts the elements of an array, using the given projection for the keys and returning a new array. Elements are compared using <see cref="M:Microsoft.FSharp.Core.Operators.compare" />.</summary>
<remarks>This is not a stable sort, i.e. the original order of equal elements is not necessarily preserved. For a stable sort, consider using <see cref="M:Microsoft.FSharp.Collections.SeqModule.Sort" />.</remarks>
<param name="projection">The function to transform array elements into the type that is compared.</param>
<param name="array">The input array.</param>
<returns>The sorted array.</returns>
<exception cref="T:System.ArgumentNullException">Thrown when the input array is null.</exception>
val day : DateTime
val tn : float
val tm : float
val tx : float
val arr : (DateTime * float * float * float) []
DateTime.ToShortDateString() : string
namespace Plotly
namespace Plotly.NET
val legend : Legend
val tmp : Legend
Multiple items
type Legend = inherit DynamicObj new : unit -> Legend static member init : ?BGColor:string * ?BorderColor:string * ?Borderwidth:float * ?Orientation:Orientation * ?TraceOrder:TraceOrder * ?TraceGroupGap:float * ?ItemSizing:TraceItemSizing * ?ItemWidth:int * ?ItemClick:TraceItemClickOptions * ?ItemDoubleClick:TraceItemClickOptions * ?X:float * ?XAnchor:LegendXAnchorPosition * ?Y:float * ?YAnchor:LegendYAnchorPosition * ?VerticalAlign:VerticalAlign * ?Title:string -> Legend static member style : ?BGColor:string * ?BorderColor:string * ?Borderwidth:float * ?Orientation:Orientation * ?TraceOrder:TraceOrder * ?TraceGroupGap:float * ?ItemSizing:TraceItemSizing * ?ItemWidth:int * ?ItemClick:TraceItemClickOptions * ?ItemDoubleClick:TraceItemClickOptions * ?X:float * ?XAnchor:LegendXAnchorPosition * ?Y:float * ?YAnchor:LegendYAnchorPosition * ?VerticalAlign:VerticalAlign * ?Title:string -> (Legend -> Legend)
<summary> Legend </summary>

--------------------
new : unit -> Legend
val createTempChart : days:seq<#IConvertible> * tm:seq<#IConvertible> * tmUpper:seq<#IConvertible> * tmLower:seq<#IConvertible> -> GenericChart.GenericChart
 This function will take 'processedData' as input and return a range chart with a line for the average temperature
 and a different colored area for the range between minimal and maximal temperature at that date.
val days : seq<#IConvertible>
val tm : seq<#IConvertible>
val tmUpper : seq<#IConvertible>
val tmLower : seq<#IConvertible>
type Chart = static member Area : x:seq<#IConvertible> * y:seq<#IConvertible> * ?Name:string * ?ShowMarkers:bool * ?Showlegend:bool * ?MarkerSymbol:Symbol * ?Color:string * ?Opacity:float * ?Labels:seq<#IConvertible> * ?TextPosition:TextPosition * ?TextFont:Font * ?Dash:DrawingStyle * ?Width:float -> GenericChart + 1 overload static member Bar : keys:seq<#IConvertible> * values:seq<#IConvertible> * ?Name:string * ?Showlegend:bool * ?Color:string * ?Opacity:float * ?Labels:seq<#IConvertible> * ?TextPosition:TextPosition * ?TextFont:Font * ?Marker:Marker -> GenericChart + 1 overload static member BoxPlot : ?x:'a0 * ?y:'a1 * ?Name:string * ?Showlegend:bool * ?Color:string * ?Fillcolor:'a2 * ?Opacity:float * ?Whiskerwidth:'a3 * ?Boxpoints:Boxpoints * ?Boxmean:BoxMean * ?Jitter:'a4 * ?Pointpos:'a5 * ?Orientation:Orientation * ?Marker:Marker * ?Line:Line * ?Alignmentgroup:'a6 * ?Offsetgroup:'a7 * ?Notched:bool * ?NotchWidth:float * ?QuartileMethod:QuartileMethod -> GenericChart + 1 overload static member Bubble : x:seq<#IConvertible> * y:seq<#IConvertible> * sizes:seq<#IConvertible> * ?Name:string * ?Showlegend:bool * ?MarkerSymbol:Symbol * ?Color:string * ?Opacity:float * ?Labels:seq<#IConvertible> * ?TextPosition:TextPosition * ?TextFont:Font * ?StackGroup:string * ?Orientation:Orientation * ?GroupNorm:GroupNorm * ?UseWebGL:bool -> GenericChart + 1 overload static member Candlestick : open:seq<#IConvertible> * high:seq<#IConvertible> * low:seq<#IConvertible> * close:seq<#IConvertible> * x:seq<#IConvertible> * ?Increasing:Line * ?Decreasing:Line * ?WhiskerWidth:float * ?Line:Line * ?XCalendar:Calendar -> GenericChart + 1 overload static member ChoroplethMap : locations:seq<string> * z:seq<#IConvertible> * ?Text:seq<#IConvertible> * ?Locationmode:LocationFormat * ?Autocolorscale:bool * ?Colorscale:Colorscale * ?Colorbar:Colorbar * ?Marker:Marker * ?GeoJson:'a2 * ?FeatureIdKey:string * ?Zmin:float * ?Zmax:float -> GenericChart static member ChoroplethMapbox : locations:seq<#IConvertible> * z:seq<#IConvertible> * geoJson:'a2 * ?FeatureIdKey:string * ?Text:seq<#IConvertible> * ?Below:string * ?Colorscale:Colorscale * ?Colorbar:Colorbar * ?ZAuto:bool * ?ZMin:float * ?ZMid:float * ?ZMax:float -> GenericChart static member Column : keys:seq<#IConvertible> * values:seq<#IConvertible> * ?Name:string * ?Showlegend:bool * ?Color:string * ?Opacity:float * ?Labels:seq<#IConvertible> * ?TextPosition:TextPosition * ?TextFont:Font * ?Marker:Marker -> GenericChart + 1 overload static member Contour : data:seq<#seq<'a1>> * ?X:seq<#IConvertible> * ?Y:seq<#IConvertible> * ?Name:string * ?Showlegend:bool * ?Opacity:float * ?Colorscale:Colorscale * ?Showscale:'a4 * ?zSmooth:SmoothAlg * ?Colorbar:'a5 -> GenericChart (requires 'a1 :> IConvertible) static member DensityMapbox : lon:seq<#IConvertible> * lat:seq<#IConvertible> * ?Z:seq<#IConvertible> * ?Radius:float * ?Opacity:float * ?Text:seq<#IConvertible> * ?Below:string * ?Colorscale:Colorscale * ?Colorbar:Colorbar * ?Showscale:bool * ?ZAuto:bool * ?ZMin:float * ?ZMid:float * ?ZMax:float -> GenericChart + 1 overload ...
<summary> Provides a set of static methods for creating charts. </summary>
static member Chart.Range : xy:seq<#IConvertible * #IConvertible> * upper:seq<#IConvertible> * lower:seq<#IConvertible> * mode:StyleParam.Mode * ?Name:string * ?ShowMarkers:bool * ?Showlegend:bool * ?Color:string * ?RangeColor:string * ?Labels:seq<#IConvertible> * ?UpperLabels:seq<#IConvertible> * ?LowerLabels:seq<#IConvertible> * ?TextPosition:StyleParam.TextPosition * ?TextFont:Font * ?LowerName:string * ?UpperName:string -> GenericChart.GenericChart
static member Chart.Range : x:seq<#IConvertible> * y:seq<#IConvertible> * upper:seq<#IConvertible> * lower:seq<#IConvertible> * mode:StyleParam.Mode * ?Name:string * ?ShowMarkers:bool * ?Showlegend:bool * ?Color:string * ?RangeColor:string * ?Labels:seq<#IConvertible> * ?UpperLabels:seq<#IConvertible> * ?LowerLabels:seq<#IConvertible> * ?TextPosition:StyleParam.TextPosition * ?TextFont:Font * ?LowerName:string * ?UpperName:string -> GenericChart.GenericChart
module StyleParam from Plotly.NET
type Mode = | None | Lines | Lines_Markers | Lines_Text | Lines_Markers_Text | Markers | Markers_Text | Text static member convert : (Mode -> obj) static member toString : (Mode -> string)
union case StyleParam.Mode.Lines_Markers: StyleParam.Mode
static member Chart.withLegend : legend:Legend -> (GenericChart.GenericChart -> GenericChart.GenericChart)
static member Chart.withLegend : showlegend:bool -> (GenericChart.GenericChart -> GenericChart.GenericChart)
static member Chart.withY_AxisStyle : title:string * ?MinMax:(float * float) * ?Showgrid:bool * ?Showline:bool * ?Side:StyleParam.Side * ?Overlaying:StyleParam.AxisAnchorId * ?Id:int * ?Domain:(float * float) * ?Position:float * ?Zeroline:bool * ?Anchor:StyleParam.AxisAnchorId -> (GenericChart.GenericChart -> GenericChart.GenericChart)
static member Chart.withSize : width:float * height:float -> (GenericChart.GenericChart -> GenericChart.GenericChart)
val rawChart : GenericChart.GenericChart
 Chart for original data set
module GenericChart from Plotly.NET
<summary> Module to represent a GenericChart </summary>
val toChartHTML : gChart:GenericChart.GenericChart -> string
<summary> Converts a GenericChart to it HTML representation. The div layer has a default size of 600 if not specified otherwise. </summary>
namespace FSharp.Stats
val smootheTemp : ws:int -> order:int -> days:'a * tm:float [] * tmUpper:float [] * tmLower:float [] -> 'a * float [] * float [] * float []
val ws : int
val order : int
val days : 'a
val tm : float []
val tmUpper : float []
val tmLower : float []
val tm' : float []
namespace FSharp.Stats.Signal
module Filtering from FSharp.Stats.Signal
val savitzkyGolay : windowSize:int -> order:int -> deriv:int -> rate:int -> data:float [] -> float []
<summary> Smooth (and optionally differentiate) data with a Savitzky-Golay filter. The Savitzky-Golay filter is a type of low-pass filter and removes high frequency noise from data. </summary>
val tmUpper' : float []
val tmLower' : float []
val smoothedChart : GenericChart.GenericChart