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 }