Report a bug
If you spot a problem with this page, click here to create a Github issue.
Improve this page
Quickly fork, edit online, and submit a pull request for this page. Requires a signed-in GitHub account. This works well for small changes. If you'd like to make larger changes you may want to consider using a local clone.

mir.series

Index-series

The module contains Series data structure with special iteration and indexing methods. It is aimed to construct index or time-series using Mir and Phobos algorithms.

Public imports: mir.ndslice.slice.

Authors:
Ilya Yaroshenko
Examples:
import std.datetime: Date;
import std.algorithm.mutation: move;

import mir.array.allocation: array;

import mir.algorithm.setops: multiwayUnion;
import mir.ndslice.allocation: slice;

//////////////////////////////////////
// Constructs two time-series.
//////////////////////////////////////
auto index0 = [
    Date(2017, 01, 01),
    Date(2017, 03, 01),
    Date(2017, 04, 01)];

auto data0 = [1.0, 3, 4];
auto series0 = index0.series(data0);

assert(series0.get(Date(2017, 03, 01)) == 3);

auto index1 = [
    Date(2017, 01, 01),
    Date(2017, 02, 01),
    Date(2017, 05, 01)];

auto data1 = [10.0, 20, 50];
auto series1 = index1.series(data1);

//////////////////////////////////////
// Merges multiple series into one.
// Allocates using GC. M
// Makes exactly two allocations per merge:
// one for index/time and one for data.
//////////////////////////////////////
auto m0 = unionSeries(series0, series1);
auto m1 = unionSeries(series1, series0); // order is matter

assert(m0.index == [
    Date(2017, 01, 01),
    Date(2017, 02, 01),
    Date(2017, 03, 01),
    Date(2017, 04, 01),
    Date(2017, 05, 01)]);

assert(m0.index == m1.index);
assert(m0.data == [ 1, 20,  3,  4, 50]);
assert(m1.data == [10, 20,  3,  4, 50]);

//////////////////////////////////////
// Joins two time-series into a one with two columns.
//////////////////////////////////////
auto u = [index0, index1].multiwayUnion;
auto index = u.move.array;
auto data = slice!double([index.length, 2], 0); // initialized to 0 value
auto series = index.series(data);

series[0 .. $, 0][] = series0; // fill first column
series[0 .. $, 1][] = series1; // fill second column

assert(data == [
    [1, 10],
    [0, 20],
    [3,  0],
    [4,  0],
    [0, 50]]);
struct Observation(Index, Data);
Plain index/time observation data structure. Observation are used as return tuple for for indexing Series.
Index index;
Date, date-time, time, or index.
alias time = index;
An alias for time-series index.
alias key = index;
An alias for key-value representation.
Data data;
Value or ndslice.
alias value = index;
An alias for key-value representation.
auto observation(Index, Data)(Index index, Data data);
Convenient function for Observation construction.
struct Series(IndexIterator, SliceKind kind, size_t[] packs, Iterator);
Plain index series data structure.
*.index[i] corresponds to *.data[i].
Examples:
1-dimensional data
auto index = [1, 2, 3, 4];
auto data = [2.1, 3.4, 5.6, 7.8];
auto series = index.series(data);
const cseries = series;

assert(series.contains(2));
assert( ()@trusted{ return (2 in series) is &data[1]; }() );

assert(!series.contains(5));
assert( ()@trusted{ return (5 in series) is null; }() );

assert(series.lowerBound(2) == series[0 .. 1]);
assert(series.upperBound(2) == series[2 .. $]);

assert(cseries.lowerBound(2) == cseries[0 .. 1]);
assert(cseries.upperBound(2) == cseries[2 .. $]);

// slicing type deduction for const / immutable series
static assert(is(typeof(series[]) == 
    Series!(int*, cast(SliceKind)2, [1LU], double*)));
static assert(is(typeof(cseries[]) == 
    Series!(const(int)*, cast(SliceKind)2, [1LU], const(double)*)));
static assert(is(typeof((cast(immutable) series)[]) == 
    Series!(immutable(int)*, cast(SliceKind)2, [1LU], immutable(double)*)));

/// slicing
auto seriesSlice  = series[1 .. $ - 1];
assert(seriesSlice.index == index[1 .. $ - 1]);
assert(seriesSlice.data == data[1 .. $ - 1]);
static assert(is(typeof(series) == typeof(seriesSlice)));

/// indexing
assert(series[1] == observation(2, 3.4));

/// range primitives
assert(series.length == 4);
assert(series.front == observation(1, 2.1));

