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:
See_also: sort, unionSeries, troykaSeries, troykaGalop.
import mir.ndslice;
import mir.series;

import mir.array.allocation: array;
import mir.algorithm.setops: multiwayUnion;

import std.datetime: Date;
import std.algorithm.mutation: move;
import std.exception: collectExceptionMsg;

//////////////////////////////////////
// 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);

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

//////////////////////////////////////
// asSlice method
//////////////////////////////////////
assert(series0
    .asSlice
    // ref qualifier is optional
    .map!((ref key, ref value) => key.month == value)
    .all);

//////////////////////////////////////
// get* methods
//////////////////////////////////////

auto refDate = Date(2017, 03, 01);
auto missingDate = Date(2016, 03, 01);

// default value
double defaultValue = 100;
assert(series0.get(refDate, defaultValue) == 3);
assert(series0.get(missingDate, defaultValue) == defaultValue);

// Exceptions handlers
assert(series0.get(refDate) == 3);
assert(series0.get(refDate, new Exception("My exception msg")) == 3);
assert(series0.getVerbose(refDate) == 3);    
assert(series0.getExtraVerbose(refDate, "My exception msg") == 3);    

assert(collectExceptionMsg!Exception(
        series0.get(missingDate)
    ) == "Series double[Date]: Missing required key");

assert(collectExceptionMsg!Exception(
        series0.get(missingDate, new Exception("My exception msg"))
    ) == "My exception msg");

assert(collectExceptionMsg!Exception(
        series0.getVerbose(missingDate)
    ) == "Series double[Date]: Missing 2016-Mar-01 key");

assert(collectExceptionMsg!Exception(
        series0.getExtraVerbose(missingDate, "My exception msg")
    ) == "My exception msg. Series double[Date]: Missing 2016-Mar-01 key");

// assign with get*
series0.get(refDate) = 100; 
assert(series0.get(refDate) == 100); 
series0.get(refDate) = 3; 

// tryGet
double val;
assert(series0.tryGet(refDate, val));
assert(val == 3);
assert(!series0.tryGet(missingDate, val));
assert(val == 3); // val was not changed

//////////////////////////////////////
// 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 = data;
An alias for key-value representation.
auto observation(Index, Data)(Index index, Data data);
Convenient function for Observation construction.
template SeriesMap(K, V)
Convinient alias for 1D Contiguous Series.
Examples:
import std.traits;
import mir.series;

static assert (is(SeriesMap!(string, double) == Series!(string*, Contiguous, [1], double*)));

/// LHS, RHS
static assert (isAssignable!(SeriesMap!(string, double), typeof(null)));

static assert (isAssignable!(SeriesMap!(const string, double), SeriesMap!(string, double)));
static assert (isAssignable!(SeriesMap!(string, const double), SeriesMap!(string, double)));
static assert (isAssignable!(SeriesMap!(const string, const double), SeriesMap!(string, double)));

