1 module dataframes.rows;
2 
3 import std.traits;
4 import std.conv : text;
5 import std.range;
6 import std.format;
7 import std.variant;
8 import core.exception;
9 import std.datetime;
10 import std.json;
11 
12 import dataframes;
13 
14 struct Row(T)
15 {
16     DataFrame!T df;
17     size_t index;
18 
19     alias fieldNames = FieldNameTuple!(T);
20     alias fieldTypes = FieldTypeTuple!(T);
21 
22     static foreach(idx, name; fieldNames)
23     {
24         // For each DataFrame column, add a function to access the
25         // row by name. For example,
26         //     double price() { return this.df.price[this.rowIndex]; }
27         mixin(i"$(fieldTypes[idx].stringof) $(name)() {return this.df.$(name)[this.index];}".text);
28     }
29 
30     /**
31      * Access the column by label
32      */
33     Variant opIndex(string label)
34     {
35         Variant output;
36     s1: switch (label)
37         {
38             static foreach(name; fieldNames)
39             {
40                 mixin("case \"" ~ name ~ "\": output = this.df." ~ name ~ "[this.index]; break s1;");
41             }
42         default:
43             throw new RangeError("invalid label");
44         }
45         return output;
46     }
47 
48     /**
49        Access the column by index
50      */
51     Variant opIndex(size_t idx)
52     {
53         return opIndex(this.df.columnNames[idx]);
54     }
55 
56     string toString()
57     {
58         auto output = appender!string;
59 
60         output ~= format("Row(index=%s", this.index);
61 
62         static foreach(name; fieldNames)
63         {
64             static if(isNumeric!(typeof( __traits(getMember, this.df, name).values[0])))
65             {
66                 if (is(typeof( __traits(getMember, this.df, name).values[0]) == double))
67                     output.put(format(", %.2f", __traits(getMember, this.df, name).values[index]));
68                 else
69                     output.put(format(", %s", __traits(getMember, this.df, name).values[index]));
70             }
71             else
72                 output.put(format(", %s", __traits(getMember, this.df, name).values[index]));
73         }
74 
75         return output.data ~ ")";
76     }
77 
78     T1 toDataFrameStruct(T1)()
79     {
80         alias outputFieldNames = FieldNameTuple!(T1);
81         T1 output;
82 
83         static foreach(name; outputFieldNames)
84         {
85             static if(hasMember!(T, name))
86                 __traits(getMember, output, name) = __traits(getMember, this, name);
87         }
88 
89         return output;
90     }
91 
92     JSONValue toJSON()
93     {
94         JSONValue rowdata;
95         static foreach(name; fieldNames)
96             mixin("rowdata[name] = this." ~ name ~ ";");
97 
98         return rowdata;
99     }
100 }
101 
102 DataFrame!T toDataFrame(T, Range)(Range rows)
103 {
104     auto df = new DataFrame!T;
105 
106     foreach(row; rows)
107         df.add(row.toDataFrameStruct!T);
108 
109     return df;
110 }