Deedle


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: 
#I "../../packages/FSharp.Charting"
#I "../../packages/Deedle"
#load "FSharp.Charting.fsx"
#load "Deedle.fsx"

open System
open Deedle
open FSharp.Charting

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: 
// Create from sequence of keys and sequence of values
let dates  = 
  [ DateTime(2013,1,1); 
    DateTime(2013,1,4); 
    DateTime(2013,1,8) ]
let values = 
  [ 10.0; 20.0; 30.0 ]
let first = Series(dates, values)

// Create from a single list of observations
Series.ofObservations
  [ DateTime(2013,1,1) => 10.0
    DateTime(2013,1,4) => 20.0
    DateTime(2013,1,8) => 30.0 ]

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: 
// Shorter alternative to 'Series.ofObservations'
series [ 1 => 1.0; 2 => 2.0 ]

// Create series with implicit (ordinal) keys
Series.ofValues [ 10.0; 20.0; 30.0 ]

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: 
/// Generate date range from 'first' with 'count' days
let dateRange (first:System.DateTime) count = (...)

/// Generate 'count' number of random doubles
let rand count = (...)

// A series with values for 10 days 
let second = Series(dateRange (DateTime(2013,1,1)) 10, rand 10)

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: 
let df1 = Frame(["first"; "second"], [first; second])

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: 
// The same as previously
let df2 = Frame.ofColumns ["first" => first; "second" => second]

// Transposed - here, rows are "first" and "second" & columns are dates
let df3 = Frame.ofRows ["first" => first; "second" => second]

// Create from individual observations (row * column * value)
let df4 = 
  [ ("Monday", "Tomas", 1.0); ("Tuesday", "Adam", 2.1)
    ("Tuesday", "Tomas", 4.0); ("Wednesday", "Tomas", -5.4) ]
  |> Frame.ofValues

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: 
// Assuming we have a record 'Price' and a collection 'values'
type Price = { Day : DateTime; Open : float }
let prices = 
  [ { Day = DateTime.Now; Open = 10.1 }
    { Day = DateTime.Now.AddDays(1.0); Open = 15.1 }
    { Day = DateTime.Now.AddDays(2.0); Open = 9.1 } ]

// Creates a data frame with columns 'Day' and 'Open'
let df5 = Frame.ofRecords prices

Finally, we can also load data frame from CSV:

1: 
2: 
let msftCsv = Frame.ReadCsv(__SOURCE_DIRECTORY__ + "/data/stocks/MSFT.csv")
let fbCsv = Frame.ReadCsv(__SOURCE_DIRECTORY__ + "/data/stocks/FB.csv")

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: 
// Use the Date column as the index & order rows
let msftOrd = 
  msftCsv
  |> Frame.indexRowsDate "Date"
  |> Frame.sortRowsByKey

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: 
// Create data frame with just Open and Close prices
let msft = msftOrd.Columns.[ ["Open"; "Close"] ]

// Add new column with the difference between Open & Close
msft?Difference <- msft?Open - msft?Close

// Do the same thing for Facebook
let fb = 
  fbCsv
  |> Frame.indexRowsDate "Date"
  |> Frame.sortRowsByKey
  |> Frame.sliceCols ["Open"; "Close"]
fb?Difference <- fb?Open - fb?Close

// Now we can easily plot the differences
Chart.Combine
  [ Chart.Line(msft?Difference |> Series.observations) 
    Chart.Line(fb?Difference |> Series.observations) ]

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: 
// Change the column names so that they are unique
let msftNames = ["MsftOpen"; "MsftClose"; "MsftDiff"]
let msftRen = msft |> Frame.indexColsWith msftNames

let fbNames = ["FbOpen"; "FbClose"; "FbDiff"]
let fbRen = fb |> Frame.indexColsWith fbNames

// Outer join (align & fill with missing values)
let joinedOut = msftRen.Join(fbRen, kind=JoinKind.Outer)

// Inner join (remove rows with missing values)
let joinedIn = msftRen.Join(fbRen, kind=JoinKind.Inner)