series.popFront;
assert(series.front == observation(2, 3.4));

series.popBackN(10);
assert(series.empty);
Examples:
2-dimensional data
import std.datetime: Date;
import mir.ndslice.topology: canonical, iota;

size_t row_length = 5;

auto index = [
    Date(2017, 01, 01),
    Date(2017, 02, 01),
    Date(2017, 03, 01),
    Date(2017, 04, 01)];

//  1,  2,  3,  4,  5
//  6,  7,  8,  9, 10
// 11, 12, 13, 14, 15
// 16, 17, 18, 19, 20
auto data = iota([index.length, row_length], 1);

// canonical and universal ndslices are more flexible then contiguous
auto series = index.series(data.canonical);

/// slicing
auto seriesSlice  = series[1 .. $ - 1, 2 .. 4];
assert(seriesSlice.index == index[1 .. $ - 1]);
assert(seriesSlice.data == data[1 .. $ - 1, 2 .. 4]);

static if (kindOf!(typeof(series.data)) != Contiguous)
    static assert(is(typeof(series) == typeof(seriesSlice)));

/// indexing
assert(series[1, 4] == observation(Date(2017, 02, 01), 10));
assert(series[2] == observation(Date(2017, 03, 01), iota([row_length], 11)));

/// range primitives
assert(series.length == 4);
assert(series.length!1 == 5);

series.popFront!1;
assert(series.length!1 == 4);
Slice!(kind, packs, Iterator) _data;
IndexIterator _index;
alias Index = typeof(this.front.index);
alias Data = typeof(this.front.data);
this()(Slice!(Contiguous, [1], IndexIterator) index, Slice!(kind, packs, Iterator) data);
const bool opEquals(RhsIndexIterator, SliceKind rhsKind, size_t[] rhsPacks, RhsIterator)(Series!(RhsIndexIterator, rhsKind, rhsPacks, RhsIterator) rhs);
@property @trusted auto index()();

const @property @trusted auto index()();

immutable @property @trusted auto index()();
Index series is assumed to be sorted.
IndexIterator is an iterator on top of date, date-time, time, or numbers or user defined types with defined opCmp. For example, Date*, DateTime*, immutable(long)*, mir.ndslice.iterator.IotaIterator.
alias time = index;
An alias for time-series index.
alias key = index;
An alias for key-value representation.
@property @trusted auto data()();

const @property @trusted auto data()();

immutable @property @trusted auto data()();
Data is any ndslice with only one constraints, data and index lengths should be equal.
alias value = index;
An alias for key-value representation.
void opIndexAssign(IndexIterator_, SliceKind kind_, size_t[] packs_, Iterator_)(Series!(IndexIterator_, kind_, packs_, Iterator_) r);
Special [] = index-assign operator for index-series. Assigns data from r with index intersection. If a index index in r is not in the index index for this series, then no op-assign will take place. This and r series are assumed to be sorted.
Parameters:
Series!(IndexIterator_, kind_, packs_, Iterator_) r rvalue index-series
Examples:
auto index = [1, 2, 3, 4];
auto data = [10.0, 10, 10, 10];
auto series = index.series(data);

auto rindex = [0, 2, 4, 5];
auto rdata = [1.0, 2, 3, 4];
auto rseries = rindex.series(rdata);

// series[] = rseries;
series[] = rseries;
assert(series.data == [10, 2, 10, 3]);
void opIndexOpAssign(string op, IndexIterator_, SliceKind kind_, size_t[] packs_, Iterator_)(Series!(IndexIterator_, kind_, packs_, Iterator_) r);
Special [] op= index-op-assign operator for index-series. Op-assigns data from r with index intersection. If a index index in r is not in the index index for this series, then no op-assign will take place. This and r series are assumed to be sorted.
Parameters:
Series!(IndexIterator_, kind_, packs_, Iterator_) r rvalue index-series
Examples:
auto index = [1, 2, 3, 4];
auto data = [10.0, 10, 10, 10];
auto series = index.series(data);

auto rindex = [0, 2, 4, 5];
auto rdata = [1.0, 2, 3, 4];
auto rseries = rindex.series(rdata);

series[] += rseries;
assert(series.data == [10, 12, 10, 13]);
auto lowerBound(SearchPolicy sp = SearchPolicy.binarySearch, Index)(Index moment);

