1 module magpie.axis; 2 3 import std.datetime: DateTime; 4 import std.range.primitives: ElementType; 5 import std.traits: isArray; 6 import std.variant: Algebraic; 7 8 alias DataType = Algebraic!(bool, int, long, float, double, string, DateTime); 9 10 /++ 11 Structure to return an entire row or column of DataFrame. 12 The operations on this structure enables column/row binary operations. 13 +/ 14 struct Axis(T...) 15 { 16 static if(T.length == 1 && is(T[0] == void)) 17 alias AxisType = DataType[]; 18 else static if(T.length == 1 && isArray!(T[0])) 19 alias AxisType = T[0]; 20 else 21 alias AxisType = T; 22 23 /// Data being returned by the DataFrame 24 AxisType data; 25 26 /++ 27 auto convertTo(U...)() 28 Description: Change the type of Axis.data 29 @params: u - The type to which data needs to be converted to. 30 +/ 31 auto convertTo(U...)() @property 32 if(U.length == T.length) 33 { 34 import std.conv: to; 35 static if(U.length == 1) 36 { 37 static if(is(T[0] == void)) 38 { 39 Axis!U ret; 40 foreach(i; data) 41 { 42 import std.variant: VariantException; 43 try 44 { 45 ret.data ~= i.get!(ElementType!(U[0])); 46 } 47 catch(VariantException e) 48 { 49 ret.data ~= to!(ElementType!(U[0]))(i.get!(double)); 50 } 51 } 52 53 return ret; 54 } 55 else 56 { 57 Axis!U ret; 58 ret.data = to!(U[0])(data); 59 return ret; 60 } 61 } 62 else 63 { 64 Axis!U ret; 65 static foreach(i; 0 .. U.length) 66 ret.data[i] = to!(U[i])(data[i]); 67 return ret; 68 } 69 } 70 71 /++ 72 Binary Operations on DataFrame row/column 73 +/ 74 auto opBinary(string op, U...)(Axis!U rhs) 75 if(U.length == T.length) 76 { 77 static if(is(T[0] == void) || is(U[0] == void)) 78 Axis!void ret; 79 else 80 Axis!T ret; 81 82 static if(op == "+") 83 { 84 static if(U.length == 1) 85 { 86 assert(data.length == rhs.data.length, "Size mismatch"); 87 foreach(i; 0 .. data.length) 88 { 89 static if(is(T[0] == void)) 90 ret.data ~= DataType(data[i] + rhs.data[i]); 91 else 92 ret.data ~= data[i] + rhs.data[i]; 93 } 94 return ret; 95 } 96 else 97 { 98 static foreach(i; 0 .. U.length) 99 ret.data[i] = data[i] + rhs.data[i]; 100 101 return ret; 102 } 103 } 104 else static if(op == "-") 105 { 106 static if(U.length == 1) 107 { 108 assert(data.length == rhs.data.length, "Size mismatch"); 109 110 foreach(i; 0 .. data.length) 111 { 112 static if(is(T[0] == void)) 113 ret.data ~= DataType(data[i] - rhs.data[i]); 114 else 115 ret.data ~= data[i] - rhs.data[i]; 116 } 117 return ret; 118 } 119 else 120 { 121 static foreach(i; 0 .. U.length) 122 ret.data[i] = data[i] - rhs.data[i]; 123 124 return ret; 125 } 126 } 127 else static if(op == "*") 128 { 129 static if(U.length == 1) 130 { 131 assert(data.length == rhs.data.length, "Size mismatch"); 132 133 foreach(i; 0 .. data.length) 134 { 135 static if(is(T[0] == void)) 136 ret.data ~= DataType(data[i] * rhs.data[i]); 137 else 138 ret.data ~= data[i] * rhs.data[i]; 139 } 140 return ret; 141 } 142 else 143 { 144 static foreach(i; 0 .. U.length) 145 ret.data[i] = data[i] * rhs.data[i]; 146 147 return ret; 148 } 149 } 150 else static if(op == "/") 151 { 152 static if(U.length == 1) 153 { 154 assert(data.length == rhs.data.length, "Size mismatch"); 155 156 foreach(i; 0 .. data.length) 157 { 158 static if(is(T[0] == void) || is(U[0] == void)) 159 ret.data ~= DataType(DataType(data[i]) / DataType(rhs.data[i])); 160 else 161 ret.data ~= data[i] / rhs.data[i]; 162 } 163 return ret; 164 } 165 else 166 { 167 static foreach(i; 0 .. U.length) 168 ret.data[i] = data[i] / rhs.data[i]; 169 170 return ret; 171 } 172 } 173 174 assert(0); 175 } 176 } 177 178 // Covert a probable column 179 unittest 180 { 181 Axis!(int[]) column; 182 column.data = [1,2,3,4]; 183 auto res = column.convertTo!(double[]); 184 assert(res.data == [1.0, 2.0, 3.0, 4.0]); 185 assert(is(typeof(res.data) == double[])); 186 } 187 188 // Convert a probable row 189 unittest 190 { 191 Axis!(int, double) row; 192 row.data[0] = 12; 193 row.data[1] = 14.7; 194 auto res = row.convertTo!(double, int); 195 assert(res.data[0] == 12.0); 196 assert(res.data[1] == 14); 197 assert(is(typeof(res.data[0]) == double)); 198 assert(is(typeof(res.data[1]) == int)); 199 } 200 201 // Addition operation on multiple column 202 unittest 203 { 204 Axis!(double[]) a1; 205 a1.data = [1,2,3.7,4]; 206 Axis!(int[]) a2; 207 a2.data = [1,2,3,4]; 208 Axis!(double[]) a3; 209 a3.data = [1,2,3.7,4]; 210 Axis!(double[]) a4; 211 a4.data = [1,2,3,4]; 212 213 assert((a1 + a2).data == [2, 4, 6.7, 8]); 214 assert(a1.data == [1,2,3.7,4]); 215 assert(a2.data == [1,2,3,4]); 216 assert((a1 + a2 + a3 + a4).data == [4, 8, 13.4, 16]); 217 } 218 219 // Addition operation on multiple rows 220 unittest 221 { 222 Axis!(int, double) a1; 223 a1.data[0] = 1; 224 a1.data[1] = 1.786; 225 226 Axis!(int, int) a2; 227 a2.data[0] = 1; 228 a2.data[1] = 1; 229 230 Axis!(int, double) a3; 231 a3.data[0] = 1; 232 a3.data[1] = 1.786; 233 234 Axis!(int, double) a4; 235 a4.data[0] = 1; 236 a4.data[1] = 1; 237 238 import std.math: approxEqual; 239 auto res = a1 + a2; 240 assert(res.data[0] == 2); 241 assert(approxEqual(res.data[1], 2.786, 1e-3)); 242 243 auto res2 = a1 + a2 + a3 + a4; 244 assert(res2.data[0] == 4); 245 assert(approxEqual(res2.data[1], 5.572, 1e-3)); 246 } 247 248 // Subtraction operation on multiple column 249 unittest 250 { 251 Axis!(double[]) a1; 252 a1.data = [1,2,3.765,4]; 253 Axis!(int[]) a2; 254 a2.data = [1,2,3,4]; 255 Axis!(double[]) a3; 256 a3.data = [1,2,3.765,4]; 257 Axis!(double[]) a4; 258 a4.data = [1,2,3,4]; 259 260 import std.math: approxEqual; 261 static foreach(i; 0 .. 4) 262 { 263 assert(approxEqual((a1 - a2).data[i], a1.data[i] - a2.data[i], 1e-3)); 264 assert(approxEqual((a1 - a2 - a3 - a4).data[i], a1.data[i] - a2.data[i] - a3.data[i] - a4.data[i], 1e-3)); 265 } 266 267 } 268 269 // Subtraction operation on multiple rows 270 unittest 271 { 272 Axis!(int, double) a1; 273 a1.data[0] = 1; 274 a1.data[1] = 1.786; 275 276 Axis!(int, int) a2; 277 a2.data[0] = 1; 278 a2.data[1] = 1; 279 280 Axis!(int, double) a3; 281 a3.data[0] = 1; 282 a3.data[1] = 1.786; 283 284 Axis!(int, double) a4; 285 a4.data[0] = 1; 286 a4.data[1] = 1; 287 288 auto res = a1 - a2; 289 auto res2 = a1 - a2 - a3 - a4; 290 291 import std.math: approxEqual; 292 static foreach(i; 0 .. 2) 293 { 294 assert(approxEqual(res.data[i], a1.data[i] - a2.data[i], 1e-3)); 295 assert(approxEqual(res2.data[i], a1.data[i] - a2.data[i] - a3.data[i] - a4.data[i], 1e-3)); 296 } 297 } 298 299 // Multiplication operation on multiple column 300 unittest 301 { 302 Axis!(double[]) a1; 303 a1.data = [1,2,3.765,4]; 304 Axis!(int[]) a2; 305 a2.data = [1,2,3,4]; 306 Axis!(double[]) a3; 307 a3.data = [1,2,3.765,4]; 308 Axis!(double[]) a4; 309 a4.data = [1,2,3,4]; 310 311 import std.math: approxEqual; 312 static foreach(i; 0 .. 4) 313 { 314 assert(approxEqual((a1 * a2).data[i], a1.data[i] * a2.data[i], 1e-6)); 315 assert(approxEqual((a1 * a2 * a3 * a4).data[i], a1.data[i] * a2.data[i] * a3.data[i] * a4.data[i], 1e-6)); 316 } 317 318 } 319 320 // Multiplication operation on multiple rows 321 unittest 322 { 323 Axis!(int, double) a1; 324 a1.data[0] = 1; 325 a1.data[1] = 1.786; 326 327 Axis!(int, int) a2; 328 a2.data[0] = 1; 329 a2.data[1] = 1; 330 331 Axis!(int, double) a3; 332 a3.data[0] = 1; 333 a3.data[1] = 1.786; 334 335 Axis!(int, double) a4; 336 a4.data[0] = 1; 337 a4.data[1] = 1; 338 339 auto res = a1 * a2; 340 auto res2 = a1 * a2 * a3 * a4; 341 342 import std.math: approxEqual; 343 static foreach(i; 0 .. 2) 344 { 345 assert(approxEqual(res.data[i], a1.data[i] * a2.data[i], 1e-6)); 346 assert(approxEqual(res2.data[i], a1.data[i] * a2.data[i] * a3.data[i] * a4.data[i], 1e-6)); 347 } 348 } 349 350 // Division operation on multiple column 351 unittest 352 { 353 Axis!(double[]) a1; 354 a1.data = [1,2,3.765,4]; 355 Axis!(int[]) a2; 356 a2.data = [1,2,3,4]; 357 Axis!(double[]) a3; 358 a3.data = [1,2,3.765,4]; 359 Axis!(double[]) a4; 360 a4.data = [1,2,3,4]; 361 362 import std.math: approxEqual; 363 static foreach(i; 0 .. 4) 364 { 365 assert(approxEqual((a1 / a2).data[i], a1.data[i] / a2.data[i], 1e-3)); 366 assert(approxEqual((a1 / a2 / a3 / a4).data[i], a1.data[i] / a2.data[i] / a3.data[i] / a4.data[i], 1e-3)); 367 } 368 369 } 370 371 // Division operation on multiple rows 372 unittest 373 { 374 Axis!(int, double) a1; 375 a1.data[0] = 1; 376 a1.data[1] = 1.786; 377 378 Axis!(int, int) a2; 379 a2.data[0] = 1; 380 a2.data[1] = 1; 381 382 Axis!(int, double) a3; 383 a3.data[0] = 1; 384 a3.data[1] = 1.786; 385 386 Axis!(int, double) a4; 387 a4.data[0] = 1; 388 a4.data[1] = 1; 389 390 auto res = a1 / a2; 391 auto res2 = a1 / a2 / a3 / a4; 392 393 import std.math: approxEqual; 394 static foreach(i; 0 .. 2) 395 { 396 assert(approxEqual(res.data[i], a1.data[i] / a2.data[i], 1e-3)); 397 assert(approxEqual(res2.data[i], a1.data[i] / a2.data[i] / a3.data[i] / a4.data[i], 1e-3)); 398 } 399 } 400 401 // Axis with data of type Variant 402 unittest 403 { 404 Axis!(void) a; 405 foreach(i; 0 .. 5) 406 a.data ~= DataType(i + 1.7); 407 assert(a.data == [1.7, 2.7, 3.7, 4.7, 5.7]); 408 } 409 410 // Bianry Ops on Variant Axis 411 unittest 412 { 413 Axis!(void) a; 414 Axis!(void) b; 415 foreach(i; 0 .. 5) 416 { 417 a.data ~= DataType(i + 1.7); 418 b.data ~= DataType(i); 419 } 420 421 auto c = a + b; 422 assert(c.data == [1.7, 3.7, 5.7, 7.7, 9.7]); 423 assert(typeid(a) == typeid(c)); 424 425 c = a - b; 426 import std.math: approxEqual; 427 foreach(i; c.data) 428 assert(approxEqual(i.get!double, 1.7, 1e-1)); 429 430 c = a * b; 431 foreach(i; 0 .. 5) 432 assert(approxEqual(c.data[i].get!double, ((i + 1.7) * i), 1e-2)); 433 434 c = b / a; 435 foreach(i; 0 .. 5) 436 assert(approxEqual(c.data[i].get!double, (i / (i + 1.7)), 1e-4)); 437 } 438 439 // Binary Ops on Variant + Other DataType 440 unittest 441 { 442 Axis!(void) a; 443 Axis!(int[]) b; 444 445 foreach(i; 0 .. 5) 446 { 447 a.data ~= DataType(i + 1.7); 448 b.data ~= i; 449 } 450 451 auto c = a + b; 452 assert(c.data == [1.7, 3.7, 5.7, 7.7, 9.7]); 453 assert(typeid(a) == typeid(c)); 454 455 c = a - b; 456 import std.math: approxEqual; 457 foreach(i; c.data) 458 assert(approxEqual(i.get!double, 1.7, 1e-1)); 459 460 c = a * b; 461 foreach(i; 0 .. 5) 462 assert(approxEqual(c.data[i].get!double, ((i + 1.7) * i), 1e-2)); 463 464 c = b / a; 465 foreach(i; 0 .. 5) 466 assert(approxEqual(c.data[i].get!double, (i / (i + 1.7)), 1e-4)); 467 } 468 469 // Converting Variant Axis to other types 470 unittest 471 { 472 Axis!(void) a; 473 474 foreach(i; 0 .. 5) 475 { 476 a.data ~= DataType(i + 1.7); 477 } 478 479 auto b = a.convertTo!(int[]); 480 assert(is(typeof(b.data) == int[])); 481 assert(b.data == [1, 2, 3, 4, 5]); 482 483 auto c = a.convertTo!(double[]); 484 assert(is(typeof(c.data) == double[])); 485 486 import std.math: approxEqual; 487 foreach(i; 0 .. 5) 488 assert(approxEqual(c.data[i], (i + 1.7), 1e-1)); 489 }