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

.44

.6

.44

.49

.08

...

.39

.32

.55

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

.4366

1/2/2013 12:00:00 AM

N/A

.5981

1/3/2013 12:00:00 AM

N/A

.4351

1/4/2013 12:00:00 AM

20

.4865

1/5/2013 12:00:00 AM

N/A

.08

1/6/2013 12:00:00 AM

N/A

.3986

1/7/2013 12:00:00 AM

N/A

.4996

1/8/2013 12:00:00 AM

30

.386

1/9/2013 12:00:00 AM

N/A

.3239

1/10/2013 12:00:00 AM

N/A

.553

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 can 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

2013-11-07

49.24

49.87

...

47.56

96925500

47.56

1

2013-11-06

50.26

50.45

...

49.12

67648600

49.12

2

2013-11-05

47.79

50.18

...

50.11

76668300

50.11

3

2013-11-04

49.37

49.75

...

48.22

80206200

48.22

4

2013-11-01

50.85

52.09

...

49.75

94822200

49.75

5

2013-10-31

47.16

52.00

...

50.21

248388200

50.21

6

2013-10-30

50.00

50.21

...

49.01

116674400

49.01

7

2013-10-29

50.73

50.79

...

49.40

101859700

49.40

...

...

...

...

...

...

...

...

367

2012-05-23

31.37

32.50

...

32.00

73600000

32.00

368

2012-05-22

32.61

33.59

...

31.00

101786600

31.00

369

2012-05-21

36.53

36.66

...

34.03

168192700

34.03

370

2012-05-18

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

7/14/2015 12:00:00 AM

7/15/2015 12:00:00 AM

7/16/2015 12:00:00 AM

7/17/2015 12:00:00 AM

7/18/2015 12:00:00 AM

...

7/21/2015 12:00:00 AM

7/22/2015 12:00:00 AM

7/23/2015 12:00:00 AM

Values

.79

.21

.45

.67

.48

...

.02

.06

.58

Keys

7/14/2015 1:38:48 PM

7/15/2015 1:38:48 PM

7/16/2015 1:38:48 PM

7/17/2015 1:38:48 PM

7/18/2015 1:38:48 PM

...

7/21/2015 1:38:48 PM

7/22/2015 1:38:48 PM

7/23/2015 1:38:48 PM

Values

.79

.21

.45

.67

.48

...

.02

.06

.58

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
namespace FSharp
namespace FSharp.Charting
val dates : DateTime list

Full name: Tutorial.dates
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

Full name: System.DateTime

--------------------
DateTime()
   (+0 other overloads)
DateTime(ticks: int64) : unit
   (+0 other overloads)
DateTime(ticks: int64, kind: DateTimeKind) : unit
   (+0 other overloads)
DateTime(year: int, month: int, day: int) : unit
   (+0 other overloads)
DateTime(year: int, month: int, day: int, calendar: Globalization.Calendar) : unit
   (+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int) : unit
   (+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, kind: DateTimeKind) : unit
   (+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, calendar: Globalization.Calendar) : unit
   (+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int) : unit
   (+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int, kind: DateTimeKind) : unit
   (+0 other overloads)
val values : float list

Full name: Tutorial.values
val first : Series<DateTime,float>

Full name: Tutorial.first
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<'a0 * 'a1> -> Series<'a0,'a1> (requires equality)
  static member ofOptionalObservations : observations:seq<'K * 'a1 option> -> Series<'K,'a1> (requires equality)
  static member ofValues : values:seq<'a0> -> Series<int,'a0>

Full name: Deedle.F# Series extensions.Series

--------------------
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>>
  ...

Full name: Deedle.Series<_,_>

--------------------
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<'a0 * 'a1> -> Series<'a0,'a1> (requires equality)
val series : observations:seq<'a * 'b> -> Series<'a,'b> (requires equality)

Full name: Deedle.F# Series extensions.series
static member Series.ofValues : values:seq<'a0> -> Series<int,'a0>
val dateRange : first:DateTime -> count:int -> seq<DateTime>

Full name: Tutorial.dateRange


 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>

Full name: Tutorial.rand


 Generate 'count' number of random doubles
let rnd = System.Random()
  seq { for i in 0 .. (count - 1) -> rnd.NextDouble() }
val second : Series<DateTime,float>

Full name: Tutorial.second
val df1 : Frame<DateTime,string>