const auto lowerBound(SearchPolicy sp = SearchPolicy.binarySearch, Index)(Index moment);
This function uses a search with policy sp to find the largest left subrange on which t < moment is true for all t. The search schedule and its complexity are documented in std.range.SearchPolicy.
auto upperBound(SearchPolicy sp = SearchPolicy.binarySearch, Index)(Index moment);

const auto upperBound(SearchPolicy sp = SearchPolicy.binarySearch, Index)(Index moment);
This function uses a search with policy sp to find the largest left subrange on which t > moment is true for all t. The search schedule and its complexity are documented in std.range.SearchPolicy.
inout ref auto get(Index)(Index moment, lazy Exception exc = null);
Gets data for the index.
Parameters:
Index moment index
Exception exc (lazy, optional) exception to throw if the series does not contains the index.
Returns:
data that corresponds to the index.
Throws:
Exception if the series does not contains the index.
const bool contains(Index)(Index moment);
inout @trusted auto opBinaryRight(string op : "in", Index)(Index moment);
const @property bool empty(size_t dimension = 0)()
if (dimension < packs[0]);

const @property size_t length(size_t dimension = 0)()
if (dimension < packs[0]);

@property auto front(size_t dimension = 0)()
if (dimension < packs[0]);

@property auto back(size_t dimension = 0)()
if (dimension < packs[0]);

@trusted void popFront(size_t dimension = 0)()
if (dimension < packs[0]);

void popBack(size_t dimension = 0)()
if (dimension < packs[0]);

@trusted void popFrontExactly(size_t dimension = 0)(size_t n)
if (dimension < packs[0]);

void popBackExactly(size_t dimension = 0)(size_t n)
if (dimension < packs[0]);

void popFrontN(size_t dimension = 0)(size_t n)
if (dimension < packs[0]);

void popBackN(size_t dimension = 0)(size_t n)
if (dimension < packs[0]);

const _Slice!() opSlice(size_t dimension)(size_t i, size_t j)
if (dimension < packs[0]);

const size_t opDollar(size_t dimension = 0)();

auto opIndex(Slices...)(Slices slices)
if (allSatisfy!(templateOr!(is_Slice, isIndex), Slices));

const auto opIndex(Slices...)(Slices slices)
if (allSatisfy!(templateOr!(is_Slice, isIndex), Slices));

immutable auto opIndex(Slices...)(Slices slices)
if (allSatisfy!(templateOr!(is_Slice, isIndex), Slices));

@property auto save()();
ndslice-like primitives
auto series(IndexIterator, SliceKind kind, size_t[] packs, Iterator)(Slice!(Contiguous, [1], IndexIterator) index, Slice!(kind, packs, Iterator) data);

auto series(Index, Data)(Index[] index, Data[] data);

auto series(IndexIterator, Data)(Slice!(Contiguous, [1], IndexIterator) index, Data[] data);

auto series(Index, SliceKind kind, size_t[] packs, Iterator)(Index[] index, Slice!(kind, packs, Iterator) data);
Convenient function for Series construction.
Series!(K*, Contiguous, [1], V*) series(K, V)(V[K] aa)
if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init)));

Series!(const(K)*, Contiguous, [1], const(V)*) series(K, V)(const V[K] aa)
if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init)));

Series!(immutable(K)*, Contiguous, [1], immutable(V)*) series(K, V)(immutable V[K] aa)
if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init)));

auto series(K, V)(V[K]* aa)
if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init)));
Constructs a GC-allocated series from an associative array. Performs exactly two allocations.
Parameters:
V[K] aa = associative array or a pointer to associative array
Returns:
sorted GC-allocated series.
Examples:
auto s = [1: 1.5, 3: 3.3, 2: 2.9].series;
assert(s.index == [1, 2, 3]);
assert(s.data == [1.5, 2.9, 3.3]);
assert(s.data[s.findIndex(2)] == 2.9);
Series!(K*, Contiguous, [1], V*) makeSeries(Allocator, K, V)(auto ref Allocator allocator, V[K] aa)
if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init)));

Series!(K*, Contiguous, [1], V*) makeSeries(Allocator, K, V)(auto ref Allocator allocator, V[K]* aa)
if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init)));
Constructs a manually allocated series from an associative array. Performs exactly two allocations.
Parameters:
V[K] aa = associative array or a pointer to associative array
Returns:
sorted manually allocated series.
Examples:
import std.experimental.allocator;
import std.experimental.allocator.building_blocks.region;

InSituRegion!(1024) allocator;
auto aa = [1: 1.5, 3: 3.3, 2: 2.9];