// Visualize daily differences on available values only
Chart.Rows
  [ Chart.Line(joinedIn?MsftDiff |> Series.observations) 
    Chart.Line(joinedIn?FbDiff |> Series.observations) ]

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: 
// Look for a row at a specific date
joinedIn.Rows.[DateTime(2013, 1, 2)]
val it : ObjectSeries<string> =
  FbOpen    -> 28.00            
  FbClose   -> 27.44   
  FbDiff    -> -0.5599 
  MsftOpen  -> 27.62   
  MsftClose -> 27.25    
  MsftDiff  -> -0.3700 

// Get opening Facebook price for 2 Jan 2013
joinedIn.Rows.[DateTime(2013, 1, 2)]?FbOpen
val it : float = 28.0

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: 
// Get values for the first three days of January 2013
let janDates = [ for d in 2 .. 4 -> DateTime(2013, 1, d) ]
let jan234 = joinedIn.Rows.[janDates]

// Calculate mean of Open price for 3 days
jan234?MsftOpen |> Stats.mean

// Get values corresponding to entire January 2013
let jan = joinedIn.Rows.[DateTime(2013, 1, 1) .. DateTime(2013, 1, 31)] 

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: 
// Calculate means over the period
jan?FbOpen |> Stats.mean
jan?MsftOpen |> Stats.mean

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: 
let daysSeries = Series(dateRange DateTime.Today 10, rand 10)
let obsSeries = Series(dateRange DateTime.Now 10, rand 10)

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: 
// Fails, because current time is not present
try daysSeries.[DateTime.Now] with _ -> nan
try obsSeries.[DateTime.Now] with _ -> nan

// This works - we get the value for DateTime.Today (12:00 AM)
daysSeries.Get(DateTime.Now, Lookup.ExactOrSmaller)
// This does not - there is no nearest key <= Today 12:00 AM
try obsSeries.Get(DateTime.Today, Lookup.ExactOrSmaller)
with _ -> nan

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: 
let daysFrame = [ 1 => daysSeries ] |> Frame.ofColumns
let obsFrame = [ 2 => obsSeries ] |> Frame.ofColumns

// All values in column 2 are missing (because the times do not match)
let obsDaysExact = daysFrame.Join(obsFrame, kind=JoinKind.Left)

// All values are available - for each day, we find the nearest smaller
// time in the frame indexed by later times in the day
let obsDaysPrev = 
  (daysFrame, obsFrame) 
  ||> Frame.joinAlign JoinKind.Left Lookup.ExactOrSmaller

// The first value is missing (because there is no nearest 
// value with greater key - the first one has the smallest 
// key) but the rest is available
let obsDaysNext =
  (daysFrame, obsFrame) 
  ||> Frame.joinAlign JoinKind.Left Lookup.ExactOrGreater

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: 
joinedOut?Comparison <- joinedOut |> Frame.mapRowValues (fun row -> 
  if row?MsftOpen > row?FbOpen then "MSFT" else "FB")

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: 
joinedOut.GetColumn<string>("Comparison")
|> Series.filterValues ((=) "MSFT") |> Series.countValues
val it : int = 220

joinedOut.GetColumn<string>("Comparison")
|> Series.filterValues ((=) "FB") |> Series.countValues
val it : int = 103

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: 
// Get data frame with only 'Open' columns
let joinedOpens = joinedOut.Columns.[ ["MsftOpen"; "FbOpen"] ]

// Get only rows that don't have any missing values
// and then we can safely filter & count
joinedOpens.RowsDense
|> Series.filterValues (fun row -> row?MsftOpen > row?FbOpen)
|> Series.countValues

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: 
let monthly =
  joinedIn
  |> Frame.groupRowsUsing (fun k _ -> DateTime(k.Year, k.Month, 1))

