1 module magpie.group; 2 3 import magpie.axis: Axis, DataType; 4 import magpie.dataframe: DataFrame; 5 import magpie.index: Index; 6 import magpie.helper: dropper, transposed, toArr, vectorize, isHomogeneous, suitableType, auxDispatch; 7 8 import std.meta: staticMap, AliasSeq; 9 import mir.ndslice; 10 11 /// Struct for groupBy Operation 12 struct Group(GrpRowType...) 13 { 14 /// Group names 15 string[][] groups; 16 17 /// number of elements before the particular group 18 int[] elementCountTill; 19 20 /// Flag to check if the Group is Homogeneous 21 enum bool isHomogeneousType = isHomogeneous!(GrpRowType); 22 23 static if(!isHomogeneous!(GrpRowType)) 24 alias GrpType = staticMap!(toArr, GrpRowType); 25 else 26 alias GrpType = toArr!(GrpRowType[0])[GrpRowType.length]; 27 /// Data of Group 28 GrpType data; 29 30 /// Index for group 31 Index grpIndex; 32 33 private: 34 ptrdiff_t positionInGroup(int axis)(string[] index, ptrdiff_t grpPos) 35 { 36 import std.array: appender; 37 import std.algorithm: countUntil; 38 import std.conv: to; 39 auto codes = appender!(int[]); 40 41 foreach(i; 0 .. grpIndex.indexing[axis].codes.length) 42 { 43 if(grpIndex.indexing[axis].index[i].length == 0) 44 codes.put(to!int(index[i])); 45 else 46 { 47 int indxpos = cast(int)countUntil(grpIndex.indexing[axis].index[i], index[i]); 48 if(indxpos < 0) 49 return -1; 50 codes.put(indxpos); 51 } 52 } 53 54 foreach(i; 0 .. (axis == 0)? elementCountTill[grpPos +1] - elementCountTill[grpPos]: GrpType.length) 55 { 56 bool flag = true; 57 foreach(j; 0 .. grpIndex.indexing[axis].codes.length) 58 { 59 if(grpIndex.indexing[axis].codes[j][i + ((axis == 0)? elementCountTill[grpPos]: 0)] != codes.data[j]) 60 flag = false; 61 } 62 63 if(flag) 64 return i; 65 } 66 67 return -1; 68 } 69 70 auto asSliceInternal(Type, SliceKind kind)(size_t start, size_t stop) 71 { 72 static if(__traits(isArithmetic, Type)) 73 alias RetType = Type; 74 else 75 alias RetType = string; 76 77 static if(kind == Universal) 78 Slice!(RetType*, 2, kind) ret = slice!(RetType)(stop - start, GrpRowType.length).universal; 79 else static if(kind == Canonical) 80 Slice!(RetType*, 2, kind) ret = slice!(RetType)(stop - start, GrpRowType.length).canonical; 81 else 82 Slice!(RetType*, 2, kind) ret = slice!(RetType)(stop - start, GrpRowType.length); 83 84 static foreach(i; 0 .. GrpRowType.length) 85 { 86 static if(__traits(isArithmetic, RetType, GrpRowType[i])) 87 { 88 foreach(j; start .. stop) 89 ret[j - start][i] = cast(RetType)data[i][j]; 90 } 91 else static if(is(RetType == string)) 92 { 93 import std.conv: to; 94 foreach(j; start .. stop) 95 ret[j - start][i] = to!(string)(data[i][j]); 96 } 97 } 98 99 return ret; 100 } 101 102 template resolverInternal(T, Ops...) 103 { 104 import std.traits: ReturnType; 105 static if(Ops.length) 106 { 107 alias Op = Ops[0]; 108 static if(__traits(compiles, ReturnType!(Op))) 109 alias resolverInternal = AliasSeq!(ReturnType!(Op), resolverInternal!(T, Ops[1 .. $])); 110 else static if(__traits(compiles, ReturnType!(Op!T))) 111 alias resolverInternal = AliasSeq!(ReturnType!(Op!T), resolverInternal!(T, Ops[1 .. $])); 112 else static if(__traits(compiles, ReturnType!(Op!(toArr!T)))) 113 alias resolverInternal = AliasSeq!(ReturnType!(Op!(toArr!T)), resolverInternal!(T, Ops[1 .. $])); 114 else 115 alias resolverInternal = AliasSeq!(ReturnType!(Op!(T,T)), resolverInternal!(T, Ops[1 .. $])); 116 } 117 else 118 alias resolverInternal = AliasSeq!(); 119 } 120 121 template aggregateType(Ops...) 122 { 123 alias fwdType = suitableType!(GrpRowType); 124 alias Resolved = resolverInternal!(fwdType, Ops); 125 alias aggregateType = Resolved; 126 } 127 128 public: 129 /++ 130 ptrdiff_t getGroupPosition(string[] grpTitles) 131 Description: Get position of a particular group in the sruct 132 @params: grpTitles - the group you wnant to search for 133 +/ 134 ptrdiff_t getGroupPosition(string[] grpTitles) 135 { 136 foreach(i, ele; groups) 137 if(grpTitles == ele) 138 return cast(int)i; 139 140 return -1; 141 } 142 143 /++ 144 void createGroup(T)(DataFrame!T df, int[] levels) 145 Description: Funtion to create a groupBy object from a DataFrame 146 @parmas: df - DataFrame on which groupBy is operated 147 @params: levels - levels to consider for grouping 148 +/ 149 void createGroup(int[] dataLevels = [], T...)(DataFrame!T df, int[] indexLevels) 150 { 151 assert(dataLevels.length + indexLevels.length > 0, "Cannot group without specifying any levels"); 152 import std.algorithm: reduce, max, min, sort; 153 154 if(indexLevels.length > 0) 155 { 156 assert(indexLevels.reduce!max < df.indx.row.index.length); 157 assert(indexLevels.reduce!min > -1); 158 } 159 160 static if(dataLevels.length > 0) 161 { 162 assert(dataLevels.reduce!max < df.RowType.length); 163 assert(dataLevels.reduce!min > -1); 164 } 165 166 string[][] levels; 167 levels.length = df.rows; 168 foreach(i; 0 .. df.rows) 169 { 170 levels[i].length = indexLevels.length + dataLevels.length; 171 foreach(j, ele; indexLevels) 172 { 173 if(df.indx.row.index[ele].length == 0) 174 { 175 import std.conv: to; 176 levels[i][j] = to!string(df.indx.row.codes[ele][i]); 177 } 178 else 179 levels[i][j] = df.indx.row.index[ele][df.indx.row.codes[ele][i]]; 180 } 181 182 static foreach(j, ele; dataLevels) 183 { 184 static if(is(df.RowType[ele] == string)) 185 levels[i][indexLevels.length + j] = df.data[ele][i]; 186 else 187 { 188 import std.conv: to; 189 levels[i][indexLevels.length + j] = to!string(df.data[ele][i]); 190 } 191 } 192 } 193 194 // This is required as dropper!(dataLevels, df.data) won't work 195 auto dropped = df.drop!(1, dataLevels); 196 197 static foreach(i; 0 .. GrpRowType.length) 198 data[i] = dropped.data[i]; 199 200 grpIndex.column = dropped.indx.column; 201 grpIndex.row.titles = dropper(indexLevels, df.indx.row.titles); 202 grpIndex.row.index = dropper(indexLevels, df.indx.row.index); 203 204 import std.range: zip; 205 int[] codes = vectorize(levels); 206 int[][] rcodes = transposed(dropper(indexLevels, df.indx.row.codes)); 207 // Simultaneously arranging all the relavent fields using a zip as displayed in the docs 208 209 static if(isHomogeneousType) 210 auto sortdata = transposed(data); 211 else 212 alias sortdata = data; 213 214 auto arrange = zip(levels, codes[1 .. $], sortdata, rcodes).sort!((a, b) => a[1] < b[1]); 215 216 static if(isHomogeneousType) 217 foreach(i, ele; sortdata) 218 foreach(j, iele; ele) 219 data[j][i] = iele; 220 221 grpIndex.row.codes = transposed(rcodes); 222 223 elementCountTill.length = codes[0] + 1; 224 groups.length = codes[0]; 225 groups[0] = levels[0]; 226 elementCountTill[0] = 0; 227 int uele = 0, count = 0; 228 229 foreach(i; codes[1 .. $]) 230 { 231 if(i != uele) 232 { 233 groups[uele + 1] = levels[count]; 234 elementCountTill[uele + 1] = count; 235 ++uele; 236 } 237 count++; 238 } 239 240 elementCountTill[uele + 1] = count; 241 } 242 243 /++ 244 string display(T)(T[] groupIndex = [],bool getStr = false, int termianlw = 0) 245 Description: Display groupBy on terminal 246 @params: groupIndex - Group index in form of integer array or string array 247 @params: getStr - Returns the generated display sring 248 @params: termianlw - Set terminal width 249 +/ 250 string display(T)(T[] groupIndex = [],bool getStr = false, int termianlw = 0) 251 { 252 import std.array: appender; 253 auto retstr = appender!(string); 254 255 ptrdiff_t[] pos; 256 static if(is(T == int)) 257 { 258 import std.algorithm: reduce, max, min; 259 assert(groupIndex.reduce!max < groups.length, "Index out of bound"); 260 assert(groupIndex.reduce!min > -1, "Index out of bound"); 261 pos.length = groupIndex.length; 262 foreach(i, ele; groupIndex) 263 pos[i] = ele; 264 } 265 else static if(is(T == string)) 266 { 267 pos.length = 1; 268 ptrdiff_t indxpos = getGroupPosition(groupIndex); 269 assert(indxpos > -1, "Group not found"); 270 pos[0] = indxpos; 271 } 272 else static if(is(T == string[])) 273 { 274 pos.length = groupIndex.length; 275 foreach(i, ele; groupIndex) 276 { 277 ptrdiff_t indxpos = getGroupPosition(ele); 278 assert(indxpos > -1, "Group not found"); 279 pos[i] = indxpos; 280 } 281 } 282 else static assert(0, "Group Indexes must be an array of integer, 1D array of string or 2D array of string"); 283 284 DataFrame!(true, GrpRowType) displayHelper; 285 displayHelper.indx.column = grpIndex.column; 286 displayHelper.indx.row.titles = grpIndex.row.titles; 287 displayHelper.indx.row.index = grpIndex.row.index; 288 displayHelper.indx.row.codes.length = grpIndex.row.codes.length; 289 290 foreach(i, ele; pos) 291 { 292 foreach(j; 0 .. displayHelper.indx.row.codes.length) 293 { 294 displayHelper.indx.row.codes[j] = grpIndex.row.codes[j][elementCountTill[ele] .. elementCountTill[ele + 1]]; 295 } 296 297 static foreach(j; 0 .. GrpRowType.length) 298 displayHelper.data[j] = data[j][elementCountTill[ele] .. elementCountTill[ele + 1]]; 299 300 displayHelper.rows = elementCountTill[ele + 1] - elementCountTill[ele]; 301 string display = displayHelper.display(true, termianlw); 302 303 if(getStr) 304 { 305 retstr.put(display); 306 retstr.put("\n"); 307 } 308 else 309 { 310 import std.stdio: writeln; 311 writeln("Group: ", groups[i]); 312 writeln("Group Dimension: [ ", elementCountTill[ele + 1] - elementCountTill[ele]," X ", GrpType.length, " ]"); 313 writeln(display); 314 } 315 } 316 317 return retstr.data; 318 } 319 320 /++ 321 string display(bool getStr = false, int termianlw = 0) 322 Description: Display complete groupBy on terminal 323 @params: getStr - Returns the generated display sring 324 @params: termianlw - Set terminal width 325 +/ 326 string display(bool getStr = false, int termianlw = 0) 327 { 328 int[] pos; 329 pos.length = groups.length; 330 foreach(i; 0 .. groups.length) 331 pos[i] = cast(int)i; 332 333 return display(pos, getStr, termianlw); 334 } 335 336 /++ 337 string[][] getGroups() @property 338 Description: Get the generated string array containing all the groups 339 +/ 340 string[][] getGroups() @property 341 { 342 return groups; 343 } 344 345 /++ 346 auto combine(T)(T[] groupIndex) 347 Description: Combines selective groups into a DataFrame 348 @params: groupIndex - Group index in form of integer array or string array 349 +/ 350 auto combine(T)(T[] groupIndex) 351 { 352 ptrdiff_t[] pos; 353 static if(is(T == int)) 354 { 355 import std.algorithm: reduce, max, min; 356 assert(groupIndex.reduce!max < groups.length, "Index out of bound"); 357 assert(groupIndex.reduce!min > -1, "Index out of bound"); 358 pos.length = groupIndex.length; 359 foreach(i, ele; groupIndex) 360 pos[i] = ele; 361 } 362 else static if(is(T == string)) 363 { 364 pos.length = 1; 365 ptrdiff_t indxpos = getGroupPosition(groupIndex); 366 assert(indxpos > -1, "Group not found"); 367 pos[0] = indxpos; 368 } 369 else static if(is(T == string[])) 370 { 371 pos.length = groupIndex.length; 372 foreach(i, ele; groupIndex) 373 { 374 ptrdiff_t indxpos = getGroupPosition(ele); 375 assert(indxpos > -1, "Group not found"); 376 pos[i] = indxpos; 377 } 378 } 379 else 380 { 381 assert(0, "Group Indexes must be an array of integer, 1D array of string or 2D array of string"); 382 } 383 384 DataFrame!(true, GrpRowType) combinator; 385 combinator.indx.column = grpIndex.column; 386 foreach(i; 0 .. groups[0].length) 387 { 388 import std.conv: to; 389 ++combinator.indx.row.titles.length; 390 combinator.indx.row.titles[i] = "GroupL" ~ to!(string)(i + 1); 391 } 392 393 combinator.indx.row.titles ~= grpIndex.row.titles; 394 combinator.indx.row.index.length = combinator.indx.row.titles.length; 395 combinator.indx.row.codes.length = combinator.indx.row.titles.length; 396 397 foreach(i; 0 .. grpIndex.row.index.length) 398 combinator.indx.row.index[groups[0].length + i] = grpIndex.row.index[i]; 399 400 foreach(i, elei; pos) 401 { 402 foreach(j, elej; groups[pos[i]]) 403 { 404 combinator.indx.row.index[j].length += elementCountTill[elei + 1] - elementCountTill[elei]; 405 foreach(k; 0 .. elementCountTill[pos[i] + 1] - elementCountTill[pos[i]]) 406 combinator.indx.row.index[j][$ - 1 - k] = elej; 407 } 408 409 foreach(j; 0 .. grpIndex.row.index.length) 410 combinator.indx.row.codes[groups[0].length + j] ~= grpIndex.row.codes[j][elementCountTill[elei] .. elementCountTill[elei + 1]]; 411 412 static foreach(j; 0 .. GrpRowType.length) 413 combinator.data[j] ~= data[j][elementCountTill[elei] .. elementCountTill[elei + 1]]; 414 415 combinator.rows += elementCountTill[elei + 1] - elementCountTill[elei]; 416 } 417 418 combinator.indx.generateCodes(); 419 420 return combinator; 421 } 422 423 /++ 424 auto combine(T)(T[] groupIndex) 425 Description: Combines all te groups into a DataFrame 426 +/ 427 auto combine() 428 { 429 int[] pos; 430 pos.length = groups.length; 431 foreach(i; 0 .. groups.length) 432 pos[i] = cast(int)i; 433 434 return combine(pos); 435 } 436 437 /++ 438 Index operation of the form gp[size_t, size_t, size_t] 439 +/ 440 auto opIndex(size_t i1, size_t i2, size_t i3) 441 { 442 auto returnAux(ptrdiff_t si = -1)(size_t ri = 0) @property 443 { 444 static if(si > -1) 445 alias i = si; 446 else 447 size_t i = ri; 448 449 return data[i][elementCountTill[i1] + i2]; 450 } 451 452 mixin auxDispatch!(returnAux, isHomogeneousType, GrpRowType); 453 return auxDispatch(i3); 454 } 455 456 /++ 457 Index operation of the form gp[["Grp Index"], ["Row-Index"], ["Column-Index"]] 458 +/ 459 auto opIndex(T, U, V)(T i1, U i2, V i3) 460 if((is(T == string) || is(T == string[])) 461 && (is(U == string) || is(U == string[])) 462 && (is(V == string) || is(V == string[]))) 463 { 464 ptrdiff_t[3] pos; 465 static if(is(T == string)) 466 pos[0] = getGroupPosition([i1]); 467 else 468 pos[0] = getGroupPosition(i1); 469 assert(pos[0] > -1, "Index out of bound"); 470 471 static if(is(U == string)) 472 pos[1] = positionInGroup!(0)([i2], pos[0]); 473 else 474 pos[1] = positionInGroup!(0)(i2, pos[0]); 475 assert(pos[1] > -1, "Index out of bound"); 476 477 static if(is(V == string)) 478 pos[2] = positionInGroup!(1)([i3], pos[0]); 479 else 480 pos[2] = positionInGroup!(1)(i3, pos[0]); 481 assert(pos[2] > -1, "Index out of bound"); 482 483 auto returnAux(ptrdiff_t si = -1)(size_t ri) @property 484 { 485 static if(si > -1) 486 alias i = si; 487 else 488 size_t i = ri; 489 490 return data[i][elementCountTill[pos[0]] + pos[1]]; 491 } 492 493 mixin auxDispatch!(returnAux, isHomogeneousType, GrpRowType); 494 return auxDispatch(pos[2]); 495 } 496 497 /++ 498 opIndex that returns Axis for column/row binary operations 499 +/ 500 auto opIndex(T, U, Args...)(T groupIndex, U index, Args args) 501 if((is(T == string) || is(T == string[])) 502 && (is(U == string) || is(U == string[])) 503 && (Args.length == 0 || (Args.length == 1 && is(Args[0] == int)))) 504 { 505 ptrdiff_t[2] pos; 506 static if(is(T == string)) 507 { 508 ptrdiff_t grpPos = getGroupPosition([groupIndex]); 509 assert(grpPos > -1, "Index out of bound"); 510 pos[0] = grpPos; 511 } 512 else 513 { 514 ptrdiff_t grpPos = getGroupPosition(groupIndex); 515 assert(grpPos > -1, "Index out of bound"); 516 pos[0] = grpPos; 517 } 518 519 static if(is(U == string)) 520 { 521 ptrdiff_t axisPos = positionInGroup!(1 - Args.length)([index], pos[0]); 522 assert(axisPos > -1, "Index out of bound"); 523 pos[1] = axisPos; 524 } 525 else 526 { 527 ptrdiff_t axisPos = positionInGroup!(1 - Args.length)(index, pos[0]); 528 assert(axisPos > -1, "Index out of bound"); 529 pos[1] = axisPos; 530 } 531 532 static if(1 - Args.length) 533 { 534 Axis!(void) ret; 535 void dataSetterAux(ptrdiff_t si = -1)(size_t ri = 0) @property 536 { 537 static if(si > -1) 538 alias i = si; 539 else 540 size_t i = ri; 541 542 foreach(j; data[i][elementCountTill[pos[0]] .. elementCountTill[pos[0] + 1]]) 543 ret.data ~= DataType(j); 544 } 545 546 mixin auxDispatch!(dataSetterAux, isHomogeneousType, GrpRowType); 547 auxDispatch(pos[1]); 548 549 return ret; 550 } 551 else 552 { 553 Axis!(GrpRowType) ret; 554 static foreach(i; 0 .. GrpRowType.length) 555 { 556 ret.data[i] = data[i][elementCountTill[pos[0]] + pos[1]]; 557 } 558 559 return ret; 560 } 561 } 562 563 /// Binary Operation on DataFrame rows/columns 564 void opIndexAssign(T...)(Axis!T elements, string[] groupTitle, string[] index, int axis = 1) 565 { 566 opIndexOpAssign!("")(elements, groupTitle, index, axis); 567 } 568 569 /// Short Hand Binary operation 570 void opIndexOpAssign(string op, T...)(Axis!T elements, string[] groupTitle, string[] index, int axis = 1) 571 { 572 ptrdiff_t[2] pos; 573 ptrdiff_t p = getGroupPosition(groupTitle); 574 assert(p > -1, "Group index out of bound"); 575 pos[0] = p; 576 577 if(axis) 578 { 579 p = positionInGroup!(1)(index, pos[0]); 580 assert(p > -1, "Index out of bound"); 581 pos[1] = p; 582 } 583 else 584 { 585 p = positionInGroup!(0)(index, pos[0]); 586 assert(p > -1, "Index out of bound"); 587 pos[1] = p; 588 } 589 590 import std.traits: isArray; 591 static if(is(T[0] == void) || (T.length == 1 && isArray!(T[0]))) 592 { 593 assert(elements.data.length == elementCountTill[pos[0] + 1] - elementCountTill[pos[0]], 594 "Size of data doesn't match size of group column"); 595 596 void mixinAux(ptrdiff_t si = -1)(size_t ri = 0) @property 597 { 598 static if(si > -1) 599 { 600 alias i = si; 601 enum size_t typepos = si; 602 } 603 else 604 { 605 size_t i = ri; 606 enum size_t typepos = 0; 607 } 608 609 static if(is(T[0] == void)) 610 enum string append = ".get!(GrpRowType[typepos]);"; 611 else 612 enum string append = ";"; 613 614 foreach(j; elementCountTill[pos[0]] .. elementCountTill[pos[0] + 1]) 615 { 616 mixin("data[i][j] " ~ op ~ "= elements.data[j - elementCountTill[pos[0]]]" ~ append); 617 } 618 } 619 620 mixin auxDispatch!(mixinAux, isHomogeneousType, GrpRowType); 621 auxDispatch(pos[1]); 622 } 623 else 624 { 625 assert(elements.data.length == GrpRowType.length, "Size of data doesn't match size of group column"); 626 static foreach(i; 0.. GrpRowType.length) 627 { 628 mixin("data[i][elementCountTill[pos[0]] + pos[1]] " ~ op ~ "= elements.data[i];"); 629 } 630 } 631 } 632 633 /// Slice assignment operation 634 void opIndexAssign(T, SliceKind kind)(Slice!(T*, 1, kind) sl, string[] groupTitle, string[] index, int axis = 1) 635 { 636 opIndexOpAssign!("")(sl, groupTitle, index, axis); 637 } 638 639 /// Slice assignment operation 640 void opIndexOpAssign(string op, T, SliceKind kind)(Slice!(T*, 1, kind) sl, string[] groupTitle, string[] index, int axis = 1) 641 { 642 if(axis) 643 { 644 Axis!void fwd; 645 fwd.data.length = sl.shape[0]; 646 foreach(i; 0 .. sl.shape[0]) 647 fwd.data[i] = DataType(sl[i]); 648 649 opIndexOpAssign!(op)(fwd, groupTitle, index, axis); 650 } 651 else 652 { 653 Axis!(GrpRowType) fwd; 654 static foreach(i; 0 .. GrpRowType.length) 655 static if(__traits(isArithmetic, GrpRowType[i], T)) 656 fwd.data[i] = cast(GrpRowType[i])sl[i]; 657 else 658 { 659 import std.conv: to; 660 fwd.data[i] = to!(GrpRowType[i])(sl[i]); 661 } 662 663 opIndexOpAssign!(op)(fwd, groupTitle, index, axis); 664 } 665 } 666 667 /// Assigning a Slice to group 668 void opIndexAssign(T, SliceKind kind)(Slice!(T*, 2, kind) sl, string[] groupTitle) 669 { 670 assert(sl.shape[1] <= GrpRowType.length, "Slice is larger than the Group that it is being assigned to"); 671 672 ptrdiff_t pos = getGroupPosition(groupTitle); 673 assert(pos > -1, "Group not found"); 674 assert(sl.shape[0] <= elementCountTill[pos + 1] - elementCountTill[pos], "Slice is larger than the Group that it is being assigned to"); 675 676 static foreach(i; 0 .. GrpRowType.length) 677 { 678 if(__traits(isArithmetic, T, GrpRowType[i])) 679 foreach(j; elementCountTill[pos] .. elementCountTill[pos + 1]) 680 data[i][j] = cast(GrpRowType[i])sl[j - elementCountTill[pos]][i]; 681 else 682 { 683 import std.conv: to; 684 foreach(j; elementCountTill[pos] .. elementCountTill[pos + 1]) 685 data[i][j] = to!(GrpRowType[i])(sl[j - elementCountTill[pos]][i]); 686 } 687 } 688 } 689 690 /// Assign a Slice to the Group 691 void opAssign(T, SliceKind kind)(Slice!(T*, 2, kind) input) 692 { 693 assert(input.shape[0] <= elementCountTill[$ - 1] && input.shape[1] <= GrpRowType.length, "Given Slice is larger than group"); 694 static foreach(i; 0 .. GrpRowType.length) 695 { 696 static if(__traits(isArithmetic, T, GrpRowType[i])) 697 foreach(j; 0 .. input.shape[0]) 698 data[i][j] = cast(GrpRowType[i])input[j][i]; 699 else 700 { 701 import std.conv: to; 702 foreach(j; 0 .. input.shape[0]) 703 data[i][j] = to!(GrpRowType[i])(input[j][i]); 704 } 705 706 } 707 } 708 709 /// Get entire 710 auto asSlice(T, SliceKind kind)() @property 711 { 712 return asSliceInternal!(T, kind)(0, elementCountTill[$ - 1]); 713 } 714 715 /// Get a specific group 716 auto asSlice(T, SliceKind kind, U)(U grp) 717 if(is(U == string) || is(U == string[])) 718 { 719 ptrdiff_t pos; 720 static if(is(U == string)) 721 pos = getGroupPosition([grp]); 722 else 723 pos = getGroupPosition(grp); 724 725 assert(pos > -1, "Group not found"); 726 return asSliceInternal!(T, kind)(elementCountTill[pos], elementCountTill[pos + 1]); 727 } 728 729 auto asSlice(SliceKind kind, Type = string, int axis = 0, U)(U grp, U index) 730 if(is(U == string[]) || is(U == int)) 731 { 732 ptrdiff_t[2] pos; 733 static if(is(U == string[])) 734 { 735 pos[0] = getGroupPosition(grp); 736 pos[1] = positionInGroup!(axis)(index, pos[0]); 737 } 738 else 739 { 740 pos[0] = grp; 741 pos[1] = index; 742 } 743 744 assert(pos[0] > -1, "Group not found"); 745 assert(pos[1] > -1, "Index not found"); 746 747 static if(axis) 748 { 749 750 static if(kind == Universal) 751 Slice!(Type*, 1, kind) ret = slice!(Type)(elementCountTill[pos[0] + 1] - elementCountTill[pos[0]]).universal; 752 else static if(kind == Canonical) 753 Slice!(Type*, 1, kind) ret = slice!(Type)(elementCountTill[pos[0] + 1] - elementCountTill[pos[0]]).canonical; 754 else 755 Slice!(Type*, 1, kind) ret = slice!(Type)(elementCountTill[pos[0] + 1] - elementCountTill[pos[0]]); 756 757 void assignmentAux(ptrdiff_t si = -1)(size_t ri = 0) @property 758 { 759 static if(si > -1) 760 { 761 alias i = si; 762 enum size_t typepos = si; 763 } 764 else 765 { 766 size_t i = ri; 767 enum size_t typepos = 0; 768 } 769 770 static if(__traits(isArithmetic, Type, GrpRowType[typepos]) || is(Type == GrpRowType[typepos])) 771 foreach(j; elementCountTill[pos[0]] .. elementCountTill[pos[0] + 1]) 772 ret[j - elementCountTill[pos[0]]] = cast(Type)data[i][j]; 773 774 } 775 776 mixin auxDispatch!(assignmentAux, isHomogeneousType, GrpRowType); 777 auxDispatch(pos[1]); 778 779 return ret; 780 } 781 else 782 { 783 Slice!(string*, 1, kind) ret; 784 static if(kind == Universal) 785 ret = slice!(string)(GrpRowType.length).universal; 786 else static if(kind == Canonical) 787 ret = slice!(string)(GrpRowType.length).canonical; 788 else 789 ret = slice!(string)(GrpRowType.length); 790 791 static foreach(i; 0 .. GrpRowType.length) 792 { 793 import std.conv: to; 794 ret[i] = to!string(data[i][elementCountTill[pos[0]] + pos[1]]); 795 } 796 797 return ret; 798 } 799 } 800 801 auto aggregate(int axis, Ops...)() @property 802 { 803 void init(int axis, T)(ref T arg) 804 { 805 enum int perpendicular = ((axis)? 0: 1); 806 807 arg.groups = groups; 808 arg.grpIndex.indexing[axis] = grpIndex.indexing[axis]; 809 arg.grpIndex.indexing[perpendicular].index.length = 1; 810 arg.grpIndex.indexing[perpendicular].codes.length = 1; 811 812 static if(axis) 813 { 814 ret.grpIndex.row.titles = ["Operation"]; 815 ret.grpIndex.row.index[0].length = ret.groups.length * Ops.length; 816 ret.elementCountTill.length = groups.length + 1; 817 818 foreach(i; 0 .. ret.elementCountTill.length) 819 ret.elementCountTill[i] = cast(int)(i * Ops.length); 820 821 static foreach(i; 0 .. GrpType.length) 822 ret.data[i].length = ret.groups.length * Ops.length; 823 824 foreach(i; 0 .. ret.groups.length) 825 static foreach(j; 0 .. Ops.length) 826 ret.grpIndex.row.index[0][i * Ops.length + j] = __traits(identifier, Ops[j]); 827 } 828 else 829 { 830 ret.elementCountTill = elementCountTill; 831 ret.grpIndex.column.index[0].length = Ops.length; 832 833 static foreach(i; 0 .. Ops.length) 834 ret.grpIndex.column.index[0][i] = __traits(identifier, Ops[i]); 835 836 static foreach(i; 0 .. Ops.length) 837 ret.data[i].length = ret.elementCountTill[$ -1]; 838 } 839 } 840 841 static if(axis) 842 { 843 import std.meta: staticMap; 844 845 alias Resolve(T) = suitableType!(resolverInternal!(T, Ops)); 846 alias ResolvedTypes = staticMap!(Resolve, GrpRowType); 847 Group!(ResolvedTypes) ret; 848 init!(axis)(ret); 849 850 foreach(i; 0 .. ret.groups.length) 851 static foreach(j; 0 .. Ops.length) 852 static foreach(k; 0 .. GrpRowType.length) 853 static if(__traits(isArithmetic, GrpRowType[k])) 854 { 855 static if(__traits(compiles, Ops[j](data[k][elementCountTill[i] .. elementCountTill[i + 1]]))) 856 ret.data[k][i * Ops.length + j] = Ops[j](data[k][elementCountTill[i] .. elementCountTill[i + 1]]); 857 else 858 { 859 import std.algorithm: map, reduce; 860 if(__traits(compiles, data[k][elementCountTill[i] .. elementCountTill[i + 1]].map!(e => e).reduce!(Ops[j]))) 861 ret.data[k][i * Ops.length + j] = cast(ResolvedTypes[k])data[k][elementCountTill[i] .. elementCountTill[i + 1]].map!(e => e).reduce!(Ops[j]); 862 } 863 } 864 } 865 else 866 { 867 alias AggregateType = aggregateType!(Ops); 868 Group!(AggregateType) ret; 869 init!(axis)(ret); 870 871 suitableType!(GrpRowType)[GrpRowType.length] opArr; 872 size_t k; 873 874 foreach(i; 0 .. ret.elementCountTill[$ - 1]) 875 { 876 k = 0; 877 static foreach(j; 0 .. GrpType.length) 878 static if(__traits(isArithmetic, GrpRowType[j])) 879 { 880 opArr[k] = data[j][i]; 881 ++k; 882 } 883 884 if(k == 0) 885 break; 886 887 static foreach(j; 0 .. Ops.length) 888 { 889 static if(__traits(compiles, Ops[j](opArr[0 .. k]))) 890 ret.data[j][i] = Ops[j](opArr[0 .. k]); 891 else 892 { 893 import std.algorithm: map, reduce; 894 if(__traits(compiles, opArr[0 .. k].map!(e => e).reduce!(Ops[j]))) 895 ret.data[j][i] = cast(AggregateType[j])opArr[0 .. k].map!(e => e).reduce!(Ops[j]); 896 } 897 } 898 } 899 } 900 901 ret.grpIndex.generateCodes(); 902 return ret; 903 } 904 } 905 906 // Basic groubBy operation done manually 907 unittest 908 { 909 DataFrame!(int, 5) df; 910 Index inx; 911 inx.setIndex([["Hello", "Hi", "Hey"], ["Hi", "Hello", "Hey"], ["Hey", "Hello", "Hi"]], ["1", "2", "3"]); 912 df.setFrameIndex(inx); 913 df.assign!1(2, [1,2,3]); 914 df.assign!1(4, [1,2,3]); 915 string str1 = df.display(true, 200); 916 917 Group!(int, int, int, int) gp; 918 gp.createGroup!([2])(df, [0, 1]); 919 920 assert(gp.groups.length == 3); 921 assert(gp.groups == [["Hello", "Hi", "1"], ["Hi", "Hello", "2"], ["Hey", "Hey", "3"]]); 922 assert(gp.elementCountTill == [0, 1, 2, 3]); 923 assert(gp.data[3] == [1, 2, 3]); 924 925 string str2 = df.display(true, 200); 926 assert(str1 == str2); 927 } 928 929 // getGroupPosition 930 unittest 931 { 932 DataFrame!(int, 5) df; 933 Index inx; 934 inx.setIndex([["Hello", "Hi", "Hey"], ["Hi", "Hello", "Hey"], ["Hey", "Hello", "Hi"]], ["1", "2", "3"]); 935 df.setFrameIndex(inx); 936 df.assign!1(2, [1,2,3]); 937 938 auto gp = df.groupBy!([2])([0, 1]); 939 // gp.display(); 940 assert(gp.getGroupPosition(["Hello", "Hi", "1"]) == 0); 941 assert(gp.getGroupPosition(["Hi", "Hello", "2"]) == 1); 942 assert(gp.getGroupPosition(["Hey", "Hey", "3"]) == 2); 943 } 944 945 // Display 946 unittest 947 { 948 DataFrame!(int, 5) df; 949 Index inx; 950 inx.setIndex([["Hello", "Hi", "Hey"], ["Hi", "Hello", "Hey"], ["Hey", "Hello", "Hi"]], ["1", "2", "3"]); 951 df.setFrameIndex(inx); 952 df.assign!1(2, [1,2,3]); 953 df.assign!1(4, [1,2,3]); 954 string str1 = df.display(true, 200); 955 956 Group!(int, int, int, int) gp; 957 gp.createGroup!([2])(df, [0, 1]); 958 959 // Full Group 960 assert(gp.display(true, 200) == "3 0 1 3 4\n" 961 ~ "Hey 0 0 0 1\n\n" 962 ~ "3 0 1 3 4\n" 963 ~ "Hello 0 0 0 2\n\n" 964 ~ "3 0 1 3 4\n" 965 ~ "Hi 0 0 0 3\n\n" 966 ); 967 968 // Single Group 969 assert(gp.display(["Hello", "Hi", "1"], true, 200) == "3 0 1 3 4\n" 970 ~ "Hey 0 0 0 1\n\n" 971 ); 972 973 // Array of groups 974 assert(gp.display([["Hello", "Hi", "1"], ["Hi", "Hello", "2"]], true, 200) == "3 0 1 3 4\n" 975 ~ "Hey 0 0 0 1\n\n" 976 ~ "3 0 1 3 4\n" 977 ~ "Hello 0 0 0 2\n\n" 978 ); 979 980 // Array of groups 981 assert(gp.display([["Hello", "Hi", "1"], ["Hi", "Hello", "2"], ["Hey", "Hey", "3"]], true, 200) == "3 0 1 3 4\n" 982 ~ "Hey 0 0 0 1\n\n" 983 ~ "3 0 1 3 4\n" 984 ~ "Hello 0 0 0 2\n\n" 985 ~ "3 0 1 3 4\n" 986 ~ "Hi 0 0 0 3\n\n" 987 ); 988 989 // Single Group - integer indexes 990 assert(gp.display([0], true, 200) == "3 0 1 3 4\n" 991 ~ "Hey 0 0 0 1\n\n" 992 ); 993 994 // Array of groups - integer indexes 995 assert(gp.display([0, 1], true, 200) == "3 0 1 3 4\n" 996 ~ "Hey 0 0 0 1\n\n" 997 ~ "3 0 1 3 4\n" 998 ~ "Hello 0 0 0 2\n\n" 999 ); 1000 1001 // Array of groups - integer indexes 1002 assert(gp.display([0, 1, 2], true, 200) == "3 0 1 3 4\n" 1003 ~ "Hey 0 0 0 1\n\n" 1004 ~ "3 0 1 3 4\n" 1005 ~ "Hello 0 0 0 2\n\n" 1006 ~ "3 0 1 3 4\n" 1007 ~ "Hi 0 0 0 3\n\n" 1008 ); 1009 } 1010 1011 // Pandas groupBy Example 1012 unittest 1013 { 1014 DataFrame!(double) df; 1015 Index inx; 1016 inx.constructFromLevels!(0)([["Falcon", "Parrot"], ["Captive", "Wild"]], ["Animal", "Type"]); 1017 inx.constructFromLevels!(1)([["Max-Speed"]]); 1018 df.setFrameIndex(inx); 1019 df.assign!1(0, [380.0, 370.0, 24.0, 26.0]); 1020 // df.display(); 1021 1022 Group!(double) gp; 1023 gp.createGroup(df, [0]); 1024 assert(gp.groups.length == 2); 1025 assert(gp.groups == [["Falcon"], ["Parrot"]]); 1026 assert(gp.elementCountTill == [0, 2, 4]); 1027 assert(gp.data[0] == [380.0, 370.0, 24.0, 26.0]); 1028 1029 // Display full 1030 assert(gp.display(true, 200) == "Type Max-Speed\n" 1031 ~ "Captive 380 \n" 1032 ~ "Wild 370 \n\n" 1033 ~ "Type Max-Speed\n" 1034 ~ "Captive 24 \n" 1035 ~ "Wild 26 \n\n" 1036 ); 1037 1038 // Display Falcon 1039 assert(gp.display(["Falcon"], true, 200) == "Type Max-Speed\n" 1040 ~ "Captive 380 \n" 1041 ~ "Wild 370 \n\n" 1042 ); 1043 1044 // Display Parrot 1045 assert(gp.display(["Parrot"], true, 200) == "Type Max-Speed\n" 1046 ~ "Captive 24 \n" 1047 ~ "Wild 26 \n\n" 1048 ); 1049 1050 // Display Falcon - Integer Index 1051 assert(gp.display([0], true, 200) == "Type Max-Speed\n" 1052 ~ "Captive 380 \n" 1053 ~ "Wild 370 \n\n" 1054 ); 1055 1056 // Display Parrot - Integer Index 1057 assert(gp.display([1], true, 200) == "Type Max-Speed\n" 1058 ~ "Captive 24 \n" 1059 ~ "Wild 26 \n\n" 1060 ); 1061 } 1062 1063 // getGroups 1064 unittest 1065 { 1066 DataFrame!(double) df1; 1067 Index inx1; 1068 inx1.constructFromLevels!(0)([["Falcon", "Parrot"], ["Captive", "Wild"]], ["Animal", "Type"]); 1069 inx1.constructFromLevels!(1)([["Max-Speed"]]); 1070 df1.setFrameIndex(inx1); 1071 df1.assign!1(0, [380.0, 370.0, 24.0, 26.0]); 1072 // df.display(); 1073 1074 Group!(double) gp1; 1075 gp1.createGroup(df1, [0]); 1076 assert(gp1.getGroups == [["Falcon"], ["Parrot"]]); 1077 1078 DataFrame!(int, 5) df2; 1079 Index inx2; 1080 inx2.setIndex([["Hello", "Hi", "Hey"], ["Hi", "Hello", "Hey"], ["Hey", "Hello", "Hi"]], ["1", "2", "3"]); 1081 df2.setFrameIndex(inx2); 1082 df2.assign!1(2, [1,2,3]); 1083 df2.assign!1(4, [1,2,3]); 1084 1085 Group!(int, int, int, int) gp2; 1086 gp2.createGroup!([2])(df2, [0, 1]); 1087 assert(gp2.getGroups == [["Hello", "Hi", "1"], ["Hi", "Hello", "2"], ["Hey", "Hey", "3"]]); 1088 } 1089 1090 // Combine groups to get a DataFrame 1091 unittest 1092 { 1093 DataFrame!(double) df1; 1094 Index inx1; 1095 inx1.constructFromLevels!(0)([["Falcon", "Parrot"], ["Captive", "Wild"]], ["Animal", "Type"]); 1096 inx1.constructFromLevels!(1)([["Max-Speed"]]); 1097 df1.setFrameIndex(inx1); 1098 df1.assign!1(0, [380.0, 370.0, 24.0, 26.0]); 1099 // df1.display(); 1100 1101 Group!(double) gp1; 1102 gp1.createGroup(df1, [0]); 1103 // gp1.display(); 1104 1105 // Combine one group 1106 assert(gp1.combine([0]).display(true, 200) == "GroupL1 Type Max-Speed\n" 1107 ~ "Falcon Captive 380 \n" 1108 ~ "Falcon Wild 370 \n" 1109 ); 1110 1111 // Combine both the groups 1112 assert(gp1.combine([0, 1]).display(true, 200) == "GroupL1 Type Max-Speed\n" 1113 ~ "Falcon Captive 380 \n" 1114 ~ "Falcon Wild 370 \n" 1115 ~ "Parrot Captive 24 \n" 1116 ~ "Parrot Wild 26 \n" 1117 ); 1118 1119 // Cobine all - overload 1120 assert(gp1.combine().display(true, 200) == "GroupL1 Type Max-Speed\n" 1121 ~ "Falcon Captive 380 \n" 1122 ~ "Falcon Wild 370 \n" 1123 ~ "Parrot Captive 24 \n" 1124 ~ "Parrot Wild 26 \n" 1125 ); 1126 1127 assert(gp1.combine(["Falcon"]).display(true, 200) == "GroupL1 Type Max-Speed\n" 1128 ~ "Falcon Captive 380 \n" 1129 ~ "Falcon Wild 370 \n" 1130 ); 1131 1132 // Combine both the groups 1133 assert(gp1.combine([["Falcon"], ["Parrot"]]).display(true, 200) == "GroupL1 Type Max-Speed\n" 1134 ~ "Falcon Captive 380 \n" 1135 ~ "Falcon Wild 370 \n" 1136 ~ "Parrot Captive 24 \n" 1137 ~ "Parrot Wild 26 \n" 1138 ); 1139 1140 // Just Parrot 1141 assert(gp1.combine(["Parrot"]).display(true, 200) == "GroupL1 Type Max-Speed\n" 1142 ~ "Parrot Captive 24 \n" 1143 ~ "Parrot Wild 26 \n" 1144 ); 1145 } 1146 1147 unittest 1148 { 1149 DataFrame!(int, 5) df; 1150 Index inx; 1151 inx.setIndex([["Hello", "Hi", "Hey"], ["Hi", "Hello", "Hey"], ["Hey", "Hello", "Hi"]], ["1", "2", "3"]); 1152 df.setFrameIndex(inx); 1153 df.assign!1(2, [1,2,3]); 1154 df.assign!1(4, [1,2,3]); 1155 1156 Group!(int, int, int, int) gp; 1157 gp.createGroup!([2])(df, [0, 1]); 1158 1159 // Complete Group 1160 assert(gp.combine().display(true, 200) == "GroupL1 GroupL2 GroupL3 3 0 1 3 4\n" 1161 ~ "Hello Hi 1 Hey 0 0 0 1\n" 1162 ~ "Hi Hello 2 Hello 0 0 0 2\n" 1163 ~ "Hey Hey 3 Hi 0 0 0 3\n" 1164 ); 1165 1166 // Single Group 1167 assert(gp.combine([0]).display(true, 200) == "GroupL1 GroupL2 GroupL3 3 0 1 3 4\n" 1168 ~ "Hello Hi 1 Hey 0 0 0 1\n" 1169 ); 1170 1171 // 2 Groups combine 1172 assert(gp.combine([0, 1]).display(true, 200) == "GroupL1 GroupL2 GroupL3 3 0 1 3 4\n" 1173 ~ "Hello Hi 1 Hey 0 0 0 1\n" 1174 ~ "Hi Hello 2 Hello 0 0 0 2\n" 1175 ); 1176 1177 // Complete group again 1178 assert(gp.combine([0, 1, 2]).display(true, 200) == "GroupL1 GroupL2 GroupL3 3 0 1 3 4\n" 1179 ~ "Hello Hi 1 Hey 0 0 0 1\n" 1180 ~ "Hi Hello 2 Hello 0 0 0 2\n" 1181 ~ "Hey Hey 3 Hi 0 0 0 3\n" 1182 ); 1183 1184 // Groups using string array 1185 assert(gp.combine(["Hello", "Hi", "1"]).display(true, 200) == "GroupL1 GroupL2 GroupL3 3 0 1 3 4\n" 1186 ~ "Hello Hi 1 Hey 0 0 0 1\n" 1187 ); 1188 1189 // Groups using 2D string array 1190 assert(gp.combine([["Hello", "Hi", "1"], ["Hi", "Hello", "2"]]).display(true, 200) == "GroupL1 GroupL2 GroupL3 3 0 1 3 4\n" 1191 ~ "Hello Hi 1 Hey 0 0 0 1\n" 1192 ~ "Hi Hello 2 Hello 0 0 0 2\n" 1193 ); 1194 } 1195 1196 // Index operation 1197 unittest 1198 { 1199 DataFrame!(int, 5) df; 1200 Index inx; 1201 inx.setIndex([["Hello", "Hi", "Hey"], ["Hi", "Hello", "Hey"], ["Hey", "Hello", "Hi"]], ["1", "2", "3"]); 1202 df.setFrameIndex(inx); 1203 df.assign!1(2, [1,2,3]); 1204 df.assign!1(4, [1,2,3]); 1205 1206 Group!(int, int, int, int) gp; 1207 gp.createGroup!([2])(df, [0, 1]); 1208 1209 assert(gp[["Hello", "Hi", "1"], ["4"]].data == [1]); 1210 assert(gp[["Hello", "Hi", "1"], ["3"]].data == [0]); 1211 1212 assert(gp[["Hi", "Hello", "2"], ["4"]].data == [2]); 1213 assert(gp[["Hi", "Hello", "2"], ["3"]].data == [0]); 1214 1215 assert(gp[["Hey", "Hey", "3"], ["4"]].data == [3]); 1216 assert(gp[["Hey", "Hey", "3"], ["3"]].data == [0]); 1217 1218 assert(gp[["Hello", "Hi", "1"], ["Hey"], 0].data.length == 4); 1219 assert(gp[["Hello", "Hi", "1"], ["Hey"], 0].data[3] == 1); 1220 1221 assert(gp[["Hi", "Hello", "2"], ["Hello"], 0].data.length == 4); 1222 assert(gp[["Hi", "Hello", "2"], ["Hello"], 0].data[3] == 2); 1223 1224 assert(gp[["Hey", "Hey", "3"], ["Hi"], 0].data.length == 4); 1225 assert(gp[["Hey", "Hey", "3"], ["Hi"], 0].data[3] == 3); 1226 } 1227 1228 // Index operation 1229 unittest 1230 { 1231 DataFrame!(double) df1; 1232 Index inx1; 1233 inx1.constructFromLevels!(0)([["Falcon", "Parrot"], ["Captive", "Wild"]], ["Animal", "Type"]); 1234 inx1.constructFromLevels!(1)([["Max-Speed"]]); 1235 df1.setFrameIndex(inx1); 1236 df1.assign!1(0, [380.0, 370.0, 24.0, 26.0]); 1237 // df.display(); 1238 1239 Group!(double) gp; 1240 gp.createGroup(df1, [0]); 1241 assert(gp[["Falcon"], ["Max-Speed"]].data == [380.0, 370.0]); 1242 assert(gp[["Parrot"], ["Max-Speed"]].data == [24.0, 26.0]); 1243 1244 assert(gp[["Falcon"], ["Captive"], 0].data[0] == 380); 1245 assert(gp[["Falcon"], ["Wild"], 0].data[0] == 370); 1246 assert(gp[["Parrot"], ["Captive"], 0].data[0] == 24); 1247 assert(gp[["Parrot"], ["Wild"], 0].data[0] == 26); 1248 1249 // No need to pass array if the level of indexing or grouping is 1 1250 assert(gp["Falcon", "Max-Speed"].data == [380.0, 370.0]); 1251 assert(gp["Parrot", "Max-Speed"].data == [24.0, 26.0]); 1252 1253 assert(gp["Falcon", "Captive", 0].data[0] == 380); 1254 assert(gp["Falcon", "Wild", 0].data[0] == 370); 1255 assert(gp["Parrot", "Captive", 0].data[0] == 24); 1256 assert(gp["Parrot", "Wild", 0].data[0] == 26); 1257 } 1258 1259 // Element access using indexes 1260 unittest 1261 { 1262 DataFrame!(double) df1; 1263 Index inx1; 1264 inx1.constructFromLevels!(0)([["Falcon", "Parrot"], ["Captive", "Wild"]], ["Animal", "Type"]); 1265 inx1.constructFromLevels!(1)([["Max-Speed"]]); 1266 df1.setFrameIndex(inx1); 1267 df1.assign!1(0, [380.0, 370.0, 24.0, 26.0]); 1268 // df.display(); 1269 1270 Group!(double) gp; 1271 gp.createGroup(df1, [0]); 1272 1273 assert(gp["Falcon", "Captive", "Max-Speed"] == 380); 1274 assert(gp[0, 0, 0] == 380); 1275 assert(gp["Falcon", "Wild", "Max-Speed"] == 370); 1276 assert(gp[0, 1, 0] == 370); 1277 1278 assert(gp["Parrot", "Captive", "Max-Speed"] == 24); 1279 assert(gp[1, 0, 0] == 24); 1280 assert(gp["Parrot", "Wild", "Max-Speed"] == 26); 1281 assert(gp[1, 1, 0] == 26); 1282 } 1283 1284 // Element access using index 1285 unittest 1286 { 1287 DataFrame!(int, 5) df; 1288 Index inx; 1289 inx.setIndex([["Hello", "Hi", "Hey"], ["Hi", "Hello", "Hey"], ["Hey", "Hello", "Hi"]], ["1", "2", "3"]); 1290 df.setFrameIndex(inx); 1291 df.assign!1(2, [1,2,3]); 1292 df.assign!1(4, [1,2,3]); 1293 1294 Group!(int, int, int, int) gp; 1295 gp.createGroup!([2])(df, [0, 1]); 1296 1297 assert(gp[["Hello", "Hi", "1"], ["Hey"], ["4"]] == 1); 1298 assert(gp[0, 0, 3] == 1); 1299 assert(gp[["Hello", "Hi", "1"], ["Hey"], ["3"]] == 0); 1300 assert(gp[0, 0, 2] == 0); 1301 1302 assert(gp[["Hi", "Hello", "2"], ["Hello"], ["4"]] == 2); 1303 assert(gp[1, 0, 3] == 2); 1304 assert(gp[["Hi", "Hello", "2"], ["Hello"], ["3"]] == 0); 1305 assert(gp[1, 0, 2] == 0); 1306 1307 assert(gp[["Hey", "Hey", "3"], ["Hi"], ["4"]] == 3); 1308 assert(gp[2, 0, 3] == 3); 1309 assert(gp[["Hey", "Hey", "3"], ["Hi"], ["3"]] == 0); 1310 assert(gp[2, 0, 2] == 0); 1311 1312 // Checking different variations 1313 assert(gp[["Hi", "Hello", "2"], "Hello", ["3"]] == 0); 1314 assert(gp[["Hi", "Hello", "2"], ["Hello"], "3"] == 0); 1315 assert(gp[["Hi", "Hello", "2"], "Hello", "3"] == 0); 1316 } 1317 1318 // Assignment operation on rows 1319 unittest 1320 { 1321 DataFrame!(double) df1; 1322 Index inx1; 1323 inx1.constructFromLevels!(0)([["Falcon", "Parrot"], ["Captive", "Wild"]], ["Animal", "Type"]); 1324 inx1.constructFromLevels!(1)([["Max-Speed"]]); 1325 df1.setFrameIndex(inx1); 1326 df1.assign!1(0, [380.0, 370.0, 24.0, 26.0]); 1327 // df.display(); 1328 1329 Group!(double) gp; 1330 gp.createGroup(df1, [0]); 1331 1332 // Row assignment within the same group 1333 gp[["Falcon"], ["Captive"], 0] = gp[["Falcon"], ["Wild"], 0]; 1334 assert(gp[0, 0, 0] == gp[0, 1, 0]); 1335 1336 // Row assignment within the same group 1337 gp[["Parrot"], ["Captive"], 0] = gp[["Parrot"], ["Wild"], 0]; 1338 assert(gp[1, 0, 0] == gp[1, 1, 0]); 1339 1340 // Row assignment cross group 1341 gp[["Parrot"], ["Captive"], 0] = gp[["Falcon"], ["Wild"], 0]; 1342 assert(gp[0, 0, 0] == gp[1, 0, 0]); 1343 } 1344 1345 unittest 1346 { 1347 DataFrame!(int, 5) df; 1348 Index inx; 1349 inx.setIndex([["Hello", "Hi", "Hey"], ["Hi", "Hello", "Hey"], ["Hey", "Hello", "Hi"]], ["1", "2", "3"]); 1350 df.setFrameIndex(inx); 1351 df.assign!1(2, [1,2,3]); 1352 df.assign!1(4, [1,2,3]); 1353 1354 Group!(int, int, int, int) gp; 1355 gp.createGroup!([2])(df, [0, 1]); 1356 1357 gp[["Hello", "Hi", "1"], ["3"]] = gp[["Hello", "Hi", "1"], ["4"]]; 1358 assert(gp[["Hello", "Hi", "1"], ["3"]].data == [1]); 1359 1360 gp[["Hello", "Hi", "1"], ["3"]] = gp[["Hello", "Hi", "1"], ["0"]] + gp[["Hello", "Hi", "1"], ["3"]] + gp[["Hello", "Hi", "1"], ["4"]]; 1361 assert(gp[["Hello", "Hi", "1"], ["3"]].data == [2]); 1362 1363 gp[["Hello", "Hi", "1"], ["3"]] = gp[["Hello", "Hi", "1"], ["4"]] + gp[["Hi", "Hello", "2"], ["4"]]; 1364 assert(gp[["Hello", "Hi", "1"], ["3"]].data == [3]); 1365 } 1366 1367 // Short hand operations 1368 unittest 1369 { 1370 DataFrame!(int, 5) df; 1371 Index inx; 1372 inx.setIndex([["Hello", "Hi", "Hey"], ["Hi", "Hello", "Hey"], ["Hey", "Hello", "Hi"]], ["1", "2", "3"]); 1373 df.setFrameIndex(inx); 1374 df.assign!1(2, [1,2,3]); 1375 df.assign!1(4, [1,2,3]); 1376 1377 Group!(int, int, int, int) gp; 1378 gp.createGroup!([2])(df, [0, 1]); 1379 1380 gp[["Hello", "Hi", "1"], ["3"]] += gp[["Hello", "Hi", "1"], ["4"]]; 1381 assert(gp[["Hello", "Hi", "1"], ["3"]].data == [1]); 1382 1383 gp[["Hello", "Hi", "1"], ["3"]] -= gp[["Hello", "Hi", "1"], ["0"]] + gp[["Hello", "Hi", "1"], ["3"]] + gp[["Hello", "Hi", "1"], ["4"]]; 1384 assert(gp[["Hello", "Hi", "1"], ["3"]].data == [-1]); 1385 1386 gp[["Hello", "Hi", "1"], ["3"]] *= gp[["Hello", "Hi", "1"], ["4"]] + gp[["Hi", "Hello", "2"], ["4"]]; 1387 assert(gp[["Hello", "Hi", "1"], ["3"]].data == [-3]); 1388 1389 gp[["Hello", "Hi", "1"], ["3"]] /= gp[["Hello", "Hi", "1"], ["3"]]; 1390 assert(gp[["Hello", "Hi", "1"], ["3"]].data == [1]); 1391 } 1392 1393 // Retrieving as Slice and assignment of Slice to DataFrame 1394 unittest 1395 { 1396 DataFrame!(int, 5) df; 1397 Index inx; 1398 inx.setIndex([["Hello", "Hi", "Hey"], ["Hi", "Hello", "Hey"], ["Hey", "Hello", "Hi"]], ["1", "2", "3"]); 1399 df.setFrameIndex(inx); 1400 df.assign!1(2, [1,2,3]); 1401 df.assign!1(4, [1,2,3]); 1402 1403 Group!(int, int, int, int) gp; 1404 gp.createGroup!([2])(df, [0, 1]); 1405 1406 DataFrame!(int, 5) df2; 1407 df2.setFrameIndex(inx); 1408 df2.assign!1(2, [1,2,3]); 1409 1410 Group!(int, int, int, int) gp2; 1411 gp2.createGroup!([2])(df2, [0, 1]); 1412 1413 gp2 = gp.asSlice!(int, Universal); 1414 assert(gp.display(true, 200) == gp2.display(true, 200)); 1415 } 1416 1417 // Slice assignment operation 1418 unittest 1419 { 1420 DataFrame!(int, 5) df; 1421 Index inx; 1422 inx.setIndex([["Hello", "Hi", "Hey"], ["Hi", "Hello", "Hey"], ["Hey", "Hello", "Hi"]], ["1", "2", "3"]); 1423 df.setFrameIndex(inx); 1424 df.assign!1(2, [1,2,3]); 1425 df.assign!1(4, [1,2,3]); 1426 1427 Group!(int, int, int, int) gp; 1428 gp.createGroup!([2])(df, [0, 1]); 1429 1430 // Assign a column 1431 gp[["Hello", "Hi", "1"], ["0"]] = ([42]).sliced(1).universal; 1432 assert(gp[["Hello", "Hi", "1"], ["Hey"], ["0"]] == 42); 1433 1434 // Assign a row 1435 gp[["Hello", "Hi", "1"], ["Hey"], 0] = ([42, 42, 42, 42]).sliced(4).universal; 1436 assert(gp.data[0][0] == 42 && gp.data[1][0] == 42 && gp.data[2][0] == 42 && gp.data[3][0] == 42); 1437 } 1438 1439 // Getting a Group as Slice 1440 unittest 1441 { 1442 DataFrame!(int, 5) df; 1443 Index inx; 1444 inx.setIndex([["Hello", "Hi", "Hey"], ["Hi", "Hello", "Hey"], ["Hey", "Hello", "Hi"]], ["1", "2", "3"]); 1445 df.setFrameIndex(inx); 1446 df.assign!1(2, [1,2,3]); 1447 df.assign!1(4, [1,2,3]); 1448 1449 Group!(int, int, int, int) gp; 1450 gp.createGroup!([2])(df, [0, 1]); 1451 1452 // Assigning values of one group to another using Slice 1453 gp[["Hello", "Hi", "1"]] = gp.asSlice!(int, Universal)(["Hi", "Hello", "2"]); 1454 assert(gp[["Hello", "Hi", "1"], ["Hey"], ["4"]] == 2); 1455 } 1456 1457 // asSlice 1458 unittest 1459 { 1460 DataFrame!(int, 5) df; 1461 Index inx; 1462 inx.setIndex([["Hello", "Hi", "Hey"], ["Hi", "Hello", "Hey"], ["Hey", "Hello", "Hi"]], ["1", "2", "3"]); 1463 df.setFrameIndex(inx); 1464 df.assign!1(2, [1,2,3]); 1465 df.assign!1(4, [1,2,3]); 1466 1467 Group!(int, int, int, int) gp; 1468 gp.createGroup!([2])(df, [0, 1]); 1469 1470 assert(gp.asSlice!(Universal, int, 1)(["Hello", "Hi", "1"], ["4"]) == [1]); 1471 assert(gp.asSlice!(Universal)(["Hello", "Hi", "1"], ["Hey"]) == ["0", "0", "0", "1"]); 1472 } 1473 1474 // asSlice - row 1475 unittest 1476 { 1477 DataFrame!(int, 5) df; 1478 Index inx; 1479 inx.setIndex([["Hello", "Hi", "Hey"], ["Hi", "Hello", "Hey"], ["Hey", "Hello", "Hi"]], ["1", "2", "3"]); 1480 df.setFrameIndex(inx); 1481 df.assign!1(2, [1,2,3]); 1482 df.assign!1(4, [1,2,3]); 1483 1484 Group!(int, int, int, int) gp; 1485 gp.createGroup!([2])(df, [0, 1]); 1486 1487 gp[["Hello", "Hi", "1"], ["Hey"], 0] = gp.asSlice!(Universal)(["Hi", "Hello", "2"], ["Hello"]); 1488 assert(gp[["Hello", "Hi", "1"], ["Hey"], ["4"]] == 2); 1489 } 1490 1491 // Aggregate on Group column 1492 unittest 1493 { 1494 DataFrame!(int, 2, double, 2) df; 1495 Index inx; 1496 inx[0] = [["Hello", "Hello", "Hi", "Hi"], ["1", "2", "3", "4"]]; 1497 inx[1] = ["1", "2", "3", "4"]; 1498 1499 df.setFrameIndex(inx); 1500 df = [[1, 2, 3.3, 4.7], [1, 3, 4.8, 5.2], [2, 5, 6, 7.6], [2, 6, 7, 8.9]]; 1501 // df.display(); 1502 1503 auto grp = df.groupBy!([0])([0]); 1504 // grp.display(); 1505 1506 import std.algorithm: max, min; 1507 import std.math: approxEqual; 1508 1509 auto maxgrp = grp.aggregate!(1, max); 1510 static assert(is(maxgrp.GrpType == AliasSeq!(int[], double[], double[]))); 1511 assert(maxgrp.data[0] == [3, 6]); 1512 assert(approxEqual(maxgrp.data[1][0], 4.8, 1e-8) && approxEqual(maxgrp.data[1][1], 7, 1e-8)); 1513 assert(approxEqual(maxgrp.data[2][0], 5.2, 1e-8) && approxEqual(maxgrp.data[2][1], 8.9, 1e-8)); 1514 1515 auto dub = grp.aggregate!(1, max, min); 1516 static assert(is(dub.GrpType == AliasSeq!(int[], double[], double[]))); 1517 assert(dub.data[0] == [3, 2, 6, 5]); 1518 assert(approxEqual(dub.data[1][0], 4.8, 1e-8) && approxEqual(dub.data[1][1], 3.3, 1e-8) && approxEqual(dub.data[1][2], 7, 1e-8) && approxEqual(dub.data[1][3], 6, 1e-8)); 1519 assert(approxEqual(dub.data[2][0], 5.2, 1e-8) && approxEqual(dub.data[2][1], 4.7, 1e-8) && approxEqual(dub.data[2][2], 8.9, 1e-8) && approxEqual(dub.data[2][3], 7.6, 1e-8)); 1520 } 1521 1522 // Aggregate on Group Row 1523 unittest 1524 { 1525 DataFrame!(int, 2, double, 2) df; 1526 Index inx; 1527 inx[0] = [["Hello", "Hello", "Hi", "Hi"], ["1", "2", "3", "4"]]; 1528 inx[1] = ["1", "2", "3", "4"]; 1529 1530 df.setFrameIndex(inx); 1531 df = [[1, 2, 3.3, 4.7], [1, 3, 4.8, 5.2], [2, 5, 6, 7.6], [2, 6, 7, 8.9]]; 1532 // df.display(); 1533 1534 auto grp = df.groupBy!([0])([0]); 1535 // grp.display(); 1536 1537 import std.algorithm: max, min; 1538 import std.math: approxEqual; 1539 1540 auto maxgrp = grp.aggregate!(0, max); 1541 static assert(is(maxgrp.GrpType == double[][1])); 1542 assert(approxEqual(maxgrp.data[0][0], 4.7, 1e-8) && approxEqual(maxgrp.data[0][1], 5.2, 1e-8) && approxEqual(maxgrp.data[0][2], 7.6, 1e-8) && approxEqual(maxgrp.data[0][3], 8.9, 1e-8)); 1543 1544 auto dub = grp.aggregate!(0, max, min); 1545 assert(approxEqual(dub.data[0][0], 4.7, 1e-8) && approxEqual(dub.data[0][1], 5.2, 1e-8) && approxEqual(dub.data[0][2], 7.6, 1e-8) && approxEqual(dub.data[0][3], 8.9, 1e-8)); 1546 assert(approxEqual(dub.data[1][0], 2, 1e-8) && approxEqual(dub.data[1][1], 3, 1e-8) && approxEqual(dub.data[1][2], 5, 1e-8) && approxEqual(dub.data[1][3], 6, 1e-8)); 1547 } 1548 1549 // Aggregate with custom function 1550 unittest 1551 { 1552 static auto customFunc(T)(T[] arr) 1553 { 1554 T res = 0; 1555 foreach(i, ele; arr) 1556 res += i * ele; 1557 1558 return res; 1559 } 1560 1561 DataFrame!(int, 2, double, 2) df; 1562 Index inx; 1563 inx[0] = [["Hello", "Hello", "Hi", "Hi"], ["1", "2", "3", "4"]]; 1564 inx[1] = ["1", "2", "3", "4"]; 1565 1566 df.setFrameIndex(inx); 1567 df = [[1, 2, 3.3, 4.7], [1, 3, 4.8, 5.2], [2, 5, 6, 7.6], [2, 6, 7, 8.9]]; 1568 // df.display(); 1569 1570 auto grp = df.groupBy!([0])([0]); 1571 // grp.display(); 1572 1573 import std.algorithm: max, min; 1574 import std.math: approxEqual; 1575 1576 auto maxgrp = grp.aggregate!(1, customFunc); 1577 assert(maxgrp.data[0] == [3, 6]); 1578 assert(approxEqual(maxgrp.data[1][0], 4.8, 1e-8) && approxEqual(maxgrp.data[1][1], 7, 1e-8)); 1579 assert(approxEqual(maxgrp.data[2][0], 5.2, 1e-8) && approxEqual(maxgrp.data[2][1], 8.9, 1e-8)); 1580 1581 auto custom = grp.aggregate!(0, customFunc); 1582 assert(approxEqual(custom.data[0][0], 12.7, 1e-8) && approxEqual(custom.data[0][1], 15.2, 1e-8) && approxEqual(custom.data[0][2], 21.2, 1e-8) && approxEqual(custom.data[0][3], 24.8, 1e-8)); 1583 }