1 module magpie.dataframe;
2 
3 import magpie.axis: Axis, DataType;
4 import magpie.index: Index;
5 import magpie.helper: getArgsList, toArr, isHomogeneous, suitableType, auxDispatch;
6 
7 import std.meta: AliasSeq, Repeat, staticMap;
8 import std.array: appender;
9 import std.traits: isType, isBoolean, isArray;
10 
11 import mir.ndslice;
12 
13 /++
14 The DataFrame Structure
15 +/
16 struct DataFrame(FrameFields...)
17     if(FrameFields.length > 0
18     && ((!isType!(FrameFields[0]) && is(typeof(FrameFields[0]) == bool) && FrameFields[0] == true && FrameFields.length > 1)
19     || isType!(FrameFields[0])))
20 {
21     static if(!isType!(FrameFields[0]) && is(typeof(FrameFields[0]) == bool) && FrameFields[0] == true)
22         alias RowType = FrameFields[1 .. $];
23     else
24         alias RowType = getArgsList!(FrameFields);
25 
26     /// Flag to know if the DataFrame is homogeneous
27     enum bool isHomogeneousType = isHomogeneous!(RowType);
28 
29     static if(!isHomogeneousType)
30         alias FrameType = staticMap!(toArr, RowType);
31     else
32         alias FrameType = toArr!(RowType[0])[RowType.length];
33 
34     ///
35     size_t rows = 0;
36     ///
37     size_t cols = RowType.length;
38 
39     /// DataFrame indexing
40     Index indx;
41     /// DataFrame Data
42     FrameType data;
43 
44 private:
45     ptrdiff_t getPosition(int axis)(string[] index)
46     {
47         return indx.getPosition!(axis)(index);
48     }
49 
50     template resolverInternal(T, Ops...)
51     {
52         import std.traits: ReturnType;
53         static if(Ops.length)
54         {
55             alias Op = Ops[0];
56             static if(__traits(compiles, ReturnType!(Op)))
57                 alias resolverInternal = AliasSeq!(ReturnType!(Op), resolverInternal!(T, Ops[1 .. $]));
58             else static if(__traits(compiles, ReturnType!(Op!T)))
59                 alias resolverInternal = AliasSeq!(ReturnType!(Op!T), resolverInternal!(T, Ops[1 .. $]));
60             else static if(__traits(compiles, ReturnType!(Op!(toArr!T))))
61                 alias resolverInternal = AliasSeq!(ReturnType!(Op!(toArr!T)), resolverInternal!(T, Ops[1 .. $]));
62             else
63                 alias resolverInternal = AliasSeq!(ReturnType!(Op!(T,T)), resolverInternal!(T, Ops[1 .. $]));
64         }
65         else 
66             alias resolverInternal = AliasSeq!();
67     }
68 
69     template aggregateType(Ops...)
70     {
71         alias fwdType = suitableType!(RowType);
72         alias Resolved = resolverInternal!(fwdType, Ops);
73         alias aggregateType = Resolved;
74     }
75 
76     auto dropperRuntimeInternal(int[] positions)
77     {
78         import magpie.helper: dropper;
79         import std.algorithm: max, min, reduce;
80         assert(positions.reduce!min > -1 && positions.reduce!max < rows, "Index out of bound");
81 
82         DataFrame!(true, RowType) ret;
83         ret.indx = Index();
84         ret.indx.indexing[1] = indx.indexing[1];
85         ret.indx.row.titles = indx.row.titles;
86 
87         import std.range: lockstep;
88         ret.indx.row.index.length = indx.row.index.length;
89         ret.indx.row.codes.length = indx.row.codes.length;
90         foreach(i, a, b; lockstep(indx.row.index, indx.row.codes))
91         {
92             ret.indx.row.index[i] = a;
93             ret.indx.row.codes[i] = dropper(positions, b);
94         }
95 
96         static foreach(i; 0 .. RowType.length)
97             ret.data[i] = dropper(positions, data[i]);
98 
99         ret.rows = rows - positions.length;
100         return ret;
101     }
102 
103 public:
104     /++
105     auto display(bool getStr = false, int maxwidth = 0)
106     Description: Converts the given DataFrame into a formatted string to display on the terminal
107     @params: getStr - returns the string generated.
108     @params: maxwidth - override the width of the terminal.
109     +/
110     auto display(bool getStr = false, int maxwidth = 0)
111     {
112         import std.algorithm: map, reduce, max;
113         import std.conv: to;
114 
115         auto gapCalc(T)(T[] arr)
116         {
117             static if(is(T == string))
118                 return arr.map!(e => e.length).reduce!max;
119             else
120                 return arr.map!(e => to!string(e).length).reduce!max;
121         }
122 
123         if(rows == 0)
124         {
125             if(!getStr)
126             {
127                 import std.stdio: writeln;
128                 writeln("Empty DataFrmae");
129             }
130             return "";
131         }
132 
133         const uint terminalw = (maxwidth > 100)? maxwidth: 200;
134         const uint maxColSize = 43;
135         auto gaps = appender!(size_t[]);
136 
137         size_t top, bottom;
138         const size_t totalHeight = rows + indx.column.index.length +
139             ((indx.row.titles.length > 0 && indx.column.titles.length > 0)? 1: 0);
140         const size_t totalWidth = cols + indx.row.index.length;
141 
142         if(totalHeight > 50)
143         {
144             top = 25;
145             bottom = 25;
146         }
147         else
148         {
149             top = totalHeight;
150             bottom = 0;
151         }
152 
153         size_t dataIndex = 0;
154         foreach(i; 0 .. totalWidth)
155         {
156             int extra = (indx.row.titles.length > 0 && indx.column.titles.length > 0)? 1: 0;
157             if(i < indx.row.index.length)
158             {
159                 size_t thisGap = (i < indx.row.titles.length && top > indx.column.index.length + extra)? indx.row.titles[i].length: 0;
160                 if(top > indx.column.index.length + extra)
161                 {
162                     size_t tmp = 0;
163                     if(indx.row.codes[i].length == 0)
164                         tmp = gapCalc(indx.row.index[i][0 .. top - indx.column.index.length - extra]);
165                     else if(indx.row.index[i].length == 0)
166                         tmp = gapCalc(indx.row.codes[i][0 .. top - indx.column.index.length - extra]);
167                     else
168                         tmp = indx.row.codes[i][0 .. top - indx.column.index.length - extra].map!(e => indx.row.index[i][e].length).reduce!max;
169 
170                     thisGap = max(thisGap, tmp);
171                 }
172 
173                 if(bottom > 0)
174                 {
175                     if(bottom > indx.row.index[i].length)
176                     {
177                         size_t tmp = 0;
178                         if(indx.row.codes[i].length == 0)
179                             tmp = gapCalc(indx.row.index[i]);
180                         else if(indx.row.index[i].length == 0)
181                             tmp = gapCalc(indx.row.codes[i]);
182                         else
183                             tmp = indx.row.codes[i].map!(e => indx.row.index[i][e].length).reduce!max;
184 
185                         thisGap = max(thisGap, tmp);
186                     }
187                     else
188                     {
189                         size_t tmp = 0;
190                         if(indx.row.codes[i].length == 0)
191                             tmp = gapCalc(indx.row.index[i][$ - bottom .. $]);
192                         else if(indx.row.index[i].length == 0)
193                             tmp = gapCalc(indx.row.codes[i][$ - bottom .. $]);
194                         else
195                             tmp = indx.row.codes[i][$ - bottom .. $].map!(e => indx.row.index[i][e].length).reduce!max;
196 
197                         thisGap = max(thisGap, tmp);
198                     }
199                 }
200 
201                 if(i == indx.row.index.length - 1 && indx.column.titles.length > 0)
202                 {
203                     const auto tmp = (indx.column.titles.length > top)
204                         ? gapCalc(indx.column.titles[0 .. top])
205                         : gapCalc(indx.column.titles);
206 
207                     thisGap = max(thisGap, tmp);
208                 }
209 
210                 gaps.put((thisGap < maxColSize)? thisGap: maxColSize);
211             }
212             else
213             {
214                 size_t maxGap = 0;
215                 foreach(j; 0 .. (top > indx.column.index.length)? indx.column.index.length: top)
216                 {
217                     size_t lenCol = 0;
218                     if(indx.column.codes[j].length == 0)
219                         lenCol = indx.column.index[j][dataIndex].length;
220                     else if(indx.column.index[j].length == 0)
221                         lenCol = to!string(indx.column.codes[j][dataIndex]).length;
222                     else
223                         lenCol = indx.column.index[j][indx.column.codes[j][dataIndex]].length;
224 
225                     maxGap = max(maxGap, lenCol);
226                 }
227 
228                 foreach(j; totalHeight - bottom .. indx.column.index.length)
229                 {
230                     size_t lenCol = 0;
231                     if(indx.column.codes[j].length == 0)
232                         lenCol = indx.column.index[j][dataIndex].length;
233                     else if(indx.column.index[j].length == 0)
234                         lenCol = to!string(indx.column.codes[j][dataIndex]).length;
235                     else
236                         lenCol = indx.column.index[j][indx.column.codes[j][dataIndex]].length;
237 
238                     maxGap = max(maxGap, lenCol);
239                 }
240 
241                 size_t maxsize1 = 0, maxsize2 = 0;
242 
243                 void maxGapCalc(ptrdiff_t si = -1)(size_t ri = 0) @property
244                 {
245                     static if(si > -1)
246                         alias j = si;
247                     else
248                         size_t j = ri;
249 
250                     if(top > indx.column.index.length + extra)
251                         maxsize1 = gapCalc(data[j][0 .. top - indx.column.index.length - extra]);
252                     if(bottom > data[j].length)
253                         maxsize2 = gapCalc(data[j]);
254                     else if(bottom > 0)
255                         maxsize2 = gapCalc(data[j][$ - bottom .. $]);
256                 }
257 
258                 mixin auxDispatch!(maxGapCalc, isHomogeneousType, RowType);
259                 auxDispatch(dataIndex);
260 
261                 maxGap = max(maxGap, maxsize1, maxsize2);
262                 gaps.put((maxGap < maxColSize)? maxGap: maxColSize);
263                 ++dataIndex;
264             }
265         }
266 
267         auto cwidth = gaps.data;
268         size_t left = 0, right = 0;
269         int wOccupied = 0;
270         foreach(i; cwidth)
271         {
272             if(wOccupied + i + 4 < terminalw/2)
273             {
274                 wOccupied += i + 2;
275                 left++;
276             }
277             else
278                 break;
279         }
280 
281         wOccupied = 0;
282         foreach_reverse(i; cwidth)
283         {
284             if(wOccupied + i + 5 < terminalw/2)
285             {
286                 wOccupied += i + 2;
287                 right++;
288             }
289             else
290                 break;
291         }
292 
293         if(left + right > cwidth.length)
294         {
295             left = cwidth.length;
296             right = 0;
297         }
298 
299         auto dispstr = appender!string;
300         foreach(ele; [[0, top], [totalHeight - bottom, totalHeight]])
301         {
302             const int extra = (indx.row.titles.length > 0 && indx.column.titles.length > 0)? 1: 0;
303             if(ele[0] < indx.column.index.length + extra)
304             {
305                 dataIndex = 0;
306             }
307             else
308             {
309                 dataIndex = ele[0] - (indx.column.index.length + extra);
310             }
311             foreach(i; ele[0] .. ele[1])
312             {
313                 bool skipIndex = true;
314                 foreach(lim; [[0, left], [totalWidth - right, totalWidth]])
315                 {
316                     foreach(j; lim[0] .. lim[1])
317                     {
318                         if(i < indx.column.index.length)
319                         {
320                             if(j < indx.row.index.length)
321                             {
322                                 if(j == indx.row.index.length - 1
323                                     && indx.column.titles.length != 0)
324                                 {
325                                     if(indx.column.titles[i].length > maxColSize)
326                                     {
327                                         dispstr.put(indx.column.titles[i][0 .. maxColSize]);
328                                         dispstr.put("  ");
329                                     }
330                                     else
331                                     {
332                                         dispstr.put(indx.column.titles[i]);
333                                         foreach(k;indx.column.titles[i].length .. cwidth[j] + 2)
334                                         {
335                                             dispstr.put(" ");
336                                         }
337                                     }
338                                 }
339                                 else if(i == indx.column.index.length - 1
340                                     && indx.column.titles.length == 0)
341                                 {
342                                     if(indx.row.titles[j].length > maxColSize)
343                                     {
344                                         dispstr.put(indx.row.titles[j][0 .. maxColSize]);
345                                         dispstr.put("  ");
346                                     }
347                                     else
348                                     {
349                                         dispstr.put(indx.row.titles[j]);
350                                         foreach(k; indx.row.titles[j].length .. cwidth[j] + 2)
351                                         {
352                                             dispstr.put(" ");
353                                         }
354                                     }
355                                 }
356                                 else
357                                 {
358                                     foreach(k; 0 .. cwidth[j] + 2)
359                                     {
360                                         dispstr.put(" ");
361                                     }
362                                 }
363                             }
364                             else
365                             {
366                                 string colindx ="";
367                                 if(indx.column.codes[i].length == 0)
368                                     colindx = indx.column.index[i][j - indx.row.index.length];
369                                 else if(indx.column.index[i].length == 0)
370                                     colindx = to!string(indx.column.codes[i][j - indx.row.index.length]);
371                                 else
372                                     colindx = indx.column.index[i][indx.column.codes[i][j - indx.row.index.length]];
373 
374                                 if(colindx.length > maxColSize)
375                                 {
376                                     dispstr.put(colindx[0 .. maxColSize]);
377                                     dispstr.put("  ");
378                                 }
379                                 else
380                                 {
381                                     dispstr.put(colindx);
382                                     foreach(k; colindx.length .. cwidth[j] + 2)
383                                     {
384                                         dispstr.put(" ");
385                                     }
386                                 }
387                             }
388                         }
389                         else if(i == indx.column.index.length && indx.column.titles.length != 0)
390                         {
391                             if(j < indx.row.titles.length)
392                             {
393                                 if(indx.row.titles[j].length > maxColSize)
394                                 {
395                                     dispstr.put(indx.row.titles[j][0 .. maxColSize]);
396                                     dispstr.put("  ");
397                                 }
398                                 else
399                                 {
400                                     dispstr.put(indx.row.titles[j]);
401                                     foreach(k; indx.row.titles[j].length .. cwidth[j] + 2)
402                                     {
403                                         dispstr.put(" ");
404                                     }
405                                 }
406                             }
407                         }
408                         else
409                         {
410                             if(j < indx.row.index.length)
411                             {
412                                 string idx = "";
413                                 if(indx.row.codes[j].length == 0)
414                                     idx = indx.row.index[j][dataIndex];
415                                 else if(indx.row.index[j].length == 0)
416                                     idx = to!string(indx.row.codes[j][dataIndex]);
417                                 else if(dataIndex > 0 && j < indx.row.index.length
418                                     && indx.row.codes[j][dataIndex] == indx.row.codes[j][dataIndex - 1]
419                                     && skipIndex && indx.isMultiIndexed)
420                                     idx = "";
421                                 else
422                                 {
423                                     idx = indx.row.index[j][indx.row.codes[j][dataIndex]];
424                                     skipIndex = false;
425                                 }
426 
427                                 if(idx.length > maxColSize)
428                                 {
429                                     dispstr.put(idx[0 .. maxColSize]);
430                                     dispstr.put("  ");
431                                 }
432                                 else
433                                 {
434                                     dispstr.put(idx);
435                                     foreach(k; idx.length .. cwidth[j] + 2)
436                                     {
437                                         dispstr.put(" ");
438                                     }
439                                 }
440                             }
441                             else
442                             {
443                                 string idx = "";
444 
445                                 string toStringAux(ptrdiff_t si = -1)(size_t ri = 0) @property
446                                 {
447                                     static if(si > -1)
448                                         alias i = si;
449                                     else
450                                         size_t i = ri;
451 
452                                     return to!string(data[i][dataIndex]);
453                                 }
454 
455                                 mixin auxDispatch!(toStringAux, isHomogeneousType, RowType);
456                                 idx = auxDispatch(j - indx.row.index.length);
457                                 
458                                 if(idx.length > maxColSize)
459                                 {
460                                     dispstr.put(idx[0 .. maxColSize]);
461                                     dispstr.put("  ");
462                                 }
463                                 else
464                                 {
465                                     dispstr.put(idx);
466                                     foreach(k; idx.length .. cwidth[j] + 2)
467                                     {
468                                         dispstr.put(" ");
469                                     }
470                                 }
471                             }
472                         }
473                     }
474                     if(right > 0 && lim[0] == 0)
475                     {
476                         dispstr.put("...  ");
477                     }
478                 }
479                 dispstr.put("\n");
480                 if(i >= indx.column.index.length + extra)
481                     ++dataIndex;
482             }
483             if(bottom > 0 && ele[0] == 0)
484             {
485                 foreach(i; 0 .. left)
486                 {
487                     foreach(j; 0 .. cwidth[i])
488                         dispstr.put(".");
489                     dispstr.put("  ");
490                 }
491 
492                 if(right > 0)
493                 {
494                     dispstr.put("...  ");
495                 }
496 
497                 foreach(i; cwidth.length - right .. cwidth.length)
498                 {
499                     foreach(j; 0 .. cwidth[i])
500                         dispstr.put(".");
501                     dispstr.put("  ");
502                 }
503                 dispstr.put("\n");
504             }
505         }
506 
507         import std.stdio: writeln;
508         import std.array: replace;
509         if(!getStr)
510         {
511             writeln(dispstr.data.replace("  \n", "\n"));
512             // writeln(RowType.length);
513             // writeln(gaps.data);
514             // writeln(left,"\t", right);
515 
516             writeln("Dataframe Dimension: [ ", totalHeight," X ", totalWidth, " ]");
517             writeln("Data Dimension: [ ", rows," X ", cols, " ]");
518         }
519         return ((getStr)? dispstr.data.replace("  \n", "\n"): "");
520     }
521 
522     /++
523     void to_csv(string path, bool writeIndex = true, bool writecolumn.index = true, char sep = ',')
524     Description: Writes given DataFrame to a CSV file
525     @params: path - path to the output file
526     @params: writeIndex - write row index to the file
527     @params: writecolumn.index - write column index to the file
528     @params: sep - data seperator
529     +/
530     void to_csv(int precision = 0)(string path, bool writeIndex = true, bool writeColumn = true, char sep = ',')
531     {
532         import std.array: appender;
533         import std.conv: to;
534         import std.format: format;
535 
536         string formatData(T)(T ele)
537         {
538             static if(__traits(isIntegral, T))
539                 return format!"%d"(ele);
540             else static if(__traits(isFloating, T) && precision)
541                 return format!("%." ~ to!string(precision) ~"f")(ele);
542             else
543                 return to!string(ele);
544         }
545 
546         auto formatter = appender!(string);
547         const size_t totalHeight = rows + indx.column.index.length +
548             ((indx.row.titles.length > 0 && indx.column.titles.length > 0)? 1: 0);
549 
550         if(rows == 0)
551         {
552             return;
553         }
554 
555         if(writeColumn)
556         {
557             foreach(i; 0 .. indx.column.index.length)
558             {
559                 if(writeIndex)
560                 {
561                     foreach(j; 0 .. indx.row.index.length)
562                     {
563                         if(i != indx.column.index.length - 1 && j < indx.row.index.length - 1)
564                         {
565                             formatter.put(sep);
566                         }
567                         else if(i == indx.column.index.length - 1 && indx.column.titles.length == 0)
568                         {
569                             formatter.put(indx.row.titles[j]);
570                             formatter.put(sep);
571                         }
572                         else if(j == indx.row.index.length - 1 && indx.column.titles.length != 0)
573                         {
574                             formatter.put(indx.column.titles[i]);
575                             formatter.put(sep);
576                         }
577                         else
578                         {
579                             formatter.put(sep);
580                         }
581                     }
582                 }
583 
584                 foreach(j; 0 .. cols)
585                 {
586                     string colindx ="";
587                     if(indx.column.codes[i].length == 0)
588                         colindx = indx.column.index[i][j];
589                     else if(indx.column.index[i].length == 0)
590                         colindx = to!string(indx.column.codes[i][j]);
591                     else
592                         colindx = indx.column.index[i][indx.column.codes[i][j]];
593 
594                     formatter.put(colindx);
595                     if(j < cols - 1)
596                         formatter.put(sep);
597                 }
598 
599                 formatter.put("\n");
600             }
601             if(indx.column.titles.length != 0 && writeIndex)
602             {
603                 formatter.put(indx.row.titles[0]);
604                 foreach(j; 1 .. indx.row.index.length)
605                 {
606                     formatter.put(sep);
607                     formatter.put(indx.row.titles[j]);
608                 }
609                 formatter.put("\n");
610             }
611         }
612 
613         foreach(i; 0 .. rows)
614         {
615             if(writeIndex)
616             {
617                 bool skipIndex = true;
618                 foreach(j; 0 .. indx.row.index.length)
619                 {
620                     string idx = "";
621                     if(indx.row.codes[j].length == 0)
622                         idx = indx.row.index[j][i];
623                     else if(indx.row.index[j].length == 0)
624                         idx = to!string(indx.row.codes[j][i]);
625                     else if(i > 0 && j < indx.row.index.length
626                         && indx.row.codes[j][i] == indx.row.codes[j][i - 1]
627                         && skipIndex && indx.isMultiIndexed)
628                         idx = "";
629                     else
630                     {
631                         idx = indx.row.index[j][indx.row.codes[j][i]];
632                         skipIndex = false;
633                     }
634 
635                     formatter.put(idx);
636                     formatter.put(sep);
637                 }
638             }
639             formatter.put(formatData(data[0][i]));
640             static foreach(j; 1 .. RowType.length)
641             {
642                 formatter.put(sep);
643                 formatter.put(formatData(data[j][i]));
644             }
645 
646             formatter.put("\n");
647         }
648 
649         import std.stdio: File;
650         File outfile = File(path, "w");
651         outfile.write(formatter.data);
652         outfile.close();
653     }
654 
655     /++
656     void from_csv(string path, size_t indexDepth, size_t columnDepth, size_t[] column.index = [], char sep = ',')
657     Description: Parsing of DataFrame from a CSV file
658     @params: path - File path of csv file
659     @params: indexDepth - Number of column row index span
660     @params: columnDepth - Number of rows column index span
661     @params: column.index - Integer row.index of column to selectively parse
662     @params: sep - Data seperator in the file
663     +/
664     void from_csv(string path, size_t indexDepth, size_t columnDepth, size_t[] columns = [], char sep = ',')
665     {
666         import std.array: appender, split;
667         import std.stdio: File;
668         import std.string: chomp;
669 
670         if(columns.length == 0)
671         {
672             auto all = appender!(size_t[]);
673             foreach(i; 0 .. cols)
674                 all.put(i);
675             columns = all.data;
676         }
677 
678         assert(columns.length == cols, "The dimension of columns must be same as dimension of the DataFrame");
679 
680         File csvfile = File(path, "r");
681         bool bothTitle = false;
682         size_t line = 0;
683         indx = Index();
684 
685         indx.row.codes.length = indexDepth;
686         indx.row.index.length = indexDepth;
687 
688         size_t dataIndex = 0;
689         while(!csvfile.eof())
690         {
691             string[] fields = chomp(csvfile.readln()).split(sep);
692 
693             if(line < columnDepth)
694             {
695                 if(indexDepth > 0 && line == columnDepth - 1 && fields[0].length > 0)
696                 {
697                     indx.row.titles = fields[0 .. indexDepth];
698                 }
699                 else if(indexDepth > 0 && fields[indexDepth - 1].length > 0)
700                 {
701                     indx.column.titles ~= fields[indexDepth - 1];
702                     bothTitle = true;
703                 }
704 
705                 indx.column.index ~= [[]];
706                 indx.column.codes ~= [[]];
707                 foreach(i; 0 .. cols)
708                 {
709                     size_t pos = columns[i];
710                     string colindx = fields[indexDepth + pos];
711 
712                     if(i > 0 && colindx.length == 0)
713                     {
714                         indx.column.codes[line] ~= indx.column.codes[line][$ - 1];
715                     }
716                     else
717                     {
718                         import std.algorithm: countUntil;
719                         int idxpos = cast(int)countUntil(indx.column.index[line], colindx);
720 
721                         if(idxpos > -1)
722                         {
723                             indx.column.codes[line] ~= cast(int)idxpos;
724                         }
725                         else
726                         {
727                             indx.column.index[line] ~= colindx;
728                             indx.column.codes[line] ~= cast(uint)indx.column.index[line].length - 1;
729                         }
730                     }
731                 }
732             }
733             else if(line == columnDepth && bothTitle)
734             {
735                 indx.row.titles = fields[0 .. indexDepth];
736             }
737             else
738             {
739                 if(indexDepth == 1 && columnDepth == 1 && line == columnDepth && fields.length == 1)
740                 {
741                     bothTitle = true;
742                     indx.row.titles = fields;
743                 }
744                 else if(fields.length > 0)
745                 {
746                     foreach(i; 0 .. indexDepth)
747                     {
748                         import std.algorithm: countUntil;
749                         int indxpos = cast(int)countUntil(indx.row.index[i], fields[i]);
750                         if(fields[i].length == 0 && dataIndex > 0)
751                         {
752                             indx.row.codes[i] ~= indx.row.codes[$ - 1];
753                         }
754                         else if(indxpos > -1)
755                         {
756                             indx.row.codes[i] ~= cast(uint)indxpos;
757                         }
758                         else
759                         {
760                             indx.row.index[i] ~= fields[i];
761                             indx.row.codes[i] ~= cast(uint)indx.row.index[i].length - 1;
762                         }
763                     }
764 
765                     static foreach(i; 0 .. RowType.length)
766                     {
767 
768                         if(fields.length > (columns[i] + indexDepth))
769                         {
770                             import std.conv: to, ConvException;
771 
772                             try
773                             {
774                                 data[i] ~= to!(RowType[i])(fields[columns[i] + indexDepth]);
775                             }
776                             catch(ConvException e)
777                             {
778                                 data[i] ~= RowType[i].init;
779                             }
780                         }
781                         else
782                         {
783                             data[i] ~= RowType[i].init;
784                         }
785                     }
786                 }
787             }
788 
789             if(fields.length > 0)
790                 ++line;
791         }
792         csvfile.close();
793 
794         rows = line - columnDepth - ((bothTitle)?1: 0);
795 
796         if(indexDepth == 0)
797         {
798             indx.row.titles ~= "Index";
799             indx.row.index = [[]];
800             indx.row.codes = [[]];
801             foreach(i; 0 .. rows)
802                 indx.row.codes[0] ~= cast(uint)i;
803         }
804 
805         if(columnDepth == 0)
806         {
807             indx.column.index = [[]];
808             indx.column.codes = [[]];
809             foreach(i; 0 .. indexDepth)
810                 indx.row.titles ~= ["Index"];
811             foreach(i; 0 .. line)
812                 indx.column.codes[0] ~= cast(uint)i;
813         }
814 
815         indx.optimize();
816     }
817 
818     /++
819     from_csv rebuild for faster read
820     +/
821     void fastCSV(string path, size_t indexDepth, size_t columnDepth, char sep = ',')
822     {
823         import std.array: array, split;
824         import std.algorithm: map;
825         import std.stdio: File;
826         import std.string: chomp;
827 
828         File csvfile = File(path, "r");
829         string[][] lines = csvfile.byLineCopy().map!(chomp).map!(e => e.split(",")).array();
830         int totalLines = cast(int)lines.length;
831         csvfile.close();
832 
833         indx.row.titles.length = indexDepth;
834         indx.row.index.length = indexDepth;
835         indx.column.index.length = columnDepth;
836         indx.row.codes.length = indexDepth;
837         indx.column.codes.length = columnDepth;
838         indx.row.titles = lines[columnDepth - 1][0 .. indexDepth];
839 
840         foreach(i, ele; lines[0 .. columnDepth])
841             indx.column.index[i] = ele[indexDepth .. $];
842 
843         foreach(i, ele; lines[columnDepth .. $])
844         {
845             if(ele.length == 0)
846                 continue;
847 
848             foreach(j; 0 .. indexDepth)
849             {
850                 ++indx.row.index[j].length;
851                 indx.row.index[j][i] = ele[j];
852             }
853 
854             static foreach(j; 0 .. RowType.length)
855             {
856                 import std.conv: to;
857                 ++data[j].length;
858                 if(ele[indexDepth + j].length == 0)
859                     data[j][i] =  RowType[j].init;
860                 else
861                     data[j][i] = to!(RowType[j])(ele[indexDepth + j]);
862             }
863         }
864 
865         rows = totalLines - columnDepth;
866         if(indx.row.index.length == 0)
867         {
868             indx.row.index.length = 1;
869             indx.row.codes.length = 1;
870             indx.row.codes[0].length = rows;
871             foreach(i; 0 .. rows)
872                 indx.row.codes[0][i] = cast(int)i;
873 
874             indx.row.titles = ["Index"];
875         }
876 
877         if(indx.column.index.length == 0)
878         {
879             indx.column.index.length = 1;
880             indx.column.codes.length = 1;
881             indx.column.codes[0].length = rows;
882             foreach(i; 0 .. rows)
883                 indx.column.codes[0][i] = cast(int)i;
884 
885             indx.row.titles.length = indx.row.codes.length;
886             foreach(i, ref ele;indx.row.titles)
887             {
888                 import std.conv: to;
889                 ele = "Index" ~ to!(string)(i + 1);
890             }
891         }
892 
893         indx.generateCodes();
894     }
895 
896     /++
897     RowType[i2] at(size_t i1, size_t i2)()
898     Description: Getting the element directly from its index
899     @param: ii - Row index
900     @param: i2 - Column index
901     +/
902     RowType[i2] at(size_t i1, size_t i2)() @property
903         if(i2 < RowType.length)
904     {
905         return data[i2][i1];
906     }
907 
908     RowType[i2] at(size_t i2)(size_t i1) @property
909         if(i2 < RowType.length)
910     {
911         return data[i2][i1];
912     }
913 
914     auto at(size_t i1, size_t i2) @property
915     {
916         assert(i2 < cols && i1 < rows, "Index out of bound");
917 
918         auto returnAux(ptrdiff_t si = -1)(size_t ri = 0) @property
919         {
920             static if(si > -1)
921                 alias i = si;
922             else
923                 size_t i = ri;
924 
925             return data[i][i1];
926         }
927         
928         mixin auxDispatch!(returnAux, isHomogeneousType, RowType);
929         return auxDispatch(i2);
930     }
931 
932     /++
933     void opIndexAssign(Args...)(Args ele, size_t i1, size_t i2)
934     Description: Setting the element at an index
935     @param: ii - Row index
936     @param: i2 - Column index
937     +/
938     void opIndexAssign(Args...)(Args ele, size_t i1, size_t i2)
939         if(Args.length > 0)
940     {
941         assert(i1 < rows && i2 < cols, "Index out of bound");
942 
943         void assignAux(ptrdiff_t si = -1)(size_t ri = 0) @property
944         {
945             static if(si > -1)
946                 alias i = si;
947             else
948                 size_t i = ri;
949 
950             data[i][i1] = ele[0];
951         }
952 
953         mixin auxDispatch!(assignAux, isHomogeneousType, RowType);
954         auxDispatch(i2);
955     }
956 
957     /++
958     void opIndexAssign(Args...)(Args ele, string[] rindx, string[] cindx)
959     Description: Setting the element at an index
960     @param: rindx - Row index
961     @param: cindx - Column index
962     +/
963     void opIndexAssign(Ele)(Ele ele, string[] rindx, string[] cindx)
964     {
965         assert(rindx.length == indx.row.codes.length, "Size of row index don't match the index depth");
966         assert(cindx.length == indx.column.codes.length, "Size of column index don't match the index depth");
967 
968         ptrdiff_t i1 = getPosition!0(rindx);
969         ptrdiff_t i2 = getPosition!1(cindx);
970 
971         assert(i1 > -1 && i2 > -1, "Given headers don't match DataFrame Headers");
972 
973         void assignAux(ptrdiff_t si = -1)(size_t ri = 0) @property
974         {
975             static if(si > -1)
976                 alias i = si;
977             else
978                 size_t i = ri;
979 
980             data[i][i1] = ele;
981         }
982 
983         mixin auxDispatch!(assignAux, isHomogeneousType, RowType);
984         auxDispatch(i2);
985     }
986 
987     /++
988     int getRowPos(string[] row.index)
989     Description: Get integer index of the given row headers
990     Defaults to -1 if headers don't match
991     @params: row.index - Array of row.index
992     +/
993     ptrdiff_t getRowPosition(string[] indexes)
994     {
995         assert(indexes.length == indx.row.codes.length, "Size of row index don't match the indexing depth");
996         return getPosition!0(indexes);
997     }
998 
999     /++
1000     int getColPos(string[] row.index)
1001     Description: Get integer index of the given column headers
1002     Defaults to -1 if headers don't match
1003     @params: row.index - Array of row.index
1004     +/
1005     ptrdiff_t getColumnPosition(string[] indexes)
1006     {
1007         assert(indexes.length == indx.column.codes.length, "Size of column index doesn't match indexing depth");
1008         return getPosition!1(indexes);
1009     }
1010 
1011     /++
1012     void setFrameIndex(Index index)
1013     Description: Sets the frame row.index.
1014     [Please use setIndex method of Index to set the index]
1015     @params: index - Index structure to replace indexing
1016     +/
1017     void setFrameIndex(Index index)
1018     {
1019         assert(index.row.codes.length > 0 || index.column.codes.length > 0
1020             || index.row.titles.length > 0 || index.column.titles.length > 0,
1021             "Cannot set empty index to DataFrame");
1022 
1023         bool needsPadding = false;
1024 
1025         foreach(i; 0 .. 2)
1026         {
1027             if(index.indexing[i].codes.length > 0 && index.indexing[i].codes[0].length >= ((i)? cols: rows))
1028             {
1029                 indx.indexing[i].codes = index.indexing[i].codes;
1030                 indx.indexing[i].index = index.indexing[i].index;
1031                 indx.indexing[i].titles = index.indexing[i].titles;
1032             }
1033 
1034             if(index.indexing[i].codes.length == 0 && index.indexing[i].titles.length == indx.indexing[i].codes.length)
1035             {
1036                 indx.indexing[i].titles = index.indexing[i].titles;
1037             }
1038 
1039             if(i == 0 && index.row.codes[0].length != rows)
1040             {
1041                 needsPadding = true;
1042                 rows = index.row.codes[0].length;
1043             }
1044         }
1045 
1046         if(indx.column.codes.length == 0)
1047         {
1048             indx.column.index = [[]];
1049             indx.column.codes = [[]];
1050             indx.column.codes[0].length = cols;
1051             foreach(i; 0 .. cols)
1052                 indx.column.codes[0][i] = cast(int)i;
1053         }
1054 
1055         if(needsPadding)
1056         {
1057             static foreach(i; 0 .. RowType.length)
1058             {
1059                 data[i].length += rows - data[i].length;
1060                 foreach(j; data[i].length .. rows)
1061                     data[i][j] = RowType[i].init;
1062             }
1063         }
1064     }
1065 
1066     /++
1067     Direct assignment of a 2D array to DataFrame
1068     Usage: df = [[1,2], [3,4]];
1069     +/
1070     void opAssign(T)(T input)
1071         if(isArray!(T))
1072     {
1073         import std.algorithm: map, reduce, max;
1074         size_t l1 = input.length;
1075         size_t l2 = input.map!(e => e.length).reduce!max;
1076         assert(l1 > 0 && l2 > 0, "Cannot assign empty array to DataFrame");
1077         assert(l1 <= rows, "Cannot implicitly assign values of length larger than that of the DataFrame");
1078         assert(l2 <= cols, "Cannot implicitly assign values of dimension larger than that of the DataFrame");
1079 
1080         foreach(i; 0 .. l1)
1081         {
1082             static foreach(j; 0 .. RowType.length)
1083             {
1084                 if(input[i].length > j)
1085                     data[j][i] = cast(RowType[j])input[i][j];
1086                 else
1087                     data[j][i] = RowType[j].init;
1088             }
1089         }
1090 
1091         foreach(i; l1 .. rows)
1092         {
1093             static foreach(j; 0 .. RowType.length)
1094             {
1095                 data[j][i] = RowType[j].init;
1096             }
1097         }
1098     }
1099 
1100     /// Assign Slice to DataFrame
1101     void opAssign(T, SliceKind k)(Slice!(T*, 2, k) input)
1102     {
1103         assert(input.shape[0] <= rows && input.shape[1] <= cols, "Given Slice is larger than DataFrame");
1104         static foreach(i; 0 .. RowType.length)
1105             if(i < input.shape[1])
1106             {
1107 
1108                 foreach(j; 0 .. input.shape[0])
1109                     static if(__traits(isArithmetic, T, RowType[i]))
1110                         data[i][j] = cast(RowType[i])input[j][i];
1111                     else
1112                     {
1113                         import std.conv: to;
1114                         data[i][j] = to!(RowType[i])(input[j][i]);
1115                     }
1116             }
1117             
1118     }
1119 
1120     /++
1121     void assign(int axis, T, U...)(T index, U values)
1122     Description: Assign values to rows or column.index
1123     @params: axis - 0 for rows, 1 for column.index
1124     @params: index - string[] or integer index of the location to assign
1125     @params: values - values to assign
1126     +/
1127     void assign(int axis, T, U...)(T index, U values)
1128         if(U.length > 0)
1129     {
1130 
1131         ptrdiff_t pos;
1132         static if(is(T == int))
1133             pos = index;
1134         else
1135             pos = getPosition!(axis)(index);
1136 
1137         assert(pos > -1 && pos < (axis)? rows: cols, "Index out of bound");
1138 
1139         static if(axis == 0)
1140         {
1141             static foreach(i; 0 .. (RowType.length > U.length)? U.length: RowType.length)
1142                 data[i][pos] = values[i];
1143         }
1144         else
1145         {
1146             void assignAux(ptrdiff_t si = -1)(size_t ri = 0) @property
1147             {
1148                 static if(si > -1)
1149                 {
1150                     alias i = si;
1151                     enum size_t typepos = i;
1152                 }
1153                 else
1154                 {
1155                     size_t i = ri;
1156                     enum size_t typepos = 0;
1157                 }
1158 
1159                 static if(is(toArr!(RowType[typepos]) == U[0]))
1160                 {
1161                     foreach(j; 0 .. (rows > values[0].length)? values[0].length: rows)
1162                         data[i][j] = values[0][j];
1163                 }
1164             }
1165             
1166             mixin auxDispatch!(assignAux, isHomogeneousType, RowType);
1167             auxDispatch(pos);
1168         }
1169     }
1170 
1171     /++
1172     Element access based on integral index
1173     Usage: df[0, 0]
1174     +/
1175     auto opIndex(size_t i1, size_t i2)
1176     {
1177         assert(i1 <= rows, "Row index out of bound");
1178         assert(i2 <= cols, "Column index out of bound");
1179 
1180         auto returnAux(ptrdiff_t si = -1)(size_t ri = 0) @property
1181         {
1182             static if(si > -1)
1183                 alias i = si;
1184             else
1185                 size_t i = ri;
1186 
1187             return data[i][i1];
1188         }
1189 
1190         mixin auxDispatch!(returnAux, isHomogeneousType, RowType);
1191         return auxDispatch(i2);
1192     }
1193 
1194     /++
1195     Element access based on integral index
1196     Usage: df[["Index1"], ["Index2"]]
1197     +/
1198     auto opIndex(T, U)(T rindx, U cindx)
1199         if((is(T == string) || is(T == string[]))
1200         && (is(U == string) || is(U == string[])))
1201     {
1202         ptrdiff_t i1 = -1;// getPosition!0(rindx);
1203         ptrdiff_t i2 = -1;//getPosition!1(cindx);
1204 
1205         static if(is(T == string))
1206             i1 = getPosition!0([rindx]);
1207         else
1208             i1 = getPosition!0(rindx);
1209 
1210         static if(is(U == string))
1211             i2 = getPosition!1([cindx]);
1212         else
1213             i2 = getPosition!1(cindx);
1214 
1215         assert(i1 > -1 && i2 > -1, "Given headers don't match DataFrame Headers");
1216         
1217         auto returnAux(ptrdiff_t si = -1)(size_t ri = 0) @property
1218         {
1219             static if(si > -1)
1220                 alias i = si;
1221             else
1222                 size_t i = ri;
1223 
1224             return data[i][i1];
1225         }
1226 
1227         mixin auxDispatch!(returnAux, isHomogeneousType, RowType);
1228         return auxDispatch(i2);
1229     }
1230 
1231     /++
1232     Get an entire row or column using index
1233     For column: df[["Index"]]
1234     For row: df[["Index"], 0]
1235     +/
1236     auto opIndex(Args...)(string[] index, Args args)
1237         if(Args.length == 0 || (Args.length == 1 && is(Args[0] == int)))
1238     {
1239         const int axis = 1 - Args.length;
1240         ptrdiff_t pos = -1;
1241         if(axis == 0)
1242             pos = getPosition!0(index);
1243         else
1244             pos = getPosition!1(index);
1245 
1246         assert(pos > -1, "Index not found");
1247 
1248         static if(axis == 1)
1249         {
1250             import magpie.axis: Axis, DataType;
1251             Axis!void retcol;
1252 
1253             void axisAssignAux(ptrdiff_t si = -1)(size_t ri = 0) @property
1254             {
1255                 static if(si > -1)
1256                     alias i = si;
1257                 else
1258                     size_t i = ri;
1259                     
1260                 retcol.data.length = data[i].length;
1261                 foreach(j, ele; data[i])
1262                     retcol.data[j] = DataType(ele);
1263             }
1264 
1265             mixin auxDispatch!(axisAssignAux, isHomogeneousType, RowType);
1266             auxDispatch(pos);
1267             return retcol;
1268         }
1269         else
1270         {
1271             import magpie.axis: Axis;
1272             Axis!(RowType) retrow;
1273             static foreach(i; 0 .. RowType.length)
1274                 retrow.data[i] = data[i][pos];
1275 
1276             return retrow;
1277         }
1278 
1279         assert(0);
1280     }
1281 
1282     /++
1283     Column/Row binary operations
1284     df[["CIndex1"]] = df[["CIndex2"]] + df[["CIndex3"]];
1285     df[["RIndex1"], 0] = df[["RIndex2"], 0] + df[["RIndex3"], 0];
1286     +/
1287     void opIndexAssign(T...)(Axis!T elements, string[] index, int axis = 1)
1288         if(T.length == RowType.length || T.length == 1)
1289     {
1290         opIndexOpAssign!("")(elements, index, axis);
1291     }
1292 
1293     /// Short Hand operations
1294     void opIndexOpAssign(string op, T...)(Axis!T elements, string[] index, int axis = 1)
1295         if(T.length == RowType.length || T.length == 1)
1296     {
1297         import std.traits: isArray;
1298         static if(is(T[0] == void) || (T.length == 1 && isArray!(T[0])))
1299         {
1300             assert(elements.data.length == rows, "Length of Axis.data is not equal to number of rows");
1301             ptrdiff_t pos = getPosition!1(index);
1302             assert(pos > -1, "Index not found");
1303 
1304             void mixinAux(ptrdiff_t si = -1)(size_t ri = 0) @property
1305             {
1306                 static if(si > -1)
1307                 {
1308                     alias i = si;
1309                     enum size_t typepos = si;
1310                 }
1311                 else
1312                 {
1313                     size_t i = ri;
1314                     enum size_t typepos = 0;
1315                 }
1316 
1317                 static if(is(T[0] == void))
1318                     enum string append = ".get!(RowType[typepos]);";
1319                 else
1320                     enum string append = ";";
1321 
1322                 foreach(j; 0 .. elements.data.length)
1323                     mixin("data[i][j] " ~ op ~"= elements.data[j]" ~ append);
1324             }
1325             
1326             mixin auxDispatch!(mixinAux, isHomogeneousType, RowType);
1327             auxDispatch(pos);
1328         }
1329         else
1330         {
1331             assert(elements.data.length == RowType.length, "Length of Axis.data is less than the number of columns");
1332             ptrdiff_t pos = getPosition!0(index);
1333             assert(pos > -1, "Index not found");
1334             static foreach(i; 0 .. RowType.length)
1335                 mixin("data[i][pos] " ~ op ~ "= elements.data[i];");
1336         }
1337     }
1338 
1339     /// Slice assignment operation
1340     void opIndexAssign(T, SliceKind kind)(Slice!(T*, 1, kind) sl, string[] index, int axis = 1)
1341     {
1342         opIndexOpAssign!("")(sl, index, axis);
1343     }
1344 
1345     /// Slice assignment operation
1346     void opIndexOpAssign(string op, T, SliceKind kind)(Slice!(T*, 1, kind) sl, string[] index, int axis = 1)
1347     {
1348         if(axis)
1349         {
1350             Axis!void fwd;
1351             fwd.data.length = sl.shape[0];
1352             foreach(i; 0 .. sl.shape[0])
1353                 fwd.data[i] = DataType(sl[i]);
1354 
1355             opIndexOpAssign!(op)(fwd, index, axis);
1356         }
1357         else
1358         {
1359             Axis!(RowType) fwd;
1360             static foreach(i; 0 .. RowType.length)
1361                 static if(__traits(isArithmetic, RowType[i], T))
1362                     fwd.data[i] = cast(RowType[i])sl[i];
1363                 else
1364                 {
1365                     import std.conv: to;
1366                     fwd.data[i] = to!(RowType[i])(sl[i]);
1367                 }
1368 
1369             opIndexOpAssign!(op)(fwd, index, axis);
1370         }
1371     }
1372 
1373     /++
1374     void apply(alias Fn, int axis, T)(T index)
1375     Description: Applies a function on a particular row or column
1376     @params: Fn - Function
1377     @params: axis - 0 for rows, 1 for columns
1378     @params: index - integer or string indexes of rows
1379     +/
1380     void apply(alias Fn, int axis, T)(T index)
1381         if(is(T == int[]) || is(T == string[][]))
1382     {
1383         if(index.length == 0)
1384             return;
1385 
1386         static if(is(T == string[][]))
1387             foreach(i; index)
1388                 assert(i.length == indx.indexing[axis].codes.length, "Index level mismatch");
1389 
1390         ptrdiff_t[] pos;
1391         static if(is(T == int[]))
1392         {
1393             pos.length = index.length;
1394             foreach(i, ele; index)
1395                 pos[i] = ele;
1396         }
1397         else
1398         {
1399             pos.length = index.length;
1400             foreach(i, ele; index)
1401                 pos[i] = getPosition!(axis)(ele);
1402         }
1403 
1404         static if(axis == 0)
1405         {
1406             static foreach(i; 0 .. RowType.length)
1407             {
1408                 foreach(j; pos)
1409                     data[i][j] = cast(RowType[i])Fn(data[i][j]);
1410             }
1411         }
1412         else
1413         {
1414             static foreach(i; 0 .. RowType.length)
1415             {
1416                 import std.algorithm: countUntil;
1417                 if(countUntil(pos, i) > -1)
1418                     foreach(j; 0 .. rows)
1419                         data[i][j] = cast(RowType[i])Fn(data[i][j]);
1420             }
1421         }
1422     }
1423 
1424     /++
1425     void apply(alias Fn, int axis, T)(T index)
1426     Description: apply overload - apply to all data
1427     @params: Fn - Function
1428     @params: axis - 0 for rows, 1 for columns
1429     +/
1430     void apply(alias Fn)()
1431     {
1432         static foreach(i; 0 .. RowType.length)
1433             foreach(j; 0 .. rows)
1434                 data[i][j] = cast(RowType[i])Fn(data[i][j]);
1435     }
1436 
1437     /++
1438     auto drop(int axis, int[] positions)() @property
1439     Description: Drop a row/column from DataFrame
1440     @params: axis - 0 for dropping row, 1 for dropping columns
1441     @params: positions - integer array of positions of all the rows/colmns to be dropped
1442     +/
1443     auto drop(int axis, int[] positions)() @property
1444     {
1445         import magpie.helper: dropper;
1446         import std.algorithm: reduce, max, min;
1447 
1448         static if(positions.length == 0)
1449         {
1450             return this;
1451         }
1452         else static if(axis == 0)
1453             return dropperRuntimeInternal(positions);
1454         else
1455         {
1456             assert(positions.reduce!min > -1 && positions.reduce!max < cols, "Index out of bound");
1457             DataFrame!(true, dropper!(positions, RowType)) ret;
1458             ret.indx = indx;
1459             ret.indx = Index();
1460             ret.indx.indexing[0] = indx.indexing[0];
1461             ret.indx.column.titles = indx.column.titles;
1462 
1463             ret.indx.column.index.length = indx.column.index.length;
1464             ret.indx.column.codes.length = indx.column.index.length;
1465 
1466             import std.range: lockstep;
1467             foreach(pos, a, b; lockstep(indx.column.index, indx.column.codes))
1468             {
1469                 ret.indx.column.index[pos] = a;
1470                 ret.indx.column.codes[pos] = dropper(positions, b);
1471             }
1472 
1473             static if(isHomogeneousType)
1474                 auto retdata = dropper(positions, data);
1475             else
1476                 auto retdata = dropper!(positions, data);
1477             
1478             static foreach(i; 0 .. ret.RowType.length)
1479                 ret.data[i] = retdata[i];
1480 
1481             ret.rows = rows;
1482             return ret;
1483         }
1484     }
1485 
1486     /++
1487     auto columnToIndex(int position)() @property
1488     Description: Converts a column into row index level
1489     @params: position - integral index of column to be converted to index
1490     +/
1491     auto columnToIndex(int position)() @property
1492     {
1493         assert(position > -1 && position < cols, "Index out of bound");
1494 
1495         import std.conv: to;
1496         auto ret = this.drop!(1, [position]);
1497         static if(is(RowType[position] == string))
1498         {
1499             ret.indx.row.index ~= data[position];
1500             ret.indx.row.codes ~= [[]];
1501         }
1502         else static if(is(RowType[position] == int))
1503         {
1504             ret.indx.row.index ~= [[]];
1505             ret.indx.row.codes ~= data[position];
1506         }
1507         else
1508         {
1509             ret.indx.row.index ~= to!(string[])(data[position]);
1510             ret.indx.row.codes ~= [[]];
1511         }
1512 
1513         if(indx.column.index[$ - 1].length == 0)
1514             ret.indx.row.titles ~= to!(string)(indx.column.codes[$ - 1][position]);
1515         else
1516             ret.indx.row.titles ~= indx.column.index[$ - 1][indx.column.codes[$ - 1][position]];
1517 
1518         ret.indx.generateCodes();
1519 
1520         return ret;
1521     }
1522 
1523     /++
1524     auto indexToData(int position, Dtype...)(int indexLevel, string[] dataIndex)
1525     Description: Convers a level of indexing to data
1526     @params: position - position to insert data at
1527     @params: DataType - Data type for the new data column
1528     @params: indexLevel - Index level to convert to data
1529     @params: dataIndex - Index for the new data column
1530     +/
1531     auto indexToData(int position, DataType)(int indexLevel, string[] dataIndex)
1532         if(position <= RowType.length)
1533     {
1534         DataFrame!(true, RowType[0 .. position], DataType, RowType[position .. $]) ret;
1535         ret.indx = indx;
1536         ret.indx.extend!1(dataIndex, position);
1537 
1538         ret.indx.row.index = ret.indx.row.index[0 .. indexLevel] ~ ret.indx.row.index[indexLevel + 1 .. $];
1539         ret.indx.row.codes = ret.indx.row.codes[0 .. indexLevel] ~ ret.indx.row.codes[indexLevel + 1 .. $];
1540         ret.indx.row.titles = ret.indx.row.titles[0 .. indexLevel] ~ ret.indx.row.titles[indexLevel + 1 .. $];
1541 
1542         ret.indx.optimize();
1543 
1544         static foreach(i; 0 .. RowType.length + 1)
1545         {
1546             static if(i < position)
1547                 ret.data[i] = data[i];
1548             else static if(i == position)
1549             {
1550                 import std.conv: to;
1551                 import std.array: array;
1552                 import std.algorithm: map;
1553 
1554                 toArr!DataType newData;
1555                 if(indx.row.index[indexLevel].length == 0)
1556                     newData = to!(toArr!DataType)(indx.row.codes[indexLevel]);
1557                 else
1558                     newData = to!(toArr!DataType)(indx.row.codes[indexLevel].map!(e => indx.row.index[indexLevel][e]).array());
1559 
1560                 ret.data[i] = newData;
1561             }
1562             else
1563                 ret.data[i] = data[i - 1];
1564         }
1565 
1566         ret.rows = rows;
1567         return ret;
1568     }
1569 
1570     /++
1571     Group DataFrame rows based on column values
1572     Params:
1573         dataLevels: Integer position of data columns to consider for groupBy
1574         indexLevels: Levels of row index to consider for grouping
1575     Returns:
1576         A Group object
1577     +/
1578     auto groupBy(int[] dataLevels = [])(int[] indexLevels = [])
1579     {
1580         import magpie.group: Group;
1581         import magpie.helper: dropper;
1582 
1583         Group!(dropper!(dataLevels, RowType)) grp;
1584         grp.createGroup!(dataLevels)(this, indexLevels);
1585 
1586         return grp;
1587     }
1588 
1589     /++
1590     Get DataFrame data as a Slice
1591     Params:
1592         Type: Iterator type for the resultant slice
1593         Kind: SliceKind for the resultant Slice
1594     Returns:
1595         A Slice of type Slice!(Type*, 2, kind)
1596     +/
1597     auto asSlice(Type, SliceKind kind)() @property
1598     {
1599         static if(__traits(isArithmetic, Type))
1600             alias RetType = Type;
1601         else
1602             alias RetType = string;
1603 
1604         static if(kind == Universal)
1605             Slice!(RetType*, 2, kind) ret = slice!(RetType)(rows, cols).universal;
1606         else static if(kind == Canonical)
1607             Slice!(RetType*, 2, kind) ret = slice!(RetType)(rows, cols).canonical;
1608         else
1609             Slice!(RetType*, 2, kind) ret = slice!(RetType)(rows, cols);
1610 
1611         static foreach(i; 0 .. RowType.length)
1612         {
1613             static if(__traits(isArithmetic, RowType[i], RetType) || is(RetType == RowType[i]))
1614             {
1615                 foreach(j; 0 .. rows)
1616                     ret[j][i] = cast(RetType)data[i][j];
1617             }
1618             else static if(is(RetType == string))
1619             {
1620                 import std.conv: to;
1621                 foreach(j; 0 .. rows)
1622                     ret[j][i] = to!(string)(data[i][j]);
1623             }
1624         }
1625 
1626         return ret;
1627     }
1628 
1629     auto asSlice(SliceKind kind, Type = string, int axis = 0, T)(T index)
1630         if(is(T == string[]) || is(T == int))
1631     {
1632         ptrdiff_t pos;
1633         static if(is(T == string[]))
1634             pos = getPosition!(axis)(index);
1635         else
1636             pos = index;
1637 
1638         assert(pos > -1, "Index not found");
1639         static if(axis)
1640         {
1641             static if(kind == Universal)
1642                 Slice!(Type*, 1, kind) ret = slice!(Type)(rows).universal;
1643             else static if(kind == Canonical)
1644                 Slice!(Type*, 1, kind) ret = slice!(Type)(rows).canonical;
1645             else
1646                 Slice!(Type*, 1, kind) ret = slice!(Type)(rows);
1647 
1648             void sliceAssignAux(ptrdiff_t si = -1)(size_t ri = 0) @property
1649             {
1650                 static if(si > -1)
1651                 {
1652                     alias i = si;
1653                     enum size_t typepos = si;
1654                 }
1655                 else
1656                 {
1657                     size_t i = ri;
1658                     enum size_t typepos = 0;
1659                 }
1660 
1661                 static if(__traits(isArithmetic, Type, RowType[typepos]) || is(Type == RowType[typepos]))
1662                     foreach(j; 0 .. rows)
1663                         ret[j] = cast(Type)data[i][j];
1664             }
1665             
1666             mixin auxDispatch!(sliceAssignAux, isHomogeneousType, RowType);
1667             auxDispatch(pos);
1668             return ret;
1669         }
1670         else
1671         {
1672             import std.conv: to;
1673             Slice!(string*, 1, kind) ret;
1674             static if(kind == Universal)
1675                 ret = slice!(string)(RowType.length).universal;
1676             else static if(kind == Canonical)
1677                 ret = slice!(string)(RowType.length).canonical;
1678             else
1679                 ret = slice!(string)(RowType.length);
1680 
1681             static foreach(i; 0 .. RowType.length)
1682                 ret[i] = to!string(data[i][pos]);
1683 
1684             return ret;
1685         }
1686     }
1687 
1688     /++
1689     Applies mathematical operations on DataFrame row/column
1690     Params:
1691         axis: 0 to compute row wise, 1 to compute column wise
1692         Ops: Mathematical operations to apply
1693     Returns:
1694         DataFrame with computed operations  
1695     +/
1696     auto aggregate(int axis, Ops...)() @property
1697     {
1698         void init(int axis, T)(ref T args)
1699         {
1700             enum int perpendicular = ((axis)? 0: 1);
1701             
1702             args.indx.indexing[axis] = indx.indexing[axis];
1703             args.indx.indexing[perpendicular].index.length = 1;
1704             args.indx.indexing[perpendicular].index[0].length = Ops.length;
1705             args.indx.indexing[perpendicular].codes.length = 1;
1706 
1707             static foreach(i; 0 .. Ops.length)
1708                 ret.indx.indexing[perpendicular].index[0][i] = __traits(identifier, Ops[0]);
1709             
1710             static if(axis)
1711             {
1712                 args.indx.row.titles = ["Operation"];
1713                 args.rows = Ops.length;
1714 
1715                 static foreach(i; 0 .. RowType.length)
1716                     ret.data[i].length = Ops.length;
1717             }
1718             else
1719             {
1720                 args.rows = rows;
1721                 static foreach(i; 0 .. Ops.length)
1722                     ret.data[i].length = rows;
1723             }
1724         }
1725 
1726         static if(axis)
1727         {
1728             import std.meta: staticMap;
1729 
1730             alias Resolve(T) = suitableType!(resolverInternal!(T, Ops));
1731             DataFrame!(true, staticMap!(Resolve, RowType)) ret;
1732             init!(axis)(ret);
1733 
1734             static foreach(i; 0 .. RowType.length)
1735                 static if(__traits(isArithmetic, RowType[i]))
1736                     static foreach(j; 0 .. Ops.length)
1737                     {
1738                         static if(__traits(compiles, Ops[j](data[i])))
1739                             ret.data[i][j] = Ops[j](data[i]);
1740                         else
1741                         {
1742                             import std.algorithm: map, reduce;
1743                             if(__traits(compiles, data[i].map!(e => e).reduce!(Ops[j])))
1744                                 ret.data[i][j] = cast(ret.RowType[i])data[i].map!(e => e).reduce!(Ops[j]);
1745                         }
1746                     }
1747         }
1748         else
1749         {
1750             DataFrame!(true, aggregateType!(Ops)) ret;
1751             init!(axis)(ret);
1752 
1753             suitableType!(RowType)[RowType.length] oparr;
1754             size_t k;
1755 
1756             foreach(i; 0 .. rows)
1757             {
1758                 k = 0;
1759                 static foreach(j; 0 .. RowType.length)
1760                     static if(__traits(isArithmetic, RowType[j]))
1761                     {
1762                         oparr[k] = data[j][i];
1763                         ++k;
1764                     }
1765                 
1766                 static foreach(j; 0 .. Ops.length)
1767                 {
1768                     static if(__traits(compiles, Ops[j](oparr[0 .. k])))
1769                     {
1770                         if(k > 0)
1771                             ret.data[j][i] = Ops[j](oparr[0 .. k]);
1772                     }
1773                     else
1774                     {
1775                         import std.algorithm: map, reduce;
1776                         if(k > 0 && __traits(compiles, oparr[0 .. k].map!(e => e).reduce!(Ops[j])))
1777                             ret.data[j][i] = cast(ret.RowType[j])oparr[0 .. k].map!(e => e).reduce!(Ops[j]);
1778                     }
1779                 }
1780             }
1781         }
1782 
1783         ret.indx.generateCodes();
1784         return ret;
1785     }
1786 
1787     /++
1788     Filter the DataFrame based on the result of callback
1789     Params:
1790         filterFunc: Callback function based on whose result, the DataFrame is filtered
1791     Returns:
1792         Filtered DataFrame
1793     +/
1794     auto filter(alias filterFunc)() @property
1795     {
1796         auto applier(alias filterFunc, T...)(T element)
1797         {
1798             static if(isHomogeneousType)
1799                 toArr!(RowType[0]) rowArray = element[0][0];
1800             else
1801                 alias rowArray = element;
1802 
1803             return filterFunc(rowArray);
1804         }
1805 
1806         int[] pos;
1807         int k, count;
1808         pos.length = rows;
1809         
1810         static if(isHomogeneousType)
1811         {
1812             import magpie.helper: transposed;
1813             RowType[0][][] operableData = transposed(data);
1814         }
1815         else
1816             alias operableData = data;
1817 
1818         import std.range: zip;
1819         auto zipped = zip(operableData);
1820         foreach(ele; zipped)
1821         {
1822             if(!applier!filterFunc(ele))
1823             {
1824                 pos[k] = count;
1825                 ++k;
1826             }
1827             ++count;
1828         }
1829 
1830         return dropperRuntimeInternal(pos[0 .. k]);
1831     }
1832 }
1833 
1834 // Testing DataFrame Definition - O(n + log(n))
1835 unittest
1836 {
1837     DataFrame!(int, 2) df;
1838     assert(is(typeof(df.data) == int[][2]));
1839 }
1840 
1841 // O(log(n)) init
1842 unittest
1843 {
1844     DataFrame!(true, double, double, double) df;
1845     assert(is(typeof(df.data) == double[][3]));
1846 }
1847 
1848 // Initialize from struct - O(log(n))
1849 unittest
1850 {
1851     struct Example
1852     {
1853         int x;
1854         double y;
1855     }
1856 
1857     import std.traits: Fields;
1858     DataFrame!(true, Fields!Example) df;
1859     assert(is(typeof(df.data) == AliasSeq!(int[], double[])));
1860 }
1861 
1862 // Community suggested way to declare a DataFrame
1863 unittest
1864 {
1865     DataFrame!(int[3], double) df;
1866     assert(is(df.RowType == AliasSeq!(int, int, int, double)));
1867 }
1868 
1869 // Community suggested way to declare a DataFrame - from struct
1870 unittest
1871 {
1872     struct Example
1873     {
1874         int[2] x;
1875         double[2] y;
1876     }
1877 
1878     import std.traits: Fields;
1879     DataFrame!(Fields!Example) df;
1880     assert(is(df.RowType == AliasSeq!(int, int, double, double)));
1881 }
1882 
1883 // Getting element from it's index
1884 unittest
1885 {
1886     DataFrame!(int, 2) df;
1887     assert(is(typeof(df.data) == int[][2]));
1888 
1889     df.indx.row.titles = ["Index1"];
1890     df.indx.row.index = [["Hello", "Hi"]];
1891     df.indx.row.codes = [[]];
1892     df.indx.column.titles = [];
1893     df.indx.column.index = [["Hello", "Hi"]];
1894     df.indx.column.codes = [[]];
1895     df.rows = 2;
1896     df.data[0] = [1,2];
1897     df.data[1] = [1,2];
1898 
1899     assert(df.at!(0,0) == 1);
1900     assert(df.at!(0,1) == 1);
1901     assert(df.at!(1,0) == 2);
1902     assert(df.at!(1,1) == 2);
1903 
1904     assert(df[0, 0] == 1);
1905     assert(df[0, 1] == 1);
1906     assert(df[1, 0] == 2);
1907     assert(df[1, 1] == 2);
1908 }
1909 
1910 // Setting element at an index
1911 unittest
1912 {
1913     DataFrame!(int, 2) df;
1914     assert(is(typeof(df.data) == int[][2]));
1915 
1916     df.indx.row.titles = ["Index1"];
1917     df.indx.row.index = [["Hello", "Hi"]];
1918     df.indx.row.codes = [[]];
1919     df.indx.column.titles = [];
1920     df.indx.column.index = [["Hello", "Hi"]];
1921     df.indx.column.codes = [[]];
1922     df.rows = 2;
1923     df.data[0] = [1, 2];
1924     df.data[1] = [1, 2];
1925 
1926     df[0, 0] = 42;
1927     assert(df.data[0] == [42, 2]);
1928 }
1929 
1930 // Assignment based on string headers
1931 unittest
1932 {
1933     DataFrame!(int, 2) df;
1934     assert(is(typeof(df.data) == int[][2]));
1935 
1936     df.indx.row.titles = ["Index1", "Index2", "Index3"];
1937     df.indx.row.index = [["Hello", "Hi"], ["Hello"], []];
1938     df.indx.row.codes = [[0, 1], [0, 0], [1, 24]];
1939     df.indx.column.titles = ["Hey", "Hey", "Hey"];
1940     df.indx.column.index = [["Hello", "Hi"], [], ["Hello"]];
1941     df.indx.column.codes = [[0, 1], [1, 2], [0, 0]];
1942     df.rows = 2;
1943     df.data[0] = [1, 2];
1944     df.data[1] = [1, 2];
1945 
1946     df[["Hello", "Hello", "1"], ["Hello", "1", "Hello"]] = 48;
1947     assert(df.data[0] == [48, 2]);
1948     assert(df[["Hello", "Hello", "1"], ["Hello", "1", "Hello"]] == 48);
1949 
1950     df[["Hi", "Hello", "24"], ["Hello", "1", "Hello"]] = 29;
1951     assert(df.data[0] == [48, 29]);
1952     assert(df[["Hi", "Hello", "24"], ["Hello", "1", "Hello"]] == 29);
1953 
1954     df[["Hello", "Hello", "1"], ["Hi", "2", "Hello"]] = 96;
1955     assert(df.data[1] == [96, 2]);
1956     assert(df[["Hello", "Hello", "1"], ["Hi", "2", "Hello"]] == 96);
1957 
1958     df[["Hi", "Hello", "24"], ["Hi", "2", "Hello"]] = 43;
1959     assert(df.data[1] == [96, 43]);
1960     assert(df[["Hi", "Hello", "24"], ["Hi", "2", "Hello"]] == 43);
1961 }
1962 
1963 
1964 // getiing integer position of the given row row.index
1965 unittest
1966 {
1967     DataFrame!(int, 2) df;
1968     assert(is(typeof(df.data) == int[][2]));
1969 
1970     df.indx.row.titles = ["Index1", "Index2", "Index3"];
1971     df.indx.row.index = [["Hello", "Hi"], ["Hello"], []];
1972     df.indx.row.codes = [[0, 1], [0, 0], [1,24]];
1973     df.indx.column.titles = ["Hey", "Hey", "Hey"];
1974     df.indx.column.index = [["Hello", "Hi"], [], ["Hello"]];
1975     df.indx.column.codes = [[], [1, 2], [0, 0]];
1976     df.rows = 2;
1977     df.data[0] = [1, 2];
1978     df.data[1] = [1, 2];
1979 
1980     assert(df.getRowPosition(["Hello", "Hello", "1"]) == 0);
1981     assert(df.getRowPosition(["Hi", "Hello", "24"]) == 1);
1982     assert(df.getRowPosition(["Hi", "Hello", "54"]) == -1);
1983     assert(df.getRowPosition(["Hi", "Helo", "24"]) == -1);
1984     assert(df.getRowPosition(["H", "Hello", "54"]) == -1);
1985 }
1986 
1987 // getiing integer position of the given column row.index
1988 unittest
1989 {
1990     DataFrame!(int, 2) df;
1991     assert(is(typeof(df.data) == int[][2]));
1992 
1993     df.indx.row.titles = ["Index1", "Index2", "Index3"];
1994     df.indx.row.index = [["Hello", "Hi"], ["Hello"], []];
1995     df.indx.row.codes = [[0, 1], [0, 0], [1, 24]];
1996     df.indx.column.titles = ["Hey", "Hey", "Hey"];
1997     df.indx.column.index = [["Hello", "Hi"], [], ["Hello"]];
1998     df.indx.column.codes = [[0, 1], [1, 2], [0, 0]];
1999     df.rows = 2;
2000     df.data[0] = [1,2];
2001     df.data[1] = [1,2];
2002 
2003     assert(df.getColumnPosition(["Hello", "1", "Hello"]) == 0);
2004     assert(df.getColumnPosition(["Hi", "2", "Hello"]) == 1);
2005     assert(df.getColumnPosition(["Hello", "1", "Hell"]) == -1);
2006     assert(df.getColumnPosition(["Hello", "45", "Hello"]) == -1);
2007 }
2008 
2009 // Setting index to DataFrame
2010 unittest
2011 {
2012     DataFrame!(double, int) df;
2013     Index inx;
2014     inx.setIndex([1, 2, 3, 4, 5], ["Index"]);
2015     df.setFrameIndex(inx);
2016     string ret = df.display(true, 200);
2017     assert(ret == "Index  0    1\n"
2018         ~ "1      nan  0\n"
2019         ~ "2      nan  0\n"
2020         ~ "3      nan  0\n"
2021         ~ "4      nan  0\n"
2022         ~ "5      nan  0\n"
2023     );
2024 }
2025 
2026 // Checking if setting index works as intended when assigning using the set index
2027 unittest
2028 {
2029     DataFrame!(double, int) df;
2030     Index inx;
2031     inx.setIndex([1, 2, 3, 4, 5], ["Index"], [1, 2], ["Index"]);
2032     df.setFrameIndex(inx);
2033     df[["1"], ["2"]] = 42;
2034     assert(df.data[1] == [42, 0, 0, 0, 0]);
2035 }
2036 
2037 // Checking if setting index works as intended when assigning using the set index
2038 unittest
2039 {
2040     DataFrame!(double, int) df;
2041     Index inx;
2042     inx.setIndex([["Hello", "Hi"], ["Hi", "Hello"]], ["Index", "Index"], [1, 2], ["Index"]);
2043     df.setFrameIndex(inx);
2044     df[["Hello", "Hi"], ["2"]] = 42;
2045     assert(df.data[1] == [42, 0]);
2046 }
2047 
2048 // Checking if setting index works as intended when assigning using the set index
2049 unittest
2050 {
2051     DataFrame!(double, int) df;
2052     Index inx;
2053     inx.setIndex([["Hello", "Hi"], ["Hi", "Hello"]], ["Index", "Index"], [["Hello", "Hi"], ["Hi", "Hello"]]);
2054     df.setFrameIndex(inx);
2055     df[["Hello", "Hi"], ["Hi", "Hello"]] = 42;
2056     assert(df.data[1] == [42, 0]);
2057 }
2058 
2059 // Basic Assignment operation
2060 unittest
2061 {
2062     DataFrame!(double, int) df;
2063     Index inx;
2064     inx.setIndex([["Hello", "Hi"], ["Hi", "Hello"]], ["Index", "Index"], [["Hello", "Hi"], ["Hi", "Hello"]]);
2065     df.setFrameIndex(inx);
2066 
2067     // Assignment
2068     df = [[1, 2], [3, 4]];
2069     // df.display();
2070     assert(df.data[0] == [1, 3]);
2071     assert(df.data[1] == [2, 4]);
2072     assert(df[["Hello", "Hi"], ["Hello", "Hi"]] == 1);
2073     assert(df[["Hello", "Hi"], ["Hi", "Hello"]] == 2);
2074     assert(df[["Hi", "Hello"], ["Hello", "Hi"]] == 3);
2075     assert(df[["Hi", "Hello"], ["Hi", "Hello"]] == 4);
2076 
2077     // Assignment that needs apdding
2078     df = [[1], [2, 3]];
2079     // df.display();
2080     assert(df.data[0] == [1, 2]);
2081     assert(df.data[1] == [0, 3]);
2082     assert(df[["Hello", "Hi"], ["Hello", "Hi"]] == 1);
2083     assert(df[["Hello", "Hi"], ["Hi", "Hello"]] == 0);
2084     assert(df[["Hi", "Hello"], ["Hello", "Hi"]] == 2);
2085     assert(df[["Hi", "Hello"], ["Hi", "Hello"]] == 3);
2086 
2087     // Checking casting
2088     df = [[1.2, 1], [4.6, 7]];
2089     // df.display();
2090     assert(df.data[0] == [1.2, 4.6]);
2091     assert(df.data[1] == [1, 7]);
2092     assert(df[["Hello", "Hi"], ["Hello", "Hi"]] == 1.2);
2093     assert(df[["Hello", "Hi"], ["Hi", "Hello"]] == 1);
2094     assert(df[["Hi", "Hello"], ["Hello", "Hi"]] == 4.6);
2095     assert(df[["Hi", "Hello"], ["Hi", "Hello"]] == 7);
2096 }
2097 
2098 // Assigning an entire column & row to DataFrame
2099 unittest
2100 {
2101     DataFrame!(double, int) df;
2102     Index inx;
2103     inx.setIndex([["Hello", "Hi"], ["Hi", "Hello"]], ["Index", "Index"], [["Hello", "Hi"], ["Hi", "Hello"]]);
2104     df.setFrameIndex(inx);
2105     df.RowType ele;
2106     ele[0] = 1.77;
2107     ele[1] = 4;
2108 
2109     // Using RowType alais
2110     df.assign!0(["Hi", "Hello"], ele);
2111     assert(df.data[0][1] == 1.77);
2112     assert(df.data[1][1] == 4);
2113 
2114     // Without RowType
2115     df.assign!0(["Hi", "Hello"], 1.688, 6);
2116     assert(df.data[0][1] == 1.688);
2117     assert(df.data[1][1] == 6);
2118 
2119     // Assigning usig direct index
2120     df.assign!0(1, 1.588, 6);
2121     assert(df.data[0][1] == 1.588);
2122     assert(df.data[1][1] == 6);
2123 
2124     // Assigning column
2125     df.assign!1(["Hello", "Hi"], [1.2, 3.6]);
2126     assert(df.data[0] == [1.2, 3.6]);
2127 
2128     // Assigning column.index using direct index
2129     df.assign!1(0, [1.26, 4.6]);
2130     assert(df.data[0] == [1.26, 4.6]);
2131 
2132     // Partial Assignment - rows
2133     df.assign!0(1, 3.588);
2134     assert(df.data[0][1] == 3.588);
2135     assert(df.data[1][1] == 6);
2136 
2137     // Partial Assignment - column.index
2138     df.assign!1(0, [2.26]);
2139     assert(df.data[0] == [2.26, 3.588]);
2140 }
2141 
2142 // Getting an entire row/column from the DataFrame
2143 unittest
2144 {
2145     DataFrame!(int, 2) df;
2146     assert(is(typeof(df.data) == int[][2]));
2147 
2148     df.indx.row.titles = ["Index1", "Index2", "Index3"];
2149     df.indx.row.index = [["Hello", "Hi"],["Hello"], []];
2150     df.indx.row.codes = [[0, 1], [0, 0], [1, 24]];
2151     df.indx.column.titles = ["Hey", "Hey", "Hey"];
2152     df.indx.column.index = [["Hello", "Hi"], [], ["Hello"]];
2153     df.indx.column.codes = [[0, 1],[1, 2],[0, 0]];
2154     df.rows = 2;
2155     df.data[0] = [1, 2];
2156     df.data[1] = [1, 2];
2157 
2158     assert(df[["Hello", "1", "Hello"]].data == [1, 2]);
2159     assert(df[["Hi", "2", "Hello"]].data == [1, 2]);
2160     assert(df[["Hello", "Hello", "1"], 0].data[0] == 1);
2161     assert(df[["Hi", "Hello", "24"], 0].data[0] == 2);
2162     assert(df[["Hello", "Hello", "1"], 0].data[1] == 1);
2163     assert(df[["Hi", "Hello", "24"], 0].data[1] == 2);
2164 }
2165 
2166 // Column binary Operation
2167 unittest
2168 {
2169     DataFrame!(int, 3) df;
2170     Index inx;
2171     inx.setIndex([["Hello", "Hi"], ["Hi", "Hello"]], ["Index", "Index"]);
2172     df.setFrameIndex(inx);
2173     // df.display();
2174 
2175     df.assign!1(0, [1, 4]);
2176     df.assign!1(1, [1, 6]);
2177     df.assign!1(2, [1, 8]);
2178     // df.display();
2179 
2180     df[["0"]] = df[["1"]] + df[["2"]];
2181     assert(df.data[0] == [2, 14]);
2182     df[["Hello", "Hi"], 0] = df[["Hi", "Hello"], 0];
2183     assert(df.data[0][0] == 14 && df.data[1][0] == 6 && df.data[2][0] == 8);
2184     // df.display();
2185 
2186     df[["0"]] = df[["1"]] - df[["2"]];
2187     assert(df.data[0] == [-2, -2]);
2188     // df.display();
2189 
2190     df[["0"]] = df[["1"]] * df[["2"]];
2191     assert(df.data[0] == [48, 48]);
2192     // df.display();
2193 
2194     df[["0"]] = df[["1"]] / df[["2"]];
2195     assert(df.data[0] == [0, 0]);
2196     // df.display();
2197 
2198     df[["0"]] = df[["1"]];
2199     assert(df.data[0] == [6, 6]);
2200 }
2201 
2202 // Row binary operations
2203 unittest
2204 {
2205     DataFrame!(int, 3) df;
2206     Index inx;
2207     inx.setIndex([["Hello", "Hi", "Hey"], ["Hi", "Hello", "Hey"]], ["Index", "Index", "Index"]);
2208     df.setFrameIndex(inx);
2209     // df.display();
2210 
2211     df.assign!1(0, [1, 4, 8]);
2212     df.assign!1(1, [1, 6, 9]);
2213     df.assign!1(2, [1, 8, 17]);
2214 
2215     df[["Hello", "Hi"], 0] = df[["Hi", "Hello"], 0] + df[["Hey", "Hey"], 0];
2216     assert(df.data[0][0] == 12 && df.data[1][0] == 15 && df.data[2][0] == 25);
2217 
2218     df[["Hello", "Hi"], 0] = df[["Hi", "Hello"], 0] - df[["Hey", "Hey"], 0];
2219     assert(df.data[0][0] == -4 && df.data[1][0] == -3 && df.data[2][0] == -9);
2220 
2221     df[["Hello", "Hi"], 0] = df[["Hi", "Hello"], 0] * df[["Hey", "Hey"], 0];
2222     assert(df.data[0][0] == 32 && df.data[1][0] == 54 && df.data[2][0] == 136);
2223 
2224     df[["Hello", "Hi"], 0] = df[["Hi", "Hello"], 0] / df[["Hey", "Hey"], 0];
2225     assert(df.data[0][0] == 0 && df.data[1][0] == 0 && df.data[2][0] == 0);
2226 
2227     // df.display();
2228 }
2229 
2230 // Column Binary Operation - Heterogeneous DataFrame
2231 unittest
2232 {
2233     DataFrame!(int, 2, double, 2) df;
2234     Index inx;
2235     inx.setIndex([["Hello", "Hi"], ["Hi", "Hello"]], ["Index", "Index"]);
2236     df.setFrameIndex(inx);
2237     // df.display();
2238 
2239     df.assign!1(0, [1, 4]);
2240     df.assign!1(1, [1, 6]);
2241     df.assign!1(2, [1.9, 8.4]);
2242     df.assign!1(3, [9.2, 4.6]);
2243     // df.display();
2244 
2245     df[["0"]] = df[["1"]];
2246     assert(df.data[0] == [1, 6]);
2247 
2248     // If for some reason float is assigned to int, it gets explicitly converted
2249     // Supported only for floating point -> Integral kind
2250     df[["0"]] = df[["2"]].convertTo!(int[]);
2251     assert(df.data[0] == [1, 8]);
2252 
2253     df[["1"]] = (df[["2"]] + df[["3"]]).convertTo!(int[]);
2254     assert(df.data[1] == [11, 13]);
2255 
2256     df[["0"]] = (df[["1"]] + df[["2"]] + df[["3"]]).convertTo!(int[]);
2257     assert(df.data[0] == [22, 26]);
2258 
2259     df[["3"]] = df[["0"]];
2260     assert(df.data[3] == [22, 26]);
2261 
2262     df[["0"]] = (df[["1"]] + df[["2"]] * df[["3"]]).convertTo!(int[]);
2263     foreach(i; 0 .. 2)
2264         assert(df.data[0][i] == cast(int)(df.data[1][i] + df.data[2][i] * df.data[3][i]));
2265 
2266     df[["0"]] = (df[["1"]] - df[["2"]] / df[["3"]]).convertTo!(int[]);
2267     foreach(i; 0 .. 2)
2268         assert(df.data[0][i] == cast(int)(df.data[1][i] - df.data[2][i] / df.data[3][i]));
2269 
2270     import std.math: approxEqual;
2271     df[["2"]] = df[["0"]] * df[["1"]] / df[["3"]];
2272     foreach(i; 0 .. 2)
2273         assert(approxEqual(df.data[2][i], df.data[0][i] * df.data[1][i] / df.data[3][i], 1e-3));
2274 
2275     df[["3"]] = df[["2"]] / df[["1"]] / df[["0"]];
2276     foreach(i; 0 .. 2)
2277         assert(approxEqual(df.data[3][i], df.data[2][i] / df.data[1][i] / df.data[0][i], 1e-3));
2278 
2279     df[["Hello", "Hi"], 0] = df[["Hi", "Hello"], 0];
2280     static foreach(i; 0 .. 4)
2281         assert(approxEqual(df.data[i][0], df.data[i][1], 1e-3));
2282 }
2283 
2284 // Row binary Operation on Heterogeneous DataFrame
2285 unittest
2286 {
2287     DataFrame!(int, 2, double, 2) df;
2288     Index inx;
2289     inx.setIndex([["Hello", "Hi", "Hey", "Ahoy"], ["Hi", "Hello", "Ahoy", "Hey"]], ["Index", "Index"]);
2290     df.setFrameIndex(inx);
2291     // df.display();
2292 
2293     df.assign!1(0, [1, 4, 7, 8]);
2294     df.assign!1(1, [1, 6, 13, 45]);
2295     df.assign!1(2, [1.9, 8.4, 17.2, 34.3]);
2296     df.assign!1(3, [9.2, 4.6, 19.6, 44.3]);
2297     // df.display();
2298 
2299     import std.math: approxEqual;
2300     df[["Hello", "Hi"], 0] = df[["Hi", "Hello"], 0] + df[["Hey", "Ahoy"], 0] * df[["Ahoy", "Hey"], 0];
2301     static foreach(i; 0 .. 4)
2302         assert(approxEqual(df.data[i][0], df.data[i][1] + df.data[i][2] * df.data[i][3], 1e-1));
2303 
2304     df[["Hello", "Hi"], 0] = df[["Hi", "Hello"], 0] * df[["Hey", "Ahoy"], 0] / df[["Ahoy", "Hey"], 0];
2305     static foreach(i; 0 .. 4)
2306         assert(approxEqual(df.data[i][0], df.data[i][1] * df.data[i][2] / df.data[i][3], 1e-1));
2307 
2308     df[["Hello", "Hi"], 0] = df[["Hi", "Hello"], 0] * df[["Hey", "Ahoy"], 0] * df[["Ahoy", "Hey"], 0];
2309     static foreach(i; 0 .. 4)
2310         assert(approxEqual(df.data[i][0], df.data[i][1] * df.data[i][2] * df.data[i][3], 1e-1));
2311 
2312     df[["Hello", "Hi"], 0] = df[["Hi", "Hello"], 0] / df[["Hey", "Ahoy"], 0] / df[["Ahoy", "Hey"], 0];
2313     static foreach(i; 0 .. 4)
2314         assert(approxEqual(df.data[i][0], df.data[i][1] / df.data[i][2] / df.data[i][3], 1e-1));
2315 
2316     df[["Hello", "Hi"], 0] = (df[["Hi", "Hello"], 0] + df[["Hey", "Ahoy"], 0]) * df[["Ahoy", "Hey"], 0];
2317     static foreach(i; 0 .. 4)
2318         assert(approxEqual(df.data[i][0], (df.data[i][1] + df.data[i][2]) * df.data[i][3], 1e-1));
2319 
2320     df[["Hi", "Hello"], 0] = (df[["Hello", "Hi"], 0] + df[["Hey", "Ahoy"], 0]) * df[["Ahoy", "Hey"], 0];
2321     static foreach(i; 0 .. 4)
2322         assert(approxEqual(df.data[i][1], (df.data[i][0] + df.data[i][2]) * df.data[i][3], 1e-1));
2323 
2324     df[["Hi", "Hello"], 0] = (df[["Hello", "Hi"], 0] - df[["Hey", "Ahoy"], 0]) / df[["Ahoy", "Hey"], 0];
2325     static foreach(i; 0 .. 4)
2326         assert(approxEqual(df.data[i][1], (df.data[i][0] - df.data[i][2]) / df.data[i][3], 1e-1));
2327 }
2328 
2329 // Checking if pattern matching for steIndex works - If an index isn't assignable, it's overlooked
2330 unittest
2331 {
2332     DataFrame!(double, 2, int) df;
2333     Index inx;
2334     inx.setIndex([["Hello", "Hi"], ["Hi", "Hello"]], ["Index", "Index"], [["Hello", "Hi"], ["Hi", "Hello"]]);
2335     df.setFrameIndex(inx);
2336 
2337     assert(df.indx.column.index == [[]]);
2338     assert(df.indx.column.codes == [[0, 1, 2]]);
2339     assert(df.indx.row.index == [["Hello", "Hi"], ["Hello", "Hi"]]);
2340     assert(df.indx.row.codes == [[0, 1], [1, 0]]);
2341 }
2342 
2343 // Simple Data Frame
2344 unittest
2345 {
2346     DataFrame!(int, 2) df;
2347     assert(is(typeof(df.data) == int[][2]));
2348 
2349     df.indx.row.titles = ["Index1"];
2350     df.indx.row.index = [["Hello", "Hi"]];
2351     df.indx.row.codes = [[]];
2352     df.indx.column.titles = [];
2353     df.indx.column.index = [["Hello", "Hi"]];
2354     df.indx.column.codes = [[]];
2355     df.rows = 2;
2356     df.data[0] = [1, 2];
2357     df.data[1] = [1, 2];
2358     string ret = df.display(true, 200);
2359     assert(ret == "Index1  Hello  Hi\n"
2360         ~ "Hello   1      1 \n"
2361         ~ "Hi      2      2 \n"
2362     );
2363 }
2364 
2365 // Simple DataFrame with both row and column index title
2366 unittest
2367 {
2368     DataFrame!(int, 2) df;
2369     assert(is(typeof(df.data) == int[][2]));
2370 
2371     df.indx.row.titles = ["Index1"];
2372     df.indx.row.index = [["Hello", "Hi"]];
2373     df.indx.row.codes = [[]];
2374     df.indx.column.titles = ["Also Index"];
2375     df.indx.column.index = [["Hello", "Hi"]];
2376     df.indx.column.codes = [[]];
2377     df.rows = 2;
2378     df.data[0] = [1, 2];
2379     df.data[1] = [1, 2];
2380     string ret = df.display(true, 200);
2381 
2382     assert(ret == "Also Index  Hello  Hi\n"
2383         ~ "Index1    \n"
2384         ~ "Hello       1      1 \n"
2385         ~ "Hi          2      2 \n"
2386     );
2387 }
2388 
2389 // Multi-Indexed rows
2390 unittest
2391 {
2392     DataFrame!(int, 2) df;
2393     assert(is(typeof(df.data) == int[][2]));
2394 
2395     df.indx.row.titles = ["Index1", "Index2"];
2396     df.indx.row.index = [["Hello", "Hi"], ["Hello", "Hi"]];
2397     df.indx.row.codes = [[],[]];
2398     df.indx.column.titles = [];
2399     df.indx.column.index = [["Hello", "Hi"]];
2400     df.indx.column.codes = [[]];
2401     df.rows = 2;
2402     df.data[0] = [1, 2];
2403     df.data[1] = [1, 2];
2404     string ret = df.display(true, 200);
2405 
2406     assert(ret == "Index1  Index2  Hello  Hi\n"
2407         ~ "Hello   Hello   1      1 \n"
2408         ~ "Hi      Hi      2      2 \n"
2409     );
2410 }
2411 // Multi Indexed column.index
2412 unittest
2413 {
2414     DataFrame!(int, 2) df;
2415     assert(is(typeof(df.data) == int[][2]));
2416 
2417     df.indx.row.titles = ["Index1"];
2418     df.indx.row.index = [["Hello", "Hi"]];
2419     df.indx.row.codes = [[]];
2420     df.indx.column.titles = [];
2421     df.indx.column.index = [["Hello", "Hi"], ["Hello", "Hi"]];
2422     df.indx.column.codes = [[], []];
2423     df.rows = 2;
2424     df.data[0] = [1, 2];
2425     df.data[1] = [1, 2];
2426     string ret = df.display(true, 200);
2427 
2428     assert(ret == "        Hello  Hi\n"
2429         ~ "Index1  Hello  Hi\n"
2430         ~ "Hello   1      1 \n"
2431         ~ "Hi      2      2 \n"
2432     );
2433 }
2434 
2435 // Multi Indexed column.index with titles
2436 unittest
2437 {
2438     DataFrame!(int, 2) df;
2439     assert(is(typeof(df.data) == int[][2]));
2440 
2441     df.indx.row.titles = ["Index1"];
2442     df.indx.row.index = [["Hello", "Hi"]];
2443     df.indx.row.codes = [[]];
2444     df.indx.column.titles = ["CIndex1", "CIndex2"];
2445     df.indx.column.index = [["Hello", "Hi"], ["Hello", "Hi"]];
2446     df.indx.column.codes = [[], []];
2447     df.rows = 2;
2448     df.data[0] = [1, 2];
2449     df.data[1] = [1, 2];
2450     string ret = df.display(true, 200);
2451 
2452     assert(ret == "CIndex1  Hello  Hi\n"
2453         ~ "CIndex2  Hello  Hi\n"
2454         ~ "Index1 \n"
2455         ~ "Hello    1      1 \n"
2456         ~ "Hi       2      2 \n"
2457     );
2458 }
2459 
2460 // Wide DataFrame
2461 unittest
2462 {
2463     DataFrame!(int, 20) df;
2464     assert(is(typeof(df.data) == int[][20]));
2465 
2466     df.indx.row.titles = ["Index1"];
2467     df.indx.row.index = [["Hello", "Hi"]];
2468     df.indx.row.codes = [[]];
2469     df.indx.column.titles = [];
2470     df.indx.column.index = [[]];
2471     df.indx.column.codes = [[]];
2472     df.rows = 2;
2473     int[] arr = [12_222_222, 12_222_222];
2474 
2475     static foreach(i; 0 .. 20)
2476     {
2477         import std.conv: to;
2478         df.indx.column.index[0] ~= to!string(i);
2479         df.data[i] = arr;
2480     }
2481 
2482     string ret = df.display(true, 200);
2483     assert(ret == "Index1  0         1         2         3         4         5         6         7         ...  11        12        13        14        15        16        17        18        19      \n"
2484         ~ "Hello   12222222  12222222  12222222  12222222  12222222  12222222  12222222  12222222  ...  12222222  12222222  12222222  12222222  12222222  12222222  12222222  12222222  12222222\n"
2485         ~ "Hi      12222222  12222222  12222222  12222222  12222222  12222222  12222222  12222222  ...  12222222  12222222  12222222  12222222  12222222  12222222  12222222  12222222  12222222\n"
2486     );
2487 }
2488 
2489 // Daddy Long Legs DataFrame
2490 unittest
2491 {
2492     DataFrame!(int, 2) df;
2493     assert(is(typeof(df.data) == int[][2]));
2494 
2495     df.indx.row.titles = ["Index1"];
2496     df.indx.row.index = [[]];
2497     df.indx.row.codes = [[]];
2498     df.indx.column.titles = [];
2499     df.indx.column.index = [["Hello", "Hi"]];
2500     df.indx.column.codes = [[]];
2501     df.rows = 100;
2502     int[] arr = [];
2503     foreach(i; 0 .. 100)
2504     {
2505         arr ~= i;
2506         import std.conv: to;
2507         df.indx.row.index[0] ~= to!string(i);
2508     }
2509     df.data[0] = arr;
2510     df.data[1] = arr;
2511     string ret = df.display(true, 200);
2512 
2513     assert(ret == "Index1  Hello  Hi\n"
2514         ~ "0       0      0 \n"
2515         ~ "1       1      1 \n"
2516         ~ "2       2      2 \n"
2517         ~ "3       3      3 \n"
2518         ~ "4       4      4 \n"
2519         ~ "5       5      5 \n"
2520         ~ "6       6      6 \n"
2521         ~ "7       7      7 \n"
2522         ~ "8       8      8 \n"
2523         ~ "9       9      9 \n"
2524         ~ "10      10     10\n"
2525         ~ "11      11     11\n"
2526         ~ "12      12     12\n"
2527         ~ "13      13     13\n"
2528         ~ "14      14     14\n"
2529         ~ "15      15     15\n"
2530         ~ "16      16     16\n"
2531         ~ "17      17     17\n"
2532         ~ "18      18     18\n"
2533         ~ "19      19     19\n"
2534         ~ "20      20     20\n"
2535         ~ "21      21     21\n"
2536         ~ "22      22     22\n"
2537         ~ "23      23     23\n"
2538         ~ "......  .....  ..\n"
2539         ~ "75      75     75\n"
2540         ~ "76      76     76\n"
2541         ~ "77      77     77\n"
2542         ~ "78      78     78\n"
2543         ~ "79      79     79\n"
2544         ~ "80      80     80\n"
2545         ~ "81      81     81\n"
2546         ~ "82      82     82\n"
2547         ~ "83      83     83\n"
2548         ~ "84      84     84\n"
2549         ~ "85      85     85\n"
2550         ~ "86      86     86\n"
2551         ~ "87      87     87\n"
2552         ~ "88      88     88\n"
2553         ~ "89      89     89\n"
2554         ~ "90      90     90\n"
2555         ~ "91      91     91\n"
2556         ~ "92      92     92\n"
2557         ~ "93      93     93\n"
2558         ~ "94      94     94\n"
2559         ~ "95      95     95\n"
2560         ~ "96      96     96\n"
2561         ~ "97      97     97\n"
2562         ~ "98      98     98\n"
2563         ~ "99      99     99\n"
2564     );
2565 }
2566 
2567 // Multi Indexed DataFrame
2568 unittest
2569 {
2570     DataFrame!(int, 2) df;
2571     assert(is(typeof(df.data) == int[][2]));
2572 
2573     df.indx.row.titles = ["Index1", "Index2", "Index3"];
2574     df.indx.row.index = [["Hello", "Hi"], ["Hello"], []];
2575     df.indx.row.codes = [[0, 1], [0, 0], [1, 24]];
2576     df.indx.column.titles = [];
2577     df.indx.column.index = [["Hello", "Hi"], [], ["Hello"]];
2578     df.indx.column.codes = [[], [1, 2], [0, 0]];
2579     df.rows = 2;
2580     df.data[0] = [1, 2];
2581     df.data[1] = [1, 2];
2582     string ret = df.display(true, 200);
2583     assert(ret == "                        Hello  Hi   \n"
2584         ~ "                        1      2    \n"
2585         ~ "Index1  Index2  Index3  Hello  Hello\n"
2586         ~ "Hello   Hello   1       1      1    \n"
2587         ~ "Hi      Hello   24      2      2    \n"
2588     );
2589 }
2590 
2591 // Multi Indexed DataFrame with both row and column titles
2592 unittest
2593 {
2594     DataFrame!(int, 2) df;
2595     assert(is(typeof(df.data) == int[][2]));
2596 
2597     df.indx.row.titles = ["Index1", "Index2", "Index3"];
2598     df.indx.row.index = [["Hello", "Hi"], ["Hello"], []];
2599     df.indx.row.codes = [[0, 1], [0, 0], [1, 24]];
2600     df.indx.column.titles = ["Hey", "Hey", "Hey"];
2601     df.indx.column.index = [["Hello", "Hi"], [], ["Hello"]];
2602     df.indx.column.codes = [[], [1, 2], [0, 0]];
2603     df.rows = 2;
2604     df.data[0] = [1, 2];
2605     df.data[1] = [1, 2];
2606     string ret = df.display(true, 200);
2607     assert(ret == "                Hey     Hello  Hi   \n"
2608         ~ "                Hey     1      2    \n"
2609         ~ "                Hey     Hello  Hello\n"
2610         ~ "Index1  Index2  Index3\n"
2611         ~ "Hello   Hello   1       1      1    \n"
2612         ~ "Hi      Hello   24      2      2    \n"
2613     );
2614 
2615     // df.to_csv("");
2616 }
2617 
2618 // Multi-Indexed with skipping row index
2619 unittest
2620 {
2621     DataFrame!(int, 2) df;
2622     assert(is(typeof(df.data) == int[][2]));
2623 
2624     df.indx.row.titles = ["Index1", "Index2", "Index3"];
2625     df.indx.row.index = [["Hello", "Hi"], ["Hello"], []];
2626     df.indx.row.codes = [[1, 1], [0, 0], [1, 24]];
2627     df.indx.column.titles = [];
2628     df.indx.column.index = [["Hello", "Hi"], [], ["Hello"]];
2629     df.indx.column.codes = [[], [1, 2], [0, 0]];
2630     df.indx.isMultiIndexed = true;
2631     df.rows = 2;
2632     df.data[0] = [1, 2];
2633     df.data[1] = [1, 2];
2634     string ret = df.display(true, 200);
2635     assert(ret == "                        Hello  Hi   \n"
2636         ~ "                        1      2    \n"
2637         ~ "Index1  Index2  Index3  Hello  Hello\n"
2638         ~ "Hi      Hello   1       1      1    \n"
2639         ~ "                24      2      2    \n"
2640     );
2641 }
2642 
2643 // Middle row.index won't skip if the outer row.index aren't skipped
2644 unittest
2645 {
2646     DataFrame!(int, 2) df;
2647     assert(is(typeof(df.data) == int[][2]));
2648 
2649     df.indx.row.titles = ["Index1", "Index2", "Index3"];
2650     df.indx.row.index = [["Hello", "Hi"], ["Hello"], []];
2651     df.indx.row.codes = [[1, 0], [0, 0], [1, 24]];
2652     df.indx.column.titles = [];
2653     df.indx.column.index = [["Hello","Hi"], [], ["Hello"]];
2654     df.indx.column.codes = [[], [1, 2], [0, 0]];
2655     df.indx.isMultiIndexed = true;
2656     df.rows = 2;
2657     df.data[0] = [1, 2];
2658     df.data[1] = [1, 2];
2659     string ret = df.display(true, 200);
2660 
2661     assert(ret == "                        Hello  Hi   \n"
2662         ~ "                        1      2    \n"
2663         ~ "Index1  Index2  Index3  Hello  Hello\n"
2664         ~ "Hi      Hello   1       1      1    \n"
2665         ~ "Hello   Hello   24      2      2    \n"
2666     );
2667 
2668     // df.to_csv("", true, false);
2669 }
2670 
2671 // Writing entire dataframe to csv
2672 unittest
2673 {
2674     DataFrame!(int, 2) df;
2675     assert(is(typeof(df.data) == int[][2]));
2676 
2677     df.indx.row.titles = ["Index1", "Index2", "Index3"];
2678     df.indx.row.index = [["Hello", "Hi"], ["Hello"], []];
2679     df.indx.row.codes = [[0, 1], [0, 0], [1, 24]];
2680     df.indx.column.titles = ["Hey", "Hey", "Hey"];
2681     df.indx.column.index = [["Hello", "Hi"], [], ["Hello"]];
2682     df.indx.column.codes = [[], [1, 2], [0, 0]];
2683     df.rows = 2;
2684     df.data[0] = [1, 2];
2685     df.data[1] = [1, 2];
2686 
2687     df.to_csv("./test/tocsv/ex1p1.csv");
2688     string[] data = [
2689         ",,Hey,Hello,Hi",
2690         ",,Hey,1,2",
2691         ",,Hey,Hello,Hello",
2692         "Index1,Index2,Index3",
2693         "Hello,Hello,1,1,1",
2694         "Hi,Hello,24,2,2",
2695         ""
2696     ];
2697 
2698     import std.stdio: File;
2699     import std.string: chomp;
2700     int line = 0;
2701     File csv = File("./test/tocsv/ex1p1.csv");
2702     while(!csv.eof())
2703     {
2704         assert(chomp(csv.readln()) == data[line]);
2705         ++line;
2706     }
2707     csv.close();
2708 }
2709 
2710 // Writing only row index
2711 unittest
2712 {
2713     DataFrame!(int, 2) df;
2714     assert(is(typeof(df.data) == int[][2]));
2715 
2716     df.indx.row.titles = ["Index1", "Index2", "Index3"];
2717     df.indx.row.index = [["Hello", "Hi"], ["Hello"], []];
2718     df.indx.row.codes = [[0, 1], [0, 0], [1, 24]];
2719     df.indx.column.titles = ["Hey", "Hey", "Hey"];
2720     df.indx.column.index = [["Hello", "Hi"], [], ["Hello"]];
2721     df.indx.column.codes = [[], [1, 2], [0, 0]];
2722     df.rows = 2;
2723     df.data[0] = [1, 2];
2724     df.data[1] = [1, 2];
2725 
2726     df.to_csv("./test/tocsv/ex1p2.csv", true, false);
2727     string[] data = [
2728         "Hello,Hello,1,1,1",
2729         "Hi,Hello,24,2,2",
2730         ""
2731     ];
2732 
2733     import std.stdio: File;
2734     import std.string: chomp;
2735     int line = 0;
2736     File csv = File("./test/tocsv/ex1p2.csv");
2737     while(!csv.eof())
2738     {
2739         assert(chomp(csv.readln()) == data[line]);
2740         ++line;
2741     }
2742     csv.close();
2743 }
2744 
2745 // Writing only column index
2746 unittest
2747 {
2748     DataFrame!(int, 2) df;
2749     assert(is(typeof(df.data) == int[][2]));
2750 
2751     df.indx.row.titles = ["Index1", "Index2", "Index3"];
2752     df.indx.row.index = [["Hello", "Hi"],["Hello"], []];
2753     df.indx.row.codes = [[0, 1], [0, 0], [1, 24]];
2754     df.indx.column.titles = ["Hey", "Hey", "Hey"];
2755     df.indx.column.index = [["Hello", "Hi"], [], ["Hello"]];
2756     df.indx.column.codes = [[], [1, 2], [0, 0]];
2757     df.rows = 2;
2758     df.data[0] = [1, 2];
2759     df.data[1] = [1, 2];
2760 
2761     df.to_csv("./test/tocsv/ex1p3.csv", false, true);
2762     string[] data = [
2763         "Hello,Hi",
2764         "1,2",
2765         "Hello,Hello",
2766         "1,1",
2767         "2,2",
2768         ""
2769     ];
2770 
2771     import std.stdio: File;
2772     import std.string: chomp;
2773     int line = 0;
2774     File csv = File("./test/tocsv/ex1p3.csv");
2775     while(!csv.eof())
2776     {
2777         assert(chomp(csv.readln()) == data[line]);
2778         ++line;
2779     }
2780     csv.close();
2781 }
2782 
2783 // Writing only data to the csv
2784 unittest
2785 {
2786     DataFrame!(int, 2) df;
2787     assert(is(typeof(df.data) == int[][2]));
2788 
2789     df.indx.row.titles = ["Index1", "Index2", "Index3"];
2790     df.indx.row.index = [["Hello", "Hi"],["Hello"], []];
2791     df.indx.row.codes = [[0, 1], [0, 0], [1, 24]];
2792     df.indx.column.titles = ["Hey", "Hey", "Hey"];
2793     df.indx.column.index = [["Hello", "Hi"], [], ["Hello"]];
2794     df.indx.column.codes = [[], [1, 2], [0, 0]];
2795     df.rows = 2;
2796     df.data[0] = [1, 2];
2797     df.data[1] = [1, 2];
2798 
2799     df.to_csv("./test/tocsv/ex1p4.csv", false, false);
2800     string[] data = [
2801         "1,1",
2802         "2,2",
2803         ""
2804     ];
2805 
2806     import std.stdio: File;
2807     import std.string: chomp;
2808     int line = 0;
2809     File csv = File("./test/tocsv/ex1p4.csv");
2810     while(!csv.eof())
2811     {
2812         assert(chomp(csv.readln()) == data[line]);
2813         ++line;
2814     }
2815     csv.close();
2816 }
2817 
2818 // Changing seperator
2819 unittest
2820 {
2821     DataFrame!(int, 2) df;
2822     assert(is(typeof(df.data) == int[][2]));
2823 
2824     df.indx.row.titles = ["Index1", "Index2", "Index3"];
2825     df.indx.row.index = [["Hello", "Hi"], ["Hello"], []];
2826     df.indx.row.codes = [[0, 1], [0, 0], [1, 24]];
2827     df.indx.column.titles = ["Hey", "Hey", "Hey"];
2828     df.indx.column.index = [["Hello", "Hi"], [], ["Hello"]];
2829     df.indx.column.codes = [[], [1, 2], [0, 0]];
2830     df.rows = 2;
2831     df.data[0] = [1, 2];
2832     df.data[1] = [1, 2];
2833 
2834     df.to_csv("./test/tocsv/ex1p5.csv", false, false, '|');
2835     string[] data = [
2836         "1|1",
2837         "2|2",
2838         ""
2839     ];
2840 
2841     import std.stdio: File;
2842     import std.string: chomp;
2843     int line = 0;
2844     File csv = File("./test/tocsv/ex1p5.csv");
2845     while(!csv.eof())
2846     {
2847         assert(chomp(csv.readln()) == data[line]);
2848         ++line;
2849     }
2850     csv.close();
2851 }
2852 
2853 // Parsing a CSV
2854 unittest
2855 {
2856     DataFrame!(int, 2) df;
2857     df.from_csv("./test/tocsv/ex1p1.csv", 3, 3);
2858     // df.display();
2859     df.to_csv("./test/tocsv/ex2p1.csv");
2860 
2861     import std.stdio: File;
2862     File f1 = File("./test/tocsv/ex1p1.csv", "r");
2863     File f2 = File("./test/tocsv/ex2p1.csv", "r");
2864 
2865     while(!f1.eof())
2866     {
2867         assert(f1.readln() == f2.readln());
2868     }
2869     assert(f1.eof() && f2.eof());
2870 
2871     f1.close();
2872     f2.close();
2873 }
2874 
2875 // PArsing CSV without column headers
2876 unittest
2877 {
2878     DataFrame!(int, 2) df;
2879     df.from_csv("./test/tocsv/ex1p2.csv", 3, 0);
2880     // df.display();
2881     df.to_csv("./test/tocsv/ex2p2.csv", true, false);
2882 
2883     import std.stdio: File;
2884     File f1 = File("./test/tocsv/ex1p2.csv", "r");
2885     File f2 = File("./test/tocsv/ex2p2.csv", "r");
2886 
2887     while(!f1.eof())
2888     {
2889         assert(f1.readln() == f2.readln());
2890     }
2891     assert(f1.eof() && f2.eof());
2892 
2893     f1.close();
2894     f2.close();
2895 }
2896 
2897 // Parsing CSV without row row.index
2898 unittest
2899 {
2900     DataFrame!(int, 2) df;
2901     df.from_csv("./test/tocsv/ex1p3.csv", 0, 3);
2902     // df.display();
2903     df.to_csv("./test/tocsv/ex2p3.csv", false, true);
2904 
2905     import std.stdio: File;
2906     File f1 = File("./test/tocsv/ex1p3.csv", "r");
2907     File f2 = File("./test/tocsv/ex2p3.csv", "r");
2908 
2909     while(!f1.eof())
2910     {
2911         assert(f1.readln() == f2.readln());
2912     }
2913     assert(f1.eof() && f2.eof());
2914 
2915     f1.close();
2916     f2.close();
2917 }
2918 
2919 // Parsing CSV without any row.index
2920 unittest
2921 {
2922     DataFrame!(int, 2) df;
2923     df.from_csv("./test/tocsv/ex1p4.csv", 0, 0);
2924     // df.display();
2925     df.to_csv("./test/tocsv/ex2p4.csv", false, false);
2926 
2927     import std.stdio: File;
2928     File f1 = File("./test/tocsv/ex1p4.csv", "r");
2929     File f2 = File("./test/tocsv/ex2p4.csv", "r");
2930 
2931     while(!f1.eof())
2932     {
2933         assert(f1.readln() == f2.readln());
2934     }
2935     assert(f1.eof() && f2.eof());
2936 
2937     f1.close();
2938     f2.close();
2939 }
2940 
2941 // Different Types
2942 unittest
2943 {
2944     DataFrame!(int, long) df;
2945     df.from_csv("./test/tocsv/ex1p4.csv", 0, 0);
2946     static assert(is(typeof(df.data[1]) == long[]));
2947     // df.display();
2948     df.to_csv("./test/tocsv/ex2p5.csv", false, false);
2949 
2950     import std.stdio: File;
2951     File f1 = File("./test/tocsv/ex1p4.csv", "r");
2952     File f2 = File("./test/tocsv/ex2p5.csv", "r");
2953 
2954     while(!f1.eof())
2955     {
2956         assert(f1.readln() == f2.readln());
2957     }
2958     assert(f1.eof() && f2.eof());
2959 
2960     f1.close();
2961     f2.close();
2962 }
2963 
2964 // Partial Parsing of data
2965 unittest
2966 {
2967     DataFrame!(int) df;
2968     df.from_csv("./test/tocsv/ex1p4.csv", 0, 0, [0]);
2969     // df.display();
2970     assert(df.data[0] == [1, 2]);
2971 }
2972 
2973 // Partial parsing - second column
2974 unittest
2975 {
2976     DataFrame!(int) df;
2977     df.from_csv("./test/tocsv/ex2p6.csv", 0, 0, [1]);
2978     // df.display();
2979     assert(df.data[0] == [1, 24]);
2980 }
2981 
2982 // Parsing by mentioning all the column.index
2983 unittest
2984 {
2985     DataFrame!(int, int) df;
2986     df.from_csv("./test/tocsv/ex2p6.csv", 0, 0, [0, 1]);
2987     // df.display();
2988     df.to_csv("./test/tocsv/ex2p7.csv", false, false);
2989 
2990     import std.stdio: File;
2991     File f1 = File("./test/tocsv/ex2p6.csv", "r");
2992     File f2 = File("./test/tocsv/ex2p7.csv", "r");
2993 
2994     while(!f1.eof())
2995     {
2996         assert(f1.readln() == f2.readln());
2997     }
2998     assert(f1.eof() && f2.eof());
2999 
3000     f1.close();
3001     f2.close();
3002 }
3003 
3004 // Partial Parsing with column headers
3005 unittest
3006 {
3007     DataFrame!(int) df;
3008     df.from_csv("./test/tocsv/ex1p1.csv", 3, 3, [0]);
3009     assert(df.data[0] == [1, 2]);
3010     df.to_csv("./test/tocsv/ex2p8.csv");
3011     string[] data = [
3012         ",,Hey,Hello",
3013         ",,Hey,1",
3014         ",,Hey,Hello",
3015         "Index1,Index2,Index3",
3016         "Hello,Hello,1,1",
3017         "Hi,Hello,24,2",
3018         ""
3019     ];
3020 
3021     import std.stdio: File;
3022     import std.string: chomp;
3023     int line = 0;
3024     File csv = File("./test/tocsv/ex2p8.csv");
3025     while(!csv.eof())
3026     {
3027         assert(chomp(csv.readln()) == data[line]);
3028         ++line;
3029     }
3030     csv.close();
3031 }
3032 
3033 // Partial Parsing with column headers - second column
3034 unittest
3035 {
3036     DataFrame!(int) df;
3037     df.from_csv("./test/tocsv/ex1p1.csv", 3, 3, [1]);
3038     assert(df.data[0] == [1, 2]);
3039     df.to_csv("./test/tocsv/ex2p9.csv");
3040     string[] data = [
3041         ",,Hey,Hi",
3042         ",,Hey,2",
3043         ",,Hey,Hello",
3044         "Index1,Index2,Index3",
3045         "Hello,Hello,1,1",
3046         "Hi,Hello,24,2",
3047         ""
3048     ];
3049 
3050     import std.stdio: File;
3051     import std.string: chomp;
3052     int line = 0;
3053     File csv = File("./test/tocsv/ex2p9.csv");
3054     while(!csv.eof())
3055     {
3056         assert(chomp(csv.readln()) == data[line]);
3057         ++line;
3058     }
3059     csv.close();
3060 }
3061 
3062 // DataFrame without row indexes - testt for groupBy
3063 unittest
3064 {
3065     DataFrame!(int, int) df;
3066     df.indx.column.index = [["Data1L1", "Data2L1"], ["Data1L2", "Data2L2"]];
3067     df.indx.column.codes = [[0, 1], [0, 1]];
3068     df.data[0] = [1, 2, 3];
3069     df.data[1] = [1, 2, 3];
3070     df.rows = 3;
3071     assert(df.display(true, 200) == "Data1L1  Data2L1\n"
3072         ~ "Data1L2  Data2L2\n"
3073         ~ "1        1      \n"
3074         ~ "2        2      \n"
3075         ~ "3        3      \n"
3076     );
3077 }
3078 
3079 // PArsing of dataset 1
3080 unittest
3081 {
3082     DataFrame!(int, 9, double, int, 4) df;
3083     df.from_csv("./test/fromcsv/dataset1.csv", 0, 1);
3084     // df.display();
3085     df.to_csv("./test/tocsv/ex3p1.csv", false);
3086 
3087     import std.stdio: File;
3088     File f1 = File("./test/fromcsv/dataset1.csv", "r");
3089     File f2 = File("./test/tocsv/ex3p1.csv", "r");
3090 
3091     while(!f1.eof())
3092     {
3093         assert(f1.readln() == f2.readln());
3094     }
3095     assert(f1.eof() && f2.eof());
3096 
3097     f1.close();
3098     f2.close();
3099 }
3100 
3101 // Parsing of dataset 1
3102 unittest
3103 {
3104     DataFrame!(int, 9, double, int, 4) df;
3105     df.fastCSV("./test/fromcsv/dataset1.csv", 0, 1);
3106     // df.display();
3107     df.to_csv("./test/tocsv/ex5p1.csv", false);
3108 
3109     import std.stdio: File;
3110     File f1 = File("./test/fromcsv/dataset1.csv", "r");
3111     File f2 = File("./test/tocsv/ex5p1.csv", "r");
3112 
3113     while(!f1.eof())
3114     {
3115         assert(f1.readln() == f2.readln());
3116     }
3117     assert(f1.eof() && f2.eof());
3118 
3119     f1.close();
3120     f2.close();
3121 }
3122 
3123 // Parsing dataset 1 considering first column as index
3124 unittest
3125 {
3126     DataFrame!(int, 8, double, int, 4) df;
3127     df.from_csv("./test/fromcsv/dataset1.csv", 1, 1);
3128     // df.display();
3129     df.to_csv("./test/tocsv/ex3p2.csv");
3130 
3131     import std.stdio: File;
3132     File f1 = File("./test/fromcsv/dataset1.csv", "r");
3133     File f2 = File("./test/tocsv/ex3p2.csv", "r");
3134 
3135     while(!f1.eof())
3136     {
3137         assert(f1.readln() == f2.readln());
3138     }
3139     assert(f1.eof() && f2.eof());
3140 
3141     f1.close();
3142     f2.close();
3143 }
3144 
3145 // fastCSV dataset1
3146 unittest
3147 {
3148     DataFrame!(int, 8, double, int, 4) df;
3149     df.fastCSV("./test/fromcsv/dataset1.csv", 1, 1);
3150     // df.display();
3151     df.to_csv("./test/tocsv/ex5p2.csv");
3152 
3153     import std.stdio: File;
3154     File f1 = File("./test/fromcsv/dataset1.csv", "r");
3155     File f2 = File("./test/tocsv/ex5p2.csv", "r");
3156 
3157     while(!f1.eof())
3158     {
3159         assert(f1.readln() == f2.readln());
3160     }
3161     assert(f1.eof() && f2.eof());
3162 
3163     f1.close();
3164     f2.close();
3165 }
3166 
3167 // Parsing dataset 2 with gaps in data
3168 unittest
3169 {
3170     DataFrame!(int, double, 22) df;
3171     df.from_csv("./test/fromcsv/dataset2.csv", 2, 1);
3172     //df.display();
3173     df.to_csv!(4)("./test/tocsv/ex4p1.csv");
3174 
3175     import std.stdio: File;
3176     import std.string: chomp;
3177     import std.array: split;
3178 
3179     File f1 = File("./test/fromcsv/dataset2.csv", "r");
3180     File f2 = File("./test/tocsv/ex4p1.csv", "r");
3181 
3182     while(!f1.eof())
3183     {
3184         auto lf1 = chomp(f1.readln()).split(",");
3185         auto lf2 = chomp(f2.readln()).split(",");
3186         if(lf1.length > 0)
3187             assert(lf1[0 .. 3] == lf2[0 .. 3]);
3188     }
3189     assert(f1.eof() && f2.eof());
3190 
3191     f1.close();
3192     f2.close();
3193 }
3194 
3195 // fastCSV dataset2
3196 unittest
3197 {
3198 
3199     DataFrame!(double, 23) df;
3200     df.fastCSV("./test/fromcsv/dataset2.csv", 2, 1);
3201     // df.display();
3202     df.to_csv("./test/tocsv/ex5p3.csv");
3203 
3204     import std.stdio: File;
3205     import std.string: chomp;
3206     import std.array: split;
3207 
3208     File f1 = File("./test/fromcsv/dataset2.csv", "r");
3209     File f2 = File("./test/tocsv/ex5p3.csv", "r");
3210 
3211     while(!f1.eof())
3212     {
3213         auto lf1 = chomp(f1.readln()).split(",");
3214         auto lf2 = chomp(f2.readln()).split(",");
3215         if(lf1.length > 0)
3216             assert(lf1[0 .. 3] == lf2[0 .. 3]);
3217     }
3218     assert(f1.eof() && f2.eof());
3219 
3220     f1.close();
3221     f2.close();
3222 
3223 }
3224 
3225 // Partially parsing the complete columns of dataset2
3226 unittest
3227 {
3228     DataFrame!(int, 1) df;
3229     df.from_csv("./test/fromcsv/dataset2.csv", 2, 1, [0]);
3230     // df.display();
3231     df.to_csv("./test/tocsv/ex4p2.csv");
3232 
3233     import std.stdio: File;
3234     import std.string: chomp;
3235     import std.array: split;
3236 
3237     File f1 = File("./test/fromcsv/dataset2.csv", "r");
3238     File f2 = File("./test/tocsv/ex4p2.csv", "r");
3239 
3240     while(!f1.eof())
3241     {
3242         auto lf1 = chomp(f1.readln()).split(",");
3243         auto lf2 = chomp(f2.readln()).split(",");
3244         if(lf1.length > 0)
3245             assert(lf1[0 .. 3] == lf2);
3246     }
3247     assert(f1.eof() && f2.eof());
3248 
3249     f1.close();
3250     f2.close();
3251 }
3252 
3253 // Unittest for example in README.md
3254 unittest
3255 {
3256     DataFrame!(int, 2, double, 1) df;
3257     Index index;
3258     index.setIndex([0, 1, 2, 3, 4, 5], ["Row Index"], [0, 1, 2], ["Column Index"]);
3259     df.setFrameIndex(index);
3260     // df.display();
3261 
3262     df.assign!1(2, [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
3263     // df.display();
3264     assert(df.data[2] == [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
3265 
3266     df.assign!1(1, [1, 2, 3]);
3267     // df.display();
3268     assert(df.data[1] == [1, 2, 3, 0, 0, 0]);
3269 
3270     df.assign!0(0, 4, 5, 1.6);
3271     // df.display();
3272     assert(df.data[0][0] == 4);
3273     assert(df.data[1][0] == 5);
3274     assert(df.data[2][0] == 1.6);
3275 
3276     index.extend!0([6]);
3277     df.setFrameIndex(index);
3278     // df.display();
3279     assert(df.rows == 7);
3280 }
3281 
3282 // ditto
3283 unittest
3284 {
3285     Index inx;
3286     DataFrame!(int, 2) df;
3287     inx.setIndex([["Hello", "Hi"], ["Hi", "Hello"]], ["RL1", "RL2"],
3288                 [["Hello", "Hi"], ["Hi", "Hello"]], ["CL1", "CL2"]);
3289     df.setFrameIndex(inx);
3290     // df.display();
3291 
3292     DataFrame!(int, 3) df2;
3293     inx.extend!0(["Hey", "Hey"]);
3294     inx.extend!1(["Yo", "Yo"]);
3295     df2.setFrameIndex(inx);
3296     // df2.display();
3297 }
3298 
3299 // ditto
3300 unittest
3301 {
3302     Index inx;
3303     inx.setIndex([1, 2, 3],["rindex"]);
3304 
3305     DataFrame!(int, 2, double) df;
3306     df.setFrameIndex(inx);
3307     // df.display();
3308     df = [[1.0], [1.0, 2.0], [1.0, 2.0, 3.5]];
3309     assert(df.data[0] == [1, 1, 1]);
3310     assert(df.data[1] == [0, 2, 2]);
3311     assert(df.data[2][2] == 3.5);
3312 
3313     df[0, 0] = 42;
3314     df[["2"], ["1"]] = 17;
3315     // df.display();
3316     assert(df.data[0][0] == 42);
3317     assert(df.data[1][1] == 17);
3318 }
3319 
3320 // Apply
3321 unittest
3322 {
3323     Index inx;
3324     DataFrame!(double, 2) df;
3325     inx.setIndex([["Hello", "Hi"], ["Hi", "Hello"]], ["RL1", "RL2"],
3326                 [["Hello", "Hi"], ["Hi", "Hello"]], ["CL1", "CL2"]);
3327     df.setFrameIndex(inx);
3328     // df.display();
3329 
3330     df.assign!1(0, [1.0, 4.0]);
3331     df.assign!1(1, [16.0, 256.0]);
3332     // df.display();
3333 
3334     import std.math: sqrt;
3335     df.apply!(sqrt, 1)([1]);
3336     assert(df.data[1] == [4, 16]);
3337 
3338     df.apply!(sqrt, 1)([["Hi", "Hello"]]);
3339     assert(df.data[1] == [2, 4]);
3340 
3341     df.apply!(sqrt, 0)([1]);
3342     assert(df.data[0][1] == 2 && df.data[1][1] == 2);
3343 
3344     df.assign!0(1, 16.0, 16.0);
3345     df.apply!(sqrt, 0)([["Hi", "Hello"]]);
3346     assert(df.data[0][1] == 4 && df.data[1][1] == 4);
3347 
3348     df.assign!1(0, [16.0, 16.0]);
3349     df.assign!1(1, [16.0, 16.0]);
3350     // df.display();
3351 
3352     // Apply to the entire DataFrame
3353     df.apply!(sqrt)();
3354     assert(df.data[0] == [4, 4] && df.data[1] == [4, 4]);
3355 
3356     df.apply!(sqrt)();
3357     assert(df.data[0] == [2, 2] && df.data[1] == [2, 2]);
3358 }
3359 
3360 // Drop
3361 unittest
3362 {
3363     Index inx;
3364     DataFrame!(double, 2) df;
3365     inx.setIndex([["Hello", "Hi"], ["Hi", "Hello"]], ["RL1", "RL2"],
3366                 [["Hello", "Hi"], ["Hi", "Hello"]], ["CL1", "CL2"]);
3367     df.setFrameIndex(inx);
3368     // df.display();
3369 
3370     df.assign!1(0, [1.0, 4.0]);
3371     df.assign!1(1, [16.0, 256.0]);
3372     const string str1 = df.display(true, 200);
3373 
3374 
3375     // Dropping a row
3376     auto drow = df.drop!(0, [1]);
3377     assert(drow.rows == 1);
3378     assert(drow.data[0][0] == 1 && drow.data[1][0] == 16);
3379     assert(drow.display(true, 200) == "       CL1  Hello  Hi   \n"
3380         ~ "       CL2  Hi     Hello\n"
3381         ~ "RL1    RL2\n"
3382         ~ "Hello  Hi   1      16   \n"
3383     );
3384 
3385     // Checking if df is untouched
3386     assert(df.data[0] == [1.0, 4.0]);
3387     assert(df.data[1] == [16.0, 256.0]);
3388     const string str2 = df.display(true, 200);
3389     assert(str1 == str2);
3390 
3391     drow = df.drop!(0, [0]);
3392     assert(drow.rows == 1);
3393     assert(drow.data[0][0] == 4 && drow.data[1][0] == 256);
3394     assert(drow.display(true, 200) == "     CL1    Hello  Hi   \n"
3395         ~ "     CL2    Hi     Hello\n"
3396         ~ "RL1  RL2  \n"
3397         ~ "Hi   Hello  4      256  \n"
3398     );
3399 
3400     assert(df.data[0] == [1.0, 4.0]);
3401     assert(df.data[1] == [16.0, 256.0]);
3402     const string str3 = df.display(true, 200);
3403     assert(str1 == str3);
3404 
3405     auto dcol = df.drop!(1, [0]);
3406     assert(dcol.cols == 1);
3407     assert(dcol.data[0] == [16, 256]);
3408     assert(dcol.display(true, 200) == "       CL1    Hi   \n"
3409         ~ "       CL2    Hello\n"
3410         ~ "RL1    RL2  \n"
3411         ~ "Hello  Hi     16   \n"
3412         ~ "Hi     Hello  256  \n"
3413     );
3414 
3415     assert(df.data[0] == [1.0, 4.0]);
3416     assert(df.data[1] == [16.0, 256.0]);
3417     const string str4 = df.display(true, 200);
3418     assert(str1 == str4);
3419 
3420     dcol = df.drop!(1, [1]);
3421     assert(dcol.cols == 1);
3422     assert(dcol.data[0] == [1, 4]);
3423     assert(dcol.display(true, 200) == "       CL1    Hello\n"
3424         ~ "       CL2    Hi   \n"
3425         ~ "RL1    RL2  \n"
3426         ~ "Hello  Hi     1    \n"
3427         ~ "Hi     Hello  4    \n"
3428     );
3429 
3430     assert(df.data[0] == [1.0, 4.0]);
3431     assert(df.data[1] == [16.0, 256.0]);
3432     const string str5 = df.display(true, 200);
3433     assert(str1 == str5);
3434 }
3435 
3436 // Drop - multiple with heterogeneous
3437 unittest
3438 {
3439     Index inx;
3440     DataFrame!(int, double[2]) df;
3441     inx.setIndex([["Hello", "Hi", "Hey"], ["Hi", "Hello", "Hey"]], ["RL1", "RL2"],
3442                 [["Hello", "Hi", "Hey"], ["Hi", "Hello", "Hey"]], ["CL1", "CL2"]);
3443     df.setFrameIndex(inx);
3444     // df.display();
3445 
3446     df.assign!1(0, [1, 4, 16]);
3447     df.assign!1(1, [16.0, 256.0, 225.0]);
3448     df.assign!1(2, [1.0, 4.0, 16.0]);
3449     const string str1 = df.display(true, 200);
3450 
3451     auto drows = df.drop!(0, [1, 2]);
3452     assert(drows.rows == 1);
3453     assert(drows.data[0][0] == 1 && drows.data[1][0] == 16 && drows.data[2][0] == 1);
3454     assert(drows.display(true, 200) == "       CL1  Hello  Hi     Hey\n"
3455         ~ "       CL2  Hi     Hello  Hey\n"
3456         ~ "RL1    RL2\n"
3457         ~ "Hello  Hi   1      16     1  \n"
3458     );
3459 
3460     const string str2 = df.display(true, 200);
3461     assert(str1 == str2);
3462 
3463     drows = df.drop!(0, [0, 2]);
3464     assert(drows.rows == 1);
3465     assert(drows.data[0][0] == 4 && drows.data[1][0] == 256 && drows.data[2][0] == 4);
3466     assert(drows.display(true, 200) == "     CL1    Hello  Hi     Hey\n"
3467         ~ "     CL2    Hi     Hello  Hey\n"
3468         ~ "RL1  RL2  \n"
3469         ~ "Hi   Hello  4      256    4  \n"
3470     );
3471 
3472     const string str3 = df.display(true, 200);
3473     assert(str1 == str3);
3474 
3475     auto dcols = df.drop!(1, [1, 2]);
3476     assert(dcols.cols == 1);
3477     assert(dcols.data[0] == [1, 4, 16]);
3478     assert(dcols.display(true, 200) == "       CL1    Hello\n"
3479         ~ "       CL2    Hi   \n"
3480         ~ "RL1    RL2  \n"
3481         ~ "Hello  Hi     1    \n"
3482         ~ "Hi     Hello  4    \n"
3483         ~ "Hey    Hey    16   \n"
3484     );
3485 
3486     const string str4 = df.display(true, 200);
3487     assert(str1 == str4);
3488 
3489     auto dcols2 = df.drop!(1, [0, 1]);
3490     assert(dcols2.cols == 1);
3491     assert(dcols2.data[0] == [1, 4, 16]);
3492     assert(dcols2.display(true, 200) == "       CL1    Hey\n"
3493         ~ "       CL2    Hey\n"
3494         ~ "RL1    RL2  \n"
3495         ~ "Hello  Hi     1  \n"
3496         ~ "Hi     Hello  4  \n"
3497         ~ "Hey    Hey    16 \n"
3498     );
3499 
3500     const string str5 = df.display(true, 200);
3501     assert(str1 == str5);
3502 }
3503 
3504 // columntoIndex
3505 unittest
3506 {
3507     Index inx;
3508     DataFrame!(double, 2) df;
3509     inx.setIndex([["Hello", "Hi"], ["Hi", "Hello"]], ["RL1", "RL2"],
3510                 [["Hello", "Hi"], ["Hi", "Hello"]], ["CL1", "CL2"]);
3511     df.setFrameIndex(inx);
3512     // df.display();
3513 
3514     df.assign!1(0, [1.0, 4.0]);
3515     df.assign!1(1, [16.0, 256.0]);
3516     const string str1 = df.display(true, 200);
3517 
3518     auto extended = df.columnToIndex!(0);
3519     assert(extended.indx.row.codes.length == 3);
3520     assert(extended.indx.row.index[2] == []);
3521     assert(extended.indx.row.codes[2] == [1, 4]);
3522     assert(extended.cols == 1);
3523     assert(extended.data[0] == [16, 256]);
3524 
3525     assert(extended.display(true, 200) == "              CL1  Hi   \n"
3526         ~ "              CL2  Hello\n"
3527         ~ "RL1    RL2    Hi \n"
3528         ~ "Hello  Hi     1    16   \n"
3529         ~ "Hi     Hello  4    256  \n"
3530     );
3531 
3532     const string str2 = df.display(true, 200);
3533     assert(str1 == str2);
3534 
3535     extended = df.columnToIndex!(1);
3536     assert(extended.indx.row.codes.length == 3);
3537     assert(extended.indx.row.index[2] == []);
3538     assert(extended.indx.row.codes[2] == [16, 256]);
3539     assert(extended.cols == 1);
3540     assert(extended.data[0] == [1, 4]);
3541 
3542     assert(extended.display(true, 200) == "              CL1    Hello\n"
3543         ~ "              CL2    Hi   \n"
3544         ~ "RL1    RL2    Hello\n"
3545         ~ "Hello  Hi     16     1    \n"
3546         ~ "Hi     Hello  256    4    \n"
3547     );
3548 
3549     const string str3 = df.display(true, 200);
3550     assert(str1 == str3);
3551 }
3552 
3553 // Converting a level of index to a Data level
3554 unittest
3555 {
3556     Index inx;
3557     DataFrame!(double, 2) df;
3558     inx.setIndex([["Hello", "Hi"], ["Hi", "Hello"]], ["RL1", "RL2"],
3559                 [["Hello", "Hi"], ["Hi", "Hello"]], ["CL1", "CL2"]);
3560     df.setFrameIndex(inx);
3561     // df.display();
3562 
3563     df.assign!1(0, [1.0, 4.0]);
3564     df.assign!1(1, [16.0, 256.0]);
3565     const string str1 = df.display(true, 200);
3566 
3567     auto extended = df.columnToIndex!(0);
3568     // Putting it back in place should give same DataFrame back
3569     const string str2 = extended.indexToData!(0, double)(2, ["Hello", "Hi"]).display(true, 200);
3570 
3571     // The final DataFrame should be same as the original one
3572     assert(str1 == str2);
3573 }
3574 
3575 // Gropby on DataFrame Struct - Part of xample ported over from group.d
3576 unittest
3577 {
3578     DataFrame!(int, 5) df;
3579     Index inx;
3580     inx.setIndex([["Hello", "Hi", "Hey"], ["Hi", "Hello", "Hey"], ["Hey", "Hello", "Hi"]], ["1", "2", "3"]);
3581     df.setFrameIndex(inx);
3582     df.assign!1(2, [1,2,3]);
3583     df.assign!1(4, [1,2,3]);
3584     // string str1 = df.display(true, 200);
3585 
3586     auto grp = df.groupBy!([2])([0, 1]);
3587 
3588     import magpie.group: Group;
3589     Group!(int, int, int, int) grpBy;
3590     grpBy.createGroup!([2])(df, [0, 1]);
3591 
3592     assert(grp.display(true, 200) == grpBy.display(true, 200));
3593 }
3594 
3595 // As a universal Slice
3596 unittest
3597 {
3598     DataFrame!(int, 5) df;
3599     Index inx;
3600     inx.setIndex([["Hello", "Hi", "Hey"], ["Hi", "Hello", "Hey"], ["Hey", "Hello", "Hi"]], ["1", "2", "3"]);
3601     df.setFrameIndex(inx);
3602     df.assign!1(2, [1,2,3]);
3603     df.assign!1(4, [1,2,3]);
3604 
3605     auto dfslice = df.asSlice!(int, Universal);
3606     assert(dfslice == [[0, 0, 1, 0, 1], [0, 0, 2, 0, 2], [0, 0, 3, 0, 3]]);
3607 }
3608 
3609 // As a canonical slice
3610 unittest
3611 {
3612     DataFrame!(int, 5) df;
3613     Index inx;
3614     inx.setIndex([["Hello", "Hi", "Hey"], ["Hi", "Hello", "Hey"], ["Hey", "Hello", "Hi"]], ["1", "2", "3"]);
3615     df.setFrameIndex(inx);
3616     df.assign!1(2, [1,2,3]);
3617     df.assign!1(4, [1,2,3]);
3618 
3619     auto dfslice = df.asSlice!(int, Canonical);
3620     assert(dfslice == [[0, 0, 1, 0, 1], [0, 0, 2, 0, 2], [0, 0, 3, 0, 3]]);
3621 }
3622 
3623 // As a contiguous slice
3624 unittest
3625 {
3626     DataFrame!(int, 5) df;
3627     Index inx;
3628     inx.setIndex([["Hello", "Hi", "Hey"], ["Hi", "Hello", "Hey"], ["Hey", "Hello", "Hi"]], ["1", "2", "3"]);
3629     df.setFrameIndex(inx);
3630     df.assign!1(2, [1,2,3]);
3631     df.assign!1(4, [1,2,3]);
3632 
3633     auto dfslice = df.asSlice!(int, Contiguous);
3634     assert(dfslice == [[0, 0, 1, 0, 1], [0, 0, 2, 0, 2], [0, 0, 3, 0, 3]]);
3635 }
3636 
3637 // Slice assignment
3638 unittest
3639 {
3640     DataFrame!(int, 5) df;
3641     Index inx;
3642     inx.setIndex([["Hello", "Hi", "Hey"], ["Hi", "Hello", "Hey"], ["Hey", "Hello", "Hi"]], ["1", "2", "3"]);
3643     df.setFrameIndex(inx);
3644     df.assign!1(2, [1,2,3]);
3645     df.assign!1(4, [1,2,3]);
3646 
3647     auto dfslice = df.asSlice!(int, Contiguous);
3648     
3649     DataFrame!(int, 5) df2;
3650     df2.setFrameIndex(inx);
3651 
3652     df2 = dfslice;
3653     assert(df.display(true, 200) == df2.display(true, 200));
3654 }
3655 
3656 // PArtial Slice Assignment
3657 unittest
3658 {
3659     DataFrame!(int, 5) df;
3660     Index inx;
3661     inx.setIndex([["Hello", "Hi", "Hey"], ["Hi", "Hello", "Hey"], ["Hey", "Hello", "Hi"]], ["1", "2", "3"]);
3662     df.setFrameIndex(inx);
3663     
3664     auto assn = slice!(int)(1, 1);
3665     assn[0][0] = 42;
3666     df = assn;
3667 
3668     assert(df.data[0][0] == 42);
3669 }
3670 
3671 // asSlice oveload
3672 unittest
3673 {
3674     DataFrame!(int, 5) df;
3675     Index inx;
3676     inx.setIndex([["Hello", "Hi", "Hey"], ["Hi", "Hello", "Hey"], ["Hey", "Hello", "Hi"]], ["1", "2", "3"]);
3677     df.setFrameIndex(inx);
3678     df.assign!1(2, [1,2,3]);
3679     df.assign!1(4, [1,2,3]);
3680 
3681     assert(df.asSlice!(Universal, int, 1)(["4"]) == [1, 2, 3]);
3682     assert(df.asSlice!(Universal)(["Hello", "Hi", "Hey"]) == ["0", "0", "1", "0", "1"]);
3683 }
3684 
3685 // Slice opIndexOpAssign
3686 unittest
3687 {
3688     DataFrame!(int, 5) df;
3689     Index inx;
3690     inx.setIndex([["Hello", "Hi", "Hey"], ["Hi", "Hello", "Hey"], ["Hey", "Hello", "Hi"]], ["1", "2", "3"]);
3691     df.setFrameIndex(inx);
3692     df.assign!1(2, [1,2,3]);
3693     df.assign!1(4, [1,2,3]);
3694 
3695     df[["3"]] = df.asSlice!(Universal, int, 1)(["4"]);
3696     assert(df.asSlice!(Universal, int, 1)(["3"]) == [1, 2, 3]);
3697 
3698     df[["Hello", "Hi", "Hey"], 0] = df.asSlice!(Universal)(["Hi", "Hello", "Hello"]);
3699     assert(df.asSlice!(Universal)(["Hello", "Hi", "Hey"]) == ["0", "0", "2", "2", "2"]);
3700 }
3701 
3702 // Slice opIndexOpAssign - heterogeneous DataFrame
3703 unittest
3704 {
3705     DataFrame!(int, 3, double, 2) df;
3706     Index inx;
3707     inx.setIndex([["Hello", "Hi", "Hey"], ["Hi", "Hello", "Hey"], ["Hey", "Hello", "Hi"]], ["1", "2", "3"]);
3708     df.setFrameIndex(inx);
3709     df.assign!1(2, [1,2,3]);
3710     df.assign!1(4, [1.0, 2.0, 3.0]);
3711 
3712     df[["1"]] = df.asSlice!(Universal, int, 1)(["4"]);
3713     assert(df.asSlice!(Universal, int, 1)(["1"]) == [1, 2, 3]);
3714 
3715     df[["Hello", "Hi", "Hey"], 0] = df.asSlice!(Universal)(["Hi", "Hello", "Hello"]);
3716     assert(df.asSlice!(Universal)(["Hello", "Hi", "Hey"]) == ["0", "2", "2", "nan", "2"]);
3717 }
3718 
3719 // Aggregate Operation on DataFrame Columns
3720 unittest
3721 {
3722     DataFrame!(int, 2, double, 2) df;
3723     Index inx;
3724 
3725     inx[0] = ["Row1", "Row2"];
3726     inx[1] = ["Col1", "Col2", "Col3", "Col4"];
3727 
3728     df.setFrameIndex(inx);
3729     df = [[1, 2, 3.4, 5.6], [2, 8, 7.9, 5.6]];
3730     // df.display();
3731 
3732     import std.algorithm: max, min;
3733     assert(df.aggregate!(1, max).display(true, 200) == "Operation  Col1  Col2  Col3  Col4\n"
3734         ~ "max        2     8     7.9   5.6 \n"
3735     );
3736 
3737     auto mindf = df.aggregate!(1, min);
3738     import std.math: approxEqual;
3739     static assert(is(mindf.RowType == AliasSeq!(int, int, double, double)));
3740     assert(mindf.data[0][0] == 1 && mindf.data[1][0] == 2);
3741     assert(approxEqual(mindf.data[2][0], 3.4, 1e-8) && approxEqual(mindf.data[3][0], 5.6, 1e-8));
3742 
3743     auto doubledf = df.aggregate!(1, max, min);
3744     static assert(is(doubledf.RowType == AliasSeq!(int, int, double, double)));
3745     assert(doubledf.data[0][0] == 2 && doubledf.data[1][0] == 8);
3746     assert(approxEqual(doubledf.data[2][0], 7.9, 1e-8) && approxEqual(doubledf.data[3][0], 5.6, 1e-8));
3747     assert(doubledf.data[0][1] == 1 && doubledf.data[1][1] == 2);
3748     assert(approxEqual(doubledf.data[2][1], 3.4, 1e-8) && approxEqual(doubledf.data[3][1], 5.6, 1e-8));
3749 }
3750 
3751 // Aggregate Operation on DataFrame Columns
3752 unittest
3753 {
3754     DataFrame!(int, 2, double, 2) df;
3755     Index inx;
3756 
3757     inx[0] = ["Row1", "Row2"];
3758     inx[1] = ["Col1", "Col2", "Col3", "Col4"];
3759 
3760     df.setFrameIndex(inx);
3761     df = [[1, 2, 3.4, 5.6], [2, 8, 7.9, 5.6]];
3762     // df.display();
3763 
3764     import std.algorithm: max, min;
3765     import std.math: approxEqual;
3766 
3767     auto maxdf = df.aggregate!(0, max);
3768     assert(approxEqual(maxdf.data[0][0], 5.6, 1e-8) && approxEqual(maxdf.data[0][1], 8, 1e-8));
3769 
3770     auto mindf = df.aggregate!(0, min);
3771     assert(approxEqual(mindf.data[0][0], 1, 1e-8) && approxEqual(mindf.data[0][1], 2, 1e-8));
3772 
3773     auto doubledf = df.aggregate!(0, min, max);
3774     assert(approxEqual(doubledf.data[0][0], 1, 1e-8) && approxEqual(doubledf.data[0][1], 2, 1e-8));
3775     assert(approxEqual(doubledf.data[1][0], 5.6, 1e-8) && approxEqual(doubledf.data[1][1], 8, 1e-8));
3776 }
3777 
3778 // Custom function passed to aggregate
3779 unittest
3780 {
3781     static auto customFunc(T)(T[] arr)
3782     {
3783         T res = 0;
3784         foreach(i, ele; arr)
3785             res += i * ele;
3786         
3787         return res;
3788     }
3789 
3790     DataFrame!(int, 2, double, 2) df;
3791     Index inx;
3792 
3793     inx[0] = ["Row1", "Row2"];
3794     inx[1] = ["Col1", "Col2", "Col3", "Col4"];
3795 
3796     df.setFrameIndex(inx);
3797     df = [[1, 2, 3.4, 5.6], [2, 8, 7.9, 5.6]];
3798     // df.display();
3799 
3800     import std.math: approxEqual;
3801     auto customdf = df.aggregate!(1, customFunc);
3802     assert(customdf.data[0][0] == 2 && customdf.data[1][0] == 8);
3803     assert(approxEqual(customdf.data[2][0], 7.9, 1e-8) && approxEqual(customdf.data[3][0], 5.6, 1e-8)); 
3804 }
3805 
3806 // Return Type optimization for Aggregate
3807 unittest
3808 {
3809     DataFrame!(int, 4) df;
3810     Index inx;
3811 
3812     inx[0] = ["Row1", "Row2"];
3813     inx[1] = ["Col1", "Col2", "Col3", "Col4"];
3814 
3815     df.setFrameIndex(inx);
3816     df = [[1, 2, 3, 5], [2, 8, 7, 6]];
3817     // df.display();
3818 
3819     import std.algorithm: max, min;
3820     import std.math: approxEqual;
3821 
3822     auto maxdf = df.aggregate!(0, max);
3823     static assert(is(maxdf.FrameType == int[][1]));
3824     assert(maxdf.data[0][0] == 5 && maxdf.data[0][1] == 8);
3825 
3826     auto maxdfc = df.aggregate!(1, max);
3827     static assert(is(maxdfc.FrameType == int[][4]));
3828     assert(maxdfc.data[0][0] == 2 && maxdfc.data[1][0] == 8 && maxdfc.data[2][0] == 7 && maxdfc.data[3][0] == 6);
3829 }
3830 
3831 // Filter on Heterogeneous DataFrame
3832 unittest
3833 {
3834     DataFrame!(double, float) df;
3835     Index inx;
3836     inx[0] = ["Firm1", "Firm2", "Firm3", "Firm4", "Firm5"];
3837     inx[1] = ["Assets", "Valuation"];
3838     df.setFrameIndex(inx);
3839 
3840     static bool filterFunc(T)(T ele)
3841     {
3842         return (ele[0] > ele[1]);
3843     }
3844 
3845     df = [[1.2, 2.3], [0.8, 1.2], [4.2, 1.2], [7.2, 9.4], [1.1, 0.5]];
3846     // df.display();
3847     assert(df.filter!(filterFunc).display(true, 200) == "       Assets  Valuation\n"
3848         ~ "Firm3  4.2     1.2      \n"
3849         ~ "Firm5  1.1     0.5      \n"
3850     );
3851 
3852     version(DMD)
3853     {
3854         assert(df.filter!(e => e[0] > e[1]).display(true, 200) == "       Assets  Valuation\n"
3855             ~ "Firm3  4.2     1.2      \n"
3856             ~ "Firm5  1.1     0.5      \n"
3857         );
3858     }
3859 }
3860 
3861 // Filter on Homogeneous DataFrame
3862 unittest
3863 {
3864     DataFrame!(float, float) df;
3865     Index inx;
3866     inx[0] = ["Firm1", "Firm2", "Firm3", "Firm4", "Firm5"];
3867     inx[1] = ["Assets", "Valuation"];
3868     df.setFrameIndex(inx);
3869 
3870     static bool filterFunc(T)(T ele)
3871     {
3872         return (ele[0] > ele[1]);
3873     }
3874 
3875     df = [[1.2, 2.3], [0.8, 1.2], [4.2, 1.2], [7.2, 9.4], [1.1, 0.5]];
3876     // df.display();
3877     assert(df.filter!(filterFunc).display(true, 200) == "       Assets  Valuation\n"
3878         ~ "Firm3  4.2     1.2      \n"
3879         ~ "Firm5  1.1     0.5      \n"
3880     );
3881 }