Skip to content

Commit 6744a6b

Browse files
authored
Add new feature: rolling (#2)
Signed-off-by: Aravinda VK <vkaravinda7@gmail.com>
1 parent 324bd22 commit 6744a6b

File tree

4 files changed

+112
-0
lines changed

4 files changed

+112
-0
lines changed

README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,3 +299,45 @@ To convert a DataFrame to JSON,
299299
```d
300300
auto jsonData = df.toJSON;
301301
```
302+
303+
## Rolling
304+
Use this to apply aggregation function over a moving window. For example, to calculate the Simple moving average for the Price data.
305+
306+
```d
307+
// FUNC RETTYPE WINDOW
308+
df.sma21 = df.close.rolling!(mean, double)(21);
309+
```
310+
311+
To use the cusom function with rolling. Example Exponential Moving Average:
312+
313+
```d
314+
double[] ema(T)(T arr, int period)
315+
{
316+
double prevEMA;
317+
auto multiplier = 2.0 / (period + 1);
318+
319+
double emaFunc(int[] data)
320+
{
321+
if (isNaN(prevEMA))
322+
{
323+
prevEMA = mean(data);
324+
return prevEMA;
325+
}
326+
327+
auto lastValue = data[$-1];
328+
329+
prevEMA = lastValue * multiplier + prevEMA * (1 - multiplier);
330+
return prevEMA;
331+
}
332+
333+
return arr.rolling!(emaFunc, double)(period);
334+
}
335+
336+
void main()
337+
{
338+
auto window = 2;
339+
auto arr = [10, 20, 30, 40, 50, 60, 70, 82, 91, 100];
340+
writeln("SMA: ", arr.rolling!(mean, double)(window));
341+
writeln("EMA: ", arr.ema(window));
342+
}
343+
```

source/dataframes/columns.d

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,16 @@ struct Column(T)
4747
values ~= rhs;
4848
}
4949

50+
size_t opDollar(size_t pos)()
51+
{
52+
return values.length;
53+
}
54+
55+
auto opSlice()(size_t start, size_t end)
56+
{
57+
return values[start .. end];
58+
}
59+
5060
T opIndex(size_t idx)
5161
{
5262
return values[idx];

source/dataframes/helpers.d

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
module dataframes.helpers;
22

3+
import std.range.primitives : hasLength, hasSlicing;
34

45
class DataFrameException : Exception
56
{
@@ -9,3 +10,24 @@ class DataFrameException : Exception
910
}
1011
}
1112

13+
// Generic function for Rolling
14+
// [10, 20, 30, 40].rolling!(mean, double)(2)
15+
U[] rolling(alias func, U, T)(T data, int window)
16+
if (hasLength!T)
17+
{
18+
U[] output;
19+
if (data.length < window)
20+
{
21+
output.length = data.length;
22+
return output;
23+
}
24+
25+
// Start filling after window. Fill with output
26+
// datatype default. Ex: nan for double
27+
output.length = window - 1;
28+
29+
foreach(idx; 0..(data.length - window + 1))
30+
output ~= func(data[idx .. idx + window]);
31+
32+
return output;
33+
}

source/dataframes/package.d

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,16 @@ class DataFrame(T)
7575

7676
mixin("this(" ~ initializerArgs ~ "){" ~ initializerContent ~ "}");
7777

78+
size_t opDollar(size_t pos)()
79+
{
80+
return length;
81+
}
82+
83+
auto opSlice()(size_t start, size_t end)
84+
{
85+
return iota(start, end).map!(idx => row(idx)).array;
86+
}
87+
7888
/**
7989
* Access the DataFrame column by label
8090
*/
@@ -563,4 +573,32 @@ unittest
563573
{"count":0,"name":"B","name2":"B3","value1":5.0,"value2":120.0},
564574
{"count":0,"name":"C","name2":"C1","value1":6.0,"value2":60.0}]`;
565575
assert(df5.toJSON == parseJSON(jsonOutput));
576+
577+
struct Sample2
578+
{
579+
string name;
580+
double value;
581+
double avg;
582+
}
583+
584+
auto dfSample2 = new DataFrame!Sample2(
585+
name: ["A", "A", "B", "B", "B", "C"],
586+
value: [1, 2, 3, 4, 5, 6]
587+
);
588+
589+
import std.math : isNaN;
590+
591+
dfSample2.avg = dfSample2.value.rolling!(mean, double)(2);
592+
assert(dfSample2.avg[0].isNaN);
593+
assert(dfSample2.avg.values[1..$] == [1.5, 2.5, 3.5, 4.5, 5.5]);
594+
595+
double smaFunc(Row!Sample2[] data)
596+
{
597+
// Get the mean of the field "value"
598+
return data.map!(a => a.value).mean;
599+
}
600+
601+
dfSample2.avg = dfSample2.rolling!(smaFunc, double)(2);
602+
assert(dfSample2.avg[0].isNaN);
603+
assert(dfSample2.avg.values[1..$] == [1.5, 2.5, 3.5, 4.5, 5.5]);
566604
}

0 commit comments

Comments
 (0)