auto s = (double[int] aa) @nogc @trusted pure nothrow {
    return allocator.makeSeries(aa);
}(aa);

auto indexArray = s.index.field;
auto dataArray = s.data.field;

assert(s.index == [1, 2, 3]);
assert(s.data == [1.5, 2.9, 3.3]);
assert(s.data[s.findIndex(2)] == 2.9);

allocator.dispose(indexArray);
allocator.dispose(dataArray);
enum auto isSeries(U : Series!(IndexIterator, kind, packs, Iterator), IndexIterator, SliceKind kind, size_t[] packs, Iterator);

enum auto isSeries(U);
Returns:
packs if U is a Series type or null otherwise;
size_t findIndex(IndexIterator, SliceKind kind, size_t[] packs, Iterator, Index)(Series!(IndexIterator, kind, packs, Iterator) series, Index moment);
Finds an index such that series.index[index] == moment.
Parameters:
Series!(IndexIterator, kind, packs, Iterator) series series
Index moment index to find in the series
Returns:
size_t.max if the series does not contain the moment and appropriate index otherwise.
Examples:
auto index = [1, 2, 3, 4].sliced;
auto data = [2.1, 3.4, 5.6, 7.8].sliced;
auto series = index.series(data);

assert(series.data[series.findIndex(3)] == 5.6);
assert(series.findIndex(0) == size_t.max);
size_t find(IndexIterator, SliceKind kind, size_t[] packs, Iterator, Index)(Series!(IndexIterator, kind, packs, Iterator) series, Index moment);
Finds a backward index such that series.index[$ - backward_index] == moment.
Parameters:
Series!(IndexIterator, kind, packs, Iterator) series series
Index moment index moment to find in the series
Returns:
0 if the series does not contain the moment and appropriate backward index otherwise.
Examples:
auto index = [1, 2, 3, 4].sliced;
auto data = [2.1, 3.4, 5.6, 7.8].sliced;
auto series = index.series(data);

if (auto bi = series.find(3))
{
    assert(series.data[$ - bi] == 5.6);
}
else
{
    assert(0);
}

assert(series.find(0) == 0);
template sort(alias less = "a < b")
Sorts index-series according to the less predicate applied to index observations.
The function works only for 1-dimensional index-series data.
Examples:
1D data
auto index = [1, 2, 4, 3].sliced;
auto data = [2.1, 3.4, 5.6, 7.8].sliced;
auto series = index.series(data);
series.sort;
assert(series.index == [1, 2, 3, 4]);
assert(series.data == [2.1, 3.4, 7.8, 5.6]);
/// initial index and data are the same
assert(index.iterator is series.index.iterator);
assert(data.iterator is series.data.iterator);

foreach(obs; series)
{
    static assert(is(typeof(obs) == Observation!(int, double)));
}
Examples:
2D data
import mir.series;
import mir.ndslice.allocation: uninitSlice;

auto index = [4, 2, 3, 1].sliced;
auto data =
    [2.1, 3.4, 
     5.6, 7.8,
     3.9, 9.0,
     4.0, 2.0].sliced(4, 2);
auto series = index.series(data);

series.sort(
    uninitSlice!size_t(series.length), // index buffer
    uninitSlice!double(series.length), // data buffer
    );

assert(series.index == [1, 2, 3, 4]);
assert(series.data ==
    [[4.0, 2.0],
     [5.6, 7.8],
     [3.9, 9.0],
     [2.1, 3.4]]);
/// initial index and data are the same
assert(index.iterator is series.index.iterator);
assert(data.iterator is series.data.iterator);
Series!(IndexIterator, kind, packs, Iterator) sort(IndexIterator, SliceKind kind, size_t[] packs, Iterator)(Series!(IndexIterator, kind, packs, Iterator) series)
if (packs == [1]);
One dimensional case.
Series!(IndexIterator, kind, packs, Iterator) sort(IndexIterator, SliceKind kind, size_t[] packs, Iterator, SliceKind sortIndexKind, SortIndexIterator, SliceKind dataKind, DataIterator)(Series!(IndexIterator, kind, packs, Iterator) series, Slice!(sortIndexKind, [1], SortIndexIterator) indexBuffer, Slice!(dataKind, [1], DataIterator) dataBuffer)
if (packs.length == 1);
N-dimensional case. Requires index and data buffers.
auto unionSeries(IndexIterator, SliceKind kind, size_t[] packs, Iterator, size_t N)(Series!(IndexIterator, kind, packs, Iterator)[N] seriesTuple...)
if (N > 1 && packs.length == 1);
Merges multiple (time) series into one. Makes exactly two memory allocations.
Parameters:
Series!(IndexIterator, kind, packs, Iterator)[N] seriesTuple variadic static array of composed of series.
Returns:
sorted GC-allocated series. See_also makeUnionSeries
Examples:
import std.datetime: Date;