Full name: Tutorial.df1
Multiple items
module Frame

from Deedle

--------------------
type Frame =
  static member CreateEmpty : unit -> Frame<'R,'C> (requires equality and equality)
  static member FromArray2D : array:'T [,] -> Frame<int,int>
  static member FromColumns : cols:Series<'TColKey,Series<'TRowKey,'V>> -> Frame<'TRowKey,'TColKey> (requires equality and equality)
  static member FromColumns : cols:Series<'TColKey,ObjectSeries<'TRowKey>> -> Frame<'TRowKey,'TColKey> (requires equality and equality)
  static member FromColumns : columns:seq<KeyValuePair<'ColKey,ObjectSeries<'RowKey>>> -> Frame<'RowKey,'ColKey> (requires equality and equality)
  static member FromColumns : columns:seq<KeyValuePair<'ColKey,Series<'RowKey,'V>>> -> Frame<'RowKey,'ColKey> (requires equality and equality)
  static member FromColumns : cols:seq<Series<'ColKey,'V>> -> Frame<'ColKey,int> (requires equality)
  static member FromRecords : values:seq<'T> -> Frame<int,string>
  static member FromRecords : series:Series<'K,'R> -> Frame<'K,string> (requires equality)
  static member FromRowKeys : keys:seq<'K> -> Frame<'K,string> (requires equality)
  ...

Full name: Deedle.Frame

--------------------
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
  ...

Full name: Deedle.Frame<_,_>

--------------------
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>

Full name: Tutorial.df2
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>

Full name: Tutorial.df3
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>

Full name: Tutorial.df4
static member Frame.ofValues : values:seq<'R * 'C * 'V> -> Frame<'R,'C> (requires equality and equality)
type Price =
  {Day: DateTime;
   Open: float;}

Full name: Tutorial.Price
Price.Day: DateTime
Price.Open: float
Multiple items
val float : value:'T -> float (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.float

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

Full name: Microsoft.FSharp.Core.float

--------------------
type float<'Measure> = float

Full name: Microsoft.FSharp.Core.float<_>
val prices : Price list

Full name: Tutorial.prices
property DateTime.Now: DateTime
DateTime.AddDays(value: float) : DateTime
val df5 : Frame<int,string>

Full name: Tutorial.df5
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>

Full name: Tutorial.msftCsv
static member Frame.ReadCsv : path:string * ?hasHeaders:bool * ?inferTypes:bool * ?inferRows:int * ?schema:string * ?separators:string * ?culture:string * ?maxRows:int * ?missingValues:string [] -> 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 [] -> 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 [] -> 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 [] -> Frame<'R,string> (requires equality)
val fbCsv : Frame<int,string>

Full name: Tutorial.fbCsv
val msftOrd : Frame<DateTime,string>

Full name: Tutorial.msftOrd
val indexRowsDate : column:'C -> frame:Frame<'R1,'C> -> Frame<DateTime,'C> (requires equality and equality)

Full name: Deedle.Frame.indexRowsDate
val sortRowsByKey : frame:Frame<'R,'C> -> Frame<'R,'C> (requires equality and equality)

Full name: Deedle.Frame.sortRowsByKey
val msft : Frame<DateTime,string>

Full name: Tutorial.msft
property Frame.Columns: ColumnSeries<DateTime,string>
val fb : Frame<DateTime,string>

Full name: Tutorial.fb
val sliceCols : columns:seq<'C> -> frame:Frame<'R,'C> -> Frame<'R,'C> (requires equality and equality)

Full name: Deedle.Frame.sliceCols
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
  ...

Full name: FSharp.Charting.Chart
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)

Full name: Deedle.Series.observations
val msftNames : string list

Full name: Tutorial.msftNames
val msftRen : Frame<DateTime,string>

Full name: Tutorial.msftRen
val indexColsWith : keys:seq<'C2> -> frame:Frame<'R,'C1> -> Frame<'R,'C2> (requires equality and equality and equality)

Full name: Deedle.Frame.indexColsWith
val fbNames : string list

Full name: Tutorial.fbNames
val fbRen : Frame<DateTime,string>

Full name: Tutorial.fbRen
val joinedOut : Frame<DateTime,string>

Full name: Tutorial.joinedOut
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

Full name: Deedle.JoinKind
JoinKind.Outer: JoinKind = 0
val joinedIn : Frame<DateTime,string>

