1 module magpie.axis;
2 
3 import std.datetime: DateTime;
4 import std.range.primitives: ElementType;
5 import std.traits: isArray;
6 import std.variant: Algebraic;
7 
8 alias DataType = Algebraic!(bool, int, long, float, double, string, DateTime);
9 
10 /++
11 Structure to return an entire row or column of DataFrame.
12 The operations on this structure enables column/row binary operations.
13 +/
14 struct Axis(T...)
15 {
16     static if(T.length == 1 && is(T[0] == void))
17         alias AxisType = DataType[];
18     else static if(T.length == 1 && isArray!(T[0]))
19         alias AxisType = T[0];
20     else
21         alias AxisType = T;
22 
23     /// Data being returned by the DataFrame
24     AxisType data;
25 
26     /++
27     auto convertTo(U...)()
28     Description: Change the type of Axis.data
29     @params: u - The type to which data needs to be converted to.
30     +/
31     auto convertTo(U...)() @property
32         if(U.length == T.length)
33     {
34         import std.conv: to;
35         static if(U.length == 1)
36         {
37             static if(is(T[0] == void))
38             {
39                 Axis!U ret;
40                 foreach(i; data)
41                 {
42                     import std.variant: VariantException;
43                     try
44                     {
45                         ret.data ~= i.get!(ElementType!(U[0]));
46                     }
47                     catch(VariantException e)
48                     {
49                         ret.data ~= to!(ElementType!(U[0]))(i.get!(double));
50                     }
51                 }
52 
53                 return ret;
54             }
55             else
56             {
57                 Axis!U ret;
58                 ret.data = to!(U[0])(data);
59                 return ret;
60             }
61         }
62         else
63         {
64             Axis!U ret;
65             static foreach(i; 0 .. U.length)
66                 ret.data[i] = to!(U[i])(data[i]);
67             return ret;
68         }
69     }
70 
71     /++
72     Binary Operations on DataFrame row/column
73     +/
74     auto opBinary(string op, U...)(Axis!U rhs)
75         if(U.length == T.length)
76     {
77         static if(is(T[0] == void) || is(U[0] == void))
78             Axis!void ret;
79         else
80             Axis!T ret;
81 
82         static if(op == "+")
83         {
84             static if(U.length == 1)
85             {
86                 assert(data.length == rhs.data.length, "Size mismatch");
87                 foreach(i; 0 .. data.length)
88                 {
89                     static if(is(T[0] == void))
90                         ret.data ~= DataType(data[i] + rhs.data[i]);
91                     else
92                         ret.data ~= data[i] + rhs.data[i];
93                 }
94                 return ret;
95             }
96             else
97             {
98                 static foreach(i; 0 .. U.length)
99                     ret.data[i] = data[i] + rhs.data[i];
100 
101                 return ret;
102             }
103         }
104         else static if(op == "-")
105         {
106             static if(U.length == 1)
107             {
108                 assert(data.length == rhs.data.length, "Size mismatch");
109 
110                 foreach(i; 0 .. data.length)
111                 {
112                     static if(is(T[0] == void))
113                         ret.data ~= DataType(data[i] - rhs.data[i]);
114                     else
115                         ret.data ~= data[i] - rhs.data[i];
116                 }
117                 return ret;
118             }
119             else
120             {
121                 static foreach(i; 0 .. U.length)
122                     ret.data[i] = data[i] - rhs.data[i];
123 
124                 return ret;
125             }
126         }
127         else static if(op == "*")
128         {
129             static if(U.length == 1)
130             {
131                 assert(data.length == rhs.data.length, "Size mismatch");
132 
133                 foreach(i; 0 .. data.length)
134                 {
135                     static if(is(T[0] == void))
136                         ret.data ~= DataType(data[i] * rhs.data[i]);
137                     else
138                         ret.data ~= data[i] * rhs.data[i];
139                 }
140                 return ret;
141             }
142             else
143             {
144                 static foreach(i; 0 .. U.length)
145                     ret.data[i] = data[i] * rhs.data[i];
146 
147                 return ret;
148             }
149         }
150         else static if(op == "/")
151         {
152             static if(U.length == 1)
153             {
154                 assert(data.length == rhs.data.length, "Size mismatch");
155 
156                 foreach(i; 0 .. data.length)
157                 {
158                     static if(is(T[0] == void) || is(U[0] == void))
159                         ret.data ~= DataType(DataType(data[i]) / DataType(rhs.data[i]));
160                     else
161                         ret.data ~= data[i] / rhs.data[i];
162                 }
163                 return ret;
164             }
165             else
166             {
167                 static foreach(i; 0 .. U.length)
168                     ret.data[i] = data[i] / rhs.data[i];
169 
170                 return ret;
171             }
172         }
173 
174         assert(0);
175     }
176 }
177 
178 // Covert a probable column
179 unittest
180 {
181     Axis!(int[]) column;
182     column.data = [1,2,3,4];
183     auto res = column.convertTo!(double[]);
184     assert(res.data == [1.0, 2.0, 3.0, 4.0]);
185     assert(is(typeof(res.data) == double[]));
186 }
187 
188 // Convert a probable row
189 unittest
190 {
191     Axis!(int, double) row;
192     row.data[0] = 12;
193     row.data[1] = 14.7;
194     auto res = row.convertTo!(double, int);
195     assert(res.data[0] == 12.0);
196     assert(res.data[1] == 14);
197     assert(is(typeof(res.data[0]) == double));
198     assert(is(typeof(res.data[1]) == int));
199 }
200 
201 // Addition operation on multiple column
202 unittest
203 {
204     Axis!(double[]) a1;
205     a1.data = [1,2,3.7,4];
206     Axis!(int[]) a2;
207     a2.data = [1,2,3,4];
208     Axis!(double[]) a3;
209     a3.data = [1,2,3.7,4];
210     Axis!(double[]) a4;
211     a4.data = [1,2,3,4];
212 
213     assert((a1 + a2).data == [2, 4, 6.7, 8]);
214     assert(a1.data == [1,2,3.7,4]);
215     assert(a2.data == [1,2,3,4]);
216     assert((a1 + a2 + a3 + a4).data == [4, 8, 13.4, 16]);
217 }
218 
219 // Addition operation on multiple rows
220 unittest
221 {
222     Axis!(int, double) a1;
223     a1.data[0] = 1;
224     a1.data[1] = 1.786;
225 
226     Axis!(int, int) a2;
227     a2.data[0] = 1;
228     a2.data[1] = 1;
229 
230     Axis!(int, double) a3;
231     a3.data[0] = 1;
232     a3.data[1] = 1.786;
233 
234     Axis!(int, double) a4;
235     a4.data[0] = 1;
236     a4.data[1] = 1;
237 
238     import std.math: approxEqual;
239     auto res = a1 + a2;
240     assert(res.data[0] == 2);
241     assert(approxEqual(res.data[1], 2.786, 1e-3));
242 
243     auto res2 = a1 + a2 + a3 + a4;
244     assert(res2.data[0] == 4);
245     assert(approxEqual(res2.data[1], 5.572, 1e-3));
246 }
247 
248 // Subtraction operation on multiple column
249 unittest
250 {
251     Axis!(double[]) a1;
252     a1.data = [1,2,3.765,4];
253     Axis!(int[]) a2;
254     a2.data = [1,2,3,4];
255     Axis!(double[]) a3;
256     a3.data = [1,2,3.765,4];
257     Axis!(double[]) a4;
258     a4.data = [1,2,3,4];
259 
260     import std.math: approxEqual;
261     static foreach(i; 0 .. 4)
262     {
263         assert(approxEqual((a1 - a2).data[i], a1.data[i] - a2.data[i], 1e-3));
264         assert(approxEqual((a1 - a2 - a3 - a4).data[i], a1.data[i] - a2.data[i] - a3.data[i] - a4.data[i], 1e-3));
265     }
266 
267 }
268 
269 // Subtraction operation on multiple rows
270 unittest
271 {
272     Axis!(int, double) a1;
273     a1.data[0] = 1;
274     a1.data[1] = 1.786;
275 
276     Axis!(int, int) a2;
277     a2.data[0] = 1;
278     a2.data[1] = 1;
279 
280     Axis!(int, double) a3;
281     a3.data[0] = 1;
282     a3.data[1] = 1.786;
283 
284     Axis!(int, double) a4;
285     a4.data[0] = 1;
286     a4.data[1] = 1;
287 
288     auto res = a1 - a2;
289     auto res2 = a1 - a2 - a3 - a4;
290 
291     import std.math: approxEqual;
292     static foreach(i; 0 .. 2)
293     {
294         assert(approxEqual(res.data[i], a1.data[i] - a2.data[i], 1e-3));
295         assert(approxEqual(res2.data[i], a1.data[i] - a2.data[i] - a3.data[i] - a4.data[i], 1e-3));
296     }
297 }
298 
299 // Multiplication operation on multiple column
300 unittest
301 {
302     Axis!(double[]) a1;
303     a1.data = [1,2,3.765,4];
304     Axis!(int[]) a2;
305     a2.data = [1,2,3,4];
306     Axis!(double[]) a3;
307     a3.data = [1,2,3.765,4];
308     Axis!(double[]) a4;
309     a4.data = [1,2,3,4];
310 
311     import std.math: approxEqual;
312     static foreach(i; 0 .. 4)
313     {
314         assert(approxEqual((a1 * a2).data[i], a1.data[i] * a2.data[i], 1e-6));
315         assert(approxEqual((a1 * a2 * a3 * a4).data[i], a1.data[i] * a2.data[i] * a3.data[i] * a4.data[i], 1e-6));
316     }
317 
318 }
319 
320 // Multiplication operation on multiple rows
321 unittest
322 {
323     Axis!(int, double) a1;
324     a1.data[0] = 1;
325     a1.data[1] = 1.786;
326 
327     Axis!(int, int) a2;
328     a2.data[0] = 1;
329     a2.data[1] = 1;
330 
331     Axis!(int, double) a3;
332     a3.data[0] = 1;
333     a3.data[1] = 1.786;
334 
335     Axis!(int, double) a4;
336     a4.data[0] = 1;
337     a4.data[1] = 1;
338 
339     auto res = a1 * a2;
340     auto res2 = a1 * a2 * a3 * a4;
341 
342     import std.math: approxEqual;
343     static foreach(i; 0 .. 2)
344     {
345         assert(approxEqual(res.data[i], a1.data[i] * a2.data[i], 1e-6));
346         assert(approxEqual(res2.data[i], a1.data[i] * a2.data[i] * a3.data[i] * a4.data[i], 1e-6));
347     }
348 }
349 
350 // Division operation on multiple column
351 unittest
352 {
353     Axis!(double[]) a1;
354     a1.data = [1,2,3.765,4];
355     Axis!(int[]) a2;
356     a2.data = [1,2,3,4];
357     Axis!(double[]) a3;
358     a3.data = [1,2,3.765,4];
359     Axis!(double[]) a4;
360     a4.data = [1,2,3,4];
361 
362     import std.math: approxEqual;
363     static foreach(i; 0 .. 4)
364     {
365         assert(approxEqual((a1 / a2).data[i], a1.data[i] / a2.data[i], 1e-3));
366         assert(approxEqual((a1 / a2 / a3 / a4).data[i], a1.data[i] / a2.data[i] / a3.data[i] / a4.data[i], 1e-3));
367     }
368 
369 }
370 
371 // Division operation on multiple rows
372 unittest
373 {
374     Axis!(int, double) a1;
375     a1.data[0] = 1;
376     a1.data[1] = 1.786;
377 
378     Axis!(int, int) a2;
379     a2.data[0] = 1;
380     a2.data[1] = 1;
381 
382     Axis!(int, double) a3;
383     a3.data[0] = 1;
384     a3.data[1] = 1.786;
385 
386     Axis!(int, double) a4;
387     a4.data[0] = 1;
388     a4.data[1] = 1;
389 
390     auto res = a1 / a2;
391     auto res2 = a1 / a2 / a3 / a4;
392 
393     import std.math: approxEqual;
394     static foreach(i; 0 .. 2)
395     {
396         assert(approxEqual(res.data[i], a1.data[i] / a2.data[i], 1e-3));
397         assert(approxEqual(res2.data[i], a1.data[i] / a2.data[i] / a3.data[i] / a4.data[i], 1e-3));
398     }
399 }
400 
401 // Axis with data of type Variant
402 unittest
403 {
404     Axis!(void) a;
405     foreach(i; 0 .. 5)
406         a.data ~= DataType(i + 1.7);
407     assert(a.data == [1.7, 2.7, 3.7, 4.7, 5.7]);
408 }
409 
410 // Bianry Ops on Variant Axis
411 unittest
412 {
413     Axis!(void) a;
414     Axis!(void) b;
415     foreach(i; 0 .. 5)
416     {
417         a.data ~= DataType(i + 1.7);
418         b.data ~= DataType(i);
419     }
420 
421     auto c = a + b;
422     assert(c.data == [1.7, 3.7, 5.7, 7.7, 9.7]);
423     assert(typeid(a) == typeid(c));
424 
425     c = a - b;
426     import std.math: approxEqual;
427     foreach(i; c.data)
428         assert(approxEqual(i.get!double, 1.7, 1e-1));
429 
430     c = a * b;
431     foreach(i; 0 .. 5)
432         assert(approxEqual(c.data[i].get!double, ((i + 1.7) * i), 1e-2));
433 
434     c = b / a;
435     foreach(i; 0 .. 5)
436         assert(approxEqual(c.data[i].get!double, (i / (i + 1.7)), 1e-4));
437 }
438 
439 // Binary Ops on Variant + Other DataType
440 unittest
441 {
442     Axis!(void) a;
443     Axis!(int[]) b;
444 
445     foreach(i; 0 .. 5)
446     {
447         a.data ~= DataType(i + 1.7);
448         b.data ~= i;
449     }
450 
451     auto c = a + b;
452     assert(c.data == [1.7, 3.7, 5.7, 7.7, 9.7]);
453     assert(typeid(a) == typeid(c));
454 
455     c = a - b;
456     import std.math: approxEqual;
457     foreach(i; c.data)
458         assert(approxEqual(i.get!double, 1.7, 1e-1));
459 
460     c = a * b;
461     foreach(i; 0 .. 5)
462         assert(approxEqual(c.data[i].get!double, ((i + 1.7) * i), 1e-2));
463 
464     c = b / a;
465     foreach(i; 0 .. 5)
466         assert(approxEqual(c.data[i].get!double, (i / (i + 1.7)), 1e-4));
467 }
468 
469 // Converting Variant Axis to other types
470 unittest
471 {
472     Axis!(void) a;
473 
474     foreach(i; 0 .. 5)
475     {
476         a.data ~= DataType(i + 1.7);
477     }
478 
479     auto b = a.convertTo!(int[]);
480     assert(is(typeof(b.data) == int[]));
481     assert(b.data == [1, 2, 3, 4, 5]);
482 
483     auto c = a.convertTo!(double[]);
484     assert(is(typeof(c.data) == double[]));
485 
486     import std.math: approxEqual;
487     foreach(i; 0 .. 5)
488         assert(approxEqual(c.data[i], (i + 1.7), 1e-1));
489 }