Deedle in 10 minutes using F#
This document is a quick overview of the most important features of F# data frame library. You can also get this page as an F# script file from GitHub and run the samples interactively.
The first step is to install Deedle.dll
from NuGet.
Next, we need to load the library - in F# Interactive, this is done by loading
an .fsx
file that loads the actual .dll
with the library and registers
pretty printers for types representing data frame and series. In this sample,
we also need F# Charting, which
works similarly:
1: 2: 3: 4: 5: 6: 7: 8: |
|
Creating series and frames
A data frame is a collection of series with unique column names (although these do not actually have to be strings). So, to create a data frame, we first need to create a series:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: |
|
Keys |
1/1/2013 12:00:00 AM |
1/4/2013 12:00:00 AM |
1/8/2013 12:00:00 AM |
---|---|---|---|
Values |
10 |
20 |
30 |
1: 2: 3: 4: 5: |
|
Keys |
0 |
1 |
2 |
---|---|---|---|
Values |
10 |
20 |
30 |
Note that the series type is generic. Series<K, T>
represents a series
with keys of type K
and values of type T
. Let's now generate series
with 10 day value range and random values:
1: 2: 3: 4: 5: 6: 7: 8: |
|
Keys |
1/1/2013 12:00:00 AM |
1/2/2013 12:00:00 AM |
1/3/2013 12:00:00 AM |
1/4/2013 12:00:00 AM |
1/5/2013 12:00:00 AM |
... |
1/8/2013 12:00:00 AM |
1/9/2013 12:00:00 AM |
1/10/2013 12:00:00 AM |
---|---|---|---|---|---|---|---|---|---|
Values |
.34 |
.32 |
.36 |
.86 |
.06 |
... |
.53 |
.82 |
.11 |
Now we can easily construct a data frame that has two columns - one representing
the first
series and another representing the second
series:
1:
|
|
first |
second |
|
---|---|---|
1/1/2013 12:00:00 AM |
10 |
.3404 |
1/2/2013 12:00:00 AM |
N/A |
.3165 |
1/3/2013 12:00:00 AM |
N/A |
.3648 |
1/4/2013 12:00:00 AM |
20 |
.8585 |
1/5/2013 12:00:00 AM |
N/A |
.0569 |
1/6/2013 12:00:00 AM |
N/A |
.2683 |
1/7/2013 12:00:00 AM |
N/A |
.7142 |
1/8/2013 12:00:00 AM |
30 |
.5279 |
1/9/2013 12:00:00 AM |
N/A |
.822 |
1/10/2013 12:00:00 AM |
N/A |
.1119 |
The type representing a data frame has two generic parameters:
Frame<TRowKey, TColumnKey>
. The first parameter is represents the type of
row keys - this can be int
if we do not give the keys explicitly or DateTime
like in the example above. The second parameter is the type of column keys.
This is typically string
, but sometimes it is useful to create a
transposed frame with dates as column keys. Because a data frame can contain
heterogeneous data, there is no type of values - this needs to be specified
when getting data from the data frame.
As the output shows, creating a frame automatically combines the indices of
the two series (using "outer join" so the result has all the dates that appear
in any of the series). The data frame now contains first
column with some
missing values.
You can also use the following nicer syntax and create frame from rows as well as individual values:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: |
|
Data frame can be also easily created from a collection of F# record types (or of any classes
with public readable properties). The Frame.ofRecords
function uses reflection to find the
names and types of properties of a record and creates a data frame with the same structure.
1: 2: 3: 4: 5: 6: 7: 8: 9: |
|
Finally, we can also load data frame from CSV:
1: 2: |
|
Date |
Open |
High |
... |
Close |
Volume |
Adj Close |
|
---|---|---|---|---|---|---|---|
0 |
11/7/2013 12:00:00 AM |
49.24 |
49.87 |
... |
47.56 |
96925500 |
47.56 |
1 |
11/6/2013 12:00:00 AM |
50.26 |
50.45 |
... |
49.12 |
67648600 |
49.12 |
2 |
11/5/2013 12:00:00 AM |
47.79 |
50.18 |
... |
50.11 |
76668300 |
50.11 |
3 |
11/4/2013 12:00:00 AM |
49.37 |
49.75 |
... |
48.22 |
80206200 |
48.22 |
4 |
11/1/2013 12:00:00 AM |
50.85 |
52.09 |
... |
49.75 |
94822200 |
49.75 |
5 |
10/31/2013 12:00:00 AM |
47.16 |
52.00 |
... |
50.21 |
248388200 |
50.21 |
6 |
10/30/2013 12:00:00 AM |
50.00 |
50.21 |
... |
49.01 |
116674400 |
49.01 |
7 |
10/29/2013 12:00:00 AM |
50.73 |
50.79 |
... |
49.40 |
101859700 |
49.40 |
... |
... |
... |
... |
... |
... |
... |
... |
367 |
5/23/2012 12:00:00 AM |
31.37 |
32.50 |
... |
32.00 |
73600000 |
32.00 |
368 |
5/22/2012 12:00:00 AM |
32.61 |
33.59 |
... |
31.00 |
101786600 |
31.00 |
369 |
5/21/2012 12:00:00 AM |
36.53 |
36.66 |
... |
34.03 |
168192700 |
34.03 |
370 |
5/18/2012 12:00:00 AM |
42.05 |
45.00 |
... |
38.23 |
573576400 |
38.23 |
When loading the data, the data frame analyses the values and automatically converts
them to the most appropriate type. However, no conversion is automatically performed
for dates and times - the user needs to decide what is the desirable representation
of dates (e.g. DateTime
, DateTimeOffset
or some custom type).
Specifying index and joining
Now we have fbCsv
and msftCsv
frames containing stock prices, but they are
indexed with ordinal numbers. This means that we can get e.g. 4th price.
However, we would like to align them using their dates (in case there are some
values missing). This can be done by setting the row index to the "Date" column.
Once we set the date as the index, we also need to order the index. The Yahoo
Finance prices are ordered from the newest to the oldest, but our data-frame
requires ascending ordering.
When a frame has ordered index, we can use additional functionality that will be needed later (for example, we can select sub-range by specifying dates that are not explicitly included in the index).
1: 2: 3: 4: 5: |
|
The indexRowsDate
function uses a column of type DateTime
as a new index.
The library provides other functions for common types of indices (like indexRowsInt
)
and you can also use a generic function - when using the generic function, some
type annotations may be needed, so it is better to use a specific function.
Next, we sort the rows using another function from the Frame
module. The module
contains a large number of useful functions that you'll use all the time - it
is a good idea to go through the list to get an idea of what is supported.
Now that we have properly indexed stock prices, we can create a new data frame that only has the data we're interested (Open & Close) prices and we add a new column that shows their difference:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: |
|
When selecting columns using f.Columns.[ .. ]
it is possible to use a list of columns
(as we did), a single column key, or a range (if the associated index is ordered).
Then we use the df?Column <- (...)
syntax to add a new column to the data frame.
This is the only mutating operation that is supported on data frames - all other
operations create a new data frame and return it as the result.
Next we would like to create a single data frame that contains (properly aligned) data
for both Microsoft and Facebook. This is done using the Join
method - but before we
can do this, we need to rename their columns, because duplicate keys are not allowed:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: |
|
Selecting values and slicing
The data frame provides two key properties that we can use to access the data. The
Rows
property returns a series containing individual rows (as a series) and Columns
returns a series containing columns (as a series). We can then use various indexing and
slicing operators on the series:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: |
|
The return type of the first expression is ObjectSeries<string>
which is inherited from
Series<string, obj>
and represents an untyped series. We can use GetAs<int>("FbOpen")
to
get a value for a specifed key and convert it to a required type (or TryGetAs
). The untyped
series also hides the default ?
operator (which returns the value using the statically known
value type) and provides ?
that automatically converts anything to float
.
In the previous example, we used an indexer with a single key. You can also specify multiple keys (using a list) or a range (using the slicing syntax):
1: 2: 3: 4: 5: 6: 7: 8: 9: |
|
MsftOpen |
MsftClose |
MsftDiff |
FbOpen |
FbClose |
FbDiff |
|
---|---|---|---|---|---|---|
1/2/2013 |
27.25 |
27.62 |
-.37 |
27.44 |
28 |
-.56 |
1/3/2013 |
27.63 |
27.25 |
.38 |
27.88 |
27.77 |
.11 |
1/4/2013 |
27.27 |
26.74 |
.53 |
28.01 |
28.76 |
-.75 |
1/7/2013 |
26.77 |
26.69 |
.08 |
28.69 |
29.42 |
-.73 |
1/8/2013 |
26.75 |
26.55 |
.2 |
29.51 |
29.06 |
.45 |
1/9/2013 |
26.72 |
26.7 |
.02 |
29.67 |
30.59 |
-.92 |
1/10/2013 |
26.65 |
26.46 |
.19 |
30.6 |
31.3 |
-.7 |
1/11/2013 |
26.49 |
26.83 |
-.34 |
31.28 |
31.72 |
-.44 |
... |
... |
... |
... |
... |
... |
... |
1/28/2013 |
28.01 |
27.91 |
.1 |
31.88 |
32.47 |
-.59 |
1/29/2013 |
27.82 |
28.01 |
-.19 |
32 |
30.79 |
1.21 |
1/30/2013 |
28.01 |
27.85 |
.16 |
30.98 |
31.24 |
-.26 |
1/31/2013 |
27.79 |
27.45 |
.34 |
29.15 |
30.98 |
-1.83 |
1: 2: 3: |
|
The result of the indexing operation is a single data series when you use just a single date (the previous example) or a new data frame when you specify multiple indices or a range (this example).
The Series
module used here includes more useful functions for working
with data series, including (but not limited to) statistical functions like mean
,
sdv
and sum
.
Note that the slicing using range (the second case) does not actually generate a sequence of dates from 1 January to 31 January - it passes these to the index. Because our data frame has an ordered index, the index looks for all keys that are greater than 1 January and smaller than 31 January (this matters here, because the data frame does not contain 1 January - the first day is 2 January)
Using ordered time series
As already mentioned, if we have an ordered series or an ordered data frame, then we can leverage the ordering in a number of ways. In the previous example, slicing used lower and upper bounds rather than exact matching. Similarly, it is possible to get nearest smaller (or greater) element when using direct lookup.
For example, let's create two series with 10 values for 10 days. The daysSeries
contains keys starting from DateTime.Today
(12:00 AM) and obsSeries
has dates
with time component set to the current time (this is wrong representation, but it
can be used to ilustrate the idea):
1: 2: |
|
Keys |
6/15/2020 12:00:00 AM |
6/16/2020 12:00:00 AM |
6/17/2020 12:00:00 AM |
6/18/2020 12:00:00 AM |
6/19/2020 12:00:00 AM |
... |
6/22/2020 12:00:00 AM |
6/23/2020 12:00:00 AM |
6/24/2020 12:00:00 AM |
---|---|---|---|---|---|---|---|---|---|
Values |
.91 |
.09 |
.79 |
.17 |
.34 |
... |
.48 |
.81 |
.46 |
Keys |
6/15/2020 12:33:26 PM |
6/16/2020 12:33:26 PM |
6/17/2020 12:33:26 PM |
6/18/2020 12:33:26 PM |
6/19/2020 12:33:26 PM |
... |
6/22/2020 12:33:26 PM |
6/23/2020 12:33:26 PM |
6/24/2020 12:33:26 PM |
---|---|---|---|---|---|---|---|---|---|
Values |
.91 |
.09 |
.79 |
.17 |
.34 |
... |
.48 |
.81 |
.46 |
The indexing operation written as daysSeries.[date]
uses exact semantics so it will
fail if the exact date is not available. When using Get
method, we can provide an
additional parameter to specify the required behaviour:
1: 2: 3: 4: 5: 6: 7: 8: 9: |
|
Similarly, you can specify the semantics when calling TryGet
(to get an optional value)
or when using GetItems
(to lookup multiple keys at once). Note that this behaviour is
only supported for series or frames with ordered index. For unordered, all operations use
the exact semantics.
The semantics can be also specified when using left or right join on data frames. To demonstrate this, let's create two data frames with columns indexed by 1 and 2, respectively:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: |
|
In general, the same operation can usually be achieved using a function from the
Series
or Frame
module and using a member (or an extension member) on the object.
The previous sample shows both options - it uses Join
as a member with optional
argument first, and then it uses joinAlign
function. Choosing between the two is
a matter of preference - here, we are using joinAlign
so that we can write code
using pipelining (rather than long expression that would not fit on the page).
The Join
method takes two optional parameters - the parameter ?lookup
is ignored
when the join ?kind
is other than Left
or Right
. Also, if the data frame is not
ordered, the behaviour defaults to exact matching. The joinAlign
function behaves
the same way.
Projection and filtering
For filtering and projection, series provides Where
and Select
methods and
corresponding Series.map
and Series.filter
functions (there is also Series.mapValues
and Series.mapKeys
if you only want to transform one aspect).
The methods are not available directly on data frame, so you always need to write df.Rows
or df.Columns
(depending on which one you want). Correspondingly, the Frame
module
provides functions such as Frame.mapRows
. The following adds a new column that contains
the name of the stock with greater price ("FB" or "MSFT"):
1: 2: |
|
When projecting or filtering rows, we need to be careful about missing data. The row
accessor row?MsftOpen
reads the specified column (and converts it to float
), but when
the column is not available, it throws the MissingValueException
exception. Projection
functions such as mapRowValues
automatically catch this exception (but no other types
of exceptions) and mark the corresponding series value as missing.
To make the missing value handling more explicit, you could use Series.hasAll ["MsftOpen"; "FbOpen"]
to check that the series has all the values we need. If no, the lambda function could return
null
, which is automatically treated as a missing value (and it will be skipped by future
operations).
Now we can get the number of days when Microsoft stock prices were above Facebook and the other way round:
1: 2: 3: 4: 5: 6: 7: |
|
In this case, we should probably have used joinedIn
which only has rows where the
values are always available. But you often want to work with data frame that has missing values,
so it is useful to see how this work. Here is another alternative:
1: 2: 3: 4: 5: 6: 7: 8: |
|
The key is the use of RowsDense
on line 6. It behaves similarly to Rows
, but
only returns rows that have no missing values. This means that we can then perform
the filtering safely without any checks.
However, we do not mind if there are missing values in FbClose
, because we do not
need this column. For this reason, we first create joinedOpens
, which projects
just the two columns we need from the original data frame.
Grouping and aggregation
As a last thing, we briefly look at grouping and aggregation. For more information about grouping of time series data, see the time series features tutorial and the data frame features contains more about grouping of unordered frames.
We'll use the simplest option which is the Frame.groupRowsUsing
function (also available
as GroupRowsUsing
member). This allows us to specify key selector that selects new key
for each row. If you want to group data using a value in a column, you can use
Frame.groupRowsBy column
.
The following snippet groups rows by month and year:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: |
|
The output is trimmed to fit on the page. As you can see, we get back a frame that has
a tuple DateTime * DateTime
as the row key. This is treated in a special way as a
hierarchical (or multi-level) index. For example, the output automatically shows the
rows in groups (assuming they are correctly ordered).
A number of operations can be used on hierarchical indices. For example, we can get rows in a specified group (say, May 2013) and calculate means of columns in the group:
1: 2: 3: 4: 5: 6: 7: 8: |
|
The above snippet uses slicing notation that is only available in F# 3.1 (Visual Studio 2013).
In earlier versions, you can get the same thing using monthly.Rows.[Lookup1Of2 (DateTime(2013,5,1))]
.
The syntax indicates that we only want to specify the first part of the key and do not match
on the second component. We can also use Frame.getNumericColumns
in combination with
Stats.levelMean
to get means for all first-level groups:
1: 2: 3: 4: |
|
Here, we simply use the fact that the key is a tuple. The fst
function projects the first
date from the key (month and year) and the result is a frame that contains the first-level keys,
together with means for all available numeric columns.
namespace FSharp
--------------------
namespace Microsoft.FSharp
type DateTime =
struct
new : ticks:int64 -> DateTime + 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
...
end
--------------------
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)
module Series
from Deedle
--------------------
type Series =
static member ofNullables : values:seq<Nullable<'a0>> -> Series<int,'a0> (requires default constructor and value type and 'a0 :> ValueType)
static member ofObservations : observations:seq<'c * 'd> -> Series<'c,'d> (requires equality)
static member ofOptionalObservations : observations:seq<'K * 'a1 option> -> Series<'K,'a1> (requires equality)
static member ofValues : values:seq<'a> -> Series<int,'a>
--------------------
type Series<'K,'V (requires equality)> =
interface IFsiFormattable
interface ISeries<'K>
new : pairs:seq<KeyValuePair<'K,'V>> -> Series<'K,'V>
new : keys:'K [] * values:'V [] -> Series<'K,'V>
new : keys:seq<'K> * values:seq<'V> -> Series<'K,'V>
new : index:IIndex<'K> * vector:IVector<'V> * vectorBuilder:IVectorBuilder * indexBuilder:IIndexBuilder -> Series<'K,'V>
member After : lowerExclusive:'K -> Series<'K,'V>
member Aggregate : aggregation:Aggregation<'K> * observationSelector:Func<DataSegment<Series<'K,'V>>,KeyValuePair<'TNewKey,OptionalValue<'R>>> -> Series<'TNewKey,'R> (requires equality)
member Aggregate : aggregation:Aggregation<'K> * keySelector:Func<DataSegment<Series<'K,'V>>,'TNewKey> * valueSelector:Func<DataSegment<Series<'K,'V>>,OptionalValue<'R>> -> Series<'TNewKey,'R> (requires equality)
member AsyncMaterialize : unit -> Async<Series<'K,'V>>
...
--------------------
new : pairs:seq<Collections.Generic.KeyValuePair<'K,'V>> -> Series<'K,'V>
new : keys:seq<'K> * values:seq<'V> -> Series<'K,'V>
new : keys:'K [] * values:'V [] -> Series<'K,'V>
new : index:Indices.IIndex<'K> * vector:IVector<'V> * vectorBuilder:Vectors.IVectorBuilder * indexBuilder:Indices.IIndexBuilder -> Series<'K,'V>
Generate date range from 'first' with 'count' days
Generate 'count' number of random doubles
seq { for i in 0 .. (count - 1) -> rnd.NextDouble() }
module Frame
from Deedle
--------------------
type Frame =
static member ReadCsv : stream:Stream * hasHeaders:Nullable<bool> * inferTypes:Nullable<bool> * inferRows:Nullable<int> * schema:string * separators:string * culture:string * maxRows:Nullable<int> * missingValues:string [] * preferOptions:Nullable<bool> -> Frame<int,string>
static member ReadCsv : location:string * hasHeaders:Nullable<bool> * inferTypes:Nullable<bool> * inferRows:Nullable<int> * schema:string * separators:string * culture:string * maxRows:Nullable<int> * missingValues:string [] * preferOptions:bool -> Frame<int,string>
static member ReadReader : reader:IDataReader -> Frame<int,string>
static member CustomExpanders : Dictionary<Type,Func<obj,seq<string * Type * obj>>>
static member NonExpandableInterfaces : ResizeArray<Type>
static member NonExpandableTypes : HashSet<Type>
--------------------
type Frame<'TRowKey,'TColumnKey (requires equality and equality)> =
interface IDynamicMetaObjectProvider
interface INotifyCollectionChanged
interface IFsiFormattable
interface IFrame
new : names:seq<'TColumnKey> * columns:seq<ISeries<'TRowKey>> -> Frame<'TRowKey,'TColumnKey>
new : rowIndex:IIndex<'TRowKey> * columnIndex:IIndex<'TColumnKey> * data:IVector<IVector> * indexBuilder:IIndexBuilder * vectorBuilder:IVectorBuilder -> Frame<'TRowKey,'TColumnKey>
member AddColumn : column:'TColumnKey * series:ISeries<'TRowKey> -> unit
member AddColumn : column:'TColumnKey * series:seq<'V> -> unit
member AddColumn : column:'TColumnKey * series:ISeries<'TRowKey> * lookup:Lookup -> unit
member AddColumn : column:'TColumnKey * series:seq<'V> * lookup:Lookup -> unit
...
--------------------
new : names:seq<'TColumnKey> * columns:seq<ISeries<'TRowKey>> -> Frame<'TRowKey,'TColumnKey>
new : rowIndex:Indices.IIndex<'TRowKey> * columnIndex:Indices.IIndex<'TColumnKey> * data:IVector<IVector> * indexBuilder:Indices.IIndexBuilder * vectorBuilder:Vectors.IVectorBuilder -> Frame<'TRowKey,'TColumnKey>
static member Frame.ofColumns : cols:seq<'C * #ISeries<'R>> -> Frame<'R,'C> (requires equality and equality)
static member Frame.ofRows : rows:Series<'R,#ISeries<'C>> -> Frame<'R,'C> (requires equality and equality)
{Day: DateTime;
Open: float;}
val float : value:'T -> float (requires member op_Explicit)
--------------------
type float = Double
--------------------
type float<'Measure> = float
static member Frame.ofRecords : values:seq<'T> -> Frame<int,string>
static member Frame.ofRecords : values:Collections.IEnumerable * indexCol:string -> Frame<'R,string> (requires equality)
static member Frame.ReadCsv : stream:IO.Stream * ?hasHeaders:bool * ?inferTypes:bool * ?inferRows:int * ?schema:string * ?separators:string * ?culture:string * ?maxRows:int * ?missingValues:string [] * ?preferOptions:bool -> Frame<int,string>
static member Frame.ReadCsv : reader:IO.TextReader * ?hasHeaders:bool * ?inferTypes:bool * ?inferRows:int * ?schema:string * ?separators:string * ?culture:string * ?maxRows:int * ?missingValues:string [] * ?preferOptions:bool -> Frame<int,string>
static member Frame.ReadCsv : stream:IO.Stream * hasHeaders:Nullable<bool> * inferTypes:Nullable<bool> * inferRows:Nullable<int> * schema:string * separators:string * culture:string * maxRows:Nullable<int> * missingValues:string [] * preferOptions:Nullable<bool> -> Frame<int,string>
static member Frame.ReadCsv : location:string * hasHeaders:Nullable<bool> * inferTypes:Nullable<bool> * inferRows:Nullable<int> * schema:string * separators:string * culture:string * maxRows:Nullable<int> * missingValues:string [] * preferOptions:bool -> Frame<int,string>
static member Frame.ReadCsv : path:string * indexCol:string * ?hasHeaders:bool * ?inferTypes:bool * ?inferRows:int * ?schema:string * ?separators:string * ?culture:string * ?maxRows:int * ?missingValues:string [] * ?preferOptions:bool -> Frame<'R,string> (requires equality)
static member Area : data:seq<#value> * ?Name:string * ?Title:string * ?Labels:#seq<string> * ?Color:Color * ?XTitle:string * ?YTitle:string -> GenericChart
static member Area : data:seq<#key * #value> * ?Name:string * ?Title:string * ?Labels:#seq<string> * ?Color:Color * ?XTitle:string * ?YTitle:string -> GenericChart
static member Bar : data:seq<#value> * ?Name:string * ?Title:string * ?Labels:#seq<string> * ?Color:Color * ?XTitle:string * ?YTitle:string -> GenericChart
static member Bar : data:seq<#key * #value> * ?Name:string * ?Title:string * ?Labels:#seq<string> * ?Color:Color * ?XTitle:string * ?YTitle:string -> GenericChart
static member BoxPlotFromData : data:seq<#key * #seq<'a2>> * ?Name:string * ?Title:string * ?Color:Color * ?XTitle:string * ?YTitle:string * ?Percentile:int * ?ShowAverage:bool * ?ShowMedian:bool * ?ShowUnusualValues:bool * ?WhiskerPercentile:int -> GenericChart (requires 'a2 :> value)
static member BoxPlotFromStatistics : data:seq<#key * #value * #value * #value * #value * #value * #value> * ?Name:string * ?Title:string * ?Labels:#seq<string> * ?Color:Color * ?XTitle:string * ?YTitle:string * ?Percentile:int * ?ShowAverage:bool * ?ShowMedian:bool * ?ShowUnusualValues:bool * ?WhiskerPercentile:int -> GenericChart
static member Bubble : data:seq<#value * #value> * ?Name:string * ?Title:string * ?Labels:#seq<string> * ?Color:Color * ?XTitle:string * ?YTitle:string * ?BubbleMaxSize:int * ?BubbleMinSize:int * ?BubbleScaleMax:float * ?BubbleScaleMin:float * ?UseSizeForLabel:bool -> GenericChart
static member Bubble : data:seq<#key * #value * #value> * ?Name:string * ?Title:string * ?Labels:#seq<string> * ?Color:Color * ?XTitle:string * ?YTitle:string * ?BubbleMaxSize:int * ?BubbleMinSize:int * ?BubbleScaleMax:float * ?BubbleScaleMin:float * ?UseSizeForLabel:bool -> GenericChart
static member Candlestick : data:seq<#value * #value * #value * #value> * ?Name:string * ?Title:string * ?Labels:#seq<string> * ?Color:Color * ?XTitle:string * ?YTitle:string -> CandlestickChart
static member Candlestick : data:seq<#key * #value * #value * #value * #value> * ?Name:string * ?Title:string * ?Labels:#seq<string> * ?Color:Color * ?XTitle:string * ?YTitle:string -> CandlestickChart
...
static member Chart.Line : data:seq<#key * #value> * ?Name:string * ?Title:string * ?Labels:#seq<string> * ?Color:Drawing.Color * ?XTitle:string * ?YTitle:string -> ChartTypes.GenericChart
member Frame.Join : colKey:'TColumnKey * series:Series<'TRowKey,'V> -> Frame<'TRowKey,'TColumnKey>
member Frame.Join : otherFrame:Frame<'TRowKey,'TColumnKey> * kind:JoinKind -> Frame<'TRowKey,'TColumnKey>
member Frame.Join : colKey:'TColumnKey * series:Series<'TRowKey,'V> * kind:JoinKind -> Frame<'TRowKey,'TColumnKey>
member Frame.Join : otherFrame:Frame<'TRowKey,'TColumnKey> * kind:JoinKind * lookup:Lookup -> Frame<'TRowKey,'TColumnKey>
member Frame.Join : colKey:'TColumnKey * series:Series<'TRowKey,'V> * kind:JoinKind * lookup:Lookup -> Frame<'TRowKey,'TColumnKey>
| Outer = 0
| Inner = 1
| Left = 2
| Right = 3
static member count : frame:Frame<'R,'C> -> Series<'C,int> (requires equality and equality)
static member count : series:Series<'K,'V> -> int (requires equality)
static member describe : series:Series<'K,'V> -> Series<string,float> (requires equality and equality)
static member expandingCount : series:Series<'K,'V> -> Series<'K,float> (requires equality)
static member expandingKurt : series:Series<'K,'V> -> Series<'K,float> (requires equality)
static member expandingMax : series:Series<'K,'V> -> Series<'K,float> (requires equality)
static member expandingMean : series:Series<'K,'V> -> Series<'K,float> (requires equality)
static member expandingMin : series:Series<'K,'V> -> Series<'K,float> (requires equality)
static member expandingSkew : series:Series<'K,'V> -> Series<'K,float> (requires equality)
static member expandingStdDev : series:Series<'K,'V> -> Series<'K,float> (requires equality)
...
static member Stats.mean : series:Series<'K,'V> -> float (requires equality)
member Series.Get : key:'K * lookup:Lookup -> 'V
| Exact = 1
| ExactOrGreater = 3
| ExactOrSmaller = 5
| Greater = 2
| Smaller = 4
delegate of 'T * 'T -> int
member Frame.GetColumn : column:'TColumnKey * lookup:Lookup -> Series<'TRowKey,'R>
val string : value:'T -> string
--------------------
type string = String