Full name: Tutorial.joinedIn
JoinKind.Inner: JoinKind = 1
static member Chart.Rows : charts:seq<ChartTypes.GenericChart> -> ChartTypes.GenericChart
property Frame.Rows: RowSeries<DateTime,string>
val janDates : DateTime list

Full name: Tutorial.janDates
val d : int
val jan234 : Frame<DateTime,string>

Full name: Tutorial.jan234
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 expandingCount : series:Series<'K,float> -> Series<'K,float> (requires equality)
  static member expandingKurt : series:Series<'K,float> -> Series<'K,float> (requires equality)
  static member expandingMax : series:Series<'K,float> -> Series<'K,float> (requires equality)
  static member expandingMean : series:Series<'K,float> -> Series<'K,float> (requires equality)
  static member expandingMin : series:Series<'K,float> -> Series<'K,float> (requires equality)
  static member expandingSkew : series:Series<'K,float> -> Series<'K,float> (requires equality)
  static member expandingStdDev : series:Series<'K,float> -> Series<'K,float> (requires equality)
  static member expandingSum : series:Series<'K,float> -> Series<'K,float> (requires equality)
  ...

Full name: Deedle.Stats
static member Stats.mean : frame:Frame<'R,'C> -> Series<'C,float> (requires equality and equality)
static member Stats.mean : series:Series<'K,float> -> float (requires equality)
val jan : Frame<DateTime,string>

Full name: Tutorial.jan
val daysSeries : Series<DateTime,float>

Full name: Tutorial.daysSeries
property DateTime.Today: DateTime
val obsSeries : Series<DateTime,float>

Full name: Tutorial.obsSeries
val nan : float

Full name: Microsoft.FSharp.Core.Operators.nan
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

Full name: Deedle.Lookup
Lookup.ExactOrSmaller: Lookup = 5
val daysFrame : Frame<DateTime,int>

Full name: Tutorial.daysFrame
val obsFrame : Frame<DateTime,int>

Full name: Tutorial.obsFrame
val obsDaysExact : Frame<DateTime,int>

Full name: Tutorial.obsDaysExact
JoinKind.Left: JoinKind = 2
val obsDaysPrev : Frame<DateTime,int>

Full name: Tutorial.obsDaysPrev
val joinAlign : kind:JoinKind -> lookup:Lookup -> frame1:Frame<'R,'C> -> frame2:Frame<'R,'C> -> Frame<'R,'C> (requires equality and equality)

Full name: Deedle.Frame.joinAlign
val obsDaysNext : Frame<DateTime,int>

Full name: Tutorial.obsDaysNext
Lookup.ExactOrGreater: Lookup = 3
type Comparison<'T> =
  delegate of 'T * 'T -> int

Full name: System.Comparison<_>
val mapRowValues : f:(ObjectSeries<'C> -> 'V) -> frame:Frame<'R,'C> -> Series<'R,'V> (requires equality and equality)

Full name: Deedle.Frame.mapRowValues
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

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = String

Full name: Microsoft.FSharp.Core.string
val filterValues : f:('T -> bool) -> series:Series<'K,'T> -> Series<'K,'T> (requires equality)

Full name: Deedle.Series.filterValues
val countValues : series:Series<'K,'T> -> int (requires equality)

Full name: Deedle.Series.countValues
val joinedOpens : Frame<DateTime,string>

Full name: Tutorial.joinedOpens
property Frame.RowsDense: RowSeries<DateTime,string>
val monthly : Frame<(DateTime * DateTime),string>

Full name: Tutorial.monthly
val groupRowsUsing : selector:('R -> ObjectSeries<'C> -> 'K) -> frame:Frame<'R,'C> -> Frame<('K * 'R),'C> (requires equality and equality and equality)

Full name: Deedle.Frame.groupRowsUsing
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)

Full name: Deedle.Frame.getNumericCols
val mapValues : f:('T -> 'R) -> series:Series<'K,'T> -> Series<'K,'R> (requires equality)

Full name: Deedle.Series.mapValues
static member Stats.levelMean : level:('K -> 'L) -> series:Series<'K,float> -> Series<'L,float> (requires equality and equality)
val fst : tuple:('T1 * 'T2) -> 'T1

Full name: Microsoft.FSharp.Core.Operators.fst
Fork me on GitHub