diff --git a/data/input/test_time_series.csv b/data/input/test_time_series.csv new file mode 100644 index 0000000..03b99df --- /dev/null +++ b/data/input/test_time_series.csv @@ -0,0 +1,336 @@ +ds,y,cost,cp,stock_level,retail_price +01/01/2017,0,0,42.5340409356725,4890,152.650887920178 +01/02/2017,171,7273.321,42.5340409356725,2233,63.02 +01/03/2017,168,7150.338,42.5615357142857,3119,107.835443960089 +01/04/2017,166,7074.227,42.6158253012048,2725,63.02 +01/05/2017,211,8993.719,42.6242606635071,2507,62.694863635 +01/06/2017,278,11866.209,42.6842050359712,2583,91.9407013800594 +01/07/2017,226,9648.065,42.6905530973451,2323,107.835443960089 +01/08/2017,119,5087.746,42.7541680672269,2276,107.835443960089 +01/09/2017,152,6499.54,42.7601315789474,2338,62.885833335 +01/10/2017,239,10242.383,42.8551589958159,2633,63.02 +01/11/2017,87,3733.673,42.9157816091954,2214,63.02 +01/12/2017,133,5709.179,42.9261578947368,3179,107.835443960089 +01/13/2017,192,8232.189,42.875984375,2434,63.02 +01/14/2017,142,6085.145,42.8531338028169,2510,107.835443960089 +01/15/2017,146,6262.695,42.8951712328767,2142,63.02 +01/16/2017,92,3943.497,42.864097826087,2161,107.835443960089 +01/17/2017,107,4510.448,42.1537196261682,2458,107.835443960089 +01/18/2017,104,4341.49,41.7450961538462,2940,107.835443960089 +01/19/2017,145,6022.307,41.5331517241379,2676,62.45136607 +01/20/2017,171,7088.815,41.4550584795322,2859,107.835443960089 +01/21/2017,157,6488.643,41.3289363057325,2141,63.02 +01/22/2017,113,4659.682,41.2361238938053,3146,107.835443960089 +01/23/2017,112,4606.455,41.1290625,2466,107.835443960089 +01/24/2017,106,4261.04,40.1984905660377,9019,107.835443960089 +01/25/2017,2269,91009.308,40.1098757161745,8520,50.14 +01/26/2017,2303,92107.172,39.9944298740773,9728,50.14 +01/27/2017,1984,79289.119,39.9642736895161,11955,50.14 +01/28/2017,1634,65292.518,39.9587013463892,10265,50.14 +01/29/2017,1970,78729.548,39.9642375634518,8272,50.14 +01/30/2017,1685,67231.442,39.899965578635,9273,101.395443960089 +01/31/2017,1587,63382.228,39.9383919344676,7969,50.14 +02/01/2017,1798,71822.048,39.9455216907675,7320,50.14 +02/02/2017,1958,78094.678,39.8849223697651,6606,50.14 +02/03/2017,1877,74800.067,39.8508614810868,7912,50.14 +02/04/2017,1888,75277.974,39.8718082627119,6008,50.14 +02/05/2017,1097,43709.917,39.8449562443026,4939,101.395443960089 +02/06/2017,1756,69952.031,39.8360085421412,5335,101.395443960089 +02/07/2017,2595,103354.382,39.8282782273603,3947,84.3247031333928 +02/08/2017,198,7940.389,40.1029747474747,4228,92.7849113567261 +02/09/2017,230,9253.719,40.2335608695652,4405,87.4749256033928 +02/10/2017,305,12386.147,40.6103180327869,4582,92.8659552333927 +02/11/2017,227,9248.287,40.7413524229075,4013,107.835443960089 +02/12/2017,174,7055.609,40.5494770114943,3839,107.835443960089 +02/13/2017,162,6573.97,40.5800617283951,3672,107.835443960089 +02/14/2017,152,6191.34,40.7325,4576,107.835443960089 +02/15/2017,232,9423.421,40.6181939655172,3726,61.99601064 +02/16/2017,236,9622.819,40.774656779661,3491,62.90723611 +02/17/2017,261,10680.505,40.9214750957854,3398,63.02 +02/18/2017,221,9085.481,41.1107737556561,3101,63.02 +02/19/2017,169,6969.824,41.2415621301775,3082,92.4386054967261 +02/20/2017,179,7306.143,40.8164413407821,2811,62.8834375 +02/21/2017,210,8647.538,41.1787523809524,2860,62.95151111 +02/22/2017,88,3640.098,41.36475,3202,107.835443960089 +02/23/2017,110,4576.135,41.6012272727273,3167,92.7351107867261 +02/24/2017,126,5216.422,41.4001746031746,2951,63.02 +02/25/2017,97,4035.09,41.5988659793814,2853,63.02 +02/26/2017,86,3568.977,41.4997325581395,2426,63.02 +02/27/2017,70,2921.921,41.7417285714286,3028,107.835443960089 +02/28/2017,72,2971.001,41.2639027777778,2609,62.758375 +03/01/2017,85,3544.384,41.6986352941177,2457,63.02 +03/02/2017,107,4433.725,41.4366822429907,2728,107.835443960089 +03/03/2017,103,4244.492,41.2086601941748,2743,63.02 +03/04/2017,100,4094.95,40.9495,2404,63.02 +03/05/2017,73,2996.138,41.0429863013699,2803,107.835443960089 +03/06/2017,102,4176.189,40.9430294117647,2938,62.94248148 +03/07/2017,220,8744.37,39.7471363636364,7746,107.835443960089 +03/08/2017,1782,70621.311,39.6303653198653,7536,50.14 +03/09/2017,1492,59057.646,39.5828726541555,10712,50.14 +03/10/2017,1663,65769.123,39.5484804570054,11222,50.14 +03/11/2017,1375,54412.044,39.5723956363636,9831,50.14 +03/12/2017,1032,40820.386,39.5546375968992,8787,50.14 +03/13/2017,1084,42856.57,39.5355811808118,9246,50.14 +03/14/2017,1366,53990.655,39.5246376281113,8224,50.14 +03/15/2017,1400,55377.518,39.55537,7288,50.14 +03/16/2017,1671,66007.17,39.5015978456014,6761,50.14 +03/17/2017,1632,64447.291,39.4897616421569,5948,50.14 +03/18/2017,1566,61859.569,39.5016404853129,4380,50.14 +03/19/2017,1045,41254.979,39.4784488038278,3314,50.14 +03/20/2017,1197,47235.105,39.4612406015038,4196,50.14 +03/21/2017,1740,68730.959,39.5005511494253,2918,50.14 +03/22/2017,178,7169.895,40.280308988764,3252,107.835443960089 +03/23/2017,192,7742.795,40.3270572916667,3210,107.835443960089 +03/24/2017,257,10365.979,40.3345486381323,2641,92.6563251400594 +03/25/2017,150,6040.82,40.2721333333333,5334,107.835443960089 +03/26/2017,133,5368.805,40.3669548872181,2713,107.835443960089 +03/27/2017,145,5823.139,40.1595793103448,2484,92.8148842100594 +03/28/2017,178,7204.39,40.4741011235955,2906,107.835443960089 +03/29/2017,147,5988.401,40.7374217687075,2541,107.835443960089 +03/30/2017,173,7038.352,40.6841156069364,5271,92.8342763667261 +03/31/2017,210,8628.25,41.0869047619048,2670,63.02 +04/01/2017,167,6871.911,41.1491676646707,3051,85.1230676025446 +04/02/2017,180,7426.882,41.2604555555556,2678,62.96097794 +04/03/2017,162,6678.796,41.2271358024691,2453,62.857789475 +04/04/2017,167,6886.495,41.236497005988,2269,63.02 +04/05/2017,212,8766.558,41.3516886792453,2418,63.02 +04/06/2017,212,8875.46,41.8653773584906,2553,63.02 +04/07/2017,320,13327.721,41.649128125,2231,63.02 +04/08/2017,252,10480.18,41.5880158730159,1976,63.02 +04/09/2017,180,7513.064,41.7392444444444,1662,63.02 +04/10/2017,200,8209.41,41.04705,2167,92.3416473233928 +04/11/2017,213,8681.005,40.7558920187793,1991,62.814533335 +04/12/2017,292,11891.483,40.7242568493151,2053,62.83870588 +04/13/2017,417,16868.784,40.4527194244604,1984,62.920935715 +04/14/2017,202,8161.504,40.4034851485149,1759,92.6463935333928 +04/15/2017,189,7656.959,40.5130105820106,1569,107.835443960089 +04/16/2017,129,5222.73,40.4862790697674,0,107.835443960089 +04/17/2017,0,0,39.6269481481481,2979,152.650887920178 +04/18/2017,270,10699.276,39.6269481481481,7470,92.8773100567261 +04/19/2017,1757,69526.763,39.5712936824132,7448,50.14 +04/20/2017,1939,76730.581,39.5722439401753,7923,50.14 +04/21/2017,1728,68251.792,39.4975648148148,10554,50.14 +04/22/2017,1576,62220.08,39.4797461928934,8797,50.14 +04/23/2017,1783,70410.847,39.4900992708918,7350,84.2545848633927 +04/24/2017,1840,72644.71,39.4808206521739,7146,50.14 +04/25/2017,1587,62608.972,39.4511480781348,7685,50.14 +04/26/2017,1885,74337.055,39.4361034482759,6863,50.14 +04/27/2017,1961,77319.606,39.4286619071902,5697,50.14 +04/28/2017,1955,77071.251,39.4226347826087,6842,50.14 +04/29/2017,1559,61458.227,39.4215695958948,5102,101.395443960089 +04/30/2017,1217,47975.17,39.4208463434675,4245,101.395443960089 +05/01/2017,850,33514.149,39.4284105882353,3256,101.395443960089 +05/02/2017,2120,83558.343,39.414312735849,3621,50.14 +05/03/2017,138,5438.966,39.4127971014493,3509,92.6034388300594 +05/04/2017,140,5517.856,39.4132571428571,3308,107.835443960089 +05/05/2017,202,8015.232,39.6793663366337,3106,107.835443960089 +05/06/2017,113,4478.493,39.6326814159292,3394,107.835443960089 +05/07/2017,108,4286.008,39.6852592592593,0,107.835443960089 +05/08/2017,0,0,40.55,6122,152.650887920178 +05/09/2017,180,7299,40.55,3584,107.835443960089 +05/10/2017,138,5551.717,40.2298333333333,3809,107.835443960089 +05/11/2017,180,7281.151,40.4508388888889,3442,63.02 +05/12/2017,171,6901.396,40.3590409356725,3537,63.02 +05/13/2017,175,7101.16,40.5780571428571,2840,62.782923075 +05/14/2017,114,4614.55,40.4785087719298,3760,92.6739904167261 +05/15/2017,104,4261.916,40.9799615384615,3315,85.2865275350446 +05/16/2017,160,6552.587,40.95366875,3155,62.506333335 +05/17/2017,208,8585.482,41.2763557692308,3023,63.02 +05/18/2017,241,9920.996,41.1659585062241,2777,62.95248387 +05/19/2017,240,9878.367,41.1598625,2871,63.02 +05/20/2017,122,5031.618,41.2427704918033,2745,92.8182665600594 +05/21/2017,120,4913.015,40.9417916666667,2699,107.835443960089 +05/22/2017,201,8230.237,40.9464527363184,2602,62.3517541666667 +05/23/2017,158,6454.544,40.8515443037975,2619,63.02 +05/24/2017,251,10292.388,41.0055298804781,2331,62.506333335 +05/25/2017,298,12126.31,40.6923154362416,2209,62.83870588 +05/26/2017,211,8662.579,41.0548767772512,1974,62.95459375 +05/27/2017,154,6335.861,41.1419545454545,1819,107.835443960089 +05/28/2017,126,5126.109,40.6834047619048,1663,107.835443960089 +05/29/2017,147,5898.818,40.1280136054422,2494,107.835443960089 +05/30/2017,209,8326.593,39.8401578947368,8552,62.956984695 +05/31/2017,1649,65638.164,39.8048295936931,8282,52.88313904 +06/01/2017,1616,64356.628,39.824646039604,8743,51.98 +06/02/2017,1539,61284.996,39.8213099415205,10380,51.870331125 +06/03/2017,1035,41198.222,39.805045410628,9340,102.315443960089 +06/04/2017,1081,43027.852,39.8037483811286,8828,102.315443960089 +06/05/2017,1374,54692.01,39.8049563318777,9088,51.98 +06/06/2017,1738,69165.763,39.7961812428078,8198,51.98 +06/07/2017,1642,65367.052,39.8094104750305,7313,51.98 +06/08/2017,1724,68638.413,39.8134646171694,6961,51.98 +06/09/2017,1388,55255.291,39.8092874639769,7876,51.98 +06/10/2017,1412,56199.249,39.8011678470255,6458,51.33025 +06/11/2017,832,33111.808,39.7978461538462,5626,51.98 +06/12/2017,1511,60130.965,39.7954765056254,5509,51.98 +06/13/2017,1493,59428.146,39.80451841929,4900,52.11065089 +06/14/2017,268,10663.657,39.7897649253731,4600,84.9021288550446 +06/15/2017,329,13091.226,39.7909604863222,4242,62.63475 +06/16/2017,347,13806.867,39.789242074928,3884,92.8550306667261 +06/17/2017,247,9829.483,39.7954777327935,3645,92.0931242567261 +06/18/2017,191,7600.971,39.7956596858639,3490,107.835443960089 +06/19/2017,214,8534.008,39.8785420560748,2586,92.7627959733928 +06/20/2017,248,9900.52,39.9214516129032,2336,92.6286293067261 +06/21/2017,218,8700.277,39.9095275229358,4325,92.7993868833928 +06/22/2017,303,12077.315,39.8591254125413,2781,107.835443960089 +06/23/2017,391,15746.597,40.2726265984655,2538,63.02 +06/24/2017,213,8596.265,40.3580516431925,2133,107.835443960089 +06/25/2017,182,7367.257,40.4794340659341,2332,107.835443960089 +06/26/2017,302,12386.278,41.0141655629139,2459,92.8635229233928 +06/27/2017,207,8502.284,41.0738357487923,2392,62.50532787 +06/28/2017,209,8709.028,41.669990430622,2594,107.835443960089 +06/29/2017,193,8113.113,42.0368549222798,2617,63.02 +06/30/2017,153,6434.957,42.0585424836601,2331,63.02 +07/01/2017,144,6030.386,41.8776805555556,2421,107.835443960089 +07/02/2017,85,3529.386,41.5221882352941,2237,107.835443960089 +07/03/2017,128,5291.125,41.3369140625,2256,65.151884375 +07/04/2017,168,6929.476,41.2468809523809,2258,92.7225459733928 +07/05/2017,94,3860.778,41.0721063829787,2157,107.835443960089 +07/06/2017,103,4210.848,40.8820194174757,2048,107.835443960089 +07/07/2017,185,7592.528,41.0406918918919,2135,107.835443960089 +07/08/2017,97,3945.699,40.6773092783505,2135,107.835443960089 +07/09/2017,123,4998.921,40.6416341463415,2174,107.835443960089 +07/10/2017,136,5555.909,40.8522720588235,3461,107.835443960089 +07/11/2017,183,7408.934,40.4859781420765,7176,62.595384615 +07/12/2017,1592,63833.704,40.0965477386935,7168,51.98 +07/13/2017,1306,52286.549,40.0356424196018,8654,51.98 +07/14/2017,1632,65184.244,39.9413259803922,9779,51.98 +07/15/2017,1411,56388.283,39.9633472714387,8327,51.98 +07/16/2017,848,33863.934,39.9338844339623,7478,51.98 +07/17/2017,1619,64557.749,39.875076590488,8565,51.98 +07/18/2017,1265,50470.759,39.897833201581,7459,51.98 +07/19/2017,1277,50929.072,39.8818104933438,7278,51.98 +07/20/2017,1500,59814.049,39.8760326666667,6445,51.98 +07/21/2017,1915,76286.889,39.8364955613577,7098,51.98 +07/22/2017,1545,61538.171,39.8305313915858,5551,51.98 +07/23/2017,1371,54612.731,39.834231218089,4180,51.98 +07/24/2017,1701,67741.369,39.8244379776602,3907,51.98 +07/25/2017,1432,57021.207,39.8192786312849,3650,51.98 +07/26/2017,318,12781.842,40.1944716981132,3644,92.7452959733927 +07/27/2017,371,14902.618,40.168781671159,3464,92.8342763667261 +07/28/2017,398,16015.209,40.2392185929648,3221,107.835443960089 +07/29/2017,262,10587.207,40.4091870229008,2959,107.835443960089 +07/30/2017,185,7455.474,40.2998594594595,2774,107.835443960089 +07/31/2017,239,9646.362,40.3613472803347,2663,92.8177895633928 +08/01/2017,255,10256.54,40.2217254901961,2333,92.8039404167261 +08/02/2017,278,11319.987,40.7193776978417,2399,107.835443960089 +08/03/2017,309,12726.702,41.1867378640777,1972,107.835443960089 +08/04/2017,325,13382.205,41.1760153846154,1805,92.7796910367261 +08/05/2017,245,10138.619,41.3821183673469,1533,107.835443960089 +08/06/2017,160,6600.458,41.2528625,1411,107.835443960089 +08/07/2017,227,9476.837,41.7481806167401,2655,107.835443960089 +08/08/2017,215,8953.562,41.6444744186047,2089,107.835443960089 +08/09/2017,171,7188.559,42.0383567251462,3731,107.835443960089 +08/10/2017,229,9668.716,42.2214672489083,2328,63.02 +08/11/2017,221,9357.258,42.3405339366516,2454,63.02 +08/12/2017,197,8299.478,42.1293299492386,2256,63.02 +08/13/2017,130,5495.347,42.2719,2126,63.02 +08/14/2017,211,8985.055,42.5831990521327,2259,63.02 +08/15/2017,202,8567.05,42.4111386138614,2362,62.723875 +08/16/2017,160,6810.689,42.56680625,2751,107.835443960089 +08/17/2017,195,8293.931,42.5329794871795,2590,62.6439682533333 +08/18/2017,200,8509.278,42.54639,2562,92.8411493067261 +08/19/2017,202,8623.633,42.6912524752475,2353,63.02 +08/20/2017,159,6785.075,42.673427672956,2191,63.02 +08/21/2017,175,7445.819,42.5475371428571,2005,63.02 +08/22/2017,195,8297.547,42.5515230769231,1804,63.02 +08/23/2017,216,9221.763,42.6933472222222,2486,107.835443960089 +08/24/2017,283,12071.587,42.6557844522968,2378,92.8610023233928 +08/25/2017,307,13133.25,42.7793159609121,2516,107.835443960089 +08/26/2017,197,8430.243,42.7931116751269,2313,92.8613126400594 +08/27/2017,170,7267.621,42.7507117647059,2867,92.7533868833928 +08/28/2017,202,8665.418,42.8981089108911,2473,63.02 +08/29/2017,168,7183.898,42.7612976190476,2372,63.02 +08/30/2017,248,10612.125,42.7908266129032,2266,63.02 +08/31/2017,302,12970.226,42.9477682119205,2302,63.02 +09/01/2017,308,13242.006,42.993525974026,2158,63.02 +09/02/2017,269,11570.245,43.012063197026,1883,63.02 +09/03/2017,224,9631.855,42.9993526785714,1659,62.590163935 +09/04/2017,224,9612.973,42.9150580357143,1869,85.1883282650446 +09/05/2017,222,9535.506,42.9527297297297,1934,63.02 +09/06/2017,329,14152.03,43.0152887537994,1779,63.02 +09/07/2017,341,14682.012,43.0557536656892,2025,62.4709412125 +09/08/2017,384,16529.168,43.0447083333333,1721,63.02 +09/09/2017,183,7880.352,43.0620327868853,1535,92.3591164867261 +09/10/2017,234,10068.952,43.0297094017094,1298,92.3507126400594 +09/11/2017,247,10635.629,43.0592267206478,1676,92.8282959733928 +09/12/2017,291,12535.347,43.076793814433,2698,107.835443960089 +09/13/2017,292,12581.728,43.0881095890411,1863,63.02 +09/14/2017,279,12022.346,43.0908458781362,1820,62.8843 +09/15/2017,327,14089.953,43.0885412844037,2021,63.02 +09/16/2017,290,12490.764,43.0716,1728,60.319142855 +09/17/2017,208,8960.449,43.0790817307692,1517,62.33 +09/18/2017,237,10213.395,43.0944936708861,1897,63.02 +09/19/2017,227,9784.746,43.1046079295154,1756,63.02 +09/20/2017,302,13016.996,43.1026357615894,1811,63.02 +09/21/2017,276,11898.642,43.1110217391304,1890,63.02 +09/22/2017,305,13149.681,43.1137081967213,2375,62.92524 +09/23/2017,342,14743.873,43.1107397660819,2028,62.938310345 +09/24/2017,228,9829.763,43.1129956140351,1801,62.37787755 +09/25/2017,214,9221.229,43.0898551401869,1752,63.02 +09/26/2017,315,13538.112,42.9781333333333,2415,62.95657576 +09/27/2017,294,12631.024,42.9626666666667,678,62.96487931 +09/28/2017,0,0,42.931957957958,4005,152.650887920178 +09/29/2017,333,14296.342,42.931957957958,2261,62.81024 +09/30/2017,220,9445.945,42.9361136363636,2035,63.02 +10/01/2017,147,6310.564,42.9290068027211,1885,108.985443960089 +10/02/2017,191,8190.616,42.8828062827225,3380,108.985443960089 +10/03/2017,288,12334.398,42.8277708333333,7408,65.32 +10/04/2017,2103,90062.746,42.8258421302901,6602,54.74 +10/05/2017,1431,61270.458,42.8165324947589,8231,54.74 +10/06/2017,1572,67299.886,42.8116323155216,9577,54.74 +10/07/2017,1644,70378.079,42.809050486618,7920,54.74 +10/08/2017,1046,44784.743,42.815241873805,6860,54.74 +10/09/2017,1211,51841.537,42.8088662262593,7308,103.695443960089 +10/10/2017,1299,55606.976,42.8075257890685,6783,103.695443960089 +10/11/2017,1373,58774.558,42.8073983976693,7209,103.695443960089 +10/12/2017,1374,58817.494,42.8074919941776,6369,54.74 +10/13/2017,1690,72341.95,42.8058875739645,5794,54.74 +10/14/2017,1141,48841.882,42.8062068361087,5235,54.74 +10/15/2017,1053,45078.95,42.8100189933523,3978,54.74 +10/16/2017,1145,49010.384,42.8038288209607,4649,54.74 +10/17/2017,1278,54705.786,42.805779342723,4129,54.74 +10/18/2017,157,6750.396,42.996152866242,4148,65.32 +10/19/2017,151,6497.754,43.0314834437086,3993,66.33309524 +10/20/2017,221,9489.569,42.9392262443439,4100,68.00736842 +10/21/2017,143,6163.539,43.1016713286713,3956,66.672 +10/22/2017,118,5095.904,43.1856271186441,3837,95.8486293067261 +10/23/2017,134,5793.803,43.2373358208955,3947,88.9256386475446 +10/24/2017,105,4530.957,43.1519714285714,3835,62.33 +10/25/2017,141,6088.645,43.1818794326241,3855,95.8486293067261 +10/26/2017,140,6044.189,43.1727785714286,3700,67.4475 +10/27/2017,215,9275.179,43.1403674418605,0,67.4475 +10/28/2017,0,0,43.5072777777778,6910,152.650887920178 +10/29/2017,162,7048.179,43.5072777777778,3168,67.4475 +10/30/2017,115,4986.981,43.365052173913,3050,87.9965344800446 +10/31/2017,99,4296.642,43.4004242424243,3151,95.8486293067261 +11/01/2017,147,6415.192,43.6407619047619,3024,67.4475 +11/02/2017,164,7150.652,43.6015365853659,2946,67.4475 +11/03/2017,212,9282.983,43.7876556603774,3079,67.4475 +11/04/2017,177,7761.292,43.8491073446328,2899,67.4475 +11/05/2017,137,6001.851,43.8091313868613,2762,67.4475 +11/06/2017,148,6440.734,43.518472972973,2781,67.4475 +11/07/2017,154,6704.156,43.5334805194805,2772,67.4475 +11/08/2017,148,6428.351,43.4348040540541,2605,67.4475 +11/09/2017,208,9027.142,43.3997211538462,2395,67.4475 +11/10/2017,220,9537.216,43.3509818181818,2525,66.212023275 +11/11/2017,176,7625.437,43.3263465909091,2351,67.4475 +11/12/2017,123,5316.449,43.223162601626,2228,88.1533369800446 +11/13/2017,117,5041.355,43.0885042735043,3280,88.1459719800445 +11/14/2017,170,7304.779,42.9692882352941,6695,67.4475 +11/15/2017,1352,58169.635,43.0248779585799,5710,54.74 +11/16/2017,1455,62329.123,42.8378852233677,9924,103.695443960089 +11/17/2017,838,35895.753,42.8350274463007,8832,54.74 +11/18/2017,1055,45200.645,42.8442132701422,8042,103.695443960089 +11/19/2017,924,39586.055,42.8420508658009,7012,103.695443960089 +11/20/2017,1031,44163.348,42.8354490785645,7255,54.74 +11/21/2017,1102,47202.729,42.8336923774955,7236,54.74 +11/22/2017,985,42192.024,42.8345421319797,6402,54.74 +11/23/2017,900,38543.506,42.8261177777778,6029,54.74 +11/24/2017,1281,54852.794,42.8202919594067,5803,54.74 +11/25/2017,1032,44193.38,42.8230426356589,4747,54.74 +11/26/2017,814,34859.275,42.8246621621622,3943,54.74 +11/27/2017,657,28136.798,42.8261765601218,3878,103.695443960089 +11/28/2017,783,33527.082,42.8187509578544,3684,103.695443960089 +11/29/2017,212,9093.485,42.8937971698113,3568,83.3611775840356 +11/30/2017,246,10553.893,42.9020040650406,3538,78.0323283100255 +12/01/2017,240,10305.775,42.9407291666667,3122,81.4544813200297 diff --git a/data/input/test_time_series.xlsx b/data/input/test_time_series.xlsx new file mode 100644 index 0000000..7981ed7 Binary files /dev/null and b/data/input/test_time_series.xlsx differ diff --git a/data/input/test_ts_passengers.csv b/data/input/test_ts_passengers.csv new file mode 100644 index 0000000..0ba6bd0 --- /dev/null +++ b/data/input/test_ts_passengers.csv @@ -0,0 +1,145 @@ +Month,Passengers +1949-01,112 +1949-02,118 +1949-03,132 +1949-04,129 +1949-05,121 +1949-06,135 +1949-07,148 +1949-08,148 +1949-09,136 +1949-10,119 +1949-11,104 +1949-12,118 +1950-01,115 +1950-02,126 +1950-03,141 +1950-04,135 +1950-05,125 +1950-06,149 +1950-07,170 +1950-08,170 +1950-09,158 +1950-10,133 +1950-11,114 +1950-12,140 +1951-01,145 +1951-02,150 +1951-03,178 +1951-04,163 +1951-05,172 +1951-06,178 +1951-07,199 +1951-08,199 +1951-09,184 +1951-10,162 +1951-11,146 +1951-12,166 +1952-01,171 +1952-02,180 +1952-03,193 +1952-04,181 +1952-05,183 +1952-06,218 +1952-07,230 +1952-08,242 +1952-09,209 +1952-10,191 +1952-11,172 +1952-12,194 +1953-01,196 +1953-02,196 +1953-03,236 +1953-04,235 +1953-05,229 +1953-06,243 +1953-07,264 +1953-08,272 +1953-09,237 +1953-10,211 +1953-11,180 +1953-12,201 +1954-01,204 +1954-02,188 +1954-03,235 +1954-04,227 +1954-05,234 +1954-06,264 +1954-07,302 +1954-08,293 +1954-09,259 +1954-10,229 +1954-11,203 +1954-12,229 +1955-01,242 +1955-02,233 +1955-03,267 +1955-04,269 +1955-05,270 +1955-06,315 +1955-07,364 +1955-08,347 +1955-09,312 +1955-10,274 +1955-11,237 +1955-12,278 +1956-01,284 +1956-02,277 +1956-03,317 +1956-04,313 +1956-05,318 +1956-06,374 +1956-07,413 +1956-08,405 +1956-09,355 +1956-10,306 +1956-11,271 +1956-12,306 +1957-01,315 +1957-02,301 +1957-03,356 +1957-04,348 +1957-05,355 +1957-06,422 +1957-07,465 +1957-08,467 +1957-09,404 +1957-10,347 +1957-11,305 +1957-12,336 +1958-01,340 +1958-02,318 +1958-03,362 +1958-04,348 +1958-05,363 +1958-06,435 +1958-07,491 +1958-08,505 +1958-09,404 +1958-10,359 +1958-11,310 +1958-12,337 +1959-01,360 +1959-02,342 +1959-03,406 +1959-04,396 +1959-05,420 +1959-06,472 +1959-07,548 +1959-08,559 +1959-09,463 +1959-10,407 +1959-11,362 +1959-12,405 +1960-01,417 +1960-02,391 +1960-03,419 +1960-04,461 +1960-05,472 +1960-06,535 +1960-07,622 +1960-08,606 +1960-09,508 +1960-10,461 +1960-11,390 +1960-12,432 diff --git a/logs/cov.out b/logs/cov.out index cbee523..99bc335 100644 --- a/logs/cov.out +++ b/logs/cov.out @@ -1,11 +1,12 @@ -Name Stmts Miss Cover Missing ------------------------------------------------------------------------------------------ -/media/ph33r/Data/Project/mllib/Git/mllib/__init__.py 7 0 100% -/media/ph33r/Data/Project/mllib/Git/mllib/lib/__init__.py 7 0 100% -/media/ph33r/Data/Project/mllib/Git/mllib/lib/cluster.py 103 0 100% -/media/ph33r/Data/Project/mllib/Git/mllib/lib/knn.py 70 0 100% -/media/ph33r/Data/Project/mllib/Git/mllib/lib/model.py 44 0 100% -/media/ph33r/Data/Project/mllib/Git/mllib/lib/opt.py 157 0 100% -/media/ph33r/Data/Project/mllib/Git/mllib/lib/tree.py 79 0 100% ------------------------------------------------------------------------------------------ -TOTAL 467 0 100% +Name Stmts Miss Cover Missing +------------------------------------------------------- +mllib/__init__.py 7 0 100% +mllib/lib/__init__.py 7 0 100% +mllib/lib/cluster.py 103 0 100% +mllib/lib/knn.py 70 0 100% +mllib/lib/model.py 44 0 100% +mllib/lib/opt.py 157 0 100% +mllib/lib/timeseries.py 85 0 100% +mllib/lib/tree.py 79 0 100% +------------------------------------------------------- +TOTAL 552 0 100% diff --git a/logs/pip.out b/logs/pip.out index f61bf91..03fb79a 100644 --- a/logs/pip.out +++ b/logs/pip.out @@ -1 +1 @@ -INFO: Successfully saved requirements file in /media/ph33r/Data/Project/mllib/Git/requirements.txt +./bin/run_tests.sh: line 78: pipreqs: command not found diff --git a/logs/pylint/lib-timeseries-py.out b/logs/pylint/lib-timeseries-py.out new file mode 100644 index 0000000..e6474ef --- /dev/null +++ b/logs/pylint/lib-timeseries-py.out @@ -0,0 +1,9 @@ +************* Module mllib.lib.timeseries +timeseries.py:188:41: I1101: Module 'metrics' has no 'rsq' member, but source is unavailable. Consider adding this module to extension-pkg-whitelist if you want to perform analysis based on run-time introspection of living objects. (c-extension-no-member) +timeseries.py:189:41: I1101: Module 'metrics' has no 'mae' member, but source is unavailable. Consider adding this module to extension-pkg-whitelist if you want to perform analysis based on run-time introspection of living objects. (c-extension-no-member) +timeseries.py:190:42: I1101: Module 'metrics' has no 'mape' member, but source is unavailable. Consider adding this module to extension-pkg-whitelist if you want to perform analysis based on run-time introspection of living objects. (c-extension-no-member) +timeseries.py:191:42: I1101: Module 'metrics' has no 'rmse' member, but source is unavailable. Consider adding this module to extension-pkg-whitelist if you want to perform analysis based on run-time introspection of living objects. (c-extension-no-member) + +-------------------------------------------------------------------- +Your code has been rated at 10.00/10 (previous run: 10.00/10, +0.00) + diff --git a/logs/pylint/tests-test_timeseries-py.out b/logs/pylint/tests-test_timeseries-py.out new file mode 100644 index 0000000..d7495ee --- /dev/null +++ b/logs/pylint/tests-test_timeseries-py.out @@ -0,0 +1,4 @@ + +-------------------------------------------------------------------- +Your code has been rated at 10.00/10 (previous run: 10.00/10, +0.00) + diff --git a/mllib/__main__.py b/mllib/__main__.py index 6286c01..22414a6 100644 --- a/mllib/__main__.py +++ b/mllib/__main__.py @@ -33,6 +33,7 @@ from lib.tree import XGBoost # noqa: F841 from lib.opt import TSP # noqa: F841 from lib.opt import Transport # noqa: F841 +from lib.timeseries import TimeSeries # noqa: F841 # ============================================================================= # --- DO NOT CHANGE ANYTHING FROM HERE @@ -79,6 +80,7 @@ print("Clustering\n", "optimal k = " + str(clus_sol.optimal_k), elapsed_time("Time", start_t), + sep, sep="\n") # --- GLMNet start_t = time.time_ns() @@ -90,6 +92,7 @@ for k, v in glm_mod.model_summary.items(): print(k, str(v).rjust(69 - len(k))) print(elapsed_time("Time", start_t), + sep, sep="\n") # --- KNN start_t = time.time_ns() @@ -99,6 +102,7 @@ for k, v in mod.model_summary.items(): print(k, str(v).rjust(69 - len(k))) print(elapsed_time("Time", start_t), + sep, sep="\n") # --- Random forest start_t = time.time_ns() @@ -110,6 +114,7 @@ for k, v in mod.model_summary.items(): print(k, str(v).rjust(69 - len(k))) print(elapsed_time("Time", start_t), + sep, sep="\n") # --- XGBoost start_t = time.time_ns() @@ -121,6 +126,7 @@ for k, v in mod.model_summary.items(): print(k, str(v).rjust(69 - len(k))) print(elapsed_time("Time", start_t), + sep, sep="\n") # --- Travelling salesman start_t = time.time_ns() @@ -131,9 +137,10 @@ lat=df_ip["lat"].tolist(), lon=df_ip["lng"].tolist(), debug=False) - print("\nTSP\n") + print("\nTravelling salesman problem\n") print("Optimal value:", round(opt[1], 3)) print(elapsed_time("Time", start_t), + sep, sep="\n") # --- Transportation start_t = time.time_ns() @@ -144,8 +151,23 @@ c_lon = [-102.1, -103.0, -100.3, -106.8, -103.9, -101.6, -105.2] prob = Transport(c_loc, c_demand, c_supply, c_lat, c_lon, 1) opt_out = prob.solve(0) - print("\nTransportation\n") + print("\nTransportation problem\n") print(elapsed_time("Time", start_t), + sep, + sep="\n") + # --- Time series + start_t = time.time_ns() + df_ip = pd.read_excel(path + "input/test_time_series.xlsx", + sheet_name="product_01") + mod = TimeSeries(df=df_ip, + y_var="y", + x_var=["cost", "stock_level", "retail_price"], + ds="ds") + op = mod.model_summary + print("\nTime series\n") + print("R-squared:", op["rsq"]) + print(elapsed_time("Time", start_t), + sep, sep="\n") # --- EOF print(sep, elapsed_time("Total time", start), sep, sep="\n") diff --git a/mllib/lib/timeseries.py b/mllib/lib/timeseries.py new file mode 100644 index 0000000..fd1dffe --- /dev/null +++ b/mllib/lib/timeseries.py @@ -0,0 +1,320 @@ +""" +Time series module. + +**Available routines:** + +- class ``TimeSeries``: Builds time series model using fbprophet. + +Credits +------- +:: + + Authors: + - Diptesh + - Madhu + + Date: Oct 03, 2021 +""" + +# pylint: disable=invalid-name +# pylint: disable=R0902,R0903,R0913,C0413,R0205 + +from typing import List, Dict, Any + +import re +import sys +import os + +from inspect import getsourcefile +from os.path import abspath + +import pandas as pd +import numpy as np + +import pystan +from fbprophet import Prophet + +path = abspath(getsourcefile(lambda: 0)) +path = re.sub(r"(.+\/)(.+.py)", "\\1", path) +sys.path.insert(0, path) + +import metrics # noqa: F841 + +__all__ = ["pystan", ] + +os.environ['NUMEXPR_MAX_THREADS'] = '8' + + +class suppress_stdout_stderr(object): + """ + Suppress fbprophet stdout. + + A context manager for doing a "deep suppression" of stdout and stderr in + Python, i.e. will suppress all print, even if the print originates in a + compiled C/Fortran sub-function. + This will not suppress raised exceptions, since exceptions are printed + to stderr just before a script exits, and after the context manager has + exited (at least, I think that is why it lets exceptions through). + + """ + + def __init__(self): + """Initialize variables.""" + # Open a pair of null files + self.null_fds = [os.open(os.devnull, os.O_RDWR) for x in range(2)] + # Save the actual stdout (1) and stderr (2) file descriptors. + self.save_fds = (os.dup(1), os.dup(2)) + + def __enter__(self): + """Enter statements.""" + # Assign the null pointers to stdout and stderr. + os.dup2(self.null_fds[0], 1) + os.dup2(self.null_fds[1], 2) + + def __exit__(self, *_): + """Exit statements.""" + # Re-assign the real stdout/stderr back to (1) and (2) + os.dup2(self.save_fds[0], 1) + os.dup2(self.save_fds[1], 2) + # Close the null files + os.close(self.null_fds[0]) + os.close(self.null_fds[1]) + + +class TimeSeries(): + """Time series module. + + Parameters + ---------- + df: pandas.DataFrame + + Pandas dataframe containing the `y_var`, `ds` and `x_var` + + y_var: str + + Dependant variable + + x_var: List[str], optional + + Independant variables (the default is None). + + ds: str, optional + + Column name of the date variable (the default is None). + + k_fold: int, optional, Not implemented yet + + Number of cross validations folds (the default is None). + + uid: str, optional, Not implemented yet + + Description of parameter `uid` (the default is None). + + param: dict, optional, Not implemented yet + + Time series parameters (the default is None). + + Returns + ------- + model: object + + Final optimal model. + + model_summary: Dict + + Model summary containing key metrics like R-squared, RMSE, MSE, MAE, + MAPE. + + Methods + ------- + predict + + Example + ------- + >> > mod = TimeSeries(df=df_ip, + y_var="y", + x_var=["cost", "stock_level", "retail_price"], + ds="ds") + >> > df_op = mod.predict(x_predict) + + """ + + def __init__(self, + df: pd.DataFrame, + y_var: str, + x_var: List[str] = None, + ds: str = "ds", + k_fold: int = None, + uid: str = None, + param: Dict = None): + """Initialize variables.""" + self.y_var = y_var + self.x_var = x_var + self.ds = ds + self.df = df.reset_index(drop=True) + if uid is not None: + raise NotImplementedError("uid is not supported yet") + if k_fold is not None: + raise NotImplementedError("k_fold is not supported yet") + if param is None: + param = {"interval_width": 0.95} + self.model = None + self.model_summary = None + self.param = param + self._pre_processing() + self._fit() + self._compute_metrics() + if x_var is not None: + self.betas = self._regressor_coefficients(self.model) + + def _pre_processing(self): + self.df[self.ds] = pd.to_datetime(self.df[self.ds]) + if self.x_var is None: + self.df = self.df[[self.ds] + [self.y_var]] + else: + self.df = self.df[[self.ds] + [self.y_var] + self.x_var] + coln = list(self.df.columns) + self.df.columns = ["ds", "y"] + coln[2:] + self.y_var = "y" + self.ds = "ds" + + def _compute_metrics(self): + """Compute commonly used metrics to evaluate the model.""" + y = self.df.loc[:, self.y_var].values.tolist() + if self.x_var is None: + y_hat = list(self.model.predict(self.df[[self.ds]])["yhat"]) + else: + y_hat = list(self.model.predict(self.df[[self.ds] + + self.x_var])["yhat"]) + model_summary = {"rsq": np.round(metrics.rsq(y, y_hat), 3), + "mae": np.round(metrics.mae(y, y_hat), 3), + "mape": np.round(metrics.mape(y, y_hat), 3), + "rmse": np.round(metrics.rmse(y, y_hat), 3)} + model_summary["mse"] = np.round(model_summary["rmse"] ** 2, 3) + self.model_summary = model_summary + + @staticmethod + def _regressor_index(m, name): + """ + Given the name of a regressor, return its index in the `beta` matrix. + + Parameters + ---------- + m: object + + Prophet model object, after fitting. + + name: str + + Name of the regressor, as passed into the `add_regressor` function. + + Returns + ------- + int + + The column index of the regressor in the `beta` matrix. + + """ + op = np.extract(m.train_component_cols[name] == 1, + m.train_component_cols.index)[0] + return op + + def _regressor_coefficients(self, m): # pragma: no cover + """ + Summarise the coefficients of the extra regressors used in the model. + + For additive regressors, the coefficient represents the incremental + impact on `y` of a unit increase in the regressor. For multiplicative + regressors, the incremental impact is equal to `trend(t)` multiplied + by the coefficient. + + Coefficients are measured on the original scale of the training data. + + Parameters + ---------- + m: object + + Prophet model object, after fitting. + + Returns + ------- + pd.DataFrame + + containing: : + + regressor: Name of the regressor + regressor_mode: Additive/multiplicative effect on y + center: The mean of the regressor if standardized else 0 + coef_lower: Lower bound for the coefficient + coef: Expected value of the coefficient + coef_upper: Upper bound for the coefficient + + coef_lower/upper are estimated from MCMC samples. + It is only different to coef if mcmc_samples > 0. + + """ + assert len(m.extra_regressors) > 0, 'No extra regressors found.' + coefs = [] + for regressor, params in m.extra_regressors.items(): + beta = m.params['beta'][:, self._regressor_index(m, regressor)] + if params['mode'] == 'additive': + coef = beta * m.y_scale / params['std'] + else: + coef = beta / params['std'] + percentiles = [ + (1 - m.interval_width) / 2, + 1 - (1 - m.interval_width) / 2, + ] + coef_bounds = np.quantile(coef, q=percentiles) + record = { + 'regressor': regressor, + 'regressor_mode': params['mode'], + 'center': params['mu'], + 'coef_lower': coef_bounds[0], + 'coef': np.mean(coef), + 'coef_upper': coef_bounds[1], + } + coefs.append(record) + return pd.DataFrame(coefs) + + def _fit(self) -> Dict[str, Any]: + """Fit model.""" + with suppress_stdout_stderr(): + model = Prophet(interval_width=self.param["interval_width"]) + if self.x_var is not None: + for var in self.x_var: + model.add_regressor(var) + with suppress_stdout_stderr(): + model.fit(self.df) + self.model = model + + def predict(self, + x_predict: pd.DataFrame = None, + n_interval: int = 1) -> pd.DataFrame: + """Predict module. + + Parameters + ---------- + x_predict : pd.DataFrame, optional + + Pandas dataframe containing `ds` and `x_var` (the default is None). + + n_interval : int, optional + + Number of time period to predict (the default is 1). + + Returns + ------- + pd.DataFrame + + Pandas dataframe containing `y_var`, `ds` and `x_var`. + + """ + if self.x_var is None: + x_predict = self.model.make_future_dataframe(periods=n_interval) + x_predict = x_predict.iloc[-n_interval:, :] + df_op = x_predict.copy(deep=True) + forecast = self.model.predict(x_predict) + y_hat = forecast['yhat'].values.tolist() + df_op.insert(loc=0, column=self.y_var, value=y_hat) + return df_op diff --git a/requirements.txt b/requirements.txt index c5583d6..e7948ab 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,8 @@ -xgboost==1.3.3 -PuLP==1.6.8 -pandas==1.1.3 -numpy==1.19.5 Cython==0.29.15 +PuLP==1.6.8 +fbprophet==0.6 +numpy==1.18.1 +pandas==1.0.1 +xgboost==1.3.3 +pystan==2.17.1.0 scikit_learn==1.0 diff --git a/tests/test_timeseries.py b/tests/test_timeseries.py new file mode 100644 index 0000000..58a0bd9 --- /dev/null +++ b/tests/test_timeseries.py @@ -0,0 +1,104 @@ +""" +Test suite module for ``timeseries``. + +Credits +------- +:: + + Authors: + - Diptesh + + Date: Sep 07, 2021 +""" + +# pylint: disable=invalid-name +# pylint: disable=wrong-import-position + +import unittest +import warnings +import re +import sys + +from inspect import getsourcefile +from os.path import abspath + +import pandas as pd + +# Set base path +path = abspath(getsourcefile(lambda: 0)) +path = re.sub(r"(.+)(\/tests.*)", "\\1", path) + +sys.path.insert(0, path) + +from mllib.lib.timeseries import TimeSeries # noqa: F841 + +# ============================================================================= +# --- DO NOT CHANGE ANYTHING FROM HERE +# ============================================================================= + +path = path + "/data/input/" + +# ============================================================================= +# --- User defined functions +# ============================================================================= + + +def ignore_warnings(test_func): + """Suppress warnings.""" + + def do_test(self, *args, **kwargs): + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + test_func(self, *args, **kwargs) + return do_test + + +class TestTimeSeries(unittest.TestCase): + """Test suite for module ``TimeSeries``.""" + + def setUp(self): + """Set up for module ``TimeSeries``.""" + + @ignore_warnings + def test_multivariate(self): + """TimeSeries: Test for multivariate.""" + df_ip = pd.read_csv(path + "test_time_series.csv") + mod = TimeSeries(df=df_ip, + y_var="y", + x_var=["cost", "stock_level", "retail_price"], + ds="ds") + op = mod.model_summary + self.assertAlmostEqual(0.99, op["rsq"], places=1) + + @ignore_warnings + def test_raise_exceptions(self): + """TimeSeries: Test raise exceptions.""" + df_ip = pd.read_csv(path + "test_time_series.csv") + self.assertRaises(NotImplementedError, TimeSeries, + df=df_ip, + y_var="y", + x_var=["stock_level", "retail_price"], + ds="ds", + uid="cost") + self.assertRaises(NotImplementedError, TimeSeries, + df=df_ip, + y_var="y", + x_var=["stock_level", "retail_price"], + ds="ds", + k_fold=5) + + @ignore_warnings + def test_univariate(self): + """TimeSeries: Test for univariate.""" + df_ip = pd.read_csv(path + "test_ts_passengers.csv") + mod = TimeSeries(df=df_ip, y_var="Passengers", ds="Month") + op = mod.predict() + self.assertAlmostEqual(op["y"].values[0], 446.911, places=1) + + +# ============================================================================= +# --- Main +# ============================================================================= + +if __name__ == '__main__': + unittest.main()