1 module magpie.helper; 2 3 import magpie.dataframe: DataFrame; 4 import magpie.index: Index; 5 import magpie.group: Group; 6 7 import std.meta: AliasSeq, Repeat; 8 import std.range.primitives: ElementType; 9 import std.traits: isStaticArray, isIntegral, isFloatingPoint; 10 11 /// Build DataFrame argument list 12 template getArgsList(args...) 13 { 14 static if(args.length) 15 { 16 alias arg = args[0]; 17 import std.traits: isType; 18 static if(args.length == 1) 19 { 20 static if(isStaticArray!(arg)) 21 alias getArgsList = AliasSeq!(Repeat!(arg.length, ElementType!(arg))); 22 else 23 alias getArgsList = AliasSeq!(arg); 24 } 25 else 26 { 27 static if(isType!(args[1])) 28 { 29 static if(isStaticArray!(arg)) 30 alias getArgsList = AliasSeq!(Repeat!(arg.length, ElementType!(arg)), getArgsList!(args[1 .. $])); 31 else 32 alias getArgsList = AliasSeq!(arg, getArgsList!(args[1 .. $])); 33 } 34 else 35 { 36 alias getArgsList = AliasSeq!(Repeat!(args[1],arg), getArgsList!(args[2 .. $])); 37 } 38 } 39 } 40 else 41 alias getArgsList = AliasSeq!(); 42 } 43 44 private template dropper_internal(int[] pos, int rem, Types...) 45 { 46 static if(pos.length == 0) 47 { 48 alias dropper_internal = Types; 49 } 50 else 51 { 52 alias dropper_internal = AliasSeq!(Types[0 .. pos[0] - rem], 53 dropper_internal!(pos[1 .. $], pos[0] + 1 ,Types[pos[0] - rem + 1 .. $])); 54 } 55 } 56 57 /// Template to evaluate RowType - removes elements in Types according to the positions in pos. pos must be ascending 58 template dropper(int[] pos, Types...) 59 { 60 alias dropper = dropper_internal!(pos, 0, Types); 61 } 62 63 /// drops values at a set of position from given array 64 T[] dropper(T)(int[] pos, T[] values) 65 { 66 T[] dropper_internal(T)(int[] pos, int rem, T[] values) 67 { 68 if(pos.length == 0) 69 return values; 70 else 71 return values[0 .. pos[0] - rem] ~ dropper_internal(pos[1 .. $], pos[0] + 1, values[pos[0] - rem + 1 .. $]); 72 } 73 74 return dropper_internal(pos, 0, values); 75 } 76 77 /// Function to sort indexes in ascending order and switch the code to keep the effective positions same 78 void sortIndex(string[] index, int[] codes) 79 { 80 foreach(i; 0 .. cast(uint)index.length) 81 { 82 uint pos = i; 83 foreach(j; i + 1 .. cast(uint)index.length) 84 { 85 if(index[pos] > index[j]) 86 { 87 pos = j; 88 } 89 } 90 91 if(i == pos) 92 continue; 93 94 string tmp = index[i]; 95 index[i] = index[pos]; 96 index[pos] = tmp; 97 98 foreach(j; 0 .. codes.length) 99 { 100 if(codes[j] == pos) 101 codes[j] = i; 102 else if(codes[j] == i) 103 codes[j] = pos; 104 } 105 } 106 } 107 108 /// Does exactly what generateCode does in Index but for any general ndarray 109 int[] vectorize(T)(T[] values) 110 { 111 int[T] elementPos; 112 int[] pos; 113 int totalUnique = 0; 114 115 foreach(i; values) 116 { 117 ++pos.length; 118 pos[$ - 1] = elementPos.require(cast(immutable)i, { return totalUnique++; }()); 119 } 120 121 return totalUnique ~ pos; 122 } 123 124 /// Transposes an array of integer 125 T[][] transposed(T)(T[][] data) 126 { 127 T[][] ret; 128 ret.length = data[0].length; 129 foreach(i, eleu; data) 130 { 131 foreach(j, elel; eleu) 132 { 133 ++ret[j].length; 134 ret[j][i] = elel; 135 } 136 } 137 138 return ret; 139 } 140 141 /// Template to get array from type 142 alias toArr(T) = T[]; 143 144 /// Get unique indexes for merge 145 Index ensureUnique(int axis)(Index i1, Index i2, string lsuffix = "x_", string rsuffix = "y_") 146 { 147 assert(i1.indexing[axis].codes.length == i2.indexing[axis].codes.length, "Index level mismatch for union"); 148 149 import std.algorithm: countUntil; 150 string[][] unique; 151 152 unique.length = i1.indexing[axis].codes[0].length + i1.indexing[axis].codes[0].length; 153 154 foreach(i; 0 .. i1.indexing[axis].codes[0].length) 155 { 156 string[] indx; 157 indx.length = i1.indexing[axis].codes.length; 158 159 import std.range: lockstep; 160 foreach(j, a, b; lockstep(i1.indexing[axis].index, i1.indexing[axis].codes)) 161 { 162 if(a.length == 0) 163 { 164 import std.conv: to; 165 indx[j] = to!string(b[i]); 166 } 167 else 168 indx[j] = a[b[i]]; 169 } 170 171 // Making an asuumption that there is no conflicting indexes within the same Index struct 172 unique[i] = indx; 173 } 174 175 foreach(i; 0 .. i2.indexing[axis].codes[0].length) 176 { 177 string[] indx; 178 indx.length = i2.indexing[axis].codes.length; 179 180 import std.range: lockstep; 181 foreach(j, a, b; lockstep(i2.indexing[axis].index, i2.indexing[axis].codes)) 182 { 183 if(a.length == 0) 184 { 185 import std.conv: to; 186 indx[j] = to!string(b[i]); 187 } 188 else 189 indx[j] = a[b[i]]; 190 } 191 192 // Making an asuumption that there is no conflicting indexes within the same Index struct 193 const int p = cast(int)countUntil(unique, indx); 194 if(p < 0) 195 unique[i + i1.indexing[axis].codes[0].length] = indx; 196 else 197 { 198 foreach(j; 0 .. unique[p].length) 199 unique[p][j] = lsuffix ~ unique[p][j]; 200 201 foreach(j; 0 .. indx.length) 202 indx[j] = rsuffix ~ indx[j]; 203 204 unique[i + i1.indexing[axis].codes[0].length] = indx; 205 } 206 } 207 208 Index ret; 209 ret.indexing[axis].index = transposed(unique); 210 ret.generateCodes(); 211 212 return ret; 213 } 214 215 /// Returns the union of indexes in the order they appear 216 Index indexUnion(int axis)(Index i1, Index i2) 217 { 218 assert(i1.indexing[axis].codes.length == i2.indexing[axis].codes.length, "Index level mismatch for union"); 219 220 import std.algorithm: countUntil; 221 string[][] unique; 222 223 foreach(i; 0 .. i1.indexing[axis].codes[0].length) 224 { 225 string[] indx; 226 indx.length = i1.indexing[axis].codes.length; 227 228 import std.range: lockstep; 229 foreach(j, a, b; lockstep(i1.indexing[axis].index, i1.indexing[axis].codes)) 230 { 231 if(a.length == 0) 232 { 233 import std.conv: to; 234 indx[j] = to!string(b[i]); 235 } 236 else 237 indx[j] = a[b[i]]; 238 } 239 240 // Making an asuumption that there is no conflicting indexes within the same Index struct 241 unique ~= indx; 242 } 243 244 foreach(i; 0 .. i2.indexing[axis].codes[0].length) 245 { 246 string[] indx; 247 indx.length = i2.indexing[axis].codes.length; 248 249 import std.range: lockstep; 250 foreach(j, a, b; lockstep(i2.indexing[axis].index, i2.indexing[axis].codes)) 251 { 252 if(a.length == 0) 253 { 254 import std.conv: to; 255 indx[j] = to!string(b[i]); 256 } 257 else 258 indx[j] = a[b[i]]; 259 } 260 261 // Making an asuumption that there is no conflicting indexes within the same Index struct 262 const int p = cast(int)countUntil(unique, indx); 263 if(p < 0) 264 unique ~= indx; 265 } 266 267 Index ret; 268 ret.indexing[axis].index = transposed(unique); 269 ret.generateCodes(); 270 271 return ret; 272 } 273 274 /// find set of indexes that occur in both structures 275 Index indexIntersection(int axis)(Index i1, Index i2) 276 { 277 assert(i1.indexing[axis].codes.length == i2.indexing[axis].codes.length, "Index level mismatch for union"); 278 279 import std.algorithm: countUntil; 280 string[][] intersect; 281 282 foreach(i; 0 .. i1.indexing[axis].codes[0].length) 283 { 284 string[] indx; 285 indx.length = i1.indexing[axis].codes.length; 286 287 import std.range: lockstep; 288 foreach(j, a, b; lockstep(i1.indexing[axis].index, i1.indexing[axis].codes)) 289 { 290 if(a.length == 0) 291 { 292 import std.conv: to; 293 indx[j] = to!string(b[i]); 294 } 295 else 296 indx[j] = a[b[i]]; 297 } 298 299 // Making an asuumption that there is no conflicting indexes within the same Index struct 300 if(i2.getPosition!(axis)(indx) > -1) 301 intersect ~= indx; 302 } 303 304 Index ret; 305 ret.indexing[axis].index = transposed(intersect); 306 ret.generateCodes(); 307 308 return ret; 309 } 310 311 /// Template to check if the input is a DataFrame or not 312 template isDataFrame(T) 313 { 314 static if(is(T: DataFrame!Ty, Ty...)) 315 enum bool isDataFrame = true; 316 else 317 enum bool isDataFrame = false; 318 } 319 320 /// Template to check if the input is a Group or not 321 template isGroup(T) 322 { 323 static if(is(T: Group!Ty, Ty...)) 324 enum bool isGroup = true; 325 else 326 enum bool isGroup = false; 327 } 328 329 /// Check if given DataFrame is Homogeneous 330 template isHomogeneous(Args...) 331 { 332 static if(Args.length == 1) 333 enum bool isHomogeneous = true; 334 else 335 enum bool isHomogeneous = is(Args[0] == Args[1]) && isHomogeneous!(Args[1 .. $]); 336 } 337 338 /// Finding suitable type for aggregate 339 template suitableType(Types...) 340 { 341 import std.meta: anySatisfy; 342 import std.traits: isFloatingPoint, Largest; 343 static if(anySatisfy!(isFloatingPoint, Types)) 344 alias suitableType = double; 345 else 346 alias suitableType = Largest!(Types); 347 } 348 349 mixin template auxDispatch(alias F, bool isHomogeneousType, RowType...) 350 { 351 auto auxDispatch(size_t indx) 352 { 353 static if(isHomogeneousType) 354 return F(indx); 355 else 356 static foreach(i; 0 .. RowType.length) 357 if(i == indx) 358 return F!(i); 359 360 assert(0); 361 } 362 } 363 364 // Community suggested way ot intialize a DataFrame 365 unittest 366 { 367 assert(is(getArgsList!(int[2]) == AliasSeq!(int, int))); 368 assert(is(getArgsList!(int[2], double[3]) == AliasSeq!(int, int, double, double, double))); 369 assert(is(getArgsList!(int[1], double[2], int, 2) == AliasSeq!(int, double, double, int, int))); 370 assert(is(getArgsList!(int, int, double[2], int, 2) == AliasSeq!(int, int, double, double, int, int))); 371 } 372 373 // Sorting indexes and codes by keeping their effet position same 374 unittest 375 { 376 string[] indx = ["b", "c", "a", "d"]; 377 int[] codes = [0, 1, 2, 3]; 378 sortIndex(indx, codes); 379 assert(indx == ["a", "b", "c", "d"]); 380 assert(codes == [1, 2, 0, 3]); 381 } 382 383 // Testing dropper 384 unittest 385 { 386 assert(is(dropper!([1, 4], int, long, int, long, byte, float, bool) == AliasSeq!(int, int, long, float, bool))); 387 assert(is(dropper!([0, 4], int, long, int, long, double) == AliasSeq!(long, int, long))); 388 assert(is(dropper!([1, 3, 5], int, long, int, long, double, float, bool) == AliasSeq!(int, int, double, bool))); 389 assert(is(dropper!([0, 2, 3, 5], int, uint, byte, ubyte, long, ulong, bool) == AliasSeq!(uint, long, bool))); 390 } 391 392 // Testing dropper for arrays 393 unittest 394 { 395 assert(dropper([1, 4], [1, 2, 3, 4, 5, 6]) == [1, 3, 4, 6]); 396 assert(dropper([0, 5], [1, 2, 3, 4, 5, 6]) == [2, 3, 4, 5]); 397 assert(dropper([0, 3, 5], [1, 2, 3, 4, 5, 6]) == [2, 3, 5]); 398 } 399 400 // Vectorize test 401 unittest 402 { 403 double[] arr = [1.2, 2.7, 1.2, 5.6, 1.2, 5.6]; 404 auto varr = vectorize(arr); 405 assert(varr.length == arr.length + 1); 406 assert(varr[0] == 3); 407 assert(varr[1 .. $] == [0, 1, 0, 2, 0, 2]); 408 409 string[] arrs = ["Hello", "Hi", "Hello", "Hi"]; 410 auto varrs = vectorize(arrs); 411 assert(varrs.length == arrs.length + 1); 412 assert(varrs[0] == 2); 413 assert(varrs[1 .. $] == [0, 1, 0, 1]); 414 415 int[][] arr2d = [[1, 2], [2, 4], [1, 2], [3, 6]]; 416 auto varr2d = vectorize(arr2d); 417 assert(varr2d.length == arr2d.length + 1); 418 assert(varr2d[0] == 3); 419 assert(varr2d[1 .. $] == [0, 1, 0, 2]); 420 } 421 422 // Transposed test 423 unittest 424 { 425 int[][] a = [[1, 2, 3], [4, 5, 6]]; 426 assert(transposed(a) == [[1, 4], [2, 5], [3, 6]]); 427 428 int[][] b = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]; 429 assert(transposed(b) == [[1, 4, 7], [2, 5, 8], [3, 6, 9]]); 430 } 431 432 433 // Ensuring unque indexes on merge 434 unittest 435 { 436 Index i1; 437 Index i2; 438 439 i1.setIndex([["Hello", "Hi"], ["Hi", "Hello"]], ["1", "@"]); 440 i2.setIndex([["Hey", "Yo"], ["Yo", "Hey"]], ["!", "2"]); 441 442 auto unique = ensureUnique!0(i1, i2); 443 assert(unique.row.index == [["Hello", "Hey", "Hi", "Yo"], ["Hello", "Hey", "Hi", "Yo"]]); 444 assert(unique.row.codes == [[0, 2, 1, 3], [2, 0, 3, 1]]); 445 446 i2.setIndex([["Hey", "Hello"], ["Hi", "Hi"]], ["!", "@"]); 447 unique = ensureUnique!0(i1, i2); 448 assert(unique.row.index == [["Hey", "Hi", "x_Hello", "y_Hello"], ["Hello", "Hi", "x_Hi", "y_Hi"]]); 449 assert(unique.row.codes == [[2, 1, 0, 3], [2, 0, 1, 3]]); 450 } 451 452 // Index union 453 unittest 454 { 455 Index i1; 456 Index i2; 457 458 i1.setIndex([["Hello", "Hi"], ["Hi", "Hello"]], ["1", "@"]); 459 i2.setIndex([["Hi", "Hello"], ["Yo", "Hi"]], ["!", "2"]); 460 461 Index indxunion = indexUnion!0(i1, i2); 462 assert(indxunion.row.index == [["Hello", "Hi"], ["Hello", "Hi", "Yo"]]); 463 assert(indxunion.row.codes == [[0, 1, 1], [1, 0, 2]]); 464 } 465 466 // Index Intersection 467 unittest 468 { 469 Index i1; 470 Index i2; 471 472 i1.setIndex([["Hello", "Hi"], ["Hi", "Hello"]], ["1", "@"]); 473 i2.setIndex([["Hi", "Hello"], ["Yo", "Hi"]], ["!", "2"]); 474 475 auto indxintersect = indexIntersection!0(i1, i2); 476 assert(indxintersect.row.index == [["Hello"], ["Hi"]]); 477 assert(indxintersect.row.codes == [[0], [0]]); 478 } 479 480 // Index Intersection 481 unittest 482 { 483 Index i1; 484 Index i2; 485 486 i1.setIndex([["Hello", "Hi", "Hey"], ["Hi", "Hello", "Hey"], ["Hey", "Hi", "Hello"]], ["1", "@"]); 487 i2.setIndex([["Hi", "Hello"], ["Hello", "Hi"], ["Hi", "Hey"]], ["!", "2"]); 488 489 auto indxintersect = indexIntersection!0(i1, i2); 490 assert(indxintersect.row.index == [["Hello", "Hi"], ["Hello", "Hi"], ["Hey", "Hi"]]); 491 assert(indxintersect.row.codes == [[0, 1], [1, 0], [0, 1]]); 492 } 493 494 // isDataFrame 495 unittest 496 { 497 static assert(isDataFrame!(DataFrame!(int, 2)) == true); 498 static assert(isDataFrame!(bool) == false); 499 } 500 501 unittest 502 { 503 static assert(isHomogeneous!(int, int, int) == true); 504 static assert(isHomogeneous!(int, int, double) == false); 505 }