//////////////////////////////////////
// Constructs two time-series.
//////////////////////////////////////
auto index0 = [1,3,4];

auto data0 = [1.0, 3, 4];
auto series0 = index0.series(data0);

auto index1 = [1,2,5];

auto data1 = [10.0, 20, 50];
auto series1 = index1.series(data1);

//////////////////////////////////////
// Merges multiple series into one.
//////////////////////////////////////
auto m0 = unionSeries(series0, series1);
auto m1 = unionSeries(series1, series0); // order is matter

assert(m0.index == m1.index);
assert(m0.data == [ 1, 20,  3,  4, 50]);
assert(m1.data == [10, 20,  3,  4, 50]);
auto makeUnionSeries(IndexIterator, SliceKind kind, size_t[] packs, Iterator, size_t N, Allocator)(auto ref Allocator allocator, Series!(IndexIterator, kind, packs, Iterator)[N] seriesTuple...)
if (N > 1 && packs.length == 1);
Merges multiple (time) series into one. Makes exactly two memory allocations.
Parameters:
Allocator allocator memory allocator
Series!(IndexIterator, kind, packs, Iterator)[N] seriesTuple variadic static array of composed of series.
Returns:
sorted manually allocated series. See_also unionSeries
Examples:
import std.datetime: Date;
import std.experimental.allocator;
import std.experimental.allocator.building_blocks.region;

//////////////////////////////////////
// Constructs two time-series.
//////////////////////////////////////
auto index0 = [1,3,4];

auto data0 = [1.0, 3, 4];
auto series0 = index0.series(data0);

auto index1 = [1,2,5];

auto data1 = [10.0, 20, 50];
auto series1 = index1.series(data1);

//////////////////////////////////////
// Merges multiple series into one.
//////////////////////////////////////

InSituRegion!(1024) allocator;

auto m0 = allocator.makeUnionSeries(series0, series1);
auto m1 = allocator.makeUnionSeries(series1, series0); // order is matter

assert(m0.index == m1.index);
assert(m0.data == [ 1, 20,  3,  4, 50]);
assert(m1.data == [10, 20,  3,  4, 50]);

/// series should have the same sizes as after allocation
allocator.dispose(m0.index.field);
allocator.dispose(m0.data.field);
allocator.dispose(m1.index.field);
allocator.dispose(m1.data.field);
auto unionSeriesImpl(IndexIterator, SliceKind kind, size_t[] packs, Iterator, I, E)(Series!(IndexIterator, kind, packs, Iterator)[] seriesTuple, Series!(I*, Contiguous, packs, E*) uninitSeries)
if (packs.length == 1);
Initialize preallocated series using union of multiple (time) series. Doesn't make any allocations.
Parameters:
Series!(IndexIterator, kind, packs, Iterator)[] seriesTuple dynamic array composed of series.
Series!(I*, Contiguous, packs, E*) uninitSeries uninitialized series with exactly required length.
@property ref V[K] insertOrAssign(V, K, IndexIterator, SliceKind kind, size_t[] packs, Iterator)(return ref V[K] aa, Series!(IndexIterator, kind, packs, Iterator) series);
Inserts or assigns a series to the associative array aa.
Parameters:
V[K] aa associative array
Series!(IndexIterator, kind, packs, Iterator) series series
Returns:
associative array
Examples:
auto a = [1: 3.0, 4: 2.0];
auto s = series([1, 2, 3], [10, 20, 30]);
a.insertOrAssign = s;
assert(a.series == series([1, 2, 3, 4], [10.0, 20, 30, 2]));
@property ref V[K] insert(V, K, IndexIterator, SliceKind kind, size_t[] packs, Iterator)(return ref V[K] aa, Series!(IndexIterator, kind, packs, Iterator) series);
Inserts a series to the associative array aa.
Parameters:
V[K] aa associative array
Series!(IndexIterator, kind, packs, Iterator) series series
Returns:
associative array
Examples:
auto a = [1: 3.0, 4: 2.0];
auto s = series([1, 2, 3], [10, 20, 30]);
a.insert = s;
assert(a.series == series([1, 2, 3, 4], [3.0, 20, 30, 2]));