val monthly : Frame<(DateTime * DateTime),string> =
 
                        FbOpen  MsftOpen 
  5/1/2012 5/18/2012 -> 38.23   29.27    
           5/21/2012 -> 34.03   29.75    
           5/22/2012 -> 31.00   29.76    
  :                     ...              
  8/1/2013 8/12/2013 -> 38.22   32.87    
           8/13/2013 -> 37.02   32.23    
           8/14/2013 -> 36.65   32.35    

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: 
monthly.Rows.[DateTime(2013,5,1), *] |> Stats.mean
val it : Series<string,float> =
  FbOpen    -> 26.14 
  FbClose   -> 26.35 
  FbDiff    -> 0.20 
  MsftOpen  -> 33.95 
  MsftClose -> 33.76 
  MsftDiff  -> -0.19 

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: 
monthly 
|> Frame.getNumericCols
|> Series.mapValues (Stats.levelMean fst)
|> Frame.ofColumns

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 System
namespace Deedle
Multiple items
namespace FSharp

--------------------
namespace Microsoft.FSharp
namespace FSharp.Charting
val dates : DateTime list
Multiple items
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)
val values : float list
val first : Series<DateTime,float>
Multiple items
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>
static member Series.ofObservations : observations:seq<'c * 'd> -> Series<'c,'d> (requires equality)
val series : observations:seq<'a * 'b> -> Series<'a,'b> (requires equality)
static member Series.ofValues : values:seq<'a> -> Series<int,'a>
val dateRange : first:DateTime -> count:int -> seq<DateTime>


 Generate date range from 'first' with 'count' days
val first : DateTime
val count : int
seq { for i in 0 .. (count - 1) -> first.AddDays(float i) }
val rand : count:int -> seq<float>


 Generate 'count' number of random doubles
let rnd = System.Random()
  seq { for i in 0 .. (count - 1) -> rnd.NextDouble() }
val second : Series<DateTime,float>
val df1 : Frame<DateTime,string>
Multiple items
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>
val df2 : Frame<DateTime,string>
static member Frame.ofColumns : cols:Series<'C,#ISeries<'R>> -> Frame<'R,'C> (requires equality and equality)
static member Frame.ofColumns : cols:seq<'C * #ISeries<'R>> -> Frame<'R,'C> (requires equality and equality)
val df3 : Frame<string,DateTime>
static member Frame.ofRows : rows:seq<'R * #ISeries<'C>> -> Frame<'R,'C> (requires equality and equality)
static member Frame.ofRows : rows:Series<'R,#ISeries<'C>> -> Frame<'R,'C> (requires equality and equality)
val df4 : Frame<string,string>
static member Frame.ofValues : values:seq<'R * 'C * 'V> -> Frame<'R,'C> (requires equality and equality)
type Price =
  {Day: DateTime;
   Open: float;}
Price.Day: DateTime
Price.Open: float
Multiple items
val float : value:'T -> float (requires member op_Explicit)

--------------------
type float = Double

--------------------
type float<'Measure> = float
val prices : Price list
property DateTime.Now: DateTime
DateTime.AddDays(value: float) : DateTime
val df5 : Frame<int,string>
static member Frame.ofRecords : series:Series<'K,'R> -> Frame<'K,string> (requires equality)
static member Frame.ofRecords : values:seq<'T> -> Frame<int,string>
static member Frame.ofRecords : values:Collections.IEnumerable * indexCol:string -> Frame<'R,string> (requires equality)
val msftCsv : Frame<int,string>
static member Frame.ReadCsv : path:string * ?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: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)
val fbCsv : Frame<int,string>
val msftOrd : Frame<DateTime,string>
val indexRowsDate : column:'C -> frame:Frame<'R1,'C> -> Frame<DateTime,'C> (requires equality and equality)
val sortRowsByKey : frame:Frame<'R,'C> -> Frame<'R,'C> (requires equality and equality)
val msft : Frame<DateTime,string>
property Frame.Columns: ColumnSeries<DateTime,string>
val fb : Frame<DateTime,string>
val sliceCols : columns:seq<'C> -> frame:Frame<'R,'C> -> Frame<'R,'C> (requires equality and equality)
type Chart =
  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.Combine : charts:seq<ChartTypes.GenericChart> -> ChartTypes.GenericChart