static assert (isAssignable!(SeriesMap!(immutable string, double), SeriesMap!(immutable string, double)));
static assert (isAssignable!(SeriesMap!(immutable string, const double), SeriesMap!(immutable string, double)));
static assert (isAssignable!(SeriesMap!(const string, const double), SeriesMap!(immutable string, double)));
static assert (isAssignable!(SeriesMap!(string, immutable double), SeriesMap!(string, immutable double)));
static assert (isAssignable!(SeriesMap!(const string, immutable double), SeriesMap!(string, immutable double)));
static assert (isAssignable!(SeriesMap!(const string, const double), SeriesMap!(string, immutable double)));
// etc
struct Series(IndexIterator, SliceKind kind, size_t[] packs, Iterator);
Plain index series data structure.
*.index[i]/*.key[i]/*.time corresponds to *.data[i]/*.value.
Index is assumed to be sorted. sort can be used to normalise a series.
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!int([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!int([row_length], 11)));

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

series.popFront!1;
assert(series.length!1 == 4);
Examples:
Construct from null
import mir.series;
alias Map = Series!(string*, Contiguous, [1], double*);
Map a = null;
auto b = Map(null);
assert(a.empty);
assert(b.empty);

auto fun(Map a = null)
{
    
}
Slice!(kind, packs, Iterator) _data;
IndexIterator _index;
alias Index = typeof(this.front.index);

alias Key = Index;

alias Time = Index;
Index / Key / Time type aliases
alias Data = typeof(this.front.data);

alias Value = Data;
Data / Value type aliases
alias time = index;
An alias for time-series index.
alias key = index;
An alias for key-value representation.
alias value = data;
An alias for key-value representation.
this()(Slice!(Contiguous, [1], IndexIterator) index, Slice!(kind, packs, Iterator) data);
this()(typeof(null));
Construct from null
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.
@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.
typeof(this) opBinary(string op : "~")(typeof(this) rhs);

const auto opBinary(string op : "~")(const typeof(this) rhs);
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.
//////////////////////////////////////
// Order is matter.
// The first slice has higher priority.
auto m0 = series0 ~ series1;
auto m1 = series1 ~ series0;

assert(m0.index == m1.index);
assert(m0.data == [ 1, 20,  3,  4, 50]);
assert(m1.data == [10, 20,  3,  4, 50]);
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.
ref auto get(Index, Value)(Index moment, return ref Value _default)
if (!is(Value : const(Exception)));

const ref auto get(Index, Value)(Index moment, return ref Value _default)
if (!is(Value : const(Exception)));

immutable ref auto get(Index, Value)(Index moment, return ref Value _default)
if (!is(Value : const(Exception)));

const auto get(Index, Value)(Index moment, Value _default)
if (!is(Value : const(Exception)));

immutable auto get(Index, Value)(Index moment, Value _default)
if (!is(Value : const(Exception)));
Gets data for the index.
Parameters:
Index moment index
Value _default default value is returned if the series does not contains the index.
Returns:
data that corresponds to the index or default value.
ref auto get(Index)(Index moment);

ref auto get(Index)(Index moment, lazy const Exception exc);

const ref auto get(Index)(Index moment);

const ref auto get(Index)(Index moment, lazy const Exception exc);

immutable ref auto get(Index)(Index moment);

immutable ref auto get(Index)(Index moment, lazy const Exception exc);
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.
ref auto getVerbose(Index)(Index moment, string file = __FILE__, int line = __LINE__);

const ref auto getVerbose(Index)(Index moment, string file = __FILE__, int line = __LINE__);

immutable ref auto getVerbose(Index)(Index moment, string file = __FILE__, int line = __LINE__);
Gets data for the index (verbose exception).
Parameters:
Index moment index
Returns:
data that corresponds to the index.
Throws:
Detailed exception if the series does not contains the index.
ref auto getExtraVerbose(Index)(Index moment, string exceptionInto, string file = __FILE__, int line = __LINE__);

const ref auto getExtraVerbose(Index)(Index moment, string exceptionInto, string file = __FILE__, int line = __LINE__);

immutable ref auto getExtraVerbose(Index)(Index moment, string exceptionInto, string file = __FILE__, int line = __LINE__);
Gets data for the index (extra verbose exception).
Parameters:
Index moment index
Returns:
data that corresponds to the index.
Throws:
Detailed exception if the series does not contains the index.
const bool contains(Index)(Index moment);
auto opBinaryRight(string op : "in", Index)(Index moment);

const auto opBinaryRight(string op : "in", Index)(Index moment);

immutable auto opBinaryRight(string op : "in", Index)(Index moment);
bool tryGet(Index, Value)(Index moment, ref Value val);

const bool tryGet(Index, Value)(Index moment, ref Value val);

immutable bool tryGet(Index, Value)(Index moment, ref Value val);
@property auto asSlice()();

const @property auto asSlice()();

immutable @property auto asSlice()();
Returns:
1D Slice with creared with zip  ([0] - key, [1] - value).
See Also:
map  uses multiargument lambdas to handle zipped slices.
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));

ref auto opAssign(RIndexIterator, RIterator)(auto ref Series!(RIndexIterator, kind, packs, RIterator) rvalue)
if (isAssignable!(IndexIterator, RIndexIterator) && isAssignable!(Iterator, RIterator));

ref auto opAssign(RIndexIterator, RIterator)(auto ref const Series!(RIndexIterator, kind, packs, RIterator) rvalue)
if (isAssignable!(IndexIterator, LightConstOf!RIndexIterator) && isAssignable!(Iterator, LightConstOf!RIterator));

ref auto opAssign(RIndexIterator, RIterator)(auto ref immutable Series!(RIndexIterator, kind, packs, RIterator) rvalue)
if (isAssignable!(IndexIterator, LightImmutableOf!RIndexIterator) && isAssignable!(Iterator, LightImmutableOf!RIterator));

ref auto opAssign(typeof(null));

@property auto save()();
ndslice-like primitives
const @property auto lightConst()();

immutable @property auto lightImmutable()();

const @property @trusted auto trustedImmutable()();
const @property auto toConst()();
const void toString(Writer, Spec)(auto ref Writer w, ref const Spec f);
Examples:
import mir.series: series, sort;
auto s = ["b", "a"].series([9, 8]).sort;

import std.conv : to;
assert(s.to!string == `["a":8, "b":9]`);

import std.format : format;
assert("%s".format(s) == `["a":8, "b":9]`);
assert("%(%s %s | %)".format(s) == `"a" 8 | "b" 9`);
assert("%-(%s,%s\n%)\n".format(s) == "a,8\nb,9\n");
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.
See Also:

Attention This overloads do not sort the data. User should call directly if index was not sorted.

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.
See Also:
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);
auto assocArray(IndexIterator, SliceKind kind, size_t[] packs, Iterator)(Series!(IndexIterator, kind, packs, Iterator) series);
Returns a newly allocated associative array from a range of key/value tuples.
Parameters:
Series!(IndexIterator, kind, packs, Iterator) series index / time Series, may not be sorted
Returns:
A newly allocated associative array out of elements of the input series. Returns a null associative array reference when given an empty series.

Duplicates Associative arrays have unique keys. If r contains duplicate keys, then the result will contain the value of the last pair for that key in r.

Examples:
import mir.ndslice; //iota and etc
import mir.series;

auto s = ["c", "a", "b"].series(3.iota!int);
assert(s.assocArray == [
    "c": 0,
    "a": 1,
    "b": 2,
]);
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.
template troykaGalop(alias lfun, alias cfun, alias rfun)
Iterates union using three functions to handle each intersection case separately.
Parameters:
lfun binary function that accepts left side key (and left side value)
cfun trinary function that accepts left side key, (left side value,) and right side value
rfun binary function that accepts right side key (and right side value)
void troykaGalop(IndIterL, SliceKind kindL, size_t[] packsL, IterL, IndIterR, SliceKind kindR, size_t[] packsR, IterR)(Series!(IndIterL, kindL, packsL, IterL) lhs, Series!(IndIterR, kindR, packsR, IterR) rhs);
Parameters:
Series!(IndIterL, kindL, packsL, IterL) lhs left hand series
Series!(IndIterR, kindR, packsR, IterR) rhs right hand series
void troykaGalop(LeftRange, RightRange)(LeftRange lhs, RightRange rhs)
if (isInputRange!LeftRange && isInputRange!RightRange && !isSeries!LeftRange && !isSeries!RightRange);
Parameters:
LeftRange lhs left hand input range
RightRange rhs right hand input range
template troykaSeries(alias lfun, alias cfun, alias rfun)
Constructs union using three functions to handle each intersection case separately.
Parameters:
lfun binary function that accepts left side key and left side value
cfun trinary function that accepts left side key, left side value, and right side value
rfun binary function that accepts right side key and right side value
Examples:
import mir.ndslice;
auto a = [1, 2, 3, 9].sliced.series(iota!int([4], 1));
auto b = [0, 2, 4, 9].sliced.series(iota!int([4], 1) * 10.0);
alias unionAlgorithm = troykaSeries!(
    (key, left) => left,
    (key, left, right) => left + right,
    (key, right) => -right,
);
auto c = unionAlgorithm(a, b);
assert(c.index == [0, 1, 2, 3, 4, 9]);
assert(c.data == [-10, 1, 22, 3, -30, 44]);
auto troykaSeries(IndexIterL, SliceKind kindL, size_t[] packsL, IterL, IndexIterR, SliceKind kindR, size_t[] packsR, IterR)(Series!(IndexIterL, kindL, packsL, IterL) lhs, Series!(IndexIterR, kindR, packsR, IterR) rhs);
Parameters:
Series!(IndexIterL, kindL, packsL, IterL) lhs left hand series
Series!(IndexIterR, kindR, packsR, IterR) rhs right hand series
Returns:
GC-allocated union series with length equal to troykaLength
size_t troykaLength(IndIterL, SliceKind kindL, size_t[] packsL, IterL, IndIterR, SliceKind kindR, size_t[] packsR, IterR)(Series!(IndIterL, kindL, packsL, IterL) lhs, Series!(IndIterR, kindR, packsR, IterR) rhs);

size_t troykaLength(LeftRange, RightRange)(LeftRange lhs, RightRange rhs)
if (!isSeries!LeftRange && !isSeries!RightRange);
Length for Troyka union handlers.
Parameters:
Series!(IndIterL, kindL, packsL, IterL) lhs left hand side series/range
Series!(IndIterR, kindR, packsR, IterR) rhs right hand side series/range
Returns:
Total count of lambda function calls in troykaGalop union handler.
template troykaSeriesImpl(alias lfun, alias cfun, alias rfun)
void troykaSeriesImpl(I, E, IndexIterL, SliceKind kindL, size_t[] packsL, IterL, IndexIterR, SliceKind kindR, size_t[] packsR, IterR, UI, UE)(Series!(IndexIterL, kindL, packsL, IterL) lhs, Series!(IndexIterR, kindR, packsR, IterR) rhs, Series!(UI*, Contiguous, [1], UE*) uninitSlice);
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 one memory allocation for two series union and two memory allocation for three and more series union.
Parameters:
Series!(IndexIterator, kind, packs, Iterator)[N] seriesTuple variadic static array of composed of series, each series must be sorted.
Returns:
sorted GC-allocated series. See_also Series.opBinary 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.
//////////////////////////////////////
// Order is matter.
// The first slice has higher priority.
auto m0 = unionSeries(series0, series1);
auto m1 = unionSeries(series1, series0);

assert(m0.index == m1.index);
assert(m0.data == [ 1, 20,  3,  4, 50]);
assert(m1.data == [10, 20,  3,  4, 50]);
Examples:
import std.datetime: Date;

//////////////////////////////////////
// Constructs three 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);

auto index2 = [1, 6];
auto data2 = [100.0, 600];
auto series2 = index2.series(data2);

//////////////////////////////////////
// Merges multiple series into one.
//////////////////////////////////////
// Order is matter.
// The first slice has higher priority.
auto m0 = unionSeries(series0, series1, series2);
auto m1 = unionSeries(series1, series0, series2);
auto m2 = unionSeries(series2, series0, series1);

assert(m0.index == m1.index);
assert(m0.index == m2.index);
assert(m0.data == [  1, 20,  3,  4, 50, 600]);
assert(m1.data == [ 10, 20,  3,  4, 50, 600]);
assert(m2.data == [100, 20,  3,  4, 50, 600]);
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 one memory allocation for two series union and exactly two memory allocation for three and more series union.
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(I, E, IndexIterator, SliceKind kind, size_t[] packs, Iterator, UI, UE)(Series!(IndexIterator, kind, packs, Iterator)[] seriesTuple, Series!(UI*, Contiguous, packs, UE*) 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!(UI*, Contiguous, packs, UE*) 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]));