static member Chart.Line : data:seq<#value> * ?Name:string * ?Title:string * ?Labels:#seq<string> * ?Color:Drawing.Color * ?XTitle:string * ?YTitle:string -> ChartTypes.GenericChart
static member Chart.Line : data:seq<#key * #value> * ?Name:string * ?Title:string * ?Labels:#seq<string> * ?Color:Drawing.Color * ?XTitle:string * ?YTitle:string -> ChartTypes.GenericChart
val observations : series:Series<'K,'T> -> seq<'K * 'T> (requires equality)
val msftNames : string list
val msftRen : Frame<DateTime,string>
val indexColsWith : keys:seq<'C2> -> frame:Frame<'R,'C1> -> Frame<'R,'C2> (requires equality and equality and equality)
val fbNames : string list
val fbRen : Frame<DateTime,string>
val joinedOut : Frame<DateTime,string>
member Frame.Join : otherFrame:Frame<'TRowKey,'TColumnKey> -> Frame<'TRowKey,'TColumnKey>
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>
type JoinKind =
  | Outer = 0
  | Inner = 1
  | Left = 2
  | Right = 3
JoinKind.Outer: JoinKind = 0
val joinedIn : Frame<DateTime,string>
JoinKind.Inner: JoinKind = 1
static member Chart.Rows : charts:seq<ChartTypes.GenericChart> -> ChartTypes.GenericChart
property Frame.Rows: RowSeries<DateTime,string>
val janDates : DateTime list
val d : int
val jan234 : Frame<DateTime,string>
type Stats =
  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 : frame:Frame<'R,'C> -> Series<'C,float> (requires equality and equality)
static member Stats.mean : series:Series<'K,'V> -> float (requires equality)
val jan : Frame<DateTime,string>
val daysSeries : Series<DateTime,float>
property DateTime.Today: DateTime
val obsSeries : Series<DateTime,float>
val nan : float
member Series.Get : key:'K -> 'V
member Series.Get : key:'K * lookup:Lookup -> 'V
type Lookup =
  | Exact = 1
  | ExactOrGreater = 3
  | ExactOrSmaller = 5
  | Greater = 2
  | Smaller = 4
Lookup.ExactOrSmaller: Lookup = 5
val daysFrame : Frame<DateTime,int>
val obsFrame : Frame<DateTime,int>
val obsDaysExact : Frame<DateTime,int>
JoinKind.Left: JoinKind = 2
val obsDaysPrev : Frame<DateTime,int>
val joinAlign : kind:JoinKind -> lookup:Lookup -> frame1:Frame<'R,'C> -> frame2:Frame<'R,'C> -> Frame<'R,'C> (requires equality and equality)
val obsDaysNext : Frame<DateTime,int>
Lookup.ExactOrGreater: Lookup = 3
type Comparison<'T> =
  delegate of 'T * 'T -> int
val mapRowValues : f:(ObjectSeries<'C> -> 'V) -> frame:Frame<'R,'C> -> Series<'R,'V> (requires equality and equality)
val row : ObjectSeries<string>
member Frame.GetColumn : column:'TColumnKey -> Series<'TRowKey,'R>
member Frame.GetColumn : column:'TColumnKey * lookup:Lookup -> Series<'TRowKey,'R>
Multiple items
val string : value:'T -> string

--------------------
type string = String
val filterValues : f:('T -> bool) -> series:Series<'K,'T> -> Series<'K,'T> (requires equality)
val countValues : series:Series<'K,'T> -> int (requires equality)
val joinedOpens : Frame<DateTime,string>
property Frame.RowsDense: RowSeries<DateTime,string>
val monthly : Frame<(DateTime * DateTime),string>
val groupRowsUsing : selector:('R -> ObjectSeries<'C> -> 'K) -> frame:Frame<'R,'C> -> Frame<('K * 'R),'C> (requires equality and equality and equality)
val k : DateTime
property DateTime.Year: int
property DateTime.Month: int
property Frame.Rows: RowSeries<(DateTime * DateTime),string>
val getNumericCols : frame:Frame<'R,'C> -> Series<'C,Series<'R,float>> (requires equality and equality)
val mapValues : f:('T -> 'R) -> series:Series<'K,'T> -> Series<'K,'R> (requires equality)
static member Stats.levelMean : level:('K -> 'L) -> series:Series<'K,'V> -> Series<'L,float> (requires equality and equality)
val fst : tuple:('T1 * 'T2) -> 'T1
Fork me on GitHub