From bf4ca9b076e9d62266d0b15d5a6ce94926b3c16e Mon Sep 17 00:00:00 2001 From: Conor McCarthy Date: Wed, 3 Jun 2020 15:34:31 +0100 Subject: [PATCH 01/77] Handling of null datetimes in pandas dataframes, pandas > 1.0 now default for support --- requirements.txt | 2 +- util/util.q | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 6ff323df..a09f063a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,4 @@ scipy scikit-learn statsmodels matplotlib -pandas>=0.21 +pandas>=1.0 diff --git a/util/util.q b/util/util.q index 30f50106..e187ba83 100644 --- a/util/util.q +++ b/util/util.q @@ -41,7 +41,14 @@ float32_convert:{$[(y~0b)|x~()!();x;?[0.000001>x;"F"$string x;0.000001*floor 0.5 / Convert time zone data (0b -> UTC time; 1b -> local time) tz_convert:{$[y~0b;dt_convert;{"P"$neg[6]_/:'x[`:astype;`str][`:to_dict;<;`list]}]x} / Convert datetime/datetimetz to timestamp -dt_convert:{"p"$dt_dict[x]+1970.01.01D0} +dt_convert:{ + `e+1; + $[count nulCols:where any each x[`:isnull;::][`:to_dict;<;`list]; + [c:`$x[`:columns.to_numpy][]`; + //string the columns with NaT and cast to timestamp. Usual conversion for the others + ("P"$x[`:drop;c except nulCols;`axis pykw 1][`:astype;`str][`:to_dict;<;`list]),dt_dict[x[`:drop;nulCols;`axis pykw 1]]+1970.01.01D0]; + //No null datetime columns found so convert to int64 and do the conversion + "p"$dt_dict[x]+1970.01.01D0]} / Convert data to integer representation and return as a dict dt_dict:{x[`:astype;`int64][`:to_dict;<;`list]} / Convert datetime.date/time types to kdb+ date/time From 94e236aec4da6ee347db61eeefd3de034230c3fe Mon Sep 17 00:00:00 2001 From: Conor McCarthy Date: Wed, 3 Jun 2020 15:36:39 +0100 Subject: [PATCH 02/77] removal of error trap --- util/util.q | 1 - 1 file changed, 1 deletion(-) diff --git a/util/util.q b/util/util.q index e187ba83..051c1cb6 100644 --- a/util/util.q +++ b/util/util.q @@ -42,7 +42,6 @@ float32_convert:{$[(y~0b)|x~()!();x;?[0.000001>x;"F"$string x;0.000001*floor 0.5 tz_convert:{$[y~0b;dt_convert;{"P"$neg[6]_/:'x[`:astype;`str][`:to_dict;<;`list]}]x} / Convert datetime/datetimetz to timestamp dt_convert:{ - `e+1; $[count nulCols:where any each x[`:isnull;::][`:to_dict;<;`list]; [c:`$x[`:columns.to_numpy][]`; //string the columns with NaT and cast to timestamp. Usual conversion for the others From fd85f562c7e2996bfe82f7a0a5ac333f40a1fd36 Mon Sep 17 00:00:00 2001 From: Conor McCarthy Date: Wed, 3 Jun 2020 15:53:40 +0100 Subject: [PATCH 03/77] Minor change to structure of dt_convert --- util/util.q | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/util/util.q b/util/util.q index 051c1cb6..d270d39a 100644 --- a/util/util.q +++ b/util/util.q @@ -44,9 +44,9 @@ tz_convert:{$[y~0b;dt_convert;{"P"$neg[6]_/:'x[`:astype;`str][`:to_dict;<;`list] dt_convert:{ $[count nulCols:where any each x[`:isnull;::][`:to_dict;<;`list]; [c:`$x[`:columns.to_numpy][]`; - //string the columns with NaT and cast to timestamp. Usual conversion for the others - ("P"$x[`:drop;c except nulCols;`axis pykw 1][`:astype;`str][`:to_dict;<;`list]),dt_dict[x[`:drop;nulCols;`axis pykw 1]]+1970.01.01D0]; - //No null datetime columns found so convert to int64 and do the conversion + null_data:"P"$x[`:drop;c except nulCols;`axis pykw 1][`:astype;`str][`:to_dict;<;`list]; + non_null_data:dt_dict x[`:drop;nulCols;`axis pykw 1]; + null_data,non_null_data+1970.01.01D0]; "p"$dt_dict[x]+1970.01.01D0]} / Convert data to integer representation and return as a dict dt_dict:{x[`:astype;`int64][`:to_dict;<;`list]} From 9c5e91c7bfbad0b3b6d6c83115741000834eabdf Mon Sep 17 00:00:00 2001 From: Conor McCarthy Date: Thu, 4 Jun 2020 11:27:39 +0100 Subject: [PATCH 04/77] unneccesary conversion to timestamp --- util/util.q | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/util.q b/util/util.q index d270d39a..5f81cf57 100644 --- a/util/util.q +++ b/util/util.q @@ -47,7 +47,7 @@ dt_convert:{ null_data:"P"$x[`:drop;c except nulCols;`axis pykw 1][`:astype;`str][`:to_dict;<;`list]; non_null_data:dt_dict x[`:drop;nulCols;`axis pykw 1]; null_data,non_null_data+1970.01.01D0]; - "p"$dt_dict[x]+1970.01.01D0]} + dt_dict[x]+1970.01.01D0]} / Convert data to integer representation and return as a dict dt_dict:{x[`:astype;`int64][`:to_dict;<;`list]} / Convert datetime.date/time types to kdb+ date/time From edb90074582bf7d4b8aa72c6463bede73b786729 Mon Sep 17 00:00:00 2001 From: Dianeod Date: Thu, 18 Jun 2020 16:30:05 +0100 Subject: [PATCH 05/77] wording update --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 79657a3a..334dbc22 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,7 +22,7 @@ If you are looking to lend your development skills to the project, you can check ### Making Changes -Please fork the projects master branch to your own account. Using your fork, you are free to create changes or create branches in isolation. +Please fork the project's main branch to your own account. Using your fork, you are free to create changes or create branches in isolation. The following provides a good general guide ['Beginners Guide To Contributing'](https://akrabat.com/the-beginners-guide-to-contributing-to-a-github-project/) @@ -36,7 +36,7 @@ When committing changes, please provide a descriptive commit comment of why the You can link to a relevant issue in a commit comment by referencing the issue number prefixed with a '#'. -After pushing to your fork, submit a pull request against the main projects master branch. +After pushing to your fork, submit a pull request against the project's main branch. In order to have your pull request approved in a timely manner, please provide comments on the pull request that details what was changed & for which reasons. The complexity or size of the change indicate the size of the descriptions required, in order for the reviewer to get up to speed as quick as possible. From 4ab06ec861245504fc80e60fb6512a7e51179fbf Mon Sep 17 00:00:00 2001 From: Conor McCarthy Date: Tue, 7 Jul 2020 16:38:09 +0100 Subject: [PATCH 06/77] addition of missing xval folder from releases --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2cd62e6b..c9a4fbfd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,7 +36,7 @@ beforescript: script: - (cd clust && make && make install && make clean) - echo "Preparing version $TRAVIS_BRANCH-$TRAVIS_COMMIT" -- tar czf ml_$TRAVIS_OS_NAME-$TRAVIS_BRANCH.tgz *.q fresh/ util/ clust/ requirements.txt LICENSE README.md +- tar czf ml_$TRAVIS_OS_NAME-$TRAVIS_BRANCH.tgz *.q fresh/ xval/ util/ clust/ requirements.txt LICENSE README.md - echo "Packaged as ml_$TRAVIS_OS_NAME-$TRAVIS_BRANCH.zip" - if [[ "x$QLIC_KC" != "x" ]]; then curl -fsSL -o test.q https://github.com/KxSystems/embedpy/raw/master/test.q; From 92f08ffc3bd8ba64cd3472632db4007b4058a7ca Mon Sep 17 00:00:00 2001 From: Dianeod Date: Tue, 7 Jul 2020 17:56:22 +0100 Subject: [PATCH 07/77] updated ksdistrib to correspond with scipy --- fresh/select.q | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fresh/select.q b/fresh/select.q index 7eb690ce..c9054f09 100644 --- a/fresh/select.q +++ b/fresh/select.q @@ -1,7 +1,7 @@ \d .ml / py utils -fresh.i.ksdistrib: .p.import[`scipy.stats]`:kstwobign.sf +fresh.i.ksdistrib: .p.import[`scipy.stats]`:kstwo.sf fresh.i.kendalltau: .p.import[`scipy.stats]`:kendalltau fresh.i.fisherexact:.p.import[`scipy.stats]`:fisher_exact @@ -9,10 +9,10 @@ fresh.i.fisherexact:.p.import[`scipy.stats]`:fisher_exact fresh.i.ktau:{fresh.i.kendalltau[<;x;y]1} fresh.i.fisher:{fresh.i.fisherexact[<;count@''@\:[group@'x value group y]distinct x]1} -/ Function change due to scipy update https://github.com/scipy/scipy/blob/v1.3.2/scipy/stats/stats.py#L5385-L5573 +/ Function change due to scipy update https://github.com/scipy/scipy/commit/aa319bcfeb38b90f3c4b46c9477f02618583570d fresh.i.ks:{ k:max abs(-). value(1+d bin\:raze d)%n:count each d:asc each y group x; - fresh.i.ksdistrib[k*en:sqrt prd[n]%sum n]`} + fresh.i.ksdistrib[k;ceiling en:prd[n]%sum n]`} fresh.i.ksyx:{fresh.i.ks[y;x]} / feature significance From 8cd2bfea9f7ee11524ca1b113c0185a225fde43d Mon Sep 17 00:00:00 2001 From: Dianeod Date: Wed, 8 Jul 2020 12:55:17 +0100 Subject: [PATCH 08/77] sanity check for scipy version within select.q added --- fresh/select.q | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fresh/select.q b/fresh/select.q index c9054f09..550db5b3 100644 --- a/fresh/select.q +++ b/fresh/select.q @@ -1,7 +1,8 @@ \d .ml / py utils -fresh.i.ksdistrib: .p.import[`scipy.stats]`:kstwo.sf +sci_ver:1.5<="F"$3#.p.import[`scipy][`:__version__]` +fresh.i.ksdistrib: .p.import[`scipy.stats][$[sci_ver;`:kstwo.sf;`:kstwobign.sf];<] fresh.i.kendalltau: .p.import[`scipy.stats]`:kendalltau fresh.i.fisherexact:.p.import[`scipy.stats]`:fisher_exact @@ -12,7 +13,8 @@ fresh.i.fisher:{fresh.i.fisherexact[<;count@''@\:[group@'x value group y]distinc / Function change due to scipy update https://github.com/scipy/scipy/commit/aa319bcfeb38b90f3c4b46c9477f02618583570d fresh.i.ks:{ k:max abs(-). value(1+d bin\:raze d)%n:count each d:asc each y group x; - fresh.i.ksdistrib[k;ceiling en:prd[n]%sum n]`} + en:prd[n]%sum n; + fresh.i.ksdistrib .$[sci_ver;(k;ceiling en);enlist k*sqrt en]} fresh.i.ksyx:{fresh.i.ks[y;x]} / feature significance From 9b96d4f3807974445355b5882881c17b7f91ef6c Mon Sep 17 00:00:00 2001 From: Deanna Morgan Date: Wed, 22 Jul 2020 13:16:38 +0100 Subject: [PATCH 09/77] new grid/random/sobol searching methods --- xval/xval.q | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/xval/xval.q b/xval/xval.q index 4b665792..8d987a4c 100644 --- a/xval/xval.q +++ b/xval/xval.q @@ -16,15 +16,36 @@ xv.j.tsrolls:xv.i.idxR[xv.i.splitidx]{[k]enlist@''0 1+/:til k-1} xv.j.tschain:xv.i.idxR[xv.i.splitidx]{[k]flip(til each j;enlist@'j:1+til k-1)} xv.j.pcsplit:{[p;n;x;y]n#{[p;x;y;z](x;y)@\:/:(0,floor n*1-p)_til n:count y}[p;x;y]} xv.j.mcsplit:{[p;n;x;y]n#{[p;x;y;z](x;y)@\:/:(0,floor count[y]*1-p)_{neg[n]?n:count x}y}[p;x;y]} - xv,:xv.j,:1_{[idx;k;n;x;y;f]{[f;d]f d[]}[f]peach raze idx[k;n;x;y]}@'xv.j -gs:1_{[gs;k;n;x;y;f;p;t] - if[t=0;:gs[k;n;x;y;f;p]];i:(0,floor count[y]*1-abs t)_$[t<0;xv.i.shuffle;til count@]y; - (r;pr;f[pykwargs pr:first key desc avg each r:gs[k;n;x i 0;y i 0;f;p]](x;y)@\:/:i) - }@'{[xv;k;n;x;y;f;p]p!(xv[k;n;x;y]f pykwargs@)@'p:key[p]!/:1_'(::)cross/value p}@'xv.j + +xv.i.search:{[sf;k;n;x;y;f;p;t] + if[t=0;:sf[k;n;x;y;f;p]];i:(0,floor count[y]*1-abs t)_$[t<0;xv.i.shuffle;til count@]y; + (r;pr;f[pykwargs pr:first key desc avg each r:sf[k;n;x i 0;y i 0;f;p]](x;y)@\:/:i)} +xv.i.xvpf:{[pf;xv;k;n;x;y;f;p]p!(xv[k;n;x;y]f pykwargs@)@'p:pf p} +gs:1_xv.i.search@'xv.i.xvpf[{[p]key[p]!/:1_'(::)cross/value p}]@'xv.j +rs:1_xv.i.search@'xv.i.xvpf[{[p]hp.hpgen p}]@'xv.j xv.fitscore:{[f;p;d].[.[f[][p]`:fit;d 0]`:score;d 1]`} +hp.hpgen:{ + if[(::)~n:x`n;n:16]; + num:where any`uniform`loguniform=\:first each p:x`p; + system"S ",string$[(::)~x`random_state;42;x`random_state]; + pysobol:.p.import[`sobol_seq;`:i4_sobol_generate;<]; + genpts:$[`sobol~typ:x`typ;enlist each flip pysobol[count num;n];`random~typ;n;'"hyperparam type not supported"]; + flip hp.i.hpgen[typ;n]each p,:num!p[num],'genpts} +hp.i.hpgen:{[ns;n;p] + p:@[;0;first](0;1)_p,(); + $[(typ:p 0)~`boolean;n?0b; + typ in`rand`symbol;n?(),p[1]0; + typ~`uniform;hp.i.uniform[ns]. p 1; + typ~`loguniform;hp.i.loguniform[ns]. p 1; + '"please enter a valid type"]} +hp.i.uniform:{[ns;lo;hi;typ;p]if[hi Date: Wed, 22 Jul 2020 13:18:11 +0100 Subject: [PATCH 10/77] sobol requirements --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index a09f063a..e773cb85 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,4 @@ scikit-learn statsmodels matplotlib pandas>=1.0 +sobol_seq \ No newline at end of file From 494853269e8a6c90bc99e9dc531b93707a24e866 Mon Sep 17 00:00:00 2001 From: Deanna Morgan Date: Wed, 22 Jul 2020 14:02:08 +0100 Subject: [PATCH 11/77] requirement updates for sobol --- requirements.txt | 2 +- xval/README.md | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index e773cb85..6b1ce8c9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,4 @@ scikit-learn statsmodels matplotlib pandas>=1.0 -sobol_seq \ No newline at end of file +sobol-seq \ No newline at end of file diff --git a/xval/README.md b/xval/README.md index 509de824..16abd744 100644 --- a/xval/README.md +++ b/xval/README.md @@ -12,8 +12,9 @@ Within this folder are two scripts that constitutes the cross-validation procedu ## Requirements - embedPy +- [sobol-seq](https://pypi.org/project/sobol-seq/) -The Python dependencies for the FRESH library can be installed by following the instructions laid out in the ML-Toolkit level of this library. +The Python dependencies for the FRESH library can be installed by following the instructions laid out in the ML-Toolkit level of this library. **Note** that `sobol-seq` must be installed using pip. ## Installation From 2b4ed3f97db4731ad479de5451b15c455437b52f Mon Sep 17 00:00:00 2001 From: Deanna Morgan Date: Thu, 23 Jul 2020 15:50:12 +0100 Subject: [PATCH 12/77] check for number of random hps --- xval/xval.q | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/xval/xval.q b/xval/xval.q index 8d987a4c..8cf26443 100644 --- a/xval/xval.q +++ b/xval/xval.q @@ -29,11 +29,16 @@ xv.fitscore:{[f;p;d].[.[f[][p]`:fit;d 0]`:score;d 1]`} hp.hpgen:{ if[(::)~n:x`n;n:16]; + if[(`sobol=x`typ)&k<>floor k:xlog[2]n;'"trials must equal 2^n for sobol search"]; num:where any`uniform`loguniform=\:first each p:x`p; system"S ",string$[(::)~x`random_state;42;x`random_state]; pysobol:.p.import[`sobol_seq;`:i4_sobol_generate;<]; genpts:$[`sobol~typ:x`typ;enlist each flip pysobol[count num;n];`random~typ;n;'"hyperparam type not supported"]; - flip hp.i.hpgen[typ;n]each p,:num!p[num],'genpts} + prms:distinct flip hp.i.hpgen[typ;n]each p,:num!p[num],'genpts; + if[n>dst:count prms; + if[`sobol=x`typ;dst:"j"$xexp[2]floor xlog[2]dst;prms:neg[dst]?prms]; + -1"Number of distinct hp sets less than n, returning ",string[dst]," sets."]; + prms} hp.i.hpgen:{[ns;n;p] p:@[;0;first](0;1)_p,(); $[(typ:p 0)~`boolean;n?0b; From e147535c03dcc12e337534c31340b37f3fca145b Mon Sep 17 00:00:00 2001 From: Deanna Morgan Date: Fri, 24 Jul 2020 11:28:31 +0100 Subject: [PATCH 13/77] sobol check fix --- xval/xval.q | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/xval/xval.q b/xval/xval.q index 8cf26443..e3c37232 100644 --- a/xval/xval.q +++ b/xval/xval.q @@ -35,9 +35,7 @@ hp.hpgen:{ pysobol:.p.import[`sobol_seq;`:i4_sobol_generate;<]; genpts:$[`sobol~typ:x`typ;enlist each flip pysobol[count num;n];`random~typ;n;'"hyperparam type not supported"]; prms:distinct flip hp.i.hpgen[typ;n]each p,:num!p[num],'genpts; - if[n>dst:count prms; - if[`sobol=x`typ;dst:"j"$xexp[2]floor xlog[2]dst;prms:neg[dst]?prms]; - -1"Number of distinct hp sets less than n, returning ",string[dst]," sets."]; + if[n>dst:count prms;-1"Number of distinct hp sets less than n, returning ",string[dst]," sets."]; prms} hp.i.hpgen:{[ns;n;p] p:@[;0;first](0;1)_p,(); From 5328e0ee961b861bc39b82922895f9b0066a5f26 Mon Sep 17 00:00:00 2001 From: Deanna Morgan Date: Mon, 27 Jul 2020 09:58:52 +0100 Subject: [PATCH 14/77] gs/rs tests --- xval/tests/xval.t | 100 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 74 insertions(+), 26 deletions(-) diff --git a/xval/tests/xval.t b/xval/tests/xval.t index 98ce80db..bc1c6658 100644 --- a/xval/tests/xval.t +++ b/xval/tests/xval.t @@ -27,8 +27,16 @@ bp:{first where a=max a:avg each x} k:3 p:.2 -pr:`alpha`max_iter!(0.1 0.2;100 200 1000) -pc:enlist[`max_depth]!enlist(::;1;2;3;4;5) +seed:42 +trials:8 +gs_pr:`alpha`max_iter!(0.1 0.2;100 200 1000) +gs_pc:enlist[`max_depth]!enlist(::;1;2;3;4;5) +rs_pr_rdm:`typ`random_state`n`p!(`random;seed;trials;`alpha`max_iter!((`uniform;.1;1.;"f");(`loguniform;1;4;"j"))) +rs_pc_rdm:`typ`random_state`n`p!(`random;seed;trials;enlist[`max_depth]!enlist(`uniform;1;30;"j")) +rs_pr_sbl:`typ`random_state`n`p!(`sobol;seed;trials;`alpha`max_iter!((`uniform;.1;1.;"f");(`loguniform;1;4;"j"))) +rs_pc_sbl:`typ`random_state`n`p!(`sobol;seed;trials;enlist[`max_depth]!enlist(`uniform;1;30;"j")) +rs_pr_err:`typ`random_state`n`p!(`sobol;seed;10;`alpha`max_iter!((`uniform;.1;1.;"f");(`loguniform;1;4;"j"))) +rs_pc_err:`typ`random_state`n`p!(`sobol;seed;10;enlist[`max_depth]!enlist(`uniform;1;30;"j")) ms:enlist(2 800 2;2 200 2) ridx:1_(1 xprev l),'l:enlist each(3,0N)#t:til count yf @@ -106,30 +114,70 @@ count[.ml.xv.kfstrat[k;1;xc;yc;fs[dtc][]]]~3 / grid search -(bp .ml.gs.kfsplit[k;1;xf;yf;fs net;pr;0])~@[;1]gridsearchr[xf;yf] -(bp .ml.gs.kfsplit[k;1;xi;yi;fs net;pr;0])~@[;1]gridsearchr[xi;yi] -(rnd[(avg/).ml.gs.kfsplit[k;1;xf;yf;fs net;pr;0]]-rnd@[;0]gridsearchr[xf;yf])<.05 -(rnd[(avg/).ml.gs.kfsplit[k;1;xi;yi;fs net;pr;0]]-rnd@[;0]gridsearchr[xi;yi])<.05 -(rnd[(avg/).ml.gs.kfsplit[k;1;xb;yb;fs dtc;pc;0]]-rnd@[;0]gridsearchc[xb;yb])<.05 -(rnd[(avg/).ml.gs.kfsplit[k;1;xc;yc;fs dtc;pc;0]]-rnd@[;0]gridsearchc[xc;yc])<.05 - -((@[;2].ml.gs.kfsplit[k;1;xf;yf;fs net;pr;.2])-@[;0]gridsearchr[xf;yf])<.05 -((@[;2].ml.gs.kfsplit[k;1;xi;yi;fs net;pr;.2])-@[;0]gridsearchr[xi;yi])<.06 -((@[;2].ml.gs.kfsplit[k;1;xb;yb;fs dtc;pc;.2])-@[;0]gridsearchc[xb;yb])<.05 -((@[;2].ml.gs.kfsplit[k;1;xc;yc;fs dtc;pc;.2])-@[;0]gridsearchc[xc;yc])<.05 - -(key@[;1].ml.gs.kfsplit[k;1;xf;yf;fs net;pr;.2])~`alpha`max_iter -(key@[;1].ml.gs.kfsplit[k;1;xi;yi;fs net;pr;.2])~`alpha`max_iter -(key@[;1].ml.gs.kfsplit[k;1;xb;yb;fs dtc;pc;.2])~enlist`max_depth -(key@[;1].ml.gs.kfsplit[k;1;xc;yc;fs dtc;pc;.2])~enlist`max_depth - -.ml.shape[.ml.gs.kfsplit[ 4;2;xf;yf;.ml.xv.fitscore net;pr; .2]]~3 6 8 -.ml.shape[.ml.gs.kfshuff[ 4;2;xf;yf;.ml.xv.fitscore net;pr;-.2]]~3 6 8 -.ml.shape[.ml.gs.kfstrat[ 4;2;xb;yb;.ml.xv.fitscore dtc;pc;-.2]]~3 6 8 -.ml.shape[.ml.gs.tsrolls[ 2;5;xb;yb;.ml.xv.fitscore dtc;pc; .2]]~3 6 5 -.ml.shape[.ml.gs.tschain[ 2;5;xb;yb;.ml.xv.fitscore dtc;pc; .2]]~3 6 5 -.ml.shape[.ml.gs.pcsplit[.3;5;xf;yf;.ml.xv.fitscore net;pr; .2]]~3 6 5 -.ml.shape[.ml.gs.mcsplit[.3;5;xf;yf;.ml.xv.fitscore net;pr;-.2]]~3 6 5 +(bp .ml.gs.kfsplit[k;1;xf;yf;fs net;gs_pr;0])~@[;1]gridsearchr[xf;yf] +(bp .ml.gs.kfsplit[k;1;xi;yi;fs net;gs_pr;0])~@[;1]gridsearchr[xi;yi] +(rnd[(avg/).ml.gs.kfsplit[k;1;xf;yf;fs net;gs_pr;0]]-rnd@[;0]gridsearchr[xf;yf])<.05 +(rnd[(avg/).ml.gs.kfsplit[k;1;xi;yi;fs net;gs_pr;0]]-rnd@[;0]gridsearchr[xi;yi])<.05 +(rnd[(avg/).ml.gs.kfsplit[k;1;xb;yb;fs dtc;gs_pc;0]]-rnd@[;0]gridsearchc[xb;yb])<.05 +(rnd[(avg/).ml.gs.kfsplit[k;1;xc;yc;fs dtc;gs_pc;0]]-rnd@[;0]gridsearchc[xc;yc])<.05 + +((@[;2].ml.gs.kfsplit[k;1;xf;yf;fs net;gs_pr;.2])-@[;0]gridsearchr[xf;yf])<.05 +((@[;2].ml.gs.kfsplit[k;1;xi;yi;fs net;gs_pr;.2])-@[;0]gridsearchr[xi;yi])<.06 +((@[;2].ml.gs.kfsplit[k;1;xb;yb;fs dtc;gs_pc;.2])-@[;0]gridsearchc[xb;yb])<.05 +((@[;2].ml.gs.kfsplit[k;1;xc;yc;fs dtc;gs_pc;.2])-@[;0]gridsearchc[xc;yc])<.05 + +(key@[;1].ml.gs.kfsplit[k;1;xf;yf;fs net;gs_pr;.2])~`alpha`max_iter +(key@[;1].ml.gs.kfsplit[k;1;xi;yi;fs net;gs_pr;.2])~`alpha`max_iter +(key@[;1].ml.gs.kfsplit[k;1;xb;yb;fs dtc;gs_pc;.2])~enlist`max_depth +(key@[;1].ml.gs.kfsplit[k;1;xc;yc;fs dtc;gs_pc;.2])~enlist`max_depth + +.ml.shape[.ml.gs.kfsplit[ 4;2;xf;yf;.ml.xv.fitscore net;gs_pr; .2]]~3 6 8 +.ml.shape[.ml.gs.kfshuff[ 4;2;xf;yf;.ml.xv.fitscore net;gs_pr;-.2]]~3 6 8 +.ml.shape[.ml.gs.kfstrat[ 4;2;xb;yb;.ml.xv.fitscore dtc;gs_pc;-.2]]~3 6 8 +.ml.shape[.ml.gs.tsrolls[ 2;5;xb;yb;.ml.xv.fitscore dtc;gs_pc; .2]]~3 6 5 +.ml.shape[.ml.gs.tschain[ 2;5;xb;yb;.ml.xv.fitscore dtc;gs_pc; .2]]~3 6 5 +.ml.shape[.ml.gs.pcsplit[.3;5;xf;yf;.ml.xv.fitscore net;gs_pr; .2]]~3 6 5 +.ml.shape[.ml.gs.mcsplit[.3;5;xf;yf;.ml.xv.fitscore net;gs_pr;-.2]]~3 6 5 + +/ random search + +(key@[;1].ml.rs.kfsplit[k;1;xf;yf;fs net;rs_pr_rdm;.2])~`alpha`max_iter +(key@[;1].ml.rs.kfsplit[k;1;xi;yi;fs net;rs_pr_rdm;.2])~`alpha`max_iter +(key@[;1].ml.rs.kfsplit[k;1;xb;yb;fs dtc;rs_pc_rdm;.2])~enlist`max_depth +(key@[;1].ml.rs.kfsplit[k;1;xc;yc;fs dtc;rs_pc_rdm;.2])~enlist`max_depth + +.ml.shape[.ml.rs.kfsplit[ 4;2;xf;yf;.ml.xv.fitscore net;rs_pr_rdm; .2]]~3 8 8 +.ml.shape[.ml.rs.kfshuff[ 4;2;xf;yf;.ml.xv.fitscore net;rs_pr_rdm;-.2]]~3 8 8 +.ml.shape[.ml.rs.kfstrat[ 4;2;xb;yb;.ml.xv.fitscore dtc;rs_pc_rdm;-.2]]~3 7 8 +.ml.shape[.ml.rs.tsrolls[ 2;5;xb;yb;.ml.xv.fitscore dtc;rs_pc_rdm; .2]]~3 7 5 +.ml.shape[.ml.rs.tschain[ 2;5;xb;yb;.ml.xv.fitscore dtc;rs_pc_rdm; .2]]~3 7 5 +.ml.shape[.ml.rs.pcsplit[.3;5;xf;yf;.ml.xv.fitscore net;rs_pr_rdm; .2]]~3 8 5 +.ml.shape[.ml.rs.mcsplit[.3;5;xf;yf;.ml.xv.fitscore net;rs_pr_rdm;-.2]]~3 8 5 + +/ sobol search + +(key@[;1].ml.rs.kfsplit[k;1;xf;yf;fs net;rs_pr_sbl;.2])~`alpha`max_iter +(key@[;1].ml.rs.kfsplit[k;1;xi;yi;fs net;rs_pr_sbl;.2])~`alpha`max_iter +(key@[;1].ml.rs.kfsplit[k;1;xb;yb;fs dtc;rs_pc_sbl;.2])~enlist`max_depth +(key@[;1].ml.rs.kfsplit[k;1;xc;yc;fs dtc;rs_pc_sbl;.2])~enlist`max_depth + +.ml.shape[.ml.rs.kfsplit[ 4;2;xf;yf;.ml.xv.fitscore net;rs_pr_sbl; .2]]~3 8 8 +.ml.shape[.ml.rs.kfshuff[ 4;2;xf;yf;.ml.xv.fitscore net;rs_pr_sbl;-.2]]~3 8 8 +.ml.shape[.ml.rs.kfstrat[ 4;2;xb;yb;.ml.xv.fitscore dtc;rs_pc_sbl;-.2]]~3 8 8 +.ml.shape[.ml.rs.tsrolls[ 2;5;xb;yb;.ml.xv.fitscore dtc;rs_pc_sbl; .2]]~3 8 5 +.ml.shape[.ml.rs.tschain[ 2;5;xb;yb;.ml.xv.fitscore dtc;rs_pc_sbl; .2]]~3 8 5 +.ml.shape[.ml.rs.pcsplit[.3;5;xf;yf;.ml.xv.fitscore net;rs_pr_sbl; .2]]~3 8 5 +.ml.shape[.ml.rs.mcsplit[.3;5;xf;yf;.ml.xv.fitscore net;rs_pr_sbl;-.2]]~3 8 5 + +$[0b=@[{.ml.rs.kfsplit[ 4;2;xf;yf;.ml.xv.fitscore dtc;x;-.2];};rs_pr_err;{[err]err;0b}];1b;0b] +$[0b=@[{.ml.rs.kfshuff[ 4;2;xf;yf;.ml.xv.fitscore dtc;x;-.2];};rs_pr_err;{[err]err;0b}];1b;0b] +$[0b=@[{.ml.rs.kfstrat[ 4;2;xb;yb;.ml.xv.fitscore dtc;x;-.2];};rs_pc_err;{[err]err;0b}];1b;0b] +$[0b=@[{.ml.rs.tsrolls[ 4;2;xb;yb;.ml.xv.fitscore dtc;x;-.2];};rs_pc_err;{[err]err;0b}];1b;0b] +$[0b=@[{.ml.rs.tschain[ 4;2;xb;yb;.ml.xv.fitscore dtc;x;-.2];};rs_pc_err;{[err]err;0b}];1b;0b] +$[0b=@[{.ml.rs.pcsplit[ 4;2;xf;yf;.ml.xv.fitscore dtc;x;-.2];};rs_pr_err;{[err]err;0b}];1b;0b] +$[0b=@[{.ml.rs.mcsplit[ 4;2;xf;yf;.ml.xv.fitscore dtc;x;-.2];};rs_pr_err;{[err]err;0b}];1b;0b] + +/ scoring fs[net;::;df]~fitscore[df[0]0;df[0]1;df[1]0;df[1]1] fs[net;::;di]~fitscore[di[0]0;di[0]1;di[1]0;di[1]1] From fd693de472fc9a19d222978b127fe0eb3989746a Mon Sep 17 00:00:00 2001 From: Conor McCarthy Date: Wed, 5 Aug 2020 20:50:50 +0100 Subject: [PATCH 15/77] addition of 1st pass graph/pipeline code --- graph/README.md | 36 +++++++++++++++++++++ graph/graph.q | 84 ++++++++++++++++++++++++++++++++++++++++++++++++ graph/init.q | 2 ++ graph/pipeline.q | 51 +++++++++++++++++++++++++++++ init.q | 1 + 5 files changed, 174 insertions(+) create mode 100644 graph/README.md create mode 100644 graph/graph.q create mode 100644 graph/init.q create mode 100644 graph/pipeline.q diff --git a/graph/README.md b/graph/README.md new file mode 100644 index 00000000..16478d12 --- /dev/null +++ b/graph/README.md @@ -0,0 +1,36 @@ +# Graphing and Pipeline interface + +The functionality contained in this folder surrounds the implementation of a graph and pipeline execution structural form of kdb+. This functionality is intended to provide a common structural template and execution mechanism for complex code bases require ease of modification which is common within machine learning use cases. + +## Functionality + +Within this folder are two scripts that contains the entirity of this graph and pipeline functionality. These scripts are: + +1. graph.q: This contains all functionality required for the creation, deletion and update of nodes and edges within the graph structure. +2. pipeline.q: This contains functionality for both the compilation and execution of a user generated graph. + +## Requirements + +- kdb+ > 3 + +## Installation + +Place the `ml` library in `$QHOME` and load into a q instance using `ml/ml.q` + +### Load + +The following will load the graphing and pipeline functionality into the `.ml` namespace +```q +q)\l ml/ml.q +q).ml.loadfile`:graph/init.q +``` + +## Documentation + +Documentation is available on the [Graph](https://code.kx.com/q/ml/toolkit/graph/) homepage. + +## Status + +The graph-pipeline library is still in development and is available here as a beta release. Further functionality and improvements will be made to the library in the coming months. + +If you have any issues, questions or suggestions, please write to ai@kx.com. diff --git a/graph/graph.q b/graph/graph.q new file mode 100644 index 00000000..cc4c7f09 --- /dev/null +++ b/graph/graph.q @@ -0,0 +1,84 @@ +\d .ml + +createGraph:{[] + nodes:1!enlist`nodeId`function`inputs`outputs!(`;::;::;::); + edges:2!enlist`dstNode`dstName`srcNode`srcName`valid!(`;`;`;`;0b); + `nodes`edges!(nodes;edges)} + +addNode:{[graph;nodeId;node] + if[nodeId in exec nodeId from graph`nodes;'"invalid nodeId"]; + if[not`function`inputs`outputs~asc key node;'"invalid node"]; + if[(::)~node`inputs;node[`inputs]:(0#`)!""]; + if[-10h=type node`inputs;node[`inputs]:(1#`input)!enlist node`inputs]; + if[99h<>type node`inputs;'"invalid inputs"]; + if[-10h=type node`outputs; + node[`outputs]:(1#`output)!enlist node`outputs; + node[`function]:((1#`output)!enlist@)node[`function]::; + ]; + if[99h<>type node`outputs;'"invalid outputs"]; + graph:@[graph;`nodes;,;update nodeId from node]; + edges:flip`dstNode`dstName`srcNode`srcName`valid!(nodeId;key node`inputs;`;`;0b); + graph:@[graph;`edges;,;edges]; + graph} + +updNode:{[graph;nodeId;node] + if[not nodeId in 1_exec nodeId from graph`nodes;'"invalid nodeId"]; + if[count key[node]except`function`inputs`outputs;'"invalid node"]; + oldnode:graph[`nodes]nodeId; + if[`inputs in key node; + if[(::)~node`inputs;node[`inputs]:(0#`)!""]; + if[-10h=type node`inputs;node[`inputs]:(1#`input)!enlist node`inputs]; + if[99h<>type node`inputs;'"invalid inputs"]; + inputEdges:select from graph[`edges]where dstNode=nodeId,dstName in key oldnode`inputs; + graph:@[graph;`edges;key[inputEdges]_]; + inputEdges:flip[`dstNode`dstName!(nodeId;key node`inputs)]#inputEdges; + graph:@[graph;`edges;,;inputEdges]; + inputEdges:select from inputEdges where not null srcNode; + graph:{[graph;edge]connectEdge[graph]. edge`srcNode`srcName`dstNode`dstName}/[graph;0!inputEdges]; + ]; + if[`outputs in key node; + if[-10h=type node`outputs; + node[`outputs]:(1#`output)!enlist node`outputs; + ]; + if[99h<>type node`outputs;'"invalid outputs"]; + outputEdges:select from graph[`edges]where srcNode=nodeId,srcName in key oldnode`outputs; + graph:@[graph;`edges;key[outputEdges]_]; + outputEdges:select from outputEdges where srcName in key node`outputs; + graph:@[graph;`edges;,;outputEdges]; + outputEdges:select srcNode,srcName,dstNode,dstName from outputEdges; + graph:{[graph;edge]connectEdge[graph]. edge`srcNode`srcName`dstNode`dstName}/[graph;0!outputEdges]; + ]; + if[`function in key node; + if[(1#`output)~key graph[`nodes;nodeId]`outputs;node[`function]:((1#`output)!enlist@)node[`function]::]; + ]; + graph:@[graph;`nodes;,;update nodeId from node]; + graph} + +delNode:{[graph;nodeId] + if[not nodeId in 1_exec nodeId from graph`nodes;'"invalid nodeId"]; + graph:@[graph;`nodes;_;nodeId]; + inputEdges:select from graph[`edges]where dstNode=nodeId; + graph:@[graph;`edges;key[inputEdges]_]; + outputEdges:select from graph[`edges]where srcNode=nodeId; + graph:@[graph;`edges;,;update srcNode:`,srcName:`,valid:0b from outputEdges]; + graph} + +addCfg:{[graph;nodeId;cfg]addNode[graph;nodeId]`function`inputs`outputs!(@[;cfg];::;"!")} +updCfg:{[graph;nodeId;cfg]updNode[graph;nodeId](1#`function)!enlist cfg} +delCfg:delNode + +connectEdge:{[graph;srcNode;srcName;dstNode;dstName] + if[99h<>type srcOutputs:graph[`nodes;srcNode;`outputs];'"invalid srcNode"]; + if[99h<>type dstInputs:graph[`nodes;dstNode;`inputs];'"invalid dstNode"]; + if[not srcName in key srcOutputs;'"invalid srcName"]; + if[not dstName in key dstInputs;'"invalid dstName"]; + edge:(1#`valid)!1#srcOutputs[srcName]~dstInputs[dstName]; + graph:@[graph;`edges;,;update dstNode,dstName,srcNode,srcName from edge]; + graph} + +disconnectEdge:{[graph;dstNode;dstName] + if[not(dstNode;dstName)in key graph`edges;'"invalid edge"]; + edge:(1#`valid)!1#0b; + graph:@[graph;`edges;,;update dstNode,dstName,srcName:`,srcNode:` from edge]; + graph} + diff --git a/graph/init.q b/graph/init.q new file mode 100644 index 00000000..356a3d45 --- /dev/null +++ b/graph/init.q @@ -0,0 +1,2 @@ +.ml.loadfile`:graph/graph.q +.ml.loadfile`:graph/pipeline.q diff --git a/graph/pipeline.q b/graph/pipeline.q new file mode 100644 index 00000000..14f44846 --- /dev/null +++ b/graph/pipeline.q @@ -0,0 +1,51 @@ +\d .ml + +// Execution of a pipeline will not default to enter q debug mode but should be possible to overwrite +graphDebug:0b +changeDebug:{[x]graphDebug::$[graphDebug;0b;1b]} + +createPipeline:{[graph] + if[not all exec 1_valid from graph`edges;'"disconnected edges"]; + outputs:ungroup select srcNode:nodeId,srcName:key each outputs from 1_graph`nodes; + endpoints:exec distinct srcNode from outputs except select srcNode,srcName from graph`edges; + optimalpath:distinct raze paths idesc count each paths:i.getOptimalPath[graph]each endpoints; + pipeline:([]nodeId:optimalpath)#graph`nodes; + nodeinputs:key each exec inputs from pipeline; + pipeline:update inputs:count[i]#enlist(1#`)!1#(::),outputtypes:outputs,inputorder:nodeinputs from pipeline; + pipeline:select nodeId,complete:0b,error:`,function,inputs,outputs:inputs,outputtypes,inputorder from pipeline; + pipeline:pipeline lj select outputmap:([]srcName;dstNode;dstName)by nodeId:srcNode from graph`edges; + 1!pipeline} + +execPipeline:{[pipeline]i.execCheck i.execNext/pipeline} + + +// Pipeline creation utilities +i.getDeps:{[graph;node]exec distinct srcNode from graph[`edges]where dstNode=node} +i.getAllDeps:{[graph;node]$[count depNodes:i.getDeps[graph]node;distinct node,raze .z.s[graph]each depNodes;node]} +i.getAllPaths:{[graph;node]$[count depNodes:i.getDeps[graph]node;node,/:raze .z.s[graph]each depNodes;raze node]} +i.getLongestPath:{[graph;node]paths first idesc count each paths:reverse each i.getAllPaths[graph;node]} +i.getOptimalPath:{[graph;node]distinct raze reverse each i.getAllDeps[graph]each i.getLongestPath[graph;node]} + +i.execNext:{[pipeline] + node:first 0!select from pipeline where not complete; + -1"Executing node: ",string node`nodeId; + if[not count inputs:node[`inputs]node[`inputorder];inputs:1#(::)]; + res:`complete`error`outputs!$[i.debug; + .[(1b;`;)node[`function]::;inputs]; + .[(1b;`;)node[`function]::;inputs;{[err](0b;`$err;::)}] + ]; + / compare outputs to outputtypes ? + if[not null res`error;-2"Error: ",string res`error]; + if[res`complete; + res[`inputs]:(1#`)!1#(::); + outputmap:update data:res[`outputs]srcName from node`outputmap; + res[`outputs]:((1#`)!1#(::)),(exec distinct srcName from outputmap)_ res`outputs; + pipeline:{[pipeline;map]pipeline[map`dstNode;`inputs;map`dstName]:map`data;pipeline}/[pipeline;outputmap]; + ]; + pipeline,:update nodeId:node`nodeId from res; + pipeline} + +i.execCheck:{[pipeline] + if[any not null exec error from pipeline;:0b]; + if[all exec complete from pipeline;:0b]; + 1b} diff --git a/init.q b/init.q index 53cc37ff..a9a54ec5 100644 --- a/init.q +++ b/init.q @@ -2,4 +2,5 @@ .ml.loadfile`:fresh/init.q .ml.loadfile`:clust/init.q .ml.loadfile`:xval/init.q +.ml.loadfile`:graph/init.q From db4877d0519fd6f19ed7c16ee516732b16b771be Mon Sep 17 00:00:00 2001 From: Conor McCarthy Date: Wed, 5 Aug 2020 22:29:23 +0100 Subject: [PATCH 16/77] initial pass at graphing tests --- graph/tests/graph.t | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 graph/tests/graph.t diff --git a/graph/tests/graph.t b/graph/tests/graph.t new file mode 100644 index 00000000..8588f449 --- /dev/null +++ b/graph/tests/graph.t @@ -0,0 +1,22 @@ +// The functions contained in this file are intended in the first instance to show cases +// which will fail to produce a valid/operable graphs/pipelines in order to ensure that the +// catching mechanism for the creation of such workflows is reliable and fully understood + +\l p.q +\l ml.q +\l graph/graph.q +\l graph/pipeline.q + +g:.ml.createGraph[] +g:.ml.addCfg[g;`cfg1]`test`config!(til 10;5000) +g:.ml.addNode[g;`testnode1]`function`inputs`outputs!({x};"f";"!") + +// Attempt to add a node and configuration with the same name again +0b~$[(::)~@[{.ml.addCfg[x;y]`a`b!1 2}[g;];`cfg1;{[err]err;0b}];1b;0b] +0b~$[(::)~@[{.ml.addNode[x;y]`function`inputs`outputs!({x};"f";"!")}[g;];`testnode1;{[err]err;0b}];1b;0b] + +// Connect an edge between 2 nodes and check it is invalid +g:.ml.connectEdge[g;`cfg1;`output;`testnode1;`input] +0b~first exec valid from g where dstNode=`node1,dstName=`input +.ml.disconnectEdge[g;`node1;`input] + From 0784ce06824d30a61d9c58a8128a293685fd40e3 Mon Sep 17 00:00:00 2001 From: Conor McCarthy Date: Thu, 6 Aug 2020 09:57:37 +0100 Subject: [PATCH 17/77] Increased testing, covering base functionality --- graph/pipeline.q | 4 ++-- graph/tests/graph.t | 43 ++++++++++++++++++++++++++++++++++++++----- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/graph/pipeline.q b/graph/pipeline.q index 14f44846..4cea2893 100644 --- a/graph/pipeline.q +++ b/graph/pipeline.q @@ -2,7 +2,7 @@ // Execution of a pipeline will not default to enter q debug mode but should be possible to overwrite graphDebug:0b -changeDebug:{[x]graphDebug::$[graphDebug;0b;1b]} +updDebug:{[x]graphDebug::not graphDebug} createPipeline:{[graph] if[not all exec 1_valid from graph`edges;'"disconnected edges"]; @@ -30,7 +30,7 @@ i.execNext:{[pipeline] node:first 0!select from pipeline where not complete; -1"Executing node: ",string node`nodeId; if[not count inputs:node[`inputs]node[`inputorder];inputs:1#(::)]; - res:`complete`error`outputs!$[i.debug; + res:`complete`error`outputs!$[graphDebug; .[(1b;`;)node[`function]::;inputs]; .[(1b;`;)node[`function]::;inputs;{[err](0b;`$err;::)}] ]; diff --git a/graph/tests/graph.t b/graph/tests/graph.t index 8588f449..52c6aa27 100644 --- a/graph/tests/graph.t +++ b/graph/tests/graph.t @@ -9,14 +9,47 @@ g:.ml.createGraph[] g:.ml.addCfg[g;`cfg1]`test`config!(til 10;5000) -g:.ml.addNode[g;`testnode1]`function`inputs`outputs!({x};"f";"!") +g:.ml.addNode[g;`node1]`function`inputs`outputs!({x};"f";"!") // Attempt to add a node and configuration with the same name again 0b~$[(::)~@[{.ml.addCfg[x;y]`a`b!1 2}[g;];`cfg1;{[err]err;0b}];1b;0b] 0b~$[(::)~@[{.ml.addNode[x;y]`function`inputs`outputs!({x};"f";"!")}[g;];`testnode1;{[err]err;0b}];1b;0b] -// Connect an edge between 2 nodes and check it is invalid -g:.ml.connectEdge[g;`cfg1;`output;`testnode1;`input] -0b~first exec valid from g where dstNode=`node1,dstName=`input -.ml.disconnectEdge[g;`node1;`input] +// Connect an invalid edge between 2 nodes and check non validity +g:.ml.connectEdge[g;`cfg1;`output;`node1;`input] +0b~first exec valid from g[`edges] where dstNode=`node1,dstName=`input +g:.ml.disconnectEdge[g;`node1;`input] + +// Attempt to disconnect an unconnected edge +0b~$[(::)~@[{.ml.disconnectEdge[x;y;z]}[g;`node1];`input;{[err]err;0b}];1b;0b] + +// Attempt to delete a node that has not been populated +0b~$[(::)~@[{.ml.delNode[x;y]}[g;];`notanode;{[err]err;0b}];1b;0b] + +// Update node1 such that it is valid for connection to cfg1, function errors on execution (for pipeline testing) +g:.ml.updNode[g;`node1]`function`inputs`outputs!({`e+1};"!";"!") +g:.ml.connectEdge[g;`cfg1;`output;`node1;`input] +1b~first exec valid from g[`edges] where dstNode=`node1,dstName=`input + +// Create a second configuration and attempt to connect this to node1 (invalid due to previous connection) +g:.ml.addCfg[g;`cfg2]`test`config!10 20 +0b~$[(::)~@[{.ml.connectEdge[x;y;`output;`node1;`input]}[g;];`input;{[err]err;0b}];1b;0b] + +p:.ml.createPipeline[g] +// Execute pipeline with debug functionality inactive (returns a table with non complete rows) +not all exec complete from .ml.execPipeline[p] + +// Check current debug status, update status and check update success +not .ml.graphDebug +.ml.updDebug[] +.ml.graphDebug + +// Execute pipeline catching error with debug status activated +0b~$[(::)~@[{.ml.execPipeline[x]};p;{[err]err;0b}];1b;0b] + +// Update delete cfg2, update node1 such that execution is valid and check validity of execution +g:.ml.delCfg[g;`cfg2] +g:.ml.updNode[g;`node1]`function`inputs`outputs!({x};"!";"!") +p1:.ml.createPipeline[g] +all exec complete from .ml.execPipeline[p1] From 76bb7ee41f887c29aa84015294a90a48d0d4caad Mon Sep 17 00:00:00 2001 From: Conor McCarthy Date: Thu, 6 Aug 2020 10:21:56 +0100 Subject: [PATCH 18/77] update to account for testing and packaging of graph functionality --- .travis.yml | 4 ++-- build/package.bat | 2 +- build/test.bat | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index c9a4fbfd..ed761360 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,11 +36,11 @@ beforescript: script: - (cd clust && make && make install && make clean) - echo "Preparing version $TRAVIS_BRANCH-$TRAVIS_COMMIT" -- tar czf ml_$TRAVIS_OS_NAME-$TRAVIS_BRANCH.tgz *.q fresh/ xval/ util/ clust/ requirements.txt LICENSE README.md +- tar czf ml_$TRAVIS_OS_NAME-$TRAVIS_BRANCH.tgz *.q fresh/ xval/ util/ clust/ graph/ requirements.txt LICENSE README.md - echo "Packaged as ml_$TRAVIS_OS_NAME-$TRAVIS_BRANCH.zip" - if [[ "x$QLIC_KC" != "x" ]]; then curl -fsSL -o test.q https://github.com/KxSystems/embedpy/raw/master/test.q; - q test.q fresh/tests/ util/tests/ xval/tests clust/tests/ -q; + q test.q fresh/tests/ util/tests/ xval/tests clust/tests/ graph/tests/ -q; else echo No kdb+, no tests; diff --git a/build/package.bat b/build/package.bat index 321978a2..98d4383b 100644 --- a/build/package.bat +++ b/build/package.bat @@ -1,3 +1,3 @@ -7z a ml_windows-%ML_VERSION%.zip *.q fresh util xval clust requirements.txt LICENSE README.md +7z a ml_windows-%ML_VERSION%.zip *.q fresh util xval clust graph requirements.txt LICENSE README.md appveyor PushArtifact ml_windows-%ML_VERSION%.zip exit /b 0 diff --git a/build/test.bat b/build/test.bat index 339e8dc1..adf30347 100644 --- a/build/test.bat +++ b/build/test.bat @@ -2,5 +2,5 @@ if defined QLIC_KC ( pip -q install -r requirements.txt echo getting test.q from embedpy curl -fsSL -o test.q https://github.com/KxSystems/embedpy/raw/master/test.q - q test.q fresh/tests/ util/tests/ xval/tests/ clust/tests/ -q + q test.q fresh/tests/ util/tests/ xval/tests/ clust/tests/ graph/tests/ -q ) From 488adc81bfd47e9b4f25c2a6a4b52ef581965f8d Mon Sep 17 00:00:00 2001 From: Dianeod Date: Fri, 7 Aug 2020 10:18:02 +0100 Subject: [PATCH 19/77] updated tab2df to account for list of char being passed --- util/tests/utiltst.t | 2 ++ util/util.q | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/util/tests/utiltst.t b/util/tests/utiltst.t index a3910ca5..a64a44d6 100644 --- a/util/tests/utiltst.t +++ b/util/tests/utiltst.t @@ -24,11 +24,13 @@ df :.ml.tab2df tt:([]fcol:12?1.;jcol:12?100;scol:12?`aaa`bbb`ccc) dfj:.ml.tab2df tj:select by jcol from tt dfs:.ml.tab2df ts:select by scol from tt dfsj:.ml.tab2df tx:select by scol,jcol from tt +dfc:.ml.tab2df ([]s:`a`b`c;j:1 2 3;c:"ABC") (dfsx:.ml.tab2df tx)[`:index][:;`:names;(`scol;::)] (dfxj:.ml.tab2df tx)[`:index][:;`:names;(::;`jcol)] (dfxx:.ml.tab2df tx)[`:index][:;`:names;(::;::)] tt2:([]date:2005.07.14 2005.07.15;timesp:("N"$"12:10:30.000500000";"N"$"12:13:30.000200007");time:20:30:00.001 19:23:20.201;str:enlist each ("h";"i");ind:1.3 2.5;bool:10b) 112 112 112 10 -9 -1h~type each first (.ml.tab2df tt2)[`:values]` +(dfc[`:c.values]`)~enlist each "ABC" .ml.shape[1 2 3*/:til 10] ~ np[`:shape][1 2 3*/:til 10]` .ml.shape[enlist 1] ~ np[`:shape][enlist 1]` diff --git a/util/util.q b/util/util.q index 5f81cf57..12e98b7f 100644 --- a/util/util.q +++ b/util/util.q @@ -20,7 +20,8 @@ traintestsplit:{[x;y;sz]`xtrain`ytrain`xtest`ytest!raze(x;y)@\:/:(0,floor n*1-sz i.q2npdt:{.p.import[`numpy;`:array;("p"$@[4#+["d"$0];-16+type x]x)-"p"$1970.01m;"datetime64[ns]"]`.} / q tab to pandas dataframe tab2df:{ - r:.p.import[`pandas;`:DataFrame;@[flip 0!x;i.fndcols[x]"pmdznuvt";i.q2npdt]][@;cols x]; + updx:@[flip 0!x;i.fndcols[x;"c"];enlist each]; + r:.p.import[`pandas;`:DataFrame;@[updx;i.fndcols[x]"pmdznuvt";i.q2npdt]][@;cols x]; $[count k:keys x;r[`:set_index]k;r]} / pandas dataframe to q tab df2tab_tz:{ From c47e0a187465a21cd5cc623d98ef2247d3e31bf1 Mon Sep 17 00:00:00 2001 From: Conor McCarthy Date: Wed, 12 Aug 2020 10:22:29 +0100 Subject: [PATCH 20/77] addition of most basic modules to ml toolkit (loading/saving data) --- graph/modules/loading.q | 27 +++++++++++++++++++++++++++ graph/modules/saving.q | 29 +++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 graph/modules/loading.q create mode 100644 graph/modules/saving.q diff --git a/graph/modules/loading.q b/graph/modules/loading.q new file mode 100644 index 00000000..276c4bbf --- /dev/null +++ b/graph/modules/loading.q @@ -0,0 +1,27 @@ +\d .auto + +i.loadfname:{[cfg] + file:hsym`$$[`dir in key cfg;cfg`key;"."],"/",cfg fname; + if[()~key file;'"file does not exist"]; + file} + +i.loadfunc.splay:loadfunc.binary:{[cfg]get i.loadfname cfg} +i.loadfunc.csv:{[cfg](cfg`schema;enlist cfg`separator)0: i.loadfname cfg} +i.loadfunc.json:{[cfg].j.k first read0 i.loadfname cfg} +i.loadfunc.hdf5:{[cfg] + if[not`hdf5 in key`;@[system;"l hdf5.q";{'"unable to load hdf5 lib"}]]; + if[not .hdf5.ishdf5 fname:i.loadfname cfg;'"file is not an hdf5 file"]; + if[not .hdf5.isObject[fpath;cfg`dname];'"hdf5 dataset does not exist"]; + .hdf5.readData[fpath;cfg`dname]} +i.loadfunc.ipc:{[cfg] + h:@[hopen;cfg`prt;{'"error opening connection"}]; + ret:@[h;cfg`select;{'"error executing query"}]; + @[hclose;h;{}]; + ret} +i.loadfunc.process:{[cfg]if[not `data in key cfg;'"Data to be used must be defined"];cfg[`data]} + +i.loaddset:{[cfg] + if[null func:i.loadfunc cfg`typ;'"dataset type not supported"]; + func cfg} + +loaddset:`function`inputs`outputs!(i.loaddset;"!";"+") diff --git a/graph/modules/saving.q b/graph/modules/saving.q new file mode 100644 index 00000000..f9ca580f --- /dev/null +++ b/graph/modules/saving.q @@ -0,0 +1,29 @@ +\d .auto + +i.savefname:{[cfg] + file:hsym`$$[`dir in key cfg;cfg`key;"."],"/",cfg fname; + if[not ()~key file;'"file exists"]; + file} + +i.savedset.txt:{[cfg;dset]i.savefname[cfg]0:.h.tx[cfg`typ;dset];} +i.savedset[`csv`xml`xls]:i.savedset.txt +i.savedset.binary:{[cfg;dset]i.savefname[cfg]set dset;} +i.savedset.json:{[cfg;dset] + h:hopen i.savefname cfg; + h @[.j.j;dset;{'"error converting to json"}]; + hclose h;} +i.savedset.hdf5:{[cfg;dset] + if[not`hdf5 in key`;@[system;"l hdf5.q";{'"unable to load hdf5 lib"}]]; + .hdf5.createFile fname:i.savefname cfg; + .hdf5.writeData[fname;cfg`dname;dset]; + } +i.savedset.splay:{[cfg;dset] + dname:first` vs fname:i.savefname cfg; + fname:` sv fname,`; + fname set .Q.en[dname]dset;} + +i.savefunc:{[cfg;dset] + if[null func:i.savedset cfg`typ;'"dataset type not supported"]; + func dset} + +savedset:`function`inputs`outputs!(i.savefunc;`cfg`dset!"!+";" ") From 14c7f6639267a64d525bf2e095799bbd035069d4 Mon Sep 17 00:00:00 2001 From: Conor McCarthy Date: Wed, 12 Aug 2020 21:25:51 +0100 Subject: [PATCH 21/77] addition of loading and saving modules to ml graph library (most basic generally useful module) --- graph/init.q | 2 ++ graph/modules/loading.q | 2 +- graph/modules/saving.q | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/graph/init.q b/graph/init.q index 356a3d45..3f7568d9 100644 --- a/graph/init.q +++ b/graph/init.q @@ -1,2 +1,4 @@ .ml.loadfile`:graph/graph.q .ml.loadfile`:graph/pipeline.q +.ml.loadfile`:graph/modules/saving.q +.ml.loadfile`:graph/modules/loading.q diff --git a/graph/modules/loading.q b/graph/modules/loading.q index 276c4bbf..2c799cd9 100644 --- a/graph/modules/loading.q +++ b/graph/modules/loading.q @@ -1,4 +1,4 @@ -\d .auto +\d .ml i.loadfname:{[cfg] file:hsym`$$[`dir in key cfg;cfg`key;"."],"/",cfg fname; diff --git a/graph/modules/saving.q b/graph/modules/saving.q index f9ca580f..56159792 100644 --- a/graph/modules/saving.q +++ b/graph/modules/saving.q @@ -1,4 +1,4 @@ -\d .auto +\d .ml i.savefname:{[cfg] file:hsym`$$[`dir in key cfg;cfg`key;"."],"/",cfg fname; From fa4abf217b5a8ae3d339a820f9b70abf76e94113 Mon Sep 17 00:00:00 2001 From: Conor McCarthy Date: Fri, 14 Aug 2020 15:30:53 +0100 Subject: [PATCH 22/77] update to graph functionality to handle generic dictionaries --- graph/graph.q | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/graph/graph.q b/graph/graph.q index cc4c7f09..c212fb64 100644 --- a/graph/graph.q +++ b/graph/graph.q @@ -1,13 +1,14 @@ \d .ml createGraph:{[] - nodes:1!enlist`nodeId`function`inputs`outputs!(`;::;::;::); + nodes:1!enlist`nodeId``function`inputs`outputs!(`;::;::;::;::); edges:2!enlist`dstNode`dstName`srcNode`srcName`valid!(`;`;`;`;0b); `nodes`edges!(nodes;edges)} addNode:{[graph;nodeId;node] + node,:(1#`)!1#(::); if[nodeId in exec nodeId from graph`nodes;'"invalid nodeId"]; - if[not`function`inputs`outputs~asc key node;'"invalid node"]; + if[not``function`inputs`outputs~asc key node;'"invalid node"]; if[(::)~node`inputs;node[`inputs]:(0#`)!""]; if[-10h=type node`inputs;node[`inputs]:(1#`input)!enlist node`inputs]; if[99h<>type node`inputs;'"invalid inputs"]; @@ -22,8 +23,9 @@ addNode:{[graph;nodeId;node] graph} updNode:{[graph;nodeId;node] + node,:(1#`)!1#(::); if[not nodeId in 1_exec nodeId from graph`nodes;'"invalid nodeId"]; - if[count key[node]except`function`inputs`outputs;'"invalid node"]; + if[count key[node]except``function`inputs`outputs;'"invalid node"]; oldnode:graph[`nodes]nodeId; if[`inputs in key node; if[(::)~node`inputs;node[`inputs]:(0#`)!""]; @@ -63,7 +65,7 @@ delNode:{[graph;nodeId] graph:@[graph;`edges;,;update srcNode:`,srcName:`,valid:0b from outputEdges]; graph} -addCfg:{[graph;nodeId;cfg]addNode[graph;nodeId]`function`inputs`outputs!(@[;cfg];::;"!")} +addCfg:{[graph;nodeId;cfg]addNode[graph;nodeId]``function`inputs`outputs!(::;@[;cfg];::;"!")} updCfg:{[graph;nodeId;cfg]updNode[graph;nodeId](1#`function)!enlist cfg} delCfg:delNode From bfe68f26dd2d1d6b0f2b07a6bc04bd1e1c7330f9 Mon Sep 17 00:00:00 2001 From: Deanna Morgan Date: Fri, 21 Aug 2020 15:10:54 +0100 Subject: [PATCH 23/77] new comment format + streaming kmeans+dbscan --- clust/aprop.q | 133 ++++++---- clust/dbscan.q | 153 +++++++++--- clust/hierarchical.q | 577 +++++++++++++++++++++++++------------------ clust/kdtree.q | 181 +++++++++----- clust/kmeans.q | 171 +++++++++---- clust/score.q | 164 +++++++----- clust/util.q | 94 ++++++- 7 files changed, 961 insertions(+), 512 deletions(-) diff --git a/clust/aprop.q b/clust/aprop.q index 08b22700..dddbdf8a 100644 --- a/clust/aprop.q +++ b/clust/aprop.q @@ -1,65 +1,90 @@ \d .ml -// Affinity propagation algorithm -/* data = data points in `value flip` format -/* df = distance function -/* dmp = damping coefficient -/* diag = similarity matrix diagonal value function -/. r > return list of clusters +// Affinity Propagation + +// @kind function +// @category clust +// @fileoverview Affinity propagation algorithm +// @param data {float[][]} Points in `value flip` format +// @param df {fn} Distance function +// @param dmp {float} Damping coefficient +// @param diag {fn} Similarity matrix diagonal value function +// @return {long[]} List of clusters clust.ap:{[data;df;dmp;diag] - // check distance function and diagonal value - if[not df in key clust.i.dd;clust.i.err.dd[]]; - // create initial table with exemplars/matches and similarity, availability and responsibility matrices - info0:clust.i.apinit["f"$data;df;diag]; - // run AP algo until there is no change in results over `0.1*count data` runs - info1:{[maxiter;info]maxiter>info`matches}[.1*count data]clust.i.apalgo[dmp]/info0; - // return list of clusters - clust.i.reindex info1`exemplars} + // check distance function and diagonal value + if[not df in key clust.i.dd;clust.i.err.dd[]]; + // create initial table with exemplars/matches and similarity, availability + // and responsibility matrices + info0:clust.i.apinit["f"$data;df;diag]; + // run AP algo until there is no change in results over `0.1*count data` runs + info1:{[iter;info]iter>info`matches}[.1*count data]clust.i.apalgo[dmp]/info0; + // return list of clusters + clust.i.reindex info1`exemplars + } -// Initialize matrices -/* data = data points in `value flip` format -/* df = distance function -/* diag = similarity matrix diagonal value -/. r > returns a dictionary with similarity, availability and responsibility matrices -/ and keys for matches and exemplars to be filled during further iterations +// @kind function +// @category private +// @fileoverview Initialize matrices +// @param data {float[][]} Points in `value flip` format +// @param df {fn} Distance function +// @param diag {fn} Similarity matrix diagonal value function +// @return {dict} Similarity, availability and responsibility matrices +// and keys for matches and exemplars to be filled during further iterations clust.i.apinit:{[data;df;diag] - // calculate similarity matrix values - s:@[;;:;diag raze s]'[s:clust.i.dists[data;df;data]each k;k:til n:count data 0]; - // create lists/matrices of zeros for other variables - `matches`exemplars`s`a`r!(0;0#0;s),(2;n;n)#0f} + // calculate similarity matrix values + s:clust.i.dists[data;df;data]each k:til n:count data 0; + s:@[;;:;diag raze s]'[s;k]; + // create lists/matrices of zeros for other variables + `matches`exemplars`s`a`r!(0;0#0;s),(2;n;n)#0f + } -// Run affinity propagation algorithm -/* dmp = damping coefficient -/* info = dictionary containing exemplars and matches, similarity, availability and responsibility matrices -/. r > returns updated info +// @kind function +// @category private +// @fileoverview Run affinity propagation algorithm +// @param dmp {float} Damping coefficient +// @param info {dict} Exemplars and matches, similarity, availability and +// responsibility matrices +// @return {dict} Updated info clust.i.apalgo:{[dmp;info] - // update responsibility matrix - info[`r]:clust.i.updr[dmp;info]; - // update availability matrix - info[`a]:clust.i.upda[dmp;info]; - // find new exemplars - ex:imax each sum info`a`r; - // return updated `info` with new exemplars/matches - update exemplars:ex,matches:?[exemplars~ex;matches+1;0]from info} + // update responsibility matrix + info[`r]:clust.i.updr[dmp;info]; + // update availability matrix + info[`a]:clust.i.upda[dmp;info]; + // find new exemplars + ex:imax each sum info`a`r; + // return updated `info` with new exemplars/matches + update exemplars:ex,matches:?[exemplars~ex;matches+1;0]from info + } -// Update responsibility matrix -/* dmp = damping coefficient -/* info = dictionary containing exemplars and matches, similarity, availability and responsibility matrices -/. r > returns updated responsibility matrix +// @kind function +// @category private +// @fileoverview Update responsibility matrix +// @param dmp {float} Damping coefficient +// @param info {dict} Exemplars and matches, similarity, availability and +// responsibility matrices +// @return {float[][]} Updated responsibility matrix clust.i.updr:{[dmp;info] - // create matrix with every points max responsibility - diagonal becomes -inf, current max is becomes second max - mx:{[x;i]@[count[x]#mx;j;:;]max@[x;i,j:x?mx:max x;:;-0w]}'[sum info`s`a;til count info`r]; - // calculate new responsibility - (dmp*info`r)+(1-dmp)*info[`s]-mx} + // create matrix with every points max responsibility + // diagonal becomes -inf, current max is becomes second max + mxresp:{[x;i]@[count[x]#mx;j;:;]max@[x;i,j:x?mx:max x;:;-0w]}; + mx:mxresp'[sum info`s`a;til count info`r]; + // calculate new responsibility + (dmp*info`r)+(1-dmp)*info[`s]-mx + } -// Update availability matrix -/* dmp = damping coefficient -/* info = dictionary containing exemplars and matches, similarity, availability and responsibility matrices -/. r > returns updated availability matrix +// @kind function +// @category private +// @fileoverview Update availability matrix +// @param dmp {float} Damping coefficient +// @param info {dict} Exemplars and matches, similarity, availability and +// responsibility matrices +// @return {float[][]} Returns updated availability matrix clust.i.upda:{[dmp;info] - // sum values in positive availability matrix - s:sum@[;;:;0f]'[pv:0|info`r;k:til n:count info`a]; - // create a matrix using the negative values produced by the availability sum + responsibility diagonal - positive availability values - a:@[;;:;]'[0&(s+info[`r]@'k)-/:pv;k;s]; - // calculate new availability - (dmp*info`a)+a*1-dmp} + // sum values in positive availability matrix + s:sum@[;;:;0f]'[pv:0|info`r;k:til n:count info`a]; + // create a matrix using the negative values produced by the availability sum + // + responsibility diagonal - positive availability values + a:@[;;:;]'[0&(s+info[`r]@'k)-/:pv;k;s]; + // calculate new availability + (dmp*info`a)+a*1-dmp + } diff --git a/clust/dbscan.q b/clust/dbscan.q index eb9dace7..0f212d80 100644 --- a/clust/dbscan.q +++ b/clust/dbscan.q @@ -1,36 +1,121 @@ \d .ml -// DBSCAN algorithm -/* data = data points in `value flip` format -/* df = distance function -/* minpts = minimum number of points in epsilon radius -/* eps = epsilon radius to search -/. r > returns list of clusters -clust.dbscan:{[data;df;minpts;eps] - // check distance function - if[not df in key clust.i.dd;clust.i.err.dd[]]; - // calculate distances and find all points which are not outliers - nbhood:clust.i.nbhood["f"$data;df;eps]each til count data 0; - // update outlier cluster to null - t:update cluster:0N,corepoint:minpts<=1+count each nbhood from([]nbhood); - // find cluster for remaining points and return list of clusters - exec cluster from {[t]any t`corepoint}clust.i.dbalgo/t} - -// Find all points which are not outliers -/* data = data points in `value flip` format -/* df = distance function -/* eps = epsilon radius to search -/* idx = index of current point -/. r > returns indices of points within the epsilon radius -clust.i.nbhood:{[data;df;eps;idx]where eps>@[;idx;:;0w]clust.i.dd[df]data-data[;idx]} - -// Run DBSCAN algorithm and update cluster of each point -/* t = cluster info table -/. r > returns updated cluster table with old clusters merged -clust.i.dbalgo:{[t]update cluster:0|1+max t`cluster,corepoint:0b from t where i in .ml.clust.i.nbhoodidxs[t]/[first where t`corepoint]} - -// Find indices in each points neighborhood -/* t = cluster info table -/* idxs = indices to search neighborhood of -/. r > returns list of indices in neighborhood -clust.i.nbhoodidxs:{[t;idxs]asc distinct idxs,raze exec nbhood from t[distinct idxs,raze t[idxs]`nbhood]where corepoint} +// Density-Based Spatial Clustering of Applications with Noise (DBSCAN) + +// @kind function +// @category clust +// @fileoverview Fit DBSCAN algorithm to data +// @param data {float[][]} Points in `value flip` format +// @param df {fn} Distance function +// @param minpts {long} Minimum number of points in epsilon radius +// @param eps {float} Epsilon radius to search +// @return {long[]} List of clusters +clust.dbscan.fit:{[data;df;minpts;eps] + // check distance function + if[not df in key clust.i.dd;clust.i.err.dd[]]; + // create neighbourhood table + t:clust.i.nbhoodtab[data;df;minpts;eps;til count data 0]; + // find cluster for remaining points and return list of clusters + clt:-1^exec cluster from t:{[t]any t`corepoint}clust.i.dbalgo/t; + // return config dict + `data`df`minpts`eps`clt`t!(data;df;minpts;eps;clt;t) + } + +// @kind function +// @category clust +// @fileoverview Predict clusters using DBSCAN config +// @param data {float[][]} Points in `value flip` format +// @param cfg {dict} `data`df`minpts`eps`clt returned from DBSCAN +// clustered training data +// @return {long[]} List of predicted clusters +clust.dbscan.predict:{[data;cfg] + // predict new clusters + -1^exec cluster from clust.i.dbscanpredict[data;cfg] + } + +// @kind function +// @category clust +// @fileoverview Update DBSCAN config including new data points +// @param data {float[][]} Points in `value flip` format +// @param cfg {dict} `data`df`minpts`eps`clt`nbh returned from DBSCAN +// clustered training data +// @return {dict} Updated model config +clust.dbscan.update:{[data;cfg] + // predict new clusters + rtst:clust.i.dbscanpredict[data;cfg]; + rtrn:update corepoint:1b from cfg[`t]where cluster<>0N; + // include test points in training neighbourhood + rtrn:{[trn;tst;idx] + update nbhood:{x,'y}[nbhood;idx]from trn where i in tst`nbhood + }/[rtrn;rtst;count[rtrn]+til count rtst]; + // update clusters + t:{[t]any t`corepoint}.ml.clust.i.dbalgo/rtrn,rtst; + // start clusters from 0 + t:update{(d!til count d:distinct x)x}cluster from t where cluster<>0N; + // return updated config + cfg,`data`t`clt!(cfg[`data],'data;t;-1^exec cluster from t) + } + +// @kind function +// @category private +// @fileoverview Predict clusters using DBSCAN config +// @param data {float[][]} Points in `value flip` format +// @param cfg {dict} `data`df`minpts`eps`clt returned from DBSCAN +// clustered training data +// @return {long[]} Cluster table +clust.i.dbscanpredict:{[data;cfg] + idx:count[cfg[`data]0]+til count data 0; + // create neighbourhood table + t:clust.i.nbhoodtab[cfg[`data],'data;;;;idx]. cfg`df`minpts`eps; + // find which existing clusters new data belongs to + update cluster:{x[`clt]first y}[cfg]each nbhood from t where corepoint + } + +// @kind function +// @category private +// @fileoverview Create neighbourhood table for points at indices provided +// @param data {float[][]} Points in `value flip` format +// @param df {fn} Distance function +// @param minpts {long} Minimum number of points in epsilon radius +// @param eps {float} Epsilon radius to search +// @param idx {long[]} Data indices to find neighbourhood for +// @return {table} Neighbourhood table with `nbhood`cluster`corepoint +clust.i.nbhoodtab:{[data;df;minpts;eps;idx] + // calculate distances and find all points which are not outliers + nbhood:clust.i.nbhood[data;df;eps]each idx; + // update outlier cluster to null + update cluster:0N,corepoint:minpts<=1+count each nbhood from([]nbhood) + } + +// @kind function +// @category private +// @fileoverview Find all points which are not outliers +// @param data {float[][]} Points in `value flip` format +// @param df {fn} Distance function +// @param eps {float} Epsilon radius to search +// @param idx {long} Index of current point +// @return {long[]} Indices of points within the epsilon radius +clust.i.nbhood:{[data;df;eps;idx] + where eps>@[;idx;:;0w]clust.i.dd[df]data-data[;idx] + } + +// @kind function +// @category private +// @fileoverview Run DBSCAN algorithm and update cluster of each point +// @param t {table} Cluster info table +// @return {table} Updated cluster table with old clusters merged +clust.i.dbalgo:{[t] + nbh:.ml.clust.i.nbhoodidxs[t]/[first where t`corepoint]; + update cluster:0|1+max t`cluster,corepoint:0b from t where i in nbh + } + +// @kind function +// @category private +// @fileoverview Find indices in each points neighborhood +// @param t {table} Cluster info table +// @param idxs {long[]} Indices to search neighborhood of +// @return {long[]} Indices in neighborhood +clust.i.nbhoodidxs:{[t;idxs] + nbh:exec nbhood from t[distinct idxs,raze t[idxs]`nbhood]where corepoint; + asc distinct idxs,raze nbh + } diff --git a/clust/hierarchical.q b/clust/hierarchical.q index 6cd24852..b38eb14f 100644 --- a/clust/hierarchical.q +++ b/clust/hierarchical.q @@ -1,296 +1,383 @@ \d .ml -// CURE algorithm -/* data = data points in `value flip` format -/* df = distance function -/* n = number of representative points per cluster -/* c = compression factor for representative points -/. r > return a dendrogram table +// Clustering Using REpresentative points (CURE) + +// @kind function +// @category clust +// @fileoverview CURE algorithm +// @param data {float[][]} Points in `value flip` format +// @param df {fn} Distance function +// @param n {long} Number of representative points per cluster +// @param c {float} Compression factor for representative points +// @return {table} Dendrogram clust.cure:{[data;df;n;c] - if[not df in key clust.i.dd;clust.i.err.dd[]]; - clust.hcscc["f"$data;df;`cure;1;n;c;1b]} + if[not df in key clust.i.dd;clust.i.err.dd[]]; + clust.hcscc["f"$data;df;`cure;1;n;c;1b] + } -// Hierarchical Clustering -/* data = data points in `value flip` format -/* df = distance function -/* lf = linkage function -/. r > return a dendrogram table +// @kind function +// @category clust +// @fileoverview Hierarchical Clustering +// @param data {float[][]} Points in `value flip` format +// @param df {fn} Distance function +// @param lf {fn} Linkage function +// @return {table} Dendrogram clust.hc:{[data;df;lf] - // check distance and linkage functions - if[not df in key clust.i.dd;clust.i.err.dd[]]; - if[not lf in key clust.i.ld;clust.i.err.ld[]]; - if[lf in`complete`average`ward;:clust.hccaw["f"$data;df;lf;2;1b]]; - if[lf in`single`centroid;:clust.hcscc["f"$data;df;lf;1;::;::;1b]];} + // check distance and linkage functions + if[not df in key clust.i.dd;clust.i.err.dd[]]; + if[not lf in key clust.i.ld;clust.i.err.ld[]]; + if[lf in`complete`average`ward;:clust.hccaw["f"$data;df;lf;2;1b]]; + if[lf in`single`centroid;:clust.hcscc["f"$data;df;lf;1;::;::;1b]]; + } -// Complete, Average, Ward (CAW) Linkage -/* data = data points in `value flip` format -/* df = distance function -/* lf = linkage function -/* k = number of clusters -/* dgram = boolean indicating whether to make a dendrogram or not (1b/0b) -/. r > return dendrogram or list of clusters +// @kind function +// @category clust +// @fileoverview Complete, Average, Ward (CAW) Linkage +// @param data {float[][]} Points in `value flip` format +// @param df {fn} Distance function +// @param lf {fn} Linkage function +// @param k {long} Number of clusters +// @param dgram {bool} Generate dendrogram or not (1b/0b) +// @return {table/long[]} Dendrogram or list of clusters clust.hccaw:{[data;df;lf;k;dgram] - // check distance function for ward - if[(not df~`e2dist)&lf=`ward;clust.i.err.ward[]]; - // create initial cluster table - t0:clust.i.initcaw[data;df]; - // create linkage matrix - m:([]i1:`int$();i2:`int$();dist:`float$();n:`int$()); - // merge clusters based on chosen algorithm - r:{[k;r]k return list of clusters +// @kind function +// @category clust +// @fileoverview Single, Centroid, Cure (SCC) Linkage +// @param data {float[][]} Points in `value flip` format +// @param df {fn} Distance function +// @param lf {fn} Linkage function +// @param k {long} Number of clusters +// @param n {long} Number of representative points per cluster +// @param c {float} Compression factor for representative points +// @param dgram {bool} Generate dendrogram or not (1b/0b) +// @return {long[]} List of clusters clust.hcscc:{[data;df;lf;k;n;c;dgram] - if[(not df in `edist`e2dist)&lf=`centroid;clust.i.err.centroid[]]; - r:(count[data 0]-k).[clust.i.algoscc[data;df;lf]]/clust.i.initscc[data;df;k;n;c;dgram]; - $[dgram; - clust.i.dgramidx last[r]0; - @[;;:;]/[count[data 0]#0N;vres`points;til count vres:select from r[1]where valid]]} + if[(not df in `edist`e2dist)&lf=`centroid;clust.i.err.centroid[]]; + clustinit:clust.i.initscc[data;df;k;n;c;dgram]; + r:(count[data 0]-k).[clust.i.algoscc[data;df;lf]]/clustinit; + vres:select from r[1]where valid; + $[dgram; + clust.i.dgramidx last[r]0; + @[;;:;]/[count[data 0]#0N;vres`points;til count vres]] + } -// Convert dendrogram table to k clusters -/* t = dendrogram table -/* kval = number of clusters -/. r > returns a list of clusters +// @kind function +// @category clust +// @fileoverview Convert dendrogram table to k clusters +// @param t {table} Dendrogram +// @param kval {long} Number of clusters +// @return {long[]} List of clusters clust.hccutk:{[t;kval] - k:kval-1; - clust.i.cutdgram[t;k]} + k:kval-1; + clust.i.cutdgram[t;k] + } -// Convert dendrogram table to clusters based on distance threshold -/* t = dendrogram table -/* dthresh = cutting distance threshold -/. r > returns a list of clusters +// @kind function +// @category clust +// @fileoverview Convert dendrogram to clusters based on distance threshold +// @param t {table} Dendrogram +// @param dthresh {float} Cutting distance threshold +// @return {long[]} List of clusters clust.hccutdist:{[t;dthresh] - k:0|count[t]-exec first i from t where dist>dthresh; - clust.i.cutdgram[t;k]} + k:0|count[t]-exec first i from t where dist>dthresh; + clust.i.cutdgram[t;k] + } -// Update dendrogram for CAW with final cluster of all the points -/* t = cluster table -/* m = linkage matrix -/. r > returns updated linkage matrix +// @kind function +// @category private +// @fileoverview Update dendrogram for CAW with final cluster of all the points +// @param t {table} Cluster table +// @param m {float[][]} Linkage matrix +// @return {float[][]} Updated linkage matrix clust.i.upddgram:{[t;m] - m,:value exec first clt,first nni,first nnd,count reppt from t where nnd=min nnd; - m} + m,:value exec first clt,first nni,first nnd,count reppt from t where nnd=min nnd; + m + } -// Initialize cluster table -/* data = data points in `value flip` format -/* df = distance function -/. r > returns a table with distances, neighbors, clusters and representatives +// @kind function +// @category private +// @fileoverview Initialize cluster table +// @param data {float[][]} Points in `value flip` format +// @param df {fn} Distance function +// @return {table} Distances, neighbors, clusters and representatives clust.i.initcaw:{[data;df] - // create table with distances and nearest neighhbors noted - t:{[data;df;i]`nni`nnd!(d?m;m:min d:@[;i;:;0w]clust.i.dists[data;df;data;i])}[data;df]each til count data 0; - // update each points cluster and representatives - update clt:i,reppt:flip data from t} + // create table with distances and nearest neighhbors noted + t:{[data;df;i] + `nni`nnd!(d?m;m:min d:@[;i;:;0w]clust.i.dists[data;df;data;i]) + }[data;df]each til count data 0; + // update each points cluster and representatives + update clt:i,reppt:flip data from t + } -// CAW algo -/* data = data points in `value flip` format -/* df = distance function -/* lf = linkage function -/* l = list with cluster table and linkage matrix -/. r > returns updated l +// @kind function +// @category private +// @fileoverview CAW algo +// @param data {float[][]} Points in `value flip` format +// @param df {fn} Distance function +// @param lf {fn} Linkage function +// @param l {(table;float[][])} List with cluster table and linkage matrix +// @return {(table;float[][])} Updated l clust.i.algocaw:{[data;df;lf;l] - t:l 0;m:l 1; - // update linkage matrix - m,:value exec first clt,first nni,first nnd,count reppt from t where nnd=min nnd; - // merge closest clusters - merge:distinct value first select clt,nni from t where nnd=min nnd; - // add new cluster and reppt into table - t:update clt:1+max t`clt,reppt:count[i]#enlist sum[reppt]%count[i] from t where clt in merge; - // exec pts by cluster - cpts:exec pts:data[;i],n:count i,last reppt by clt from t; - // find points initially closest to new cluster points - chks:exec distinct clt from t where nni in merge; - // run specific algo and return updated table - t:clust.i.hcupd[lf][cpts;df;lf]/[t;chks]; - // return updated table and matrix - (t;m)} + t:l 0;m:l 1; + // update linkage matrix + m,:value exec first clt,first nni,first nnd,count reppt from t where nnd=min nnd; + // merge closest clusters + merge:distinct value first select clt,nni from t where nnd=min nnd; + // add new cluster and reppt into table + t:update clt:1+max t`clt,reppt:count[i]#enlist sum[reppt]%count[i] from t where clt in merge; + // exec pts by cluster + cpts:exec pts:data[;i],n:count i,last reppt by clt from t; + // find points initially closest to new cluster points + chks:exec distinct clt from t where nni in merge; + // run specific algo and return updated table + t:clust.i.hcupd[lf][cpts;df;lf]/[t;chks]; + // return updated table and matrix + (t;m) + } -// Complete linkage -/* cpts = points in each cluster -/* df = distance function -/* lf = linkage function -/* t = cluster table -/* chk = points to check -/. r > returns updated cluster table +// @kind function +// @category private +// @fileoverview Complete linkage +// @param cpts {float[][]} Points in each cluster +// @param df {fn} Distance function +// @param lf {fn} Linkage function +// @param t {table} Cluster table +// @param chk {long[]} Points to check +// @return {table} Updated cluster table clust.i.hcupd.complete:{[cpts;df;lf;t;chk] - // calculate cluster distances using complete method - dsts:{[df;lf;x;y]clust.i.ld[lf]raze clust.i.dd[df]x[`pts]-\:'y`pts}[df;lf;cpts chk]each cpts _ chk; - // find nearest neighbors - nidx:dsts?ndst:min dsts; - // update cluster table - update nni:nidx,nnd:ndst from t where clt=chk} + // calculate cluster distances using complete method + dsts:{[df;lf;x;y] + clust.i.ld[lf]raze clust.i.dd[df]x[`pts]-\:'y`pts + }[df;lf;cpts chk]each cpts _ chk; + // find nearest neighbors + nidx:dsts?ndst:min dsts; + // update cluster table + update nni:nidx,nnd:ndst from t where clt=chk + } -// Average linkage -/* cpts = points in each cluster -/* df = distance function -/* lf = linkage function -/* t = cluster table -/* chk = points to check -/. r > returns updated cluster table +// @kind function +// @category private +// @fileoverview Average linkage +// @param cpts {float[][]} Points in each cluster +// @param df {fn} Distance function +// @param lf {fn} Linkage function +// @param t {table} Cluster table +// @param chk {long[]} Points to check +// @return {table} Updated cluster table clust.i.hcupd.average:clust.i.hcupd.complete -// Ward linkage -/* cpts = points in each cluster -/* df = distance function -/* lf = linkage function -/* t = cluster table -/* chk = points to check -/. r > returns updated cluster table +// @kind function +// @category private +// @fileoverview Ward linkage +// @param cpts {float[][]} Points in each cluster +// @param df {fn} Distance function +// @param lf {fn} Linkage function +// @param t {table} Cluster table +// @param chk {long[]} Points to check +// @return {table} Updated cluster table clust.i.hcupd.ward:{[cpts;df;lf;t;chk] // calculate distances using ward method - dsts:{[df;lf;x;y]2*clust.i.ld[lf][x`n;y`n]clust.i.dd[df]x[`reppt]-y`reppt}[df;lf;cpts chk]each cpts _ chk; + dsts:{[df;lf;x;y] + 2*clust.i.ld[lf][x`n;y`n]clust.i.dd[df]x[`reppt]-y`reppt + }[df;lf;cpts chk]each cpts _ chk; // find nearest neighbors nidx:dsts?ndst:min dsts; // update cluster table and rep pts update nni:nidx,nnd:ndst from t where clt=chk} -// Initialize SCC clusters -/* data = data points in `value flip` format -/* df = distance function -/* k = number of clusters -/* n = number of representative points per cluster -/* c = compression factor for representative points -/. r > return list of parameters, clusters, representative points and the kdtree +// @kind function +// @category private +// @fileoverview Initialize SCC clusters +// @param data {float[][]} Points in `value flip` format +// @param df {fn} Distance function +// @param k {long} Number of clusters +// @param n {long} Number of representative points per +// cluster +// @param c {float} Compression factor for +// representative points +// @return {(dict;long[];table;table)} Parameters, clusters, representative +// points and the kdtree clust.i.initscc:{[data;df;k;n;c;dgram] - // build kdtree - kdtree:clust.kd.newtree[data]1000&ceiling .01*nd:count data 0; - // create distance table with closest clusters identified - dists:update closestClust:closestPoint from{[kdtree;data;df;i]clust.kd.nn[kdtree;data;df;i;data[;i]]}[kdtree;data;df]each til nd; - r2l:exec self idxs?til count i from select raze idxs,self:self where count each idxs from kdtree where leaf; - // create cluster table - clusts:select clusti:i,clust:i,valid:1b,reppts:enlist each i,points:enlist each i,closestDist,closestClust from dists; - // create table of representative points for each cluster - reppts:select reppt:i,clust:i,leaf:r2l,closestDist,closestClust from dists; - reppts:reppts,'flip(rpcols:`$"x",'string til count data)!data; - // create list of important parameters to carry forward - params:`k`n`c`rpcols!(k;n;c;rpcols); - lnkmat:([]i1:`int$();i2:`int$();dist:`float$();n:`int$()); - // return as a list to be passed to algos - (params;clusts;reppts;kdtree;(lnkmat;dgram))} + // build kdtree + kdtree:clust.kd.newtree[data]1000&ceiling .01*nd:count data 0; + // create distance table with closest clusters identified + dists:update closestClust:closestPoint from{[kdtree;data;df;i] + clust.kd.nn[kdtree;data;df;i;data[;i]] + }[kdtree;data;df]each til nd; + lidx:select raze idxs,self:self where count each idxs from kdtree where leaf; + r2l:exec self idxs?til count i from lidx; + // create cluster table + clusts:select clusti:i,clust:i,valid:1b,reppts:enlist each i, + points:enlist each i,closestDist,closestClust from dists; + // create table of representative points for each cluster + reppts:select reppt:i,clust:i,leaf:r2l,closestDist,closestClust from dists; + reppts:reppts,'flip(rpcols:`$"x",'string til count data)!data; + // create list of important parameters to carry forward + params:`k`n`c`rpcols!(k;n;c;rpcols); + lnkmat:([]i1:`int$();i2:`int$();dist:`float$();n:`int$()); + // return as a list to be passed to algos + (params;clusts;reppts;kdtree;(lnkmat;dgram)) + } -// Representative points for Centroid linkage -/* p = list of data points -/. r > return representative point -clust.i.centrep:{[p]enlist avg each p} +// @kind function +// @category private +// @fileoverview Representative points for Centroid linkage +// @param p {float[][]} Data points +// @return {float[]} Representative point +clust.i.centrep:{[p] + enlist avg each p + } -// Representative points for CURE -/* df = distance function -/* n = number of representative points per cluster -/* c = compression factor for representative points -/* p = list of data points -/. r > return list of representative points -clust.i.curerep:{[df;n;c;p]rpts:1_first(n&count p 0).[{[df;rpts;p] - rpts,:enlist p[;i:imax min clust.i.dd[df]each p-/:neg[1|-1+count rpts]#rpts]; - (rpts;.[p;(::;i);:;0n])}[df]]/(enlist avgpt:avg each p;p); - (rpts*1-c)+\:c*avgpt} +// @kind function +// @category private +// @fileoverview Representative points for CURE +// @param df {fn} Distance function +// @param n {long} Number of representative points per cluster +// @param c {float} Compression factor for representative points +// @param p {float[][]} List of data points +// @return {float[][]} List of representative points +clust.i.curerep:{[df;n;c;p] + rpts:1_first(n&count p 0).[{[df;rpts;p] + i:imax min clust.i.dd[df]each p-/:neg[1|-1+count rpts]#rpts; + rpts,:enlist p[;i]; + (rpts;.[p;(::;i);:;0n]) + }[df]]/(enlist avgpt:avg each p;p); + (rpts*1-c)+\:c*avgpt + } -// Update initial dendrogram structure to show path of merges so that the dendrogram can be plotted with scipy -/* dgram = dendrogram stucture produced using .ml.clust.hc[...;...;...;...;1b] -/. r > returns dendrogram +// @kind function +// @category private +// @fileoverview Update initial dendrogram structure to show path of merges so +// that the dendrogram can be plotted with scipy +// @param dgram {table} Dendrogram stucture produced using +// .ml.clust.hc[...;...;...;...;1b] +// @return {table} Updated dendrogram clust.i.dgramidx:{[dgram] - // initial cluster indices, number of merges and loop counter - cl:raze dgram`i1`i2;n:count dgram;i:0; - // increment a cluster for every occurrence in the tree - while[n>i+1;cl[where[cl=cl i]except i]:1+max cl;i+:1]; - // update dendrogram with new indices - ![dgram;();0b;`i1`i2!n cut cl]} + // initial cluster indices, number of merges and loop counter + cl:raze dgram`i1`i2;n:count dgram;i:0; + // increment a cluster for every occurrence in the tree + while[n>i+1;cl[where[cl=cl i]except i]:1+max cl;i+:1]; + // update dendrogram with new indices + ![dgram;();0b;`i1`i2!n cut cl] + } -// Convert dendrogram table to clusters -/* t = dendrogram table -/* k = define splitting value in dendrogram table -/. r > returns a list of clusters +// @kind function +// @category private +// @fileoverview Convert dendrogram table to clusters +// @param t {table} Dendrogram table +// @param k {long} Define splitting value in dendrogram table +// @return {long[]} List of clusters clust.i.cutdgram:{[t;k] - // get index of cluster made at cutting point k - idx:(2*cntt:count t)-k-1; - // exclude any clusters made after point k - exclt:i where idx>i:raze neg[k]#'allclt:t`i1`i2; - // extract indices within clusters made until k, excluding any outliers - clt:{last{count x 0}clust.i.extractclt[x;y]/(z;())}[allclt;cntt+1]each exclt except outliers:exclt where exclt<=cntt; - // update points to the cluster they belong to - @[;;:;]/[(1+cntt)#0N;clt,enlist each outliers;til k+1]} + // get index of cluster made at cutting point k + idx:(2*cntt:count t)-k-1; + // exclude any clusters made after point k + exclt:i where idx>i:raze neg[k]#'allclt:t`i1`i2; + // extract indices within clusters made until k, excluding any outliers + nout:exclt except outliers:exclt where exclt<=cntt; + clt:{last{count x 0}clust.i.extractclt[x;y]/(z;())}[allclt;cntt+1]each nout; + // update points to the cluster they belong to + @[;;:;]/[(1+cntt)#0N;clt,enlist each outliers;til k+1] + } -// Extract points within merged cluster -/* clts = list of cluster indices -/* cntt = count of dend table -/* inds = list containing index in list to search and indices points found within that cluster -/r. - returns list containing next index to search, and additional points found within cluster +// @kind function +// @category private +// @fileoverview Extract points within merged cluster +// @param clts {long[]} List of cluster indices +// @param cntt {long} Count of dend table +// @param inds {long[]} Index in list to search and indices points found within +// that cluster +// @return {long[]} Next index to search, and additional points found +// within cluster clust.i.extractclt:{[clts;cntt;inds] // extract the points that were merged at this point mrgclt:raze clts[;inds[0]-cntt]; // Store any single clts, break down clts more than single point - (mrgclt where inext;inds[1],mrgclt where not inext:mrgclt>=cntt)} + (mrgclt where inext;inds[1],mrgclt where not inext:mrgclt>=cntt) + } -// SCC algo -/* data = data points in `value flip` format -/* df = distance function -/* lf = linkage function -/* params = dictionary of parameters - k (no. clusts), n (no. reppts per clust), reppts, kdtree -/* clusts = cluster table -/* reppts = representative points and associated info -/* kdtree = k-dimensional tree storing points and distances -/. r > return list of parameters dict, clusters, representative points and kdtree tables +// @kind function +// @category private +// @fileoverview SCC algo +// @param data {float[][]} Points in `value flip` format +// @param df {fn} Distance function +// @param lf {fn} Linkage function +// @param params {dict} Parameters - k (no. clusts), n +// (no. reppts per clust), reppts, kdtree +// @param clusts {table} Cluster table +// @param reppts {float[][]} Representative points and +// associated info +// @param kdtree {table} k-dimensional tree storing +// points and distances +// @return {(dict;long[];float[][];table)} Parameters dict, clusters, +// representative points and kdtree tables clust.i.algoscc:{[data;df;lf;params;clusts;reppts;kdtree;lnkmat] - // merge closest clusters - clust0:exec clust{x?min x}closestDist from clusts where valid; - newmrg:clusts clust0,clust1:clusts[clust0]`closestClust; - newmrg:update valid:10b,reppts:(raze reppts;0#0),points:(raze points;0#0)from newmrg; -// make dendrogram if required - if[lnkmat 1;m:lnkmat 0;m,:newmrg[`clusti],fnew[`closestDist],count(fnew:first newmrg)`points;lnkmat[0]:m]; + // merge closest clusters + clust0:exec clust{x?min x}closestDist from clusts where valid; + newmrg:clusts clust0,clust1:clusts[clust0]`closestClust; + newmrg:update valid:10b,reppts:(raze reppts;0#0),points:(raze points;0#0)from newmrg; - // keep track of old reppts - oldrep:reppts newmrg[0]`reppts; - // find reps in new cluster - $[sgl:lf~`single; - // for single new reps=old reps -> no new points calculated - newrep:select reppt,clust:clust0 from oldrep; - // if centroid reps=avg, if cure=calc reps - [newrep:flip params[`rpcols]!flip$[lf~`centroid;clust.i.centrep;clust.i.curerep[df;params`n;params`c]]data[;newmrg[0]`points]; - newrep:update clust:clust0,reppt:count[i]#newmrg[0]`reppts from newrep; - // new rep leaves - newrep[`leaf]:(clust.kd.findleaf[kdtree;;kdtree 0]each flip newrep params`rpcols)`self; - newmrg[0;`reppts]:newrep`reppt; - // delete old points from leaf and update new point to new rep leaf - kdtree:.[kdtree;(oldrep`leaf;`idxs);except;oldrep`reppt]; - kdtree:.[kdtree;(newrep`leaf;`idxs);union ;newrep`reppt]; - ]]; - // update clusters and reppts - clusts:@[clusts;newmrg`clust;,;delete clust from newmrg]; - reppts:@[reppts;newrep`reppt;,;delete reppt from newrep]; - - updrep:reppts newrep`reppt; - // nneighbour to clust - if[sgl;updrep:select from updrep where closestClust in newmrg`clust]; - updrep:updrep,'clust.kd.nn[kdtree;reppts params`rpcols;df;newmrg[0]`points]each flip updrep params`rpcols; - updrep:update closestClust:reppts[closestPoint;`clust]from updrep; + // make dendrogram if required + if[lnkmat 1; + m:lnkmat 0; + m,:newmrg[`clusti],fnew[`closestDist],count(fnew:first newmrg)`points; + lnkmat[0]:m]; - if[sgl; - reppts:@[reppts;updrep`reppt;,;select closestDist,closestClust from updrep]; + // keep track of old reppts + oldrep:reppts newmrg[0]`reppts; + // find reps in new cluster + $[sgl:lf~`single; + // for single new reps=old reps -> no new points calculated + newrep:select reppt,clust:clust0 from oldrep; + // if centroid reps=avg, if cure=calc reps + [newrep:flip params[`rpcols]!flip$[lf~`centroid;clust.i.centrep;clust.i.curerep[df;params`n;params`c]]data[;newmrg[0]`points]; + newrep:update clust:clust0,reppt:count[i]#newmrg[0]`reppts from newrep; + // new rep leaves + newrep[`leaf]:(clust.kd.findleaf[kdtree;;kdtree 0]each flip newrep params`rpcols)`self; + newmrg[0;`reppts]:newrep`reppt; + // delete old points from leaf and update new point to new rep leaf + kdtree:.[kdtree;(oldrep`leaf;`idxs);except;oldrep`reppt]; + kdtree:.[kdtree;(newrep`leaf;`idxs);union ;newrep`reppt]]]; + // update clusters and reppts + clusts:@[clusts;newmrg`clust;,;delete clust from newmrg]; + reppts:@[reppts;newrep`reppt;,;delete reppt from newrep]; + updrep:reppts newrep`reppt; - ]; - // update nneighbour of new clust - updrep@:raze imin updrep`closestDist; - clusts:@[clusts;updrep`clust;,;`closestDist`closestClust#updrep]; + // nneighbour to clust + if[sgl;updrep:select from updrep where closestClust in newmrg`clust]; + updrep:updrep,'clust.kd.nn[kdtree;reppts params`rpcols;df;newmrg[0]`points]each flip updrep params`rpcols; + updrep:update closestClust:reppts[closestPoint;`clust]from updrep; + + if[sgl; + reppts:@[reppts;updrep`reppt;,;select closestDist,closestClust from updrep]; + updrep:reppts newrep`reppt]; + // update nneighbour of new clust + updrep@:raze imin updrep`closestDist; + clusts:@[clusts;updrep`clust;,;`closestDist`closestClust#updrep]; + + $[sgl; + // single - nneighbour=new clust + [clusts:update closestClust:clust0 from clusts where valid,closestClust=clust1; + reppts:update closestClust:clust0 from reppts where closestClust=clust1]; + // else do nneighbour search + if[count updcls:select from clusts where valid,closestClust in(clust0;clust1); + updcls:updcls,'{x imin x`closestDist}each clust.kd.nn[kdtree;reppts params`rpcols;df]/:' + [updcls`reppts;flip each reppts[updcls`reppts]@\:params`rpcols]; + updcls[`closestClust]:reppts[updcls`closestPoint]`clust; + clusts:@[clusts;updcls`clust;,;select closestDist,closestClust from updcls]]]; - $[sgl; - // single - nneighbour=new clust - [clusts:update closestClust:clust0 from clusts where valid,closestClust=clust1; - reppts:update closestClust:clust0 from reppts where closestClust=clust1]; - // else do nneighbour search - if[count updcls:select from clusts where valid,closestClust in(clust0;clust1); - updcls:updcls,'{x imin x`closestDist}each clust.kd.nn[kdtree;reppts params`rpcols;df]/:' - [updcls`reppts;flip each reppts[updcls`reppts]@\:params`rpcols]; - updcls[`closestClust]:reppts[updcls`closestPoint]`clust; - clusts:@[clusts;updcls`clust;,;select closestDist,closestClust from updcls]; - ]]; + (params;clusts;reppts;kdtree;lnkmat) - (params;clusts;reppts;kdtree;lnkmat)} + } diff --git a/clust/kdtree.q b/clust/kdtree.q index d90b2245..9467ec70 100644 --- a/clust/kdtree.q +++ b/clust/kdtree.q @@ -1,79 +1,128 @@ \d .ml -// Create new k-d tree -/* data = data points in `value flip` format -/* leafsz = number of points per leaf (<2*number of representatives) -/. r > returns k-d tree structure as a table -clust.kd.newtree:{[data;leafsz]clust.kd.i.tree[data;leafsz]`leaf`left`parent`self`idxs!(0b;0b;0N;0;til count data 0)} +// K-Dimensional (k-d) Tree -// Find nearest neighhbors in k-d tree -/* tree = k-d tree table -/* data = data points in `value flip` format -/* df = distance function -/* xidxs = points to exclude in search -/* pt = point to find nearest neighbor for -/. r > returns nearest neighbor dictionary with closest point, distance, points searched and points to search +// @kind function +// @category clust +// @fileoverview Create new k-d tree +// @param data {float[][]} Points in `value flip` format +// @param leafsz {long} Number of points per leaf (<2*number of reppts) +// @return {table} k-d tree +clust.kd.newtree:{[data;leafsz] + args:`leaf`left`parent`self`idxs!(0b;0b;0N;0;til count data 0); + clust.kd.i.tree[data;leafsz]args + } + +// @kind function +// @category clust +// @fileoverview Find nearest neighhbors in k-d tree +// @param tree {table} k-d tree +// @param data {float[][]} Points in `value flip` format +// @param df {fn} Distance function +// @param xidxs {long[][]} Points to exclude in search +// @param pt {long[]} Point to find nearest neighbor for +// @return {dict} Nearest neighbor dictionary with closest point, +// distance, points searched and points to search clust.kd.q.nn:clust.kd.nn:{[tree;data;df;xidxs;pt] - start:`closestPoint`closestDist`xnodes`node!(0N;0w;0#0;clust.kd.findleaf[tree;pt;tree 0]); - 2#{[nninfo]not null nninfo[`node;`self]}clust.kd.i.nncheck[tree;data;df;xidxs;pt]/start} + nninit:(0N;0w;0#0;clust.kd.findleaf[tree;pt;tree 0]); + start:`closestPoint`closestDist`xnodes`node!nninit; + stop:{[nninfo]not null nninfo[`node;`self]}; + 2#stop clust.kd.i.nncheck[tree;data;df;xidxs;pt]/start + } -// Create tree table where each row represents a node -/* data = data points in `value flip` format -/* leafsz = number of points per leaf (<2*number of representatives) -/* node = dictionary with info for a given node in the tree -/. r > returns kdtree table +// @kind function +// @category private +// @fileoverview Create tree table where each row represents a node +// @param data {float[][]} Points in `value flip` format +// @param leafsz {long} Points per leaf (<2*number of representatives) +// @param node {dict} Info for a given node in the tree +// @return {table} k-d tree table clust.kd.i.tree:{[data;leafsz;node] - if[leafsz<=.5*count node`idxs; - chk:xdata returns updated nearest neighbor info dictionary +// @kind function +// @category private +// @fileoverview Search each node and check nearest neighbors +// @param tree {table} k-d tree table +// @param data {float[][]} Points in `value flip` format +// @param df {fn} Distance function +// @param xidxs {long[][]} Points to exclude in search +// @param pt {long[]} Point to find nearest neighbor for +// @param nninfo {dict} Nearest neighbor info of a point +// @return {dict} Updated nearest neighbor info clust.kd.i.nncheck:{[tree;data;df;xidxs;pt;nninfo] - if[nninfo[`node]`leaf; - closest:clust.i.closest[data;df;pt]nninfo[`node;`idxs]except xidxs; - if[closest[`distance] returns next direction to take -clust.kd.i.findnext:{[tree;pt;node]tree node[`children]node[`midval]<=pt node`axis} +// @kind function +// @category private +// @fileoverview Find the leaf node point belongs to +// @param tree {table} k-d tree table +// @param pt {float[]} Current point to put in tree +// @param node {dict} Current node to check +// @return {dict} Leaf node pt belongs to +clust.kd.q.findleaf:clust.kd.findleaf:{[tree;pt;node] + {[node]not node`leaf}clust.kd.i.findnext[tree;pt]/node + } -// Find the leaf node point belongs to -/* tree = k-d tree table -/* pt = current point to put in tree -/* node = current node to check -/. r > returns dictionary of leaf node pt belongs to -clust.kd.q.findleaf:clust.kd.findleaf:{[tree;pt;node]{[node]not node`leaf}clust.kd.i.findnext[tree;pt]/node} +// @kind function +// @category private +// @fileoverview Sets k-d tree q or C functions +// @param b {bool} Type of code to use q or C (1/0b) +// @return {null} No return. Updates nn and findleaf functions. +clust.kd.qC:{[b] + clust.kd.c.nn:.[2:;(`:kdnn;(`kd_nn;5));::]; + clust.kd.c.findleaf:.[2:;(`:kdnn;(`kd_findleaf;3));::]; + cnn:{[tree;data;df;xidxs;pt] + args:(tree;"f"$data;(1_key clust.i.dd)?df;@[count[data 0]#0b;xidxs;:;1b];"f"$pt); + `closestPoint`closestDist!clust.kd.c.nn . args + }; + cfl:{[tree;point;node] + tree clust.kd.c.findleaf[tree;"f"$point;node`self] + }; + fntyp:not(112=type clust.kd.c.nn)&112=type clust.kd.c.findleaf; + clust.kd[`nn`findleaf]:$[b|fntyp;(clust.kd.q.nn;clust.kd.q.findleaf);(cnn;cfl)] + } -// K-D tree C functions -/* b = type of code to use q or C -clust.kd.qC:{[b] clust.kd[`nn`findleaf]: - $[b|not ((112=type clust.kd.c.findleaf:.[2:;(`:kdnn;(`kd_findleaf;3));::])&112=type clust.kd.c.nn:.[2:;(`:kdnn;(`kd_nn;5));::]); - (clust.kd.q.nn;clust.kd.q.findleaf); - ({[tree;data;df;xidxs;pt]`closestPoint`closestDist!clust.kd.c.nn[tree;"f"$data;(1_key clust.i.dd)?df;@[count[data 0]#0b;xidxs;:;1b];"f"$pt]}; - {[tree;point;node]tree clust.kd.c.findleaf[tree;"f"$point;node`self]})]}; +// Default to C implementations clust.kd.qC[0b]; diff --git a/clust/kmeans.q b/clust/kmeans.q index fb852232..c5937acf 100644 --- a/clust/kmeans.q +++ b/clust/kmeans.q @@ -1,47 +1,130 @@ \d .ml -// K-Means algorithm -/* data = data points in `value flip` format -/* df = distance function -/* k = number of clusters -/* iter = number of iterations -/* kpp = boolean indicating whether to use random initialization (`0b`) or k-means++ (`1b`) -clust.kmeans:{[data;df;k;iter;kpp] - // check distance function - if[not df in`e2dist`edist;clust.i.err.kmeans[]]; - // initialize representative points - reppts0:$[kpp;clust.i.initkpp df;clust.i.initrdm][data;k]; - // run algo `iter` times - reppts1:iter{[data;df;reppt]{[data;j]avg each data[;j]}[data]each value group clust.i.getclust[data;df;reppt]}[data;df]/reppts0; - // return list of clusters - clust.i.getclust[data;df;reppts1]} - -// Calculate final representative points -/* data = data points in `value flip` format -/* df = distance function -/* reppts = representative points of each cluster -/. r > return list of clusters -clust.i.getclust:{[data;df;reppts]max til[count dist]*dist=\:min dist:{[data;df;reppt]clust.i.dd[df]reppt-data}[data;df]each reppts} - -// Random initialization of representative points -/* data = data points in `value flip` format -/* k = number of clusters -/. r > returns k representative points -clust.i.initrdm:{[data;k]flip data[;neg[k]?count data 0]} - -// K-Means++ initialization of representative points -/* df = distance function -/* data = data points in `value flip` format -/* k = number of clusters -/. r > returns k representative points +// K-Means + +// @kind function +// @category clust +// @fileoverview Fit k-Means algorithm to data +// @param data {float[][]} Points in `value flip` format +// @param df {fn} Distance function +// @param k {long} Number of clusters +// @param iter {long} Number of iterations +// @param kpp {bool} Use kmeans++ or random initialization (1/0b) +// @return {dict} Model config `data`df`reppts`clt where data +// and df are the inputs, reppts are the calculated k means and clt +// are the associated clusters +clust.kmeans.fit:{[data;df;k;iter;kpp] + // fit algo to data + r:clust.i.kmeans[data;df;k;iter;kpp]; + // return config with new clusters + r,`data`df!(data;df) + } + +// @kind function +// @category clust +// @fileoverview Predict clusters using k-means config +// @param data {float[][]} Points in `value flip` format +// @param cfg {dict} `data`df`reppts`clt returned from kmeans +// clustered training data +// @return {long[]} List of predicted clusters +clust.kmeans.predict:{[data;cfg] + // get new clusters based on latest config + clust.i.getclust[data]. cfg`df`reppts + } + +// @kind function +// @category clust +// @fileoverview Update kmeans config including new data points +// @param data {float[][]} Points in `value flip` format +// @param cfg {dict} `data`df`reppts`clt returned from kmeans +// clustered on training data +// @return {dict} Updated model config +clust.kmeans.update:{[data;cfg] + // update data to include new points + cfg[`data]:cfg[`data],'data; + // update k means + cfg[`reppts]:clust.i.updcentres . cfg`data`df`reppts; + // get updated clusters based on new means + cfg[`clt]:clust.i.getclust . cfg`data`df`reppts; + // return updated config + cfg + } + +// @kind function +// @category clust +// @fileoverview K-Means algorithm +// @param data {float[][]} Points in `value flip` format +// @param df {fn} Distance function +// @param k {long} Number of clusters +// @param iter {long} Number of iterations +// @param kpp {bool} Use kmeans++ or random initialization (1/0b) +// @return {dict} Clusters or reppts depending on rep +clust.i.kmeans:{[data;df;k;iter;kpp] + // check distance function + if[not df in`e2dist`edist;clust.i.err.kmeans[]]; + // initialize representative points + reppts0:$[kpp;clust.i.initkpp df;clust.i.initrdm][data;k]; + // run algo `iter` times + reppts1:iter clust.i.updcentres[data;df]/reppts0; + // return representative points and clusters + `reppts`clt!(reppts1;clust.i.getclust[data;df;reppts1]) + } + +// @kind function +// @category private +// @fileoverview Update cluster centres +// @param data {float[][]} Points in `value flip` format +// @param df {fn} Distance function +// @return {float[][]} Updated representative points +clust.i.updcentres:{[data;df;reppt] + {[data;j] + avg each data[;j] + }[data]each value group clust.i.getclust[data;df;reppt] + } + +// @kind function +// @category private +// @fileoverview Calculate final representative points +// @param data {float[][]} Points in `value flip` format +// @param df {fn} Distance function +// @param reppts {float[]} Representative points of each cluster +// @return {long} List of clusters +clust.i.getclust:{[data;df;reppts] + dist:{[data;df;reppt]clust.i.dd[df]reppt-data}[data;df]each reppts; + max til[count dist]*dist=\:min dist + } + +// @kind function +// @category private +// @fileoverview Random initialization of representative points +// @param data {float[][]} Points in `value flip` format +// @param k {long} Number of clusters +// @return {float[][]} k representative points +clust.i.initrdm:{[data;k] + flip data[;neg[k]?count data 0] + } + +// @kind function +// @category private +// @fileoverview K-Means++ initialization of representative points +// @param df {fn} Distance function +// @param data {float[][]} Points in `value flip` format +// @param k {long} Number of clusters +// @return {float[][]} k representative points clust.i.initkpp:{[df;data;k] - info0:`point`dists!(data[;rand count data 0];0w); - infos:(k-1)clust.i.kpp[data;df]\info0; - infos`point} - -// K-Means++ algorithm -/* data = data points in `value flip` format -/* df = distance function -/* info = dictionary with points and distance info -/. r > returns updated info dictionary -clust.i.kpp:{[data;df;info]@[info;`point;:;data[;s binr rand last s:sums info[`dists]&:clust.i.dists[data;df;info`point;::]]]} + info0:`point`dists!(data[;rand count data 0];0w); + infos:(k-1)clust.i.kpp[data;df]\info0; + infos`point + } + +// @kind function +// @category private +// @fileoverview K-Means++ algorithm +// @param data {float[][]} Points in `value flip` format +// @param df {fn} Distance function +// @param info {dict} Points and distance info +// @return {dict} Updated info dictionary +clust.i.kpp:{[data;df;info] + s:sums info[`dists]&:clust.i.dists[data;df;info`point;::]; + @[info;`point;:;data[;s binr rand last s]] + } diff --git a/clust/score.q b/clust/score.q index eb5fd8f8..5c02bd5f 100644 --- a/clust/score.q +++ b/clust/score.q @@ -1,84 +1,132 @@ -// load in toolkit utilities for confmat +// Cluster Scoring Algorithms + +// Load in toolkit utilities for confmat .ml.loadfile`:util/init.q \d .ml // Unsupervised Learning -// Davies-Bouldin index - Euclidean distance only (edist) -/* data = data points in `value flip` format -/* clt = list of clusters produced by .ml.clust algos +// @kind function +// @category clust +// @fileoverview Davies-Bouldin index - Euclidean distance only (edist) +// @param data {float[][]} Points in `value flip` format +// @param clt {long[]} List of clusters produced by .ml.clust algos +// @return {float} Davies Bouldin index of clt clust.daviesbouldin:{[data;clt] - s:avg each clust.i.dists[;`edist;;::]'[p;a:avg@''p:{x[;y]}[data]each group clt]; - (sum{[s;a;x;y]max(s[y]+s e)%'clust.i.dists[flip a e:x _y;`edist;a y;::]}[s;a;t]each t:til n)%n:count a} + a:avg@''p:{x[;y]}[data]each group clt; + s:avg each clust.i.dists[;`edist;;::]'[p;a]; + db:{[s;a;x;y]max(s[y]+s e)%'clust.i.dists[flip a e:x _y;`edist;a y;::]}; + (sum db[s;a;t]each t:til n)%n:count a + } -// Dunn index -/* data = data points in `value flip` format -/* df = distance function -/* clt = list of clusters produced by .ml.clust algos +// @kind function +// @category clust +// @fileoverview Dunn index +// @param data {float[][]} Points in `value flip` format +// @param df {fn} Distance function +// @param clt {long[]} List of clusters produced by .ml.clust algos +// @return {float} Dunn index of clt clust.dunn:{[data;df;clt] - mx:clust.i.maxintra[df]each p:{x[;y]}[data]each group clt; - mn:min raze clust.i.mininter[df;p]each -2_({1_x}\)til count p; - mn%max raze mx} + mx:clust.i.maxintra[df]each p:{x[;y]}[data]each group clt; + mn:min raze clust.i.mininter[df;p]each -2_({1_x}\)til count p; + mn%max raze mx + } -// Silhouette score -/* data = data points in `value flip` format -/* df = distance function -/* clt = list of clusters produced by .ml.clust algos -/* isavg = boolean indicating whether to return a list of scores or the average score +// @kind function +// @category clust +// @fileoverview Silhouette score +// @param data {float[][]} Points in `value flip` format +// @param df {fn} Distance function +// @param clt {long[]} List of clusters produced by .ml.clust algos +// @param isavg {bool} List of scores or the average score (1/0b) +// @return {float} Silhouette score of clt clust.silhouette:{[data;df;clt;isavg] - $[isavg;avg;]clust.i.sil[data;df;group clt;1%(count each group clt)-1]'[clt;flip data]} + k:1%(count each group clt)-1; + $[isavg;avg;]clust.i.sil[data;df;group clt;k]'[clt;flip data] + } // Supervised Learning -// Homogeneity Score -/* x = predicted cluster labels -/* y = true cluster labels +// @kind function +// @category clust +// @fileoverview Homogeneity Score +// @param pred {long[]} Predicted cluster labels +// @param true {long[]} True cluster labels +// @return {float} Homogeneity score for true clust.homogeneity:{[pred;true] - if[count[pred]<>n:count true;'`$"distinct lengths - lenght of lists has to be the same"]; - if[not e:clust.i.entropy true;:1.]; - cm:value confmat[pred;true]; - nm:(*\:/:).((count each group@)each(pred;true))@\:til count cm; - mi:(sum/)0^cm*.[-;log(n*cm;nm)]%n; - mi%e} + if[count[pred]<>n:count true; + '`$"distinct lengths - lenght of lists has to be the same"]; + if[not e:clust.i.entropy true;:1.]; + cm:value confmat[pred;true]; + nm:(*\:/:).((count each group@)each(pred;true))@\:til count cm; + mi:(sum/)0^cm*.[-;log(n*cm;nm)]%n; + mi%e + } // Optimum number of clusters -// Elbow method -/* data = data points in `value flip` format -/* df = distance function -/* k = maximum number of clusters +// @kind function +// @category clust +// @fileoverview Elbow method +// @param data {float[][]} Points in `value flip` format +// @param df {fn} Distance function +// @param k {long} Max number of clusters +// @return {float[]} Score for each k value - plot to find elbow clust.elbow:{[data;df;k] - {[data;df;k] - sum raze clust.i.dists[;df;;::]'[p;a:avg@''p:{x[;y]}[data]each group clust.kmeans[data;df;k;100;1b]] - }[data;df]each 2+til k-1} + {[data;df;k] + clt:clust.kmeans[data;df;k;100;1b]; + sum raze clust.i.dists[;df;;::]'[p;a:avg@''p:{x[;y]}[data]each group clt] + }[data;df]each 2+til k-1 + } // Utilities -// Entropy -/* x = distribution -clust.i.entropy:{neg sum(p%n)*(-). log(p;n:sum p:count each group x)} +// @kind function +// @category private +// @fileoverview Entropy +// @param d {long[]} distribution +// @return {float} Entropy for d +clust.i.entropy:{[d] + neg sum(p%n)*(-). log(p;n:sum p:count each group d) + } -// Maximum intra-cluster distance -/* df = distance function -/* pts = data points in `value flip` format -clust.i.maxintra:{[df;pts] - max raze{[df;pts;x;y]clust.i.dists[pts;df;pts[;y];x except til 1+y]}[df;pts;n]each n:til count first pts} +// @kind function +// @category private +// @fileoverview Maximum intra-cluster distance +// @param df {fn} Distance function +// @param data {float[][]} Points in `value flip` format +// @return {float} Max intra-cluster distance +clust.i.maxintra:{[df;data] + max raze{[df;data;x;y] + clust.i.dists[data;df;data[;y];x except til 1+y] + }[df;data;n]each n:til count first data + } -// Minimum inter-cluster distance -/* df = distance function -/* pts = data points in `value flip` format -/* idxs = cluster indices -clust.i.mininter:{[df;pts;idxs] - {[df;pts;i;j](min/)clust.i.dists[pts[i];df;pts[j]]each til count pts[i]0}[df;pts;first idxs]each 1_idxs} +// @kind function +// @category private +// @fileoverview Minimum inter-cluster distance +// @param df {fn} Distance function +// @param data {float[][]} Points in `value flip` format +// @param idxs {long[]} Cluster indices +// @return {float} Min inter-cluster distance +clust.i.mininter:{[df;data;idxs] + {[df;data;i;j] + (min/)clust.i.dists[data[i];df;data[j]]each til count data[i]0 + }[df;data;first idxs]each 1_idxs + } -// Silhouette coefficient -/* data = data points in `value flip` format -/* df = distance function -/* idxs = point indices grouped by cluster -/* k = coefficient to multiply by -/* clt = cluster of the current point -/* pt = current point +// @kind function +// @category private +// @fileoverview Silhouette coefficient +// @param data {float[][]} Points in `value flip` format +// @param df {fn} Distance function +// @param idxs {dict} Point indices grouped by cluster +// @param k {float} Coefficient to multiply by +// @param clt {long} Cluster of current point +// @param pt {float} Current point +// @return {float} Silhouette coefficent for pt clust.i.sil:{[data;df;idxs;k;clt;pt] - d:clust.i.dists[data;df;pt]each idxs; - (%).((-).;max)@\:(min avg each;k[clt]*sum@)@'d@/:(key[idxs]except clt;clt)} + d:clust.i.dists[data;df;pt]each idxs; + (%).((-).;max)@\:(min avg each;k[clt]*sum@)@'d@/:(key[idxs]except clt;clt) + } diff --git a/clust/util.q b/clust/util.q index 5b05e1fa..a0cfa9c7 100644 --- a/clust/util.q +++ b/clust/util.q @@ -1,13 +1,57 @@ \d .ml +// Clustering Utilities + // Distance metric dictionary -clust.i.dd.edist:{sqrt x wsum x} -clust.i.dd.e2dist:{x wsum x} -clust.i.dd.mdist:{sum abs x} -clust.i.dd.cshev:{min abs x} -clust.i.dd.nege2dist:{neg x wsum x} -// Linkage dictionary +// @kind function +// @category private +// @fileoverview Euclidean distance calculation +// @param data {float[][]} Points +// @return {float[]} Euclidean distances for data +clust.i.dd.edist:{[data] + sqrt data wsum data + } + +// @kind function +// @category private +// @fileoverview distance calculation +// @param data {float[][]} Points +// @return {float[]} Euclidean squared distances for data +clust.i.dd.e2dist:{[data] + data wsum data + } + +// @kind function +// @category private +// @fileoverview Manhattan distance calculation +// @param data {float[][]} Points +// @return {float[]} Manhattan distances for data +clust.i.dd.mdist:{[data] + sum abs data + } + +// @kind function +// @category private +// @fileoverview Chebyshev distance calculation +// @param data {float[][]} Points +// @return {float[]} Chebyshev distances for data +clust.i.dd.cshev:{[data] + min abs data + } + +// @kind function +// @category private +// @fileoverview Negative euclidean squared distance calculation +// @param data {float[][]} Points +// @return {float[]} Negative euclidean squared distances for data +clust.i.dd.nege2dist:{[data] + neg data wsum data + } + +// @kind dictionary +// @category private +// @fileoverview Linkage dictionary clust.i.ld.single:min clust.i.ld.complete:max clust.i.ld.average:avg @@ -15,13 +59,41 @@ clust.i.ld.centroid:raze clust.i.ld.ward:{z*x*y%x+y} // Distance calculations -clust.i.dists:{[data;df;pt;idxs]clust.i.dd[df]pt-data[;idxs]} -clust.i.closest:{[data;df;pt;idxs]`point`distance!(idxs dists?md;md:min dists:clust.i.dists[data;df;pt;idxs])} -// Index functions -clust.i.reindex:{distinct[x]?x} +// @kind function +// @category private +// @param data {float[][]} Points in `value flip` format +// @param df {fn} Distance function +// @param pt {float[]} Current point +// @param idxs {long[]} Indices from data +// @return {float[]} Distances for data and pt +clust.i.dists:{[data;df;pt;idxs] + clust.i.dd[df]pt-data[;idxs] + } + +// @kind function +// @category private +// @param data {float[][]} Points in `value flip` format +// @param df {fn} Distance function +// @param pt {float[]} Current point +// @param idxs {long[]} Indices from data +// @return {float[]} Distances for data and pt +clust.i.closest:{[data;df;pt;idxs] + `point`distance!(idxs dists?md;md:min dists:clust.i.dists[data;df;pt;idxs]) + } + +// @kind function +// @category private +// @fileoverview Reindex exemplars +// @param data {#any[]} Data points +// @return {long[]} List of indices +clust.i.reindex:{ + distinct[data]?data + } -// Error dictionary +// @kind dictionary +// @category private +// @fileoverview Error dictionary clust.i.err.dd:{'`$"invalid distance metric"} clust.i.err.ld:{'`$"invalid linkage"} clust.i.err.ward:{'`$"ward must be used with e2dist"} From 9890dd1365bad5d47cf6a94ec063b6f8f3e4dc70 Mon Sep 17 00:00:00 2001 From: Conor McCarthy Date: Mon, 24 Aug 2020 20:55:06 +0100 Subject: [PATCH 24/77] minor update to graph.t to increase converage and explicitly test the output error not just that function errors --- graph/tests/graph.t | 78 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 61 insertions(+), 17 deletions(-) diff --git a/graph/tests/graph.t b/graph/tests/graph.t index 52c6aa27..dd841d7e 100644 --- a/graph/tests/graph.t +++ b/graph/tests/graph.t @@ -1,5 +1,5 @@ // The functions contained in this file are intended in the first instance to show cases -// which will fail to produce a valid/operable graphs/pipelines in order to ensure that the +// which will fail to produce a valid/operational graph/pipeline in order to ensure that the // catching mechanism for the creation of such workflows is reliable and fully understood \l p.q @@ -7,48 +7,92 @@ \l graph/graph.q \l graph/pipeline.q +// Test utilities +failingTest:{[function;data;applyType;error] + // Is function to be applied unary or multivariable + applyType:$[applyType;@;.]; + // Failure capture function + failureFunction:{[err;ret](`TestFailing;err~ret)}[error;]; + error:applyType[function;data;failureFunction]; + $[`TestFailing~first error;last error;0b] + } + +passingTest:{[function;data;applyType;expectedReturn] + // Is function to be applied unary or multivariable + applyType:$[applyType;@;.]; + // Failure capture function + failureFunction:{[err]0N!err;(`TestFailing;0b)}; + functionReturn:applyType[function;data;failureFunction]; + if[`TestFailing~first functionReturn;:0b]; + expectedReturn~functionReturn + } + + g:.ml.createGraph[] g:.ml.addCfg[g;`cfg1]`test`config!(til 10;5000) g:.ml.addNode[g;`node1]`function`inputs`outputs!({x};"f";"!") -// Attempt to add a node and configuration with the same name again -0b~$[(::)~@[{.ml.addCfg[x;y]`a`b!1 2}[g;];`cfg1;{[err]err;0b}];1b;0b] -0b~$[(::)~@[{.ml.addNode[x;y]`function`inputs`outputs!({x};"f";"!")}[g;];`testnode1;{[err]err;0b}];1b;0b] +-1"\nTesting addNode/addCfg"; + +// Attempt to add a configuration with a non unique name +nodeConfig:`a`b!1 2 +failingTest[.ml.addCfg;(g;`cfg1;nodeConfig);0b;"invalid nodeId"] + +// Attempt to add a node with a non unique name +nodeConfig:`function`inputs`outputs!({x};"f";"!") +failingTest[.ml.addNode;(g;`node1;nodeConfig);0b;"invalid nodeId"] + +// Attempt to add a node with an addition node configuration key +nodeConfig:`function`inputs`outputs`extrakey!({x};"f";"!";1f) +failingTest[.ml.addNode;(g;`failingNode;nodeConfig);0b;"invalid node"] -// Connect an invalid edge between 2 nodes and check non validity +// Attempt to add a node with an unsuitable configuration input +nodeConfig:`function`inputs`outputs!({x};enlist 1f;"!") +failingTest[.ml.addNode;(g;`failingNode;nodeConfig);0b;"invalid inputs"] + +// Attempt to add a node with an unsuitable configuration output +nodeConfig:`function`inputs`outputs!({x};"f";enlist 1f) +failingTest[.ml.addNode;(g;`failingNode;nodeConfig);0b;"invalid outputs"] + +-1"\nTesting updNode/updCfg"; +// Attempt to update + + +-1"\nTesting connectEdge/disconnectEdge"; +// Connect an invalid edge between 2 nodes and check that this is not valid g:.ml.connectEdge[g;`cfg1;`output;`node1;`input] 0b~first exec valid from g[`edges] where dstNode=`node1,dstName=`input g:.ml.disconnectEdge[g;`node1;`input] -// Attempt to disconnect an unconnected edge -0b~$[(::)~@[{.ml.disconnectEdge[x;y;z]}[g;`node1];`input;{[err]err;0b}];1b;0b] +// Attempt to disconnect a node that doesn't exist +failingTest[.ml.disconnectEdge;(g;`node;`input);0b;"invalid edge"] + +// Attempt to disconnect an edge that doesn't exist on a valid node +failingTest[.ml.disconnectEdge;(g;`node1;`test);0b;"invalid edge"] -// Attempt to delete a node that has not been populated -0b~$[(::)~@[{.ml.delNode[x;y]}[g;];`notanode;{[err]err;0b}];1b;0b] +-1"\nTesting delNode"; +// Attempt to delete a node that does not exist +failingTest[.ml.delNode;(g;`notanode);0b;"invalid nodeId"] -// Update node1 such that it is valid for connection to cfg1, function errors on execution (for pipeline testing) +// Update node1 such that it is valid for connection to cfg1, +// but function errors on execution (for pipeline testing) g:.ml.updNode[g;`node1]`function`inputs`outputs!({`e+1};"!";"!") g:.ml.connectEdge[g;`cfg1;`output;`node1;`input] 1b~first exec valid from g[`edges] where dstNode=`node1,dstName=`input -// Create a second configuration and attempt to connect this to node1 (invalid due to previous connection) -g:.ml.addCfg[g;`cfg2]`test`config!10 20 -0b~$[(::)~@[{.ml.connectEdge[x;y;`output;`node1;`input]}[g;];`input;{[err]err;0b}];1b;0b] - p:.ml.createPipeline[g] // Execute pipeline with debug functionality inactive (returns a table with non complete rows) not all exec complete from .ml.execPipeline[p] // Check current debug status, update status and check update success +-1"\nTesting graph debug"; not .ml.graphDebug .ml.updDebug[] .ml.graphDebug // Execute pipeline catching error with debug status activated -0b~$[(::)~@[{.ml.execPipeline[x]};p;{[err]err;0b}];1b;0b] +failingTest[.ml.execPipeline;p;1b;"type"] -// Update delete cfg2, update node1 such that execution is valid and check validity of execution -g:.ml.delCfg[g;`cfg2] g:.ml.updNode[g;`node1]`function`inputs`outputs!({x};"!";"!") p1:.ml.createPipeline[g] all exec complete from .ml.execPipeline[p1] From 7e4cde1f065a6e956bab44af798f3d8db4348091 Mon Sep 17 00:00:00 2001 From: Conor McCarthy Date: Mon, 24 Aug 2020 21:10:34 +0100 Subject: [PATCH 25/77] addition of node update failing functions --- graph/tests/graph.t | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/graph/tests/graph.t b/graph/tests/graph.t index dd841d7e..6271c704 100644 --- a/graph/tests/graph.t +++ b/graph/tests/graph.t @@ -28,34 +28,50 @@ passingTest:{[function;data;applyType;expectedReturn] } +// Generate a graph with a number of basic nodes g:.ml.createGraph[] g:.ml.addCfg[g;`cfg1]`test`config!(til 10;5000) g:.ml.addNode[g;`node1]`function`inputs`outputs!({x};"f";"!") +// Valid node configuration +nodeConfig:`function`inputs`outputs!({x};"f";"!") + +// Configuration nodes with various issues +extraKey:`function`inputs`outputs`extrakey!({x};"f";"!";1f) +inputType:`function`inputs`outputs!({x};enlist 1f;"!") +outputType:`function`inputs`outputs!({x};"f";enlist 1f) + + -1"\nTesting addNode/addCfg"; // Attempt to add a configuration with a non unique name -nodeConfig:`a`b!1 2 -failingTest[.ml.addCfg;(g;`cfg1;nodeConfig);0b;"invalid nodeId"] +failingTest[.ml.addCfg;(g;`cfg1;`a`b!1 2);0b;"invalid nodeId"] // Attempt to add a node with a non unique name -nodeConfig:`function`inputs`outputs!({x};"f";"!") failingTest[.ml.addNode;(g;`node1;nodeConfig);0b;"invalid nodeId"] // Attempt to add a node with an addition node configuration key -nodeConfig:`function`inputs`outputs`extrakey!({x};"f";"!";1f) -failingTest[.ml.addNode;(g;`failingNode;nodeConfig);0b;"invalid node"] +failingTest[.ml.addNode;(g;`failingNode;extraKey);0b;"invalid node"] // Attempt to add a node with an unsuitable configuration input -nodeConfig:`function`inputs`outputs!({x};enlist 1f;"!") -failingTest[.ml.addNode;(g;`failingNode;nodeConfig);0b;"invalid inputs"] +failingTest[.ml.addNode;(g;`failingNode;inputType);0b;"invalid inputs"] // Attempt to add a node with an unsuitable configuration output -nodeConfig:`function`inputs`outputs!({x};"f";enlist 1f) -failingTest[.ml.addNode;(g;`failingNode;nodeConfig);0b;"invalid outputs"] +failingTest[.ml.addNode;(g;`failingNode;outputType);0b;"invalid outputs"] + -1"\nTesting updNode/updCfg"; -// Attempt to update +// Attempt to update a node which does not exist +failingTest[.ml.updNode;(g;`notanode;nodeConfig);0b;"invalid nodeId"] + +// Attempt to update a node with an additional node configuration key +failingTest[.ml.updNode;(g;`node1;extraKey);0b;"invalid node"] + +// Attempt to update a node with an unsuitable configuration input +failingTest[.ml.updNode;(g;`node1;inputType);0b;"invalid inputs"] + +// Attempt to update a nodee with an unsuitable configuration output +failingTest[.ml.updNode;(g;`node1;outputType);0b;"invalid outputs"] -1"\nTesting connectEdge/disconnectEdge"; From a07dbabcd9af13f89150baecbf6a8a4d6e2e9d83 Mon Sep 17 00:00:00 2001 From: Dianeod Date: Wed, 26 Aug 2020 16:31:12 +0100 Subject: [PATCH 26/77] added labelencode function and tests to preproc --- util/preproc.q | 3 +++ util/tests/preproctst.t | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/util/preproc.q b/util/preproc.q index 8a3b403e..0c8fba45 100644 --- a/util/preproc.q +++ b/util/preproc.q @@ -60,6 +60,9 @@ lexiencode:{[x;c] if[(::)~c;c:i.fndcols[x;"s"]]; flip(c _ flip x),(`$string[c],\:"_lexi")!{(asc distinct x)?x}each x c,:()} +// Encode the target data to be integer values which are computer readable +labelencode:{(asc distinct x)?x} + / split temporal types into constituents i.timesplit.d:{update wd:1 Date: Thu, 27 Aug 2020 16:14:46 +0100 Subject: [PATCH 27/77] update to labelencode functionality and addition of application function for this --- graph/tests/graph.t | 2 ++ util/preproc.q | 17 +++++++++++++++-- util/tests/preproctst.t | 20 +++++++++++++++----- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/graph/tests/graph.t b/graph/tests/graph.t index 6271c704..09bfc67c 100644 --- a/graph/tests/graph.t +++ b/graph/tests/graph.t @@ -61,6 +61,7 @@ failingTest[.ml.addNode;(g;`failingNode;outputType);0b;"invalid outputs"] -1"\nTesting updNode/updCfg"; + // Attempt to update a node which does not exist failingTest[.ml.updNode;(g;`notanode;nodeConfig);0b;"invalid nodeId"] @@ -75,6 +76,7 @@ failingTest[.ml.updNode;(g;`node1;outputType);0b;"invalid outputs"] -1"\nTesting connectEdge/disconnectEdge"; + // Connect an invalid edge between 2 nodes and check that this is not valid g:.ml.connectEdge[g;`cfg1;`output;`node1;`input] 0b~first exec valid from g[`edges] where dstNode=`node1,dstName=`input diff --git a/util/preproc.q b/util/preproc.q index 0c8fba45..16129bd6 100644 --- a/util/preproc.q +++ b/util/preproc.q @@ -60,8 +60,21 @@ lexiencode:{[x;c] if[(::)~c;c:i.fndcols[x;"s"]]; flip(c _ flip x),(`$string[c],\:"_lexi")!{(asc distinct x)?x}each x c,:()} -// Encode the target data to be integer values which are computer readable -labelencode:{(asc distinct x)?x} +// Encode the a dataset to a list of integers, and provide a mapping allowing a user to +// revert new integer lists to the original version +/* x = data to be encoded and mapped +labelencode:{[x] + adx:asc distinct x; + `mapping`encoding!(adx!til count adx;adx?x) + } + +// Map a list of integers to their true representation based on a label encoding schema +/* x = data to be revert to true representation based on +/* y = label encoding map either labelencode[x]`mapping or labelencode[x] +applylabelencode:{[x;y] + if[99h<>type y;'"Input must be a dictionary"]; + $[`mapping`encoding~key y;y[`mapping]?;y?]x + } / split temporal types into constituents i.timesplit.d:{update wd:1 Date: Wed, 2 Sep 2020 14:41:00 +0100 Subject: [PATCH 28/77] new fit/predict/update structure --- clust/aprop.q | 64 ++++++++++++---- clust/hierarchical.q | 178 ++++++++++++++++++++++++++++++++++--------- clust/kdtree.q | 8 +- clust/util.q | 2 +- 4 files changed, 198 insertions(+), 54 deletions(-) diff --git a/clust/aprop.q b/clust/aprop.q index dddbdf8a..bfd5e2ea 100644 --- a/clust/aprop.q +++ b/clust/aprop.q @@ -4,22 +4,57 @@ // @kind function // @category clust -// @fileoverview Affinity propagation algorithm +// @fileoverview Fit affinity propagation algorithm // @param data {float[][]} Points in `value flip` format // @param df {fn} Distance function // @param dmp {float} Damping coefficient // @param diag {fn} Similarity matrix diagonal value function // @return {long[]} List of clusters -clust.ap:{[data;df;dmp;diag] - // check distance function and diagonal value +clust.ap.fit:{[data;df;dmp;diag] + clust.i.runap[data;df;dmp;diag;til count data 0;(::)] + } + +// @kind function +// @category clust +// @fileoverview Predict clusters using AP config +// @param data {float[][]} Points in `value flip` format +// @param cfg {dict} `data`df`reppts`clt returned from kmeans +// clustered training data +// @return {long[]} List of predicted clusters +clust.ap.predict:{[data;cfg] + neg[count data 0]#clust.ap.update[data;cfg]`clt + } + +// @kind function +// @category clust +// @fileoverview Update AP config including new data points +// @param data {float[][]} Points in `value flip` format +// @param cfg {dict} `data`df`reppts`clt returned from kmeans +// clustered on training data +// @return {dict} Updated model config +clust.ap.update:{[data;cfg] + clust.ap.fit[cfg[`data],'data]. cfg`df`dmp`diag + } + +// @kind function +// @category private +// @fileoverview Run affinity propagation algorithm +// @param data {float[][]} Points in `value flip` format +// @param df {fn} Distance function +// @param dmp {float} Damping coefficient +// @param diag {fn} Similarity matrix diagonal value function +// @param idxs {long[]} List of indicies to find distances for +// @param s0 {float[][]} Old similarity matrix (can be (::) for new run) +// @return {long[]} List of clusters +clust.i.runap:{[data;df;dmp;diag;idxs;s0] + // check valid distance function has been given if[not df in key clust.i.dd;clust.i.err.dd[]]; - // create initial table with exemplars/matches and similarity, availability - // and responsibility matrices - info0:clust.i.apinit["f"$data;df;diag]; - // run AP algo until there is no change in results over `0.1*count data` runs - info1:{[iter;info]iter>info`matches}[.1*count data]clust.i.apalgo[dmp]/info0; - // return list of clusters - clust.i.reindex info1`exemplars + // calculate distances, availability and responsibility + info0:clust.i.apinit[data;df;diag;idxs;s0]; + // update info to find clusters + info1:{[iter;info]iter>info`matches}[.2*count data]clust.i.apalgo[dmp]/info0; + // return config + `data`df`dmp`diag`info0`info1`clt!(data;df;dmp;diag;info0;info1;clust.i.reindex info1`exemplars) } // @kind function @@ -30,10 +65,13 @@ clust.ap:{[data;df;dmp;diag] // @param diag {fn} Similarity matrix diagonal value function // @return {dict} Similarity, availability and responsibility matrices // and keys for matches and exemplars to be filled during further iterations -clust.i.apinit:{[data;df;diag] +clust.i.apinit:{[data;df;diag;idxs;s0] // calculate similarity matrix values - s:clust.i.dists[data;df;data]each k:til n:count data 0; - s:@[;;:;diag raze s]'[s;k]; + s:clust.i.dists[data;df;data]each idxs; + // if adding new points, add new similarity onto old + if[not s0~(::);s:(s0,'count[s0]#flip s),s]; + // update diagonal + s:@[;;:;diag raze s]'[s;k:til n:count data 0]; // create lists/matrices of zeros for other variables `matches`exemplars`s`a`r!(0;0#0;s),(2;n;n)#0f } diff --git a/clust/hierarchical.q b/clust/hierarchical.q index b38eb14f..7ce6c2a8 100644 --- a/clust/hierarchical.q +++ b/clust/hierarchical.q @@ -1,35 +1,149 @@ \d .ml -// Clustering Using REpresentative points (CURE) +// Clustering Using REpresentatives (CURE) and Hierarchical Clustering // @kind function // @category clust -// @fileoverview CURE algorithm +// @fileoverview Fit CURE algorithm to data // @param data {float[][]} Points in `value flip` format // @param df {fn} Distance function // @param n {long} Number of representative points per cluster // @param c {float} Compression factor for representative points // @return {table} Dendrogram -clust.cure:{[data;df;n;c] +clust.cure.fit:{[data;df;n;c] if[not df in key clust.i.dd;clust.i.err.dd[]]; - clust.hcscc["f"$data;df;`cure;1;n;c;1b] + r:clust.hcscc["f"$data;df;`cure;1;n;c;1b]; + r,`data`df`n`c!(data;df;n;c) } // @kind function // @category clust -// @fileoverview Hierarchical Clustering +// @fileoverview Fit Hierarchical algorithms to data // @param data {float[][]} Points in `value flip` format // @param df {fn} Distance function // @param lf {fn} Linkage function // @return {table} Dendrogram -clust.hc:{[data;df;lf] +clust.hc.fit:{[data;df;lf] // check distance and linkage functions if[not df in key clust.i.dd;clust.i.err.dd[]]; if[not lf in key clust.i.ld;clust.i.err.ld[]]; - if[lf in`complete`average`ward;:clust.hccaw["f"$data;df;lf;2;1b]]; - if[lf in`single`centroid;:clust.hcscc["f"$data;df;lf;1;::;::;1b]]; + if[lf in`complete`average`ward;r:clust.hccaw["f"$data;df;lf;2;1b]]; + if[lf in`single`centroid;r:clust.hcscc["f"$data;df;lf;1;::;::;1b]]; + r,`data`df`lf!(data;df;lf) } +// @kind function +// @category clust +// @fileoverview Convert CURE dendrogram table to k clusters +// @param dgram {table} Dendrogram +// @param k {long} Number of clusters +// @return {long[]} List of clusters +clust.cure.cutk:{[cfg;k] + cfg,enlist[`clt]!enlist clust.i.cutdgram[cfg`dgram;k-1] + } + +// @kind function +// @category clust +// @fileoverview Convert hierarchical dendrogram table to k clusters +// @param dgram {table} Dendrogram +// @param k {long} Number of clusters +// @return {long[]} List of clusters +clust.hc.cutk:clust.cure.cutk + +// @kind function +// @category clust +// @fileoverview Convert CURE dendrogram to clusters based on distance threshold +// @param dgram {table} Dendrogram +// @param dthresh {float} Cutting distance threshold +// @return {long[]} List of clusters +clust.cure.cutdist:{[dgram;dthresh] + dgram:cfg`dgram; + k:0|count[dgram]-exec first i from dgram where dist>dthresh; + cfg,enlist[`clt]!enlist clust.i.cutdgram[dgram;k] + } + +// @kind function +// @category clust +// @fileoverview Convert CURE dendrogram to clusters based on distance threshold +// @param dgram {table} Dendrogram +// @param dthresh {float} Cutting distance threshold +// @return {long[]} List of clusters +clust.hc.cutdist:clust.cure.cutdist + +// @kind function +// @category private +// @fileoverview Predict clusters using hierarchical or CURE config +// @param data {float[][]} Points in `value flip` format +// @param cfg {dict} dict output of .ml.clust.(hc/CURE).(cutk/cutdist) +// @param ns {symbol} Namespace to use - `hc or `cure +// @return {long[]} List of predicted clusters +clust.i.hccpred:{[ns;data;cfg] + // check correct namespace and clusters given + if[not ns in`hc`cure; + '"Incorrect namespace - please use `hc or `cure"]; + if[not`clt in key cfg; + '"Clusters must be contained within cfg - please run .ml.clust.", + $[ns~`hc;"hc";"cure"],".(cutk/cutdist)"]; + // add namespace and linkage to config dictionary for cure + if[ns~`cure;cfg,:`ns`lf!(ns;`single)]; + // recalculate reppts for training clusters + reppt:clust.i.getrep[cfg]each value group cfg`clt; + // training indicies + idxs:til each c:count each reppt[;0]; + // return closest clusters to testing points + clust.i.predclosest[data;cfg;reppt;c;idxs]each til count data 0 + } + +// @kind function +// @category private +// @fileoverview Recalculate representative points from training clusters +// @param cfg {dict} Dict output of .ml.clust.(hc/CURE).(cutk/cutdist) +// @param idxs {long[][]} Training data indices +// @return {float[][]} Training data points +clust.i.getrep:{[cfg;idxs] + $[cfg[`ns]~`cure; + flip(clust.i.curerep . cfg`df`n`c)::; + cfg[`lf]in`ward`centroid; + enlist each avg each;]cfg[`data][;idxs] + } + +// @kind function +// @category private +// @fileoverview Predict new cluster for given data point +// @param data {float[][]} Points in `value flip` format +// @param cfg {dict} dict output of .ml.clust.(hc/CURE).(cutk/cutdist) +// @param reppt {float[][]} Representative points in matrix format +// @param c {long} Number of points in training clusters +// @param cltidx {long[][]} Training data indices +// @param ptidx {long[][]} Index of current data point +// @return {long[]} List of predicted clusters +clust.i.predclosest:{[data;cfg;reppt;c;cltidx;ptidx] + // intra cluster distances + dist:.ml.clust.i.dists[;cfg`df;data[;ptidx];]'[reppt;cltidx]; + // apply linkage + dist:$[`ward~cfg`lf; + 2*clust.i.ld[cfg`lf][1]'[c;dist]; + clust.i.ld[cfg`lf]each dist]; + // find closest cluster + dist?ndst:min dist + } + +// @kind function +// @category clust +// @fileoverview Predict clusters using CURE config +// @param data {float[][]} Points in `value flip` format +// @param cfg {dict} `data`df`n`c`clt returned from .ml.clust.cure.cutk/cutdist +// @return {long[]} List of predicted clusters +clust.cure.predict:clust.i.hccpred[`cure] + +// @kind function +// @category clust +// @fileoverview Predict clusters using hierarchical config +// @param data {float[][]} Points in `value flip` format +// @param cfg {dict} `data`df`lf`clt returned from .ml.clust.hc.cutk/cutdist +// @return {long[]} List of predicted clusters +clust.hc.predict:clust.i.hccpred[`hc] + // @kind function // @category clust // @fileoverview Complete, Average, Ward (CAW) Linkage @@ -49,7 +163,9 @@ clust.hccaw:{[data;df;lf;k;dgram] // merge clusters based on chosen algorithm r:{[k;r]kdthresh; - clust.i.cutdgram[t;k] - } +// Utilities // @kind function // @category private @@ -114,13 +210,23 @@ clust.i.upddgram:{[t;m] // @return {table} Distances, neighbors, clusters and representatives clust.i.initcaw:{[data;df] // create table with distances and nearest neighhbors noted - t:{[data;df;i] - `nni`nnd!(d?m;m:min d:@[;i;:;0w]clust.i.dists[data;df;data;i]) - }[data;df]each til count data 0; + t:clust.i.nncaw[data;df;data]each til count data 0; // update each points cluster and representatives update clt:i,reppt:flip data from t } +// @kind function +// @category private +// @fileoverview Find nearest neighbour index and distance +// @param data {float[][]} Points in `value flip` format +// @param df {fn} Distance function +// @param pt {float[][]} Points in `value flip` format +// @param idxs {long} Index of point in pt to find nn for +// @return {dict} Index of and distance to nn +clust.i.nncaw:{[data;df;pt;idxs] + `nni`nnd!(d?m;m:min d:@[;idxs;:;0w]clust.i.dists[data;df;pt;idxs]) + } + // @kind function // @category private // @fileoverview CAW algo diff --git a/clust/kdtree.q b/clust/kdtree.q index 9467ec70..d21ba1f2 100644 --- a/clust/kdtree.q +++ b/clust/kdtree.q @@ -41,14 +41,14 @@ clust.kd.i.tree:{[data;leafsz;node] if[leafsz<=.5*count node`idxs; chk:xdata Date: Wed, 9 Sep 2020 17:36:48 +0100 Subject: [PATCH 29/77] addition of time series and optimization functionality for oct 7th release --- .travis.yml | 4 +- build/package.bat | 2 +- build/test.bat | 2 +- docker/Dockerfile | 5 +- init.q | 3 +- optimize/init.q | 3 + optimize/optim.q | 659 +++++++++++++++++ optimize/tests/test.t | 3 + timeseries/fit.q | 136 ++++ timeseries/init.q | 7 + timeseries/misc.q | 113 +++ timeseries/predict.q | 98 +++ timeseries/tests/data/fit/AR1 | Bin 0 -> 109 bytes timeseries/tests/data/fit/AR2 | Bin 0 -> 973 bytes timeseries/tests/data/fit/AR3 | Bin 0 -> 949 bytes timeseries/tests/data/fit/AR4 | Bin 0 -> 981 bytes timeseries/tests/data/fit/ARCH1 | Bin 0 -> 152 bytes timeseries/tests/data/fit/ARCH2 | Bin 0 -> 104 bytes timeseries/tests/data/fit/ARIMA1 | Bin 0 -> 327 bytes timeseries/tests/data/fit/ARIMA2 | Bin 0 -> 1479 bytes timeseries/tests/data/fit/ARIMA3 | Bin 0 -> 1535 bytes timeseries/tests/data/fit/ARIMA4 | Bin 0 -> 1519 bytes timeseries/tests/data/fit/ARMA1 | Bin 0 -> 299 bytes timeseries/tests/data/fit/ARMA2 | Bin 0 -> 1483 bytes timeseries/tests/data/fit/ARMA3 | Bin 0 -> 1451 bytes timeseries/tests/data/fit/ARMA4 | Bin 0 -> 1547 bytes timeseries/tests/data/fit/SARIMA1 | Bin 0 -> 591 bytes timeseries/tests/data/fit/SARIMA2 | Bin 0 -> 1879 bytes timeseries/tests/data/fit/SARIMA3 | Bin 0 -> 2607 bytes timeseries/tests/data/fit/SARIMA4 | Bin 0 -> 1967 bytes timeseries/tests/data/fit/nonStat | Bin 0 -> 96 bytes timeseries/tests/data/misc/aicScore1 | Bin 0 -> 68 bytes timeseries/tests/data/misc/aicScore2 | Bin 0 -> 68 bytes timeseries/tests/data/misc/aicScore3 | Bin 0 -> 68 bytes timeseries/tests/data/misc/aicScore4 | Bin 0 -> 68 bytes timeseries/tests/data/misc/lagTab1 | Bin 0 -> 64112 bytes timeseries/tests/data/misc/lagTab2 | Bin 0 -> 64112 bytes timeseries/tests/data/misc/stationalityTab1 | Bin 0 -> 184 bytes timeseries/tests/data/misc/stationalityTab2 | Bin 0 -> 184 bytes timeseries/tests/data/misc/windowTab1 | Bin 0 -> 231725 bytes timeseries/tests/data/misc/windowTab2 | Bin 0 -> 71977 bytes timeseries/tests/data/pred/predAR1 | Bin 0 -> 8016 bytes timeseries/tests/data/pred/predAR2 | Bin 0 -> 8016 bytes timeseries/tests/data/pred/predAR3 | Bin 0 -> 8016 bytes timeseries/tests/data/pred/predAR4 | Bin 0 -> 8016 bytes timeseries/tests/data/pred/predARCH1 | Bin 0 -> 8016 bytes timeseries/tests/data/pred/predARCH2 | Bin 0 -> 8016 bytes timeseries/tests/data/pred/predARIMA1 | Bin 0 -> 8016 bytes timeseries/tests/data/pred/predARIMA2 | Bin 0 -> 8016 bytes timeseries/tests/data/pred/predARIMA3 | Bin 0 -> 8016 bytes timeseries/tests/data/pred/predARIMA4 | Bin 0 -> 8016 bytes timeseries/tests/data/pred/predARMA1 | Bin 0 -> 8016 bytes timeseries/tests/data/pred/predARMA2 | Bin 0 -> 8016 bytes timeseries/tests/data/pred/predARMA3 | Bin 0 -> 8016 bytes timeseries/tests/data/pred/predARMA4 | Bin 0 -> 8016 bytes timeseries/tests/data/pred/predSARIMA1 | Bin 0 -> 8016 bytes timeseries/tests/data/pred/predSARIMA2 | Bin 0 -> 8016 bytes timeseries/tests/data/pred/predSARIMA3 | Bin 0 -> 8016 bytes timeseries/tests/data/pred/predSARIMA4 | Bin 0 -> 8016 bytes timeseries/tests/failMessage.q | 18 + timeseries/tests/fit.t | 74 ++ timeseries/tests/misc.t | 73 ++ timeseries/tests/pred.t | 69 ++ timeseries/utils.q | 770 ++++++++++++++++++++ 64 files changed, 2033 insertions(+), 6 deletions(-) create mode 100644 optimize/init.q create mode 100644 optimize/optim.q create mode 100644 optimize/tests/test.t create mode 100644 timeseries/fit.q create mode 100644 timeseries/init.q create mode 100644 timeseries/misc.q create mode 100644 timeseries/predict.q create mode 100644 timeseries/tests/data/fit/AR1 create mode 100644 timeseries/tests/data/fit/AR2 create mode 100644 timeseries/tests/data/fit/AR3 create mode 100644 timeseries/tests/data/fit/AR4 create mode 100644 timeseries/tests/data/fit/ARCH1 create mode 100644 timeseries/tests/data/fit/ARCH2 create mode 100644 timeseries/tests/data/fit/ARIMA1 create mode 100644 timeseries/tests/data/fit/ARIMA2 create mode 100644 timeseries/tests/data/fit/ARIMA3 create mode 100644 timeseries/tests/data/fit/ARIMA4 create mode 100644 timeseries/tests/data/fit/ARMA1 create mode 100644 timeseries/tests/data/fit/ARMA2 create mode 100644 timeseries/tests/data/fit/ARMA3 create mode 100644 timeseries/tests/data/fit/ARMA4 create mode 100644 timeseries/tests/data/fit/SARIMA1 create mode 100644 timeseries/tests/data/fit/SARIMA2 create mode 100644 timeseries/tests/data/fit/SARIMA3 create mode 100644 timeseries/tests/data/fit/SARIMA4 create mode 100644 timeseries/tests/data/fit/nonStat create mode 100644 timeseries/tests/data/misc/aicScore1 create mode 100644 timeseries/tests/data/misc/aicScore2 create mode 100644 timeseries/tests/data/misc/aicScore3 create mode 100644 timeseries/tests/data/misc/aicScore4 create mode 100644 timeseries/tests/data/misc/lagTab1 create mode 100644 timeseries/tests/data/misc/lagTab2 create mode 100644 timeseries/tests/data/misc/stationalityTab1 create mode 100644 timeseries/tests/data/misc/stationalityTab2 create mode 100644 timeseries/tests/data/misc/windowTab1 create mode 100644 timeseries/tests/data/misc/windowTab2 create mode 100644 timeseries/tests/data/pred/predAR1 create mode 100644 timeseries/tests/data/pred/predAR2 create mode 100644 timeseries/tests/data/pred/predAR3 create mode 100644 timeseries/tests/data/pred/predAR4 create mode 100644 timeseries/tests/data/pred/predARCH1 create mode 100644 timeseries/tests/data/pred/predARCH2 create mode 100644 timeseries/tests/data/pred/predARIMA1 create mode 100644 timeseries/tests/data/pred/predARIMA2 create mode 100644 timeseries/tests/data/pred/predARIMA3 create mode 100644 timeseries/tests/data/pred/predARIMA4 create mode 100644 timeseries/tests/data/pred/predARMA1 create mode 100644 timeseries/tests/data/pred/predARMA2 create mode 100644 timeseries/tests/data/pred/predARMA3 create mode 100644 timeseries/tests/data/pred/predARMA4 create mode 100644 timeseries/tests/data/pred/predSARIMA1 create mode 100644 timeseries/tests/data/pred/predSARIMA2 create mode 100644 timeseries/tests/data/pred/predSARIMA3 create mode 100644 timeseries/tests/data/pred/predSARIMA4 create mode 100644 timeseries/tests/failMessage.q create mode 100644 timeseries/tests/fit.t create mode 100644 timeseries/tests/misc.t create mode 100644 timeseries/tests/pred.t create mode 100644 timeseries/utils.q diff --git a/.travis.yml b/.travis.yml index ed761360..f252f85f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,11 +36,11 @@ beforescript: script: - (cd clust && make && make install && make clean) - echo "Preparing version $TRAVIS_BRANCH-$TRAVIS_COMMIT" -- tar czf ml_$TRAVIS_OS_NAME-$TRAVIS_BRANCH.tgz *.q fresh/ xval/ util/ clust/ graph/ requirements.txt LICENSE README.md +- tar czf ml_$TRAVIS_OS_NAME-$TRAVIS_BRANCH.tgz *.q fresh/ xval/ util/ clust/ graph/ timeseries/ optimize/ requirements.txt LICENSE README.md - echo "Packaged as ml_$TRAVIS_OS_NAME-$TRAVIS_BRANCH.zip" - if [[ "x$QLIC_KC" != "x" ]]; then curl -fsSL -o test.q https://github.com/KxSystems/embedpy/raw/master/test.q; - q test.q fresh/tests/ util/tests/ xval/tests clust/tests/ graph/tests/ -q; + q test.q fresh/tests/ util/tests/ xval/tests clust/tests/ graph/tests/ timeseries/tests/ optimize/tests/ -q; else echo No kdb+, no tests; diff --git a/build/package.bat b/build/package.bat index 98d4383b..1ecfe685 100644 --- a/build/package.bat +++ b/build/package.bat @@ -1,3 +1,3 @@ -7z a ml_windows-%ML_VERSION%.zip *.q fresh util xval clust graph requirements.txt LICENSE README.md +7z a ml_windows-%ML_VERSION%.zip *.q fresh util xval clust graph timeseries optimize requirements.txt LICENSE README.md appveyor PushArtifact ml_windows-%ML_VERSION%.zip exit /b 0 diff --git a/build/test.bat b/build/test.bat index adf30347..661d593d 100644 --- a/build/test.bat +++ b/build/test.bat @@ -2,5 +2,5 @@ if defined QLIC_KC ( pip -q install -r requirements.txt echo getting test.q from embedpy curl -fsSL -o test.q https://github.com/KxSystems/embedpy/raw/master/test.q - q test.q fresh/tests/ util/tests/ xval/tests/ clust/tests/ graph/tests/ -q + q test.q fresh/tests/ util/tests/ xval/tests/ clust/tests/ graph/tests/ timeseries/tests/ optimize/tests/ -q ) diff --git a/docker/Dockerfile b/docker/Dockerfile index 8f08ad8f..9353b821 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -13,6 +13,8 @@ COPY fresh /opt/kx/ml/fresh COPY util /opt/kx/ml/util COPY xval /opt/kx/ml/xval COPY clust /opt/kx/ml/clust +COPY timeseries /opt/kx/ml/timeseries +COPY optimize /opt/kx/ml/optimize ARG VCS_REF=dev ARG BUILD_DATE=dev @@ -32,7 +34,8 @@ LABEL org.label-schema.schema-version="0.1" \ RUN chown -R kx:kx /opt/kx/ml RUN mkdir /opt/kx/q/ml RUN find /opt/kx/ml -maxdepth 1 -type f -name '*.q' | xargs ln -s -t /opt/kx/q/ml \ - && ln -s -t /opt/kx/q/ml /opt/kx/ml/fresh /opt/kx/ml/utils /opt/kx/ml/xval /opt/kx/ml/clust + && ln -s -t /opt/kx/q/ml /opt/kx/ml/fresh /opt/kx/ml/utils /opt/kx/ml/xval /opt/kx/ml/clust \ + /opt/kx/ml/timeseries /opt/kx/ml/optimize USER kx diff --git a/init.q b/init.q index a9a54ec5..15341349 100644 --- a/init.q +++ b/init.q @@ -3,4 +3,5 @@ .ml.loadfile`:clust/init.q .ml.loadfile`:xval/init.q .ml.loadfile`:graph/init.q - +.ml.loadfile`:optimize/init.q +.ml.loadfile`:timeseries/init.q diff --git a/optimize/init.q b/optimize/init.q new file mode 100644 index 00000000..bfca8622 --- /dev/null +++ b/optimize/init.q @@ -0,0 +1,3 @@ +\d .ml +loadfile`:util/util.q +loadfile`:optimize/optim.q diff --git a/optimize/optim.q b/optimize/optim.q new file mode 100644 index 00000000..b9347e78 --- /dev/null +++ b/optimize/optim.q @@ -0,0 +1,659 @@ +// Namespace appropriately +\d .ml + +// @kind function +// @category optimization +// @fileoverview Optimize a function using the +// Broyden-Fletcher-Goldfarb-Shanno (BFGS) algorithm. This implementation +// is based on https://github.com/scipy/scipy/blob/v1.5.0/scipy/optimize/optimize.py#L1058 +// and is a quasi-Newton hill-climbing optimization technique used to find +// a preferebly twice continuously differentiable stationary point of a function. +// An outline of the algorithm mathematically is provided here: +// https://en.wikipedia.org/wiki/Broyden-Fletcher-Goldfarb-Shanno_algorithm#Algorithm +// @param func {lambda} the function to be optimized. This function should take +// as its arguments a list/dictionary of parameters to be optimized and a list/dictionary +// of additional unchanging arguments +// @param x0 {num[]/dict} the first guess at the parameters to be optimized as +// a list or dictionary of numeric values +// @param args {list/dict/(::)} any unchanging parameters to required for evaluation +// of the function, these should be in the order that they are to be applied +// to the function +// @param params {dict} any modifications to be applied to the optimization procedure e.g. +// - display {bool} are the results at each optimization iteration to be printed +// - optimIter {integer} maximum number of iterations in optimization procedure +// - zoomIter {integer} maximum number of iterations when finding optimal zoom +// - wolfeIter {integer} maximum number of iterations in +// - norm {integer} order of norm (0W = max; -0W = min), otherwise calculated via +// sum[abs[vec]xexp norm]xexp 1%norm +// - gtol {float} gradient norm must be less than gtol before successful termination +// - geps {float} the absolute step size used for numerical approximation +// of the jacobian via forward differences. +// - stepSize {float} maximum allowable 'alpha' step size between calculations +// - c1 {float} armijo rule condition +// - c2 {integer} curvature conditions rule +// @returns {dict} a dictionary containing the estimated optimal parameters, number of iterations +// and the evaluated return of the function being optimized. +optimize.BFGS:{[func;x0;args;params] + // update the default behaviour of the parameters + params:i.updDefault[params]; + // format x0 based on input type + x0:i.dataFormat[x0]; + // Evaluate the function at the starting point + f0:i.funcEval[func;x0;args]; + // Calculate the starting gradient + gk:i.grad[func;x0;args;params`geps]; + // Initialize Hessian matrix as identity matrix + hess:.ml.eye count x0; + // set initial step guess i.e. the step before f0 + prev_fk:f0+sqrt[sum gk*gk]%2; + gradNorm:i.vecNorm[gk;params`norm]; + optimKeys:`xk`fk`prev_fk`gk`prev_xk`hess`gnorm`I`idx; + optimVals:(x0;f0;prev_fk;gk;0n;hess;gradNorm;hess;0); + optimDict:optimKeys!optimVals; + // Run optimization until one of the stopping conditions is met + optimDict:i.stopOptimize[;params]i.BFGSFunction[func;;args;params]/optimDict; + returnKeys:`xVals`funcRet`numIter; + // if function returned due to a null xVal or the new value being worse than the previous + // value then return the k-1 value + returnVals:$[(optimDict[`fk] x(k) + sk:alpha*pk; + // update values of x at the new position k + optimDict[`xk]:optimDict[`prev_xk]+sk; + // if null gnew, then get gradient of new x value + if[any null gnew;gnew:i.grad[func;optimDict`xk;args;params`geps]]; + // subtract new gradients + yk:gnew-optimDict`gk;; + optimDict[`gk]:gnew; + // get new norm of gradient + optimDict[`gnorm]:i.vecNorm[optimDict`gk;params`norm]; + // calculate new hessian matrix for next iteration + rhok:1%mmu[yk;sk]; + if[0w=rhok; + rhok:1000f; + -1"Division by zero in calculation of rhok, assuming rhok large";]; + A1:optimDict[`I] - sk*\:yk*rhok; + A2:optimDict[`I] - yk*\:sk*rhok; + optimDict[`hess]:mmu[A1;mmu[optimDict`hess;A2]]+rhok*(sk*/:sk); + // if x(k) returns infinite value update gnorm and fk + if[0w in abs optimDict`xk;optimDict[`gnorm`fk]:(0n;0w)]; + optimDict[`idx]+:1; + if[params`display;show optimDict;-1"";]; + optimDict + } + +// @private +// @kind function +// @category optimization +// @fileoverview complete a line search across an unconstrained minimization problem making +// use of wolfe conditions to constrain the search the naming convention for dictionary keys +// in this implementation is based on the python implementation of the same functionality here +// https://github.com/scipy/scipy/blob/v1.5.0/scipy/optimize/linesearch.py#L193 +// @param fk {float} function return evaluated at position k +// @param prev_fk {float} function return evaluated at position k-1 +// @param gk {float} gradient at position k +// @param pk {float} search direction +// @param func {lambda} function being optimized +// @param xk {num[]} parameter values at position k +// @param args {dict/num[]} function arguments that do not change per iteration +// @param params {dict} parameters controlling non default optimization behaviour +// @return {num[]} new alpha, fk and derivative values +i.wolfeSearch:{[fk;prev_fk;gk;pk;func;xk;args;params] + phiFunc :i.phi[func;pk;;xk;args]; + derphiFunc:i.derphi[func;params`geps;pk;;xk;args]; + // initial Wolfe conditions + wolfeDict:`idx`alpha0`phi0`phi_a0!(0;0;fk;fk); + // calculate the derivative at that phi0 + derphi0:gk mmu pk; + wolfeDict[`derphi_a0`derphi0]:2#derphi0; + // calculate step size this should be 0 < x < 1 + // with min(x;maxstepsize) or 1f otherwise + alpha:1.01*2*(fk - prev_fk)%derphi0; + alphaVal:$[alpha within 0 1f;min(alpha;params`stepSize);1f]; + wolfeDict[`alpha1]:alphaVal; + // function value at alpha1 + wolfeDict[`phi_a1]:phiFunc wolfeDict`alpha1; + // repeat until wolfe criteria is reached or max iterations have been done + // to get new alpha, phi and derphi values + wolfeDict:i.stopWolfe[;params]i.scalarWolfe[derphiFunc;phiFunc;pk;params]/wolfeDict; + // if the line search did not converge, use last alpha , phi and derphi + $[not any null raze wolfeDict`alpha_star`phi_star`derphi_star; + wolfeDict`alpha_star`phi_star`derphi_star; + wolfeDict`alpha1`phi_a1`derphi_a0_fin + ] + } + +// @private +// @kind function +// @category optimization +// @fileoverview apply a scalar search to find an alpha value that satisfies +// strong Wolfe conditions, a python implementation of this is outlined here +// https://github.com/scipy/scipy/blob/v1.5.0/scipy/optimize/linesearch.py#L338 +// This functions defines the bounds between which the step function can be found. +// When the optimal bound is found, the area is zoomed in on and optimal value find +// @param derphiFunc {proj} function to calculate the value of the objective function +// derivative at alpha +// @param phiFunc {proj} function to calculate the value of the objective function at alpha +// @param pk {float} search direction +// @param params {dict} parameters controlling non default optimization behaviour +// @param wolfeDict {dict} all data relevant to the calculation of the optimal +// alpha values +// @returns {dict} new alpha, fk and derivative values +i.scalarWolfe:{[derphiFunc;phiFunc;pk;params;wolfeDict] + // set up zoom function constant params + zoomSetup:i.zoomFunc[derphiFunc;phiFunc;;;params]. wolfeDict`phi0`derphi0; + // if criteria 1, zoom and break loop + if[i.wolfeCriteria1[wolfeDict;params]; + wolfeDict[`idx]:0w; + wolfeDict[i.zoomReturn]:zoomSetup wolfeDict`alpha0`alpha1`phi_a0`phi_a1`derphi_a0; + :wolfeDict + ]; + // calculate the derivative of the function at the new position + derphiCalc:derphiFunc wolfeDict`alpha1; + // update the new derivative fnc + wolfeDict[`derphi_a1]:derphiCalc`derval; + $[i.wolfeCriteria2[wolfeDict;params]; + [wolfeDict[`alpha_star] :wolfeDict`alpha1; + wolfeDict[`phi_star] :wolfeDict`phi_a1; + wolfeDict[`derphi_star]:derphiCalc`grad; + wolfeDict[`idx]:0w; + wolfeDict + ]; + 0<=wolfeDict`derphi_a1; + [wolfeDict[`idx]:0w; + wolfeDict[i.zoomReturn]:zoomSetup wolfeDict`alpha1`alpha0`phi_a1`phi_a0`derphi_a1 + ]; + // update dictionary and repeat process until criteria is met + [wolfeDict[`alpha0]:wolfeDict`alpha1; + wolfeDict[`alpha1]:2*wolfeDict`alpha1; + wolfeDict[`phi_a0]:wolfeDict`phi_a1; + wolfeDict[`phi_a1]:phiFunc wolfeDict`alpha1; + wolfeDict[`derphi_a0]:wolfeDict`derphi_a1; + wolfeDict[`derphi_a0_fin]:derphiCalc`grad; + wolfeDict[`idx]+:1 + ] + ]; + wolfeDict + } + +// @private +// @kind function +// @category optimize +// @fileoverview function to apply 'zoom' iteratively during linesearch to find optimal alpha +// value satisfying strong Wolfe conditions +// @param derphiFunc {proj} function to calculate the value of the objective function +// derivative at alpha +// @param phiFunc {proj} function to calculate the value of the objective function at alpha +// @param phi0 {float} value of function evaluation at x(k-1) +// @param derphi0 {float} value of objective function derivative at x(k-1) +// @param params {dict} parameters controlling non default optimization behaviour +// @param lst {num[]} bounding conditions for alpha, phi and derphi used in zoom algorithm +// @returns {num[]} new alpha, fk and derivative values +i.zoomFunc:{[derphiFunc;phiFunc;phi0;derphi0;params;lst] + zoomDict:i.zoomKeys!lst,phi0; + zoomDict[`idx`a_rec]:2#0f; + zoomDict:i.stopZoom[;params]i.zoom[derphiFunc;phiFunc;phi0;derphi0;params]/zoomDict; + // if zoom did not converge, set to null + $[count star:zoomDict[i.zoomReturn];star;3#0N] + } + +// @private +// @kind function +// @category optimize +// @fileoverview function to apply an individual step in 'zoom' during linesearch +// to find optimal alpha value satisfying strong Wolfe conditions. An outline of +// the python implementation of this section of the algorithm can be found here +// https://github.com/scipy/scipy/blob/v1.5.0/scipy/optimize/linesearch.py#L556 +// @param derphiFunc {proj} function to calculate the value of the objective function +// derivative at alpha +// @param phiFunc {proj} function to calculate the value of the objective function at alpha +// @param phi0 {float} value of function evaluation at x(k-1) +// @param derphi0 {float} value of objective function derivative at x(k-1) +// @param params {dict} parameters controlling non default optimization behaviour +// @param zoomDict {dict} parameters to be updated as 'zoom' procedure is applied to find +// the optimal value of alpha +// @returns {dict} parameters calculated for an individual step in line search procedure +// to find optimal alpha value satisfying strong Wolfe conditions +i.zoom:{[derphiFunc;phiFunc;phi0;derphi0;params;zoomDict] + // define high and low values + dalpha:zoomDict[`a_hi]-zoomDict`a_lo; + // These should probably be named a and b since mapping doesn't work properly? + highLow:`high`low!$[dalpha>0;zoomDict`a_hi`a_lo;zoomDict`a_lo`a_hi]; + if["i"$zoomDict`idx; + cubicCheck:dalpha*0.2; + findMin:i.cubicMin . zoomDict`a_lo`phi_lo`derphi_lo`a_hi`phi_hi`a_rec`phi_rec + ]; + if[i.quadCriteria[findMin;highLow;cubicCheck;zoomDict]; + quadCheck:0.1*dalpha; + findMin:i.quadMin . zoomDict`a_lo`phi_lo`derphi_lo`a_hi`phi_hi; + if[(findMin > highLow[`low]-quadCheck) | findMin < highLow[`high]+quadCheck; + findMin:zoomDict[`a_lo]+0.5*dalpha + ] + ]; + // update new values depending on fnd_min + phiMin:phiFunc[findMin]; + //first condition, update and continue loop + if[i.zoomCriteria1[phi0;derphi0;phiMin;findMin;zoomDict;params]; + zoomDict[`idx]+:1; + zoomDict[i.zoomKeys1]:zoomDict[`phi_hi`a_hi],findMin,phiMin; + :zoomDict + ]; + // calculate the derivative at the cubic minimum + derphiMin:derphiFunc findMin; + // second scenario, create new features and end the loop + $[i.zoomCriteria2[derphi0;derphiMin;params]; + [zoomDict[`idx]:0w; + zoomDict:zoomDict,i.zoomReturn!findMin,phiMin,enlist derphiMin`grad]; + i.zoomCriteria3[derphiMin;dalpha]; + [zoomDict[`idx]+:1; + zoomDict[i.zoomKeys1,i.zoomKeys2]:zoomDict[`phi_hi`a_hi`a_lo`phi_lo], + findMin,phiMin,derphiMin`derval]; + [zoomDict[`idx]+:1; + zoomDict[i.zoomKeys3,i.zoomKeys2]:zoomDict[`phi_lo`a_lo], + findMin,phiMin,derphiMin`derval] + ]; + zoomDict + } + + +// Vector norm calculation + +// @private +// @kind function +// @category optimization +// @fileoverview calculate the vector norm, used in calculation of the gradient norm at position k. +// Default behaviour is to use the maximum value of the gradient, this can be overwritten by +// a user, this is in line with the default python implementation. +// @param vec {num[]} calculated gradient values +// @param ord {long} order of norm (0W = max; -0W = min) +// @return the gradient norm based on the input gradient +i.vecNorm:{[vec;ord] + if[-7h<>type ord;'"ord must be +/- infinity or a long atom"]; + $[ 0W~ord;max abs vec; + -0W~ord;min abs vec; + sum[abs[vec]xexp ord]xexp 1%ord + ] + } + + +// Stopping conditions + +// @private +// @kind function +// @category optimization +// @fileoverview evaluate if the optimization function has reached a condition which is +// should result in the optimization algorithm being stopped. +// @param dict {dict} optimization function returns +// @param params {dict} parameters controlling non default optimization behaviour +// @return {bool} indication as to if the optimization has met one of it's stopping conditions +i.stopOptimize:{[dict;params] + // is the function evaluation at k an improvement on k-1? + check1:dict[`fk] < dict`prev_fk; + // has x[k] returned a non valid return? + check2:not any null dict`xk; + // have the maximum number of iterations been met? + check3:params[`optimIter] > dict`idx; + // is the gradient at position k below the accepted tolerance + check4:params[`gtol] < dict`gnorm; + check1 & check2 & check3 & check4 + } + +// @private +// @kind function +// @category optimization +// @fileoverview evaluate if the wolfe condition search has reached a condition which is +// should result in the optimization algorithm being stopped. +// @param dict {dict} optimization function returns +// @param params {dict} parameters controlling non default optimization behaviour +// @return {bool} indication as to if the optimization has met one of it's stopping conditions +i.stopWolfe:{[dict;params] + dict[`idx] < params`wolfeIter + } + +// @private +// @kind function +// @category optimization +// @fileoverview evaluate if the alpha condition 'zoom' has reached a condition which is +// should result in the optimization algorithm being stopped. +// @param dict {dict} optimization function returns +// @param params {dict} parameters controlling non default optimization behaviour +// @return {bool} indication as to if the optimization has met one of it's stopping conditions +i.stopZoom:{[dict;params] + dict[`idx] < params`zoomIter + } + + +// Function + derivative evaluation at x[k]+ p[k]*alpha[k] + +// @private +// @kind function +// @category optimization +// @fileoverview evaluate the objective function at the position x[k] + step size +// @param func {lambda} the objective function to be minimized +// @param pk {float} step direction +// @param alpha {float} size of the step to be applied +// @param xk {num[]} parameter values at position k +// @param args {dict/num[]} function arguments that do not change per iteration +// @param xk {num[]} +// @returns {float} function evaluated at at the position x[k] + step size +i.phi:{[func;pk;alpha;xk;args] + xk+:alpha*pk; + i.funcEval[func;xk;args] + } + +// @private +// @kind function +// @category optimization +// @fileoverview evaluate the derivative of the objective function at +// the position x[k] + step size +// @param func {lambda} the objective function to be minimized +// @param eps {float} the absolute step size used for numerical approximation +// of the jacobian via forward differences. +// @param pk {float} step direction +// @param alpha {float} size of the step to be applied +// @param xk {num[]} parameter values at position k +// @param args {dict/num[]} function arguments that do not change per iteration +// @returns {dict} gradient and value of scalar derivative +i.derphi:{[func;eps;pk;alpha;xk;args] + // increment xk by a small step size + xk+:alpha*pk; + // get gradient at the new position + gval:i.grad[func;xk;args;eps]; + derval:gval mmu pk; + `grad`derval!(gval;derval) + } + + +// Minimization functions + +// @private +// @kind function +// @category optimization +// @fileoverview find the minimizing solution for a cubic polynomial which +// passes through the points (a,fa), (b,fb) and (c,fc) with a derivative of the +// objective function calculated as fpa. This follows the python implementation +// outlined here https://github.com/scipy/scipy/blob/v1.5.0/scipy/optimize/linesearch.py#L482 +// @param a {float} position a +// @param b {float} position b +// @param c {float} position c +// @param fa {float} objective function evaluated at a +// @param fb {float} objective function evaluated at b +// @param fc {float} objective function evaluated at c +// @param fpa {float} derivative of the objective function evaluated at a +// @returns {num[]} minimized parameter set as a solution for the cubic polynomial +i.cubicMin:{[a;fa;fpa;b;fb;c;fc] + db:b-a; + dc:c-a; + denom:(db*dc)xexp 2*(db-dc); + d1:2 2#0f; + d1[0]:(1 -1)*xexp[;2]each(db;dc); + d1[1]:(-1 1)*xexp[;3]each(dc;db); + AB:d1 mmu(fb-fa-fpa*db;fc-fa-fpa*dc); + AB%:denom; + radical:AB[1]*AB[1]-3*AB[0]*fpa; + a+(neg[AB[1]]+sqrt(radical))%(3*AB[0]) + } + +// @private +// @kind function +// @category optimization +// @fileoverview find the minimizing solution for a quadratic polynomial which +// passes through the points (a,fa) and (b,fb) with a derivative of the objective function +// calculated as fpa. This follows the python implementation outlined here +// https://github.com/scipy/scipy/blob/v1.5.0/scipy/optimize/linesearch.py#L516 +// @param a {float} position a +// @param b {float} position b +// @param fa {float} objective function evaluated at a +// @param fb {float} objective function evaluated at b +// @param fpa {float} derivative of the objective function evaluated at a +// @returns {num[]} minimized parameter set as a solution for the quadratic polynomial +i.quadMin:{[a;fa;fpa;b;fb] + db:b-a; + B:(fb-fa-fpa*db)%(db*db); + a-fpa%(2*B) + } + + +// Gradient + function evaluation + +// @private +// @kind function +// @category optimization +// @fileoverview calculation of the gradient of the objective function for all parameters of x +// incremented individually by epsilon +// @param func {lambda} the objective function to be minimized +// @param xk {num[]} parameter values at position k +// @param args {dict/num[]} function arguments that do not change per iteration +// @param eps {float} the absolute step size used for numerical approximation +// of the jacobian via forward differences. +// @returns {dict} gradient of function at position k +i.grad:{[func;xk;args;eps] + fk:i.funcEval[func;xk;args]; + i.gradEval[fk;func;xk;args;eps]each til count xk + } + +// @private +// @kind function +// @category optimization +// @fileoverview calculation of the gradient of the objective function for a single +// parameter set x where one of the indices has been incremented by epsilon +// @param func {lambda} the objective function to be minimized +// @param xk {num[]} parameter values at position k +// @param args {dict/num[]} function arguments that do not change per iteration +// @param eps {float} the absolute step size used for numerical approximation +// of the jacobian via forward differences. +// @returns {dict} gradient of function at position k with an individual +// variable x incremented by epsilon +i.gradEval:{[fk;func;xk;args;eps;idx] + if[(::)~fk;fk:i.funcEval[func;xk;args]]; + // increment function optimisation values by epsilon + xk[idx]+:eps; + // Evaluate the gradient + (i.funcEval[func;xk;args]-fk)%eps + } + +// @private +// @kind function +// @category optimization +// @fileoverview evaluate the objective function at position x[k] with relevant +// additional arguments accounted for +// @param {lambda} the objective function to be minimized +// @param xk {num[]} parameter values at position k +// @param args {dict/num[]} function arguments that do not change per iteration +// @returns {float} the objective function evaluated at the appropriate location +i.funcEval:{[func;xk;args] + $[any args~/:((::);());func xk;func[xk;args]] + } + + +// Paramter dictionary + +// @private +// @kind function +// @category +// @fileoverview update the default behaviour of the model optimization procedure +// to account for increased sensitivity to tolerance, the number of iterations, +// how the gradient norm is calculated and various numerical updates including changes +// to the Armijo rule and curvature for calculation of the strong Wolfe conditions. +// @param dict {dict/(::)/()} if a dictionary update the default dictionary to include +// the user defined updates, otherwise use the default dictionary +// @returns {dict} updated or default parameter set depending on user input +i.updDefault:{[dict] + returnKeys:`norm`optimIter`gtol`geps`stepSize`c1`c2`wolfeIter`zoomIter`display; + returnVals:(0W;0W;1e-4;1.49e-8;0w;1e-4;0.9;10;10;0b); + returnDict:returnKeys!returnVals; + if[99h<>type dict;dict:()!()]; + i.wolfeParamCheck[returnDict,dict] + } + +// @private +// @kind function +// @category optimization +// @fileoverview Ensure that the armijo and curvature parameters are consistent +// with the expected values for calculation of the strong Wolfe conditions. +// Return an error on unsuitable conditions otherwise return the input dictionary +// @param dict {dict} updated parameter dictionary containing default information and +// any updated parameter information +// @returns {dict/err} the original input dictionary or an error suggesting that the +// Armijo and curvature parameters are unsuitable +i.wolfeParamCheck:{[dict] + check1:dict[`c1]>dict`c2; + check2:any not dict[`c1`c2]within 0 1; + $[check1 or check2; + '"When evaluating Wolfe conditions the following must hold 0 < c1 < c2 < 1"; + dict + ] + } + + +// Data Formatting + +// @private +// @kind function +// @category optimization +// @fileoverview Ensure that the input parameter x at position 0 which will +// be updated is in a format that is suitable for use with this optimization +// procedure i.e. the data is a list of values. +// @param x0 {dict/num/num[]} initial values of x to be optimized +// @returns {num[]} the initial values of x converted into a suitable numerical list format +i.dataFormat:{[x0] + $[99h=type x0;value x0;0h >type x0;enlist x0; x0] + } + + +// Conditional checks for Wolfe, zoom and quadratic condition evaluation + +// @private +// @kind function +// @category optimization +// @fileoverview ensure new values lead to improvements over the older values +// @param wolfeDict {dict} the current iterations values for the objective function and the +// derivative of the objective function evaluated +// @param params {dict} parameter dictionary containing the updated/default information +// used to modify the behaviour of the system as a whole +// @returns {bool} indication as to if a further zoom is required +i.wolfeCriteria1:{[wolfeDict;params] + check1:wolfeDict[`phi_a1]>wolfeDict[`phi0]+params[`c1]*prd wolfeDict`alpha1`derphi0; + check2:(wolfeDict[`phi_a1]>=wolfeDict`phi_a0) and (1=abs wolfeDict`derphi_a1 + } + +// @private +// @kind function +// @category optimization +// @fileoverview check if there is need to apply quadratic minimum calculation +// @param findMin {num[]} the currently calculated minimum values +// @param highLow {dict} upper and lower bounds of the search space +// @param cubicCheck {float} interpolation check parameter +// @param zoomDict {dict} parameters to be updated as 'zoom' procedure is applied to find +// the optimal value of alpha +// @returns {bool} indication as to if the value of findMin needs to be updated +i.quadCriteria:{[findMin;highLow;cubicCheck;zoomDict] + // On initial iteration the minimum has not been calculated + // as such criteria should exit early to complete the quadratic calculation + if[findMin~();:1b]; + check1:0=zoomDict`idx; + check2:findMin>highLow[`low] -cubicCheck; + check3:findMin phi0+findMin*derphi0*params`c1; + check2:phiMin>=zoomDict`phi_lo; + check1 or check2 + } + +// @private +// @kind function +// @category optimization +// @fileoverview check if the zoom conditions are sufficient +// @param derphi0 {float} derivative of the objective function evaluated at index 0 +// @param derphiMin {float} derivative of the objective function evaluated at the current minimum +// @param params {dict} parameter dictionary containing the updated/default information +// used to modify the behaviour of the system as a whole +// @returns indication as to if further zooming is required +i.zoomCriteria2:{[derphi0;derphiMin;params] + abs[derphiMin`derval]<=neg derphi0*params`c2 + } + +// @private +// @kind function +// @category optimization +// @fileoverview check if the zoom conditions are sufficient +// @param derphiMin {float} derivative of the objective function evaluated at the current minimum +// @param dalpha {float} difference between the upper and lower bound of the zoom bracket +// @returns indication as to if further zooming is required +i.zoomCriteria3:{[derphiMin;dalpha] + 0<=derphiMin[`derval]*dalpha + } + + +// Zoom dictionary + +//input keys of zoom dictionary +i.zoomKeys:`a_lo`a_hi`phi_lo`phi_hi`derphi_lo`phi_rec; +// keys to be updated in zoom each iteration +i.zoomKeys1:`phi_rec`a_rec`a_hi`phi_hi; +// extra keys that have to be updated in some scenarios +i.zoomKeys2:`a_lo`phi_lo`derphi_lo; +i.zoomKeys3:`phi_rec`a_rec +// final updated keys to be used +i.zoomReturn:`alpha_star`phi_star`derphi_star; diff --git a/optimize/tests/test.t b/optimize/tests/test.t new file mode 100644 index 00000000..80854f29 --- /dev/null +++ b/optimize/tests/test.t @@ -0,0 +1,3 @@ +// Tests to be populated for this functionality + +1~1 diff --git a/timeseries/fit.q b/timeseries/fit.q new file mode 100644 index 00000000..9aa18583 --- /dev/null +++ b/timeseries/fit.q @@ -0,0 +1,136 @@ +\d .ml + +// Fitting functionality for time series models. + +// @kind function +// @category modelFit +// @fileoverview Fit an AutoRegressive model (AR) +// @param endog {num[]} Endogenous variable (time-series) from which to build a model +// this is the target variable from which a value is to be predicted +// @param exog {tab/num[][]/(::)} Exogenous variables, are additional variables which +// may be accounted for to improve the model, if (::)/() this will be ignored +// @param lags {integer} The number/order of time lags of the model +// @param trend {boolean} Is a trend line to be accounted for in fitting of model +// @return {dict} All information required to use a fit model for the prediction of +// new values based on incoming data +ts.AR.fit:{[endog;exog;lags;trend] + // cast endog to floating value + endog:"f"$endog; + exog:ts.i.fitDataCheck[endog;exog]; + // Estimate coefficients + coeff:$[sum trend,count[exog]; + ts.i.estimateParams[endog;exog;endog;`p`q`tr!lags,0,trend]; + ts.i.durbinLevinson[endog;lags] + ]; + // Get lagged values needed for future predictions + lagvals:neg[lags]#endog; + // return dictionary with required info for predictions + keyvals:`params`tr_param`exog_param`p_param`lags; + params:(coeff;trend#coeff;coeff trend +til count exog 0;neg[lags]#coeff;lagvals); + keyvals!params + } + +// @kind function +// @category modelFit +// @fileoverview Fit an AutoRegressive Moving Average model (ARMA) +// @param endog {num[]} Endogenous variable (time-series) from which to build a model +// this is the target variable from which a value is to be predicted +// @param exog {tab/num[][]/(::)} Exogenous variables, are additional variables which +// may be accounted for to improve the model, if (::)/() this will be ignored +// @param lags {integer} The number/order of time lags of the model +// @param resid {integer} The number of residual errors to be accounted for +// @param trend {boolean} Is a trend line to be accounted for in fitting of model +// @return {dict} All information required to use a fit model for the prediction of +// new values based on incoming data +ts.ARMA.fit:{[endog;exog;lags;resid;trend] + // cast endog to floating value + endog:"f"$endog; + exog:ts.i.fitDataCheck[endog;exog]; + $[resid~0; + // if q = 0 then model is an AR model + ts.AR.fit[endog;exog;lags;trend],`q_param`resid`estresid`pred_dict! + (();();();`p`q`tr!lags,resid,trend); + ts.i.ARMA.model[endog;exog;`p`q`tr!lags,resid,trend]] + } + +// @kind function +// @category modelFit +// @fileoverview Fit an AutoRegressive Integrated Moving Average model (ARIMA) +// @param endog {num[]} Endogenous variable (time-series) from which to build a model +// this is the target variable from which a value is to be predicted +// @param exog {tab/num[][]/(::)} Exogenous variables, are additional variables which +// may be accounted for to improve the model, if (::)/() this will be ignored +// @param lags {integer} The number/order of time lags of the model +// @param diff {integer} The order of time series differencing used in integration +// @param resid {integer} The number of residual errors to be accounted for +// @param trend {boolean} Is a trend line to be accounted for in fitting of model +// @return {dict} All information required to use a fit model for the prediction of +// new values based on incoming data +ts.ARIMA.fit:{[endog;exog;lags;diff;resid;trend] + exog:ts.i.fitDataCheck[endog;exog]; + // Apply integration (non seasonal) + I:ts.i.differ[endog;diff;()!()]; + // Fit an ARMA model on the differenced time series + mdl:ts.ARMA.fit[I;diff _exog;lags;resid;trend]; + // Retrieve the original data to be used when fitting on new data + origData:neg[diff]#endog; + // Produce the relevant differenced data for use in future predictions + origDiff:enlist[`origd]!enlist diff{deltas x}/origData; + // return relevant data + mdl,origDiff + } + +// @kind function +// @category modelFit +// @fileoverview Fit a Seasonal AutoRegressive Integrated Moving Average model (SARIMA) +// @param endog {num[]} Endogenous variable (time-series) from which to build a model +// this is the target variable from which a value is to be predicted +// @param exog {tab/num[][]/(::)} Exogenous variables, are additional variables which +// may be accounted for to improve the model, if (::)/() this will be ignored +// @param lags {integer} The number/order of time lags of the model +// @param diff {integer} The order of time series differencing used in integration +// @param resid {integer} The number of residual errors to be accounted for +// @param trend {boolean} Is a trend line to be accounted for in fitting of model +// @param seas {dict} Is a dictionary containing required seasonal components +// @return {dict} All information required to use a fit model for the prediction of +// new values based on incoming data +ts.SARIMA.fit:{[endog;exog;lags;diff;resid;trend;seas] + // cast endog to floating value + endog:"f"$endog; + ts.i.dictCheck[seas;`P`Q`D`m;"seas"]; + // Apply error checking (exogenous data not converted to matrix?) + exog:ts.i.fitDataCheck[endog;exog]; + // Apply appropriate seasonal+non seasonal differencing + I:ts.i.differ[endog;diff;seas]; + // Create dictionary with p,q and seasonal components + dict:`p`q`P`Q`m`tr!lags,resid,((1+til each seas[`P`Q])*seas[`m]),seas[`m],trend; + // add additional seasonal components + dict[`seas_add_P`seas_add_Q]:(raze'){1+til[x]+/:y}'[(lags;resid);dict`P`Q]; + // Generate data for regenerate data following differencing + origDiffSeason:`origd`origs!(diff{deltas x}/neg[diff]#endog;neg[prd seas`D`m]#endog); + // Apply SARMA model and postpend differenced original data + ts.i.SARMA.model[I;exog;dict],origDiffSeason + } + +// @kind function +// @category modelFit +// @fileoverview Fit an AutoRegressive Conditional Heteroscedasticity model (ARCH) +// @param resid {num[]} Residual errors from fitted time series model +// @param lags {integer} The number/order of time lags of the model +// @return {dict} All information required to use a fit model for the prediction of +// new values based on incoming data +ts.ARCH.fit:{[resid;lags] + // cast to floating value + resid:"f"$resid; + // cast endog to floating value + sqresid:resid*resid; + // Using the resid errorrs calculate coefficients + coeff:ts.i.estimateParams[sqresid;();sqresid;`p`q`tr!lags,0,1b]; + // Get lagged values needed for future predictions + resid:neg[lags]#sqresid; + // return dictionary with required info for predictions + keyVals:`params`tr_param`p_param`resid; + params:(coeff;coeff[0];1_coeff;resid); + keyVals!params + } + diff --git a/timeseries/init.q b/timeseries/init.q new file mode 100644 index 00000000..26c2af79 --- /dev/null +++ b/timeseries/init.q @@ -0,0 +1,7 @@ +\d .ml +loadfile`:optimize/init.q +loadfile`:timeseries/utils.q +loadfile`:fresh/extract.q +loadfile`:timeseries/fit.q +loadfile`:timeseries/predict.q +loadfile`:timeseries/misc.q diff --git a/timeseries/misc.q b/timeseries/misc.q new file mode 100644 index 00000000..c2542976 --- /dev/null +++ b/timeseries/misc.q @@ -0,0 +1,113 @@ +\d .ml + +// Miscellaneous functionality relating to time-series analysis +// and model generation procedures + + +// @kind function +// @category misc +// @fileoverview Summary of the stationality of each vector of a multivariate time series +// or a single vector +// @param dset {dict/tab/num[]} a time series of interest, the entries should +// in each case be numeric data types. +// @return {keytab} informative outputs from the python adfuller test indicating +// the stationality of each vector entry of the relevant dataset +ts.stationality:{[dset] + dtype:type dset; + // Names to be provided to form the key for the return table + keyNames:$[99h=dtype;key dset; + 98h=dtype;cols dset; + enlist`data + ]; + // Column names associated with the returns from the augmented dickey fuller test + dcols:`ADFstat`pvalue`stationary,`$raze each"CriticalValue_",/:string(1;5;10),\:"%"; + scores:ts.i.stationaryScores[dset;dtype]; + keyNames!flip dcols!scores + } + +// @kind function +// @category misc +// @fileoverview Retrieve the best parameters for an ARIMA model based on the +// Akaike Information Criterion (AIC) +// @param train {dict} training data dictionary containing `endog/`exog data +// @param test {dict} testing data dictionary containing `endog/`exog data +// @param len {integer} number of steps forward to predict +// @param params {dict} parameter sets to fit ARIMA model with +// @return {dict} parameter set which produced the lowest AIC score +ts.ARIMA.aicParam:{[train;test;len;params] + ts.i.dictCheck[;`endog`exog;]'[(train;test);("train";"test")]; + ts.i.dictCheck[params;`p`d`q`tr;"params"]; + // get aic scores for each set of params + scores:ts.i.aicFitScore[train;test;len;]each flip params; + // return best value + bestScore:min scores; + scoreEntry:enlist[`score]!enlist bestScore; + params[;scores?bestScore],scoreEntry + } + + +// Time-series feature engineering functionality + +// @kind function +// @category misc +// @fileoverview Apply a set of user defined functions over variously sized sliding windows +// to a subset of columns within a table +// @param tab {tab} dataset onto which to apply the windowed functions +// @param colNames {symbol[]} names of the columns on which to apply the functions +// @param funcs {symbol[]} names of the functions to be applied +// @param wins {integer[]} list of sliding window sizes +// @return {tab} table with functions applied on specified columns over +// appropriate windows remove the first max[wins] columns as these are produced +// with insufficient information to be deemed accurate +ts.windowFeatures:{[tab;colNames;funcs;wins] + // unique combinations of columns/windows and functions to be applied to the dataset + uniCombs:(cross/)(funcs;wins;colNames); + // column names for windowed functions (remove ".") to ensure that if namespaced columns + // exist they don't jeopardize parsing of select statements. + winCols:`$ssr[;".";""]each sv["_"]each string uniCombs; + // values from applied functions over associated windows + winVals:{ts.i.slidingWindowFunction[get string y 0;y 1;x y 2]}[tab]each uniCombs; + max[wins]_tab,'flip winCols!winVals + } + + +// @kind function +// @category misc +// @fileoverview Apply a set of user defined functions over variously sized sliding windows +// to a subset of columns within a table +// @param tab {tab} dataset from which to generate lagged data +// @param colNames {symbol[]} names of the columns from which to retrieve lagged data +// @param lags {integers[]} list of lagged values to retrieve from the dataset +// @return {tab} table with columns added associated with the specied lagged +// values +ts.laggedFeatures:{[tab;colNames;lags] + if[1=count colNames;colNames,:()]; + if[1=count lags;lags,:()]; + lagNames:`$raze string[colNames],/:\:"_xprev_",/:string lags; + lagVals :raze xprev'[;tab colNames]each lags; + tab,'flip lagNames!lagVals + } + + +// Plotting functionality + +// @kind function +// @category misc +// @fileoverview Plot and display an autocorrelation plot +// @param data {num[]} dataset from which to generate the autocorrelation plot +// @return {graph} display to standard out the autocorrelation bar plot +ts.acfPlot:{[data] + acf:ts.i.autoCorrFunction[data;]each m:1_til 11&count[data]; + ts.i.plotFunction[data;acf;m;"AutoCorrelation"]; + } + +// @kind function +// @category misc +// @fileoverview Plot and display an autocorrelation plot +// @param data {num[]} dataset from which to generate the partial autocorrelation plot +// @return {graph} display to standard out the partial autocorrelation bar plot +ts.pacfPlot:{[data] + pacf:.ml.fresh.i.pacf[data;neg[1]+m:11&count data]`; + ts.i.plotFunction[data;1_pacf;1_til m;"Partial AutoCorrelation"]; + } + diff --git a/timeseries/predict.q b/timeseries/predict.q new file mode 100644 index 00000000..0bfb5c9c --- /dev/null +++ b/timeseries/predict.q @@ -0,0 +1,98 @@ +\d .ml + +// Prediction functionality for time-series models + +// @kind function +// @category modelPredict +// @fileoverview Predictions based on an AutoRegressive model (AR) +// @param mdl {dict} model parameters returned from fitting of an appropriate model +// @param exog {tab/num[][]/(::)} Exogenous variables, are additional variables which +// required for application of model prediction +// @param len {integer} number of values to be predicted +// @return {float[]} list of predicted values +ts.AR.predict:{[mdl;exog;len] + ts.i.dictCheck[mdl;ts.i.AR.keyList;"mdl"]; + exog:ts.i.predDataCheck[mdl;exog]; + mdl[`pred_dict]:`p`tr!count each mdl`p_param`tr_param; + mdl[`estresid]:(); + mdl[`resid]:(); + ts.i.predictFunction[mdl;exog;len;ts.i.AR.singlePredict] + } + +// @kind function +// @category modelPredict +// @fileoverview Predictions based on an AutoRegressive Moving Average model (ARMA) +// @param mdl {dict} model parameters returned from fitting of an appropriate model +// @param exog {tab/num[][]/(::)} Exogenous variables, are additional variables which +// required for application of model prediction +// @param len {integer} number of values to be predicted +// @return {float[]} list of predicted values +// Predict future data using an ARMA model +/. r > list of predicted values +ts.ARMA.predict:{[mdl;exog;len] + ts.i.dictCheck[mdl;ts.i.ARMA.keyList;"mdl"]; + exog:ts.i.predDataCheck[mdl;exog]; + ts.i.predictFunction[mdl;exog;len;ts.i.ARMA.singlePredict] + } + +// @kind function +// @category modelPredict +// @fileoverview Predictions based on an AutoRegressive Integrated Moving Average +// model (ARIMA) +// @param mdl {dict} model parameters returned from fitting of an appropriate model +// @param exog {tab/num[][]/(::)} Exogenous variables, are additional variables which +// required for application of model prediction +// @param len {integer} number of values to be predicted +// @return {float[]} list of predicted values +ts.ARIMA.predict:{[mdl;exog;len] + ts.i.dictCheck[mdl;ts.i.ARIMA.keyList;"mdl"]; + exog:ts.i.predDataCheck[mdl;exog]; + // Calculate predictions not accounting for differencing + pred:ts.i.predictFunction[mdl;exog;len;ts.i.ARMA.singlePredict]; + dval:count mdl`origd; + // Revert data to correct scale (remove differencing if previously applied) + $[dval;dval _dval{sums x}/mdl[`origd],pred;pred] + } + +// @kind function +// @category modelPredict +// @fileoverview Predictions based on a Seasonal AutoRegressive Integrated Moving +// Average model (SARIMA) +// @param mdl {dict} model parameters returned from fitting of an appropriate model +// @param exog {tab/num[][]/(::)} Exogenous variables, are additional variables which +// required for application of model prediction +// @param len {integer} number of values to be predicted +// @return {float[]} list of predicted values +ts.SARIMA.predict:{[mdl;exog;len] + ts.i.dictCheck[mdl;ts.i.SARIMA.keyList;"mdl"]; + exog:ts.i.predDataCheck[mdl;exog]; + // Calculate predictions not accounting for differencing + preds:$[count raze mdl[`pred_dict]; + ts.i.predictFunction[mdl;exog;len;ts.i.SARMA.singlePredict]; + ts.i.AR.predict[mdl;exog;len] + ]; + // Order of seasonal differencing originally applied + sval:count mdl`origs; + // if seasonal differenced, revert to original + if[sval;preds:ts.i.reverseSeasonDiff[mdl[`origs];preds]]; + // Order of differencing originally applied + dval:count mdl`origd; + // Revert data to correct scale (remove differencing if previously applied) + $[dval;dval _dval{sums x}/mdl[`origd],preds;preds] + } + + +// @kind function +// @category modelPredict +// @fileoverview Predictions based on an AutoRegressive Conditional Heteroskedasticity +// model (ARCH) +// @param mdl {dict} model parameters returned from fitting of an appropriate model +// @param len {integer} number of values to be predicted +// @return {float[]} list of predicted values +// Predict future volatility using an ARCH model +/. r > list of predicted values +ts.ARCH.predict:{[mdl;len] + ts.i.dictCheck[mdl;ts.i.ARCH.keyList;"mdl"]; + // predict and return future values + last{x>count y 1}[len;]ts.i.ARCH.singlePredict[mdl`params]/(mdl`resid;()) + } diff --git a/timeseries/tests/data/fit/AR1 b/timeseries/tests/data/fit/AR1 new file mode 100644 index 0000000000000000000000000000000000000000..fb1f99352d314ef58141ffe87ab5b6c4a1cc3c2f GIT binary patch literal 109 zcmey*n9R+<%D})-kXV$MTg*^W6c1)Gq*mmoLzx9oIwvu`7^o7Ynv;PMNN#@Tm1g&- VYCk8C4HAQps4^fP7)Z1`003wS8ejkb literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/fit/AR2 b/timeseries/tests/data/fit/AR2 new file mode 100644 index 0000000000000000000000000000000000000000..81d320880e07fa841a280ce1286dc966ec0344f0 GIT binary patch literal 973 zcmey*n9R+<%D})-kXV$MTg*^W6c1)Gq*mmoLzx9oIwvu`7^o7Ynv=l{Nd8|}xp4K< zI*0p{?mS-qy>5T^AuV6O3(fW!-&6(qy4vg)x04 zm7VdgcTBY3Zf?%K>Q=XXT5kKz9bEnP->xR5J^LfH--$`<{sief`>ua#3{7)d_a8Ax zzU7mbyq`7O<4><6G0ZvVFgw?ENAxD;s;piO*}i)T;Mezc?7H zVw<}^@UztP&nk`f{}Kw+wkf3Tm-L$!m?mGdpOb+RDb_d{jDWn+xEow?$H~A9j1}B5 T2$BVZ2Q3a@ngdEpv^xL*ejlxm literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/fit/AR3 b/timeseries/tests/data/fit/AR3 new file mode 100644 index 0000000000000000000000000000000000000000..1642ecb567ae1309736e426aade4e3cc309ed596 GIT binary patch literal 949 zcmey*n9R+<%D})-kXV$MTg*^W6c1)Gq*mmoLzx9oIwvu`7^o7Ynv=m4NQy{{b|q>x zI=tzee_(_9H2VoFpOtBew%JeJZ~f;*SkeBo?K2tTw8QMHCqztfkZ#^z=q!9;`uW=Z zXWG8&DakbL_f0PC>(R~JUub-y;^4|^`*g7dwtm57`}KT&r9{+D-ao$~{NZ|&+Wl`% zFRh)UIo+Ol;&h%R5y|^^ecLGiCdR_Pc!}dQw>^RTR~R+D`*l?04+0 zKXYJT;r@xu%o4^ABlaKreD~W-smA@&y&MDAGu7;`s|^t2)SJ5h$dk`je0m-BejmNx za_?;0f8N^X-)4zk`{2yvg^%4P?EicG(A1ruQtX%TZ7oaSPP7+EZ)k1bP`4#pF?SFgzV6j$h)BZ{Ogr(SG zjO;^AbS=N0Yq4kLTQ-Bmu+=`cVUn0nc;o)CW7VJf5_|R+Z}`af_iEdI&K*}4b+IMv z_uIFU{p8G^{ekRn%$9sewRf3T`7J)SWxrdpU7o}7nEf7?nl}A>(Y-&dMZ|6abD8~l znW9|}{%7vjuy`N7UTnI3 T1TH;o?{Q#g&|*~IU*P}%G|I+K literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/fit/AR4 b/timeseries/tests/data/fit/AR4 new file mode 100644 index 0000000000000000000000000000000000000000..87bae7c71befc01d7ad235363fa80f22714ec673 GIT binary patch literal 981 zcmey*n9R+<%D})-kXV$MTg*^W6c1)Gq*mmoLzx9oIwvu`7^o7Ynv=l{NGflC%fzF* z(04ePJFyAu+@I+l9&7MY?)whzWDyf1GjtZy(OM3nLBHm{kiF54Z&J@je@t9^m=O>5F&7=q3AMpCO=I%c< zQ*)kF&m8;6gF& CSu)N5 literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/fit/ARCH2 b/timeseries/tests/data/fit/ARCH2 new file mode 100644 index 0000000000000000000000000000000000000000..7af3136fdd8d75450c97824fa74f3c388eae38a9 GIT binary patch literal 104 zcmey*n9R+HDIRXG^E+E$c literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/fit/ARIMA1 b/timeseries/tests/data/fit/ARIMA1 new file mode 100644 index 0000000000000000000000000000000000000000..cd34de86b632a33770e36c86b906affdd292f51a GIT binary patch literal 327 zcmey*n9R+<#lXN&kXV$MTg*^W6c1)Gq*mmoLzx9ox)4g|B&HWL6r~ntrZA)ymp~W= zMX4$ADVfP74EaTw=_x=1L56ZNumH(xg_awaFtzLtR%?|!@b~%tcuU8d)07_Xmreik zsOZl2{ojFVfZ)e}PB6{MzyzexH93QjLjQRVI^ZR9%wanib^k9EhrRQb~j~Dxa!2yykU?>EJ1&{&a V`~*1&1mKMS4D1YyK!L|l82|=^WlsPA literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/fit/ARIMA2 b/timeseries/tests/data/fit/ARIMA2 new file mode 100644 index 0000000000000000000000000000000000000000..98e47b41a907f046daf7f836531907b8a3b8f53e GIT binary patch literal 1479 zcmeH{|5MX-9LGO=F$Ez5PvJTYrV!%@4hDoL)Jxkk0tEsGSQH!E7?a7q5e!fq8v$d} z1{=`jfynpb+XZ48)@u@KQG%l4Da03vGnFMEX6z6f-})Q+q1O-Z=ks~5_q~33yzceF zjJ1)lCI~{v6f+M?h%|A+vLiTB0e963S5>sCd?r^yh&d7-o8U;&R*X>0VJEP8tTaL( z=5g7kflEWJhyc^e-(}34q5J3?Cwxd1Uy88T@6>o}j1GcAE1xFLs=#iJvc0SN1WZTU zl+K3~qkH;mQ=eP{pvL}Ee^>8@jXsQSuNp4)JlL8XnVkbak2D~?T!EL0nAZ!$>FC@~ z@^AXl8_Oi*$0Pj;Fn1phH6y2haYm?3a@OG>@BX(}(&bQd#{SW5Dgz?e2k-fK2jj0< z4`{6cUg-DTyOuZZ6hK7J-sl=q5pGZiL`C-r(8=QfSzD(D*}dRg-`Z^O*J*Y=Z(sp6 z*S(aM%i!dZ&f+ZGGf<=H>Xm|R zbIU7x9CDD>-f}e7QG`w-b?t3rE$noQt$gCn#O8%j#*>(G?2vi#cUr39xFY1$X>%<& zoMlQg`5I_RX=#!kQo!{}I+0Q(hs*2>6i?7%QZsYx8)_Oj8v>(#_(_Kel&g#K&ol6C zuenH_A`e4U`6YdEGOShX4)fa~1b1q|Z1^z|)?f4bU`urd{_gX|cD~ae_-qZ76+tm! z`ybbT3zrpPW}>nNZ6&Rtvm(q*y_E{mFRLtW_sXA!A?v6zI@6T=O$~NNiDr6B9(oUi zU;AjT1fJ{9?`t+wLK2ylXqZ&O5xLUfn5RKyPYA`OjS3anq{SQ;23WbBJDPPq9YYt= zr#N4;pyHj*&fx_)@@_~h`Zr0D_EOMjMUV%)fAwswYey~|ymFIMTqT54Rc$I;Z8;`% z9}-?reg@x@Hj^EV@pwN@zgeXA#nf#!n-=UNF{rA^+Oo6|o^dvI@wj>DDvBk~TFR0A z)`#=uiA4}{a>IE2>$x!W$St#LI2AA={O{_UAH#_4$+57nlJKz7nKG)(2W#IVqa9y{ zc$utn?vuftDM@!XuMmgFhuZ{y=0g66z;*EWr+8*x+e5x@G}Kx!C{$4?N2BDomb%X%TZTMez1;#9jv1)TOJuk*H0L&vrO52J{Rfsb;4s?)pehK_i{cB#BtzX`TiB;_X0JuPzZvX%Q literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/fit/ARIMA3 b/timeseries/tests/data/fit/ARIMA3 new file mode 100644 index 0000000000000000000000000000000000000000..71b9e363d67ee862b5754007ad0a4bc9426cb653 GIT binary patch literal 1535 zcmeH{YcSkr6vkh-)JoD2BQhCr3z1~fPTHb7OM8~qph&CHN->u1+Rd_9dvUK_#QidV zmKB1!7NJH`vZamJwJCKQmBk=dOgf??vTBH^q`QfH?Du}?`EZ`+%sc1IJM%kpUTDw{ zB0YoPHkc>|>RMA?P+dB#2=5OioZ{h|3GG|88~%_VAhCBa zPQ^wa-#@W+HVK<3X=miaDY#*Xqr)+0VQRg5i1lU+IRB_9J3Gz8S&c4J8xJISuRO$e zTbhJjefo|J9WQ}^xx0~jZ-rq8zq<*SE_?`S7R;91F)Q5ZKJmkjbs(O0Xj-y)D8yC7 zN@#30A4eo;u2v4xv4xSbNz-95UQGIRSw0(!WuF!NkuhG3=hpx1?0S>|y)J)|Z?iJ7 zm2*xtip>M=!bd_{Dk}d5k%TlZm*{^Y}qTq+Wu)3Cv@)K@*5>m~~>4fGxovRAbfzH_77z_P>RV{G_TXl{ zmj^nwq+ei#fXAl|+VUV3MmxwB>#JA6xb)dXV`w2H8u=*o%B0}@Q@^HotQ@{B`{c5{ zNg*5(bVsF+FoFGD+yK6k2Kl2~60ei>@V2RK+9J6GI{KBx+Y<%QR~vsxu|_~Fh4kei zSrP2IbH+4g<3aw_or0TL=}>Iz82J5QE|~4j+R%DLFhgluYTe8Rvbl~7&BtK3DevFi zbgDs+9CqGNCWI)v*7<5m87!I`7ntpd;G}m;P-U_d$aOSA7nMSrXA# z)AnfRTZ$@pvRGM6$Oy1=-{LT;MPRXINi^TP3cGp%a^+eP2u=G7kG14N%rZeCD@(wy zdGwnO-AFhfyh8un@fr|@^nu^}=x|vY6t3M^1A$%n73Hf%Ao}#qoeSE6vuzAwQhr5R8p5jHWeyu(z2rB`Nis_?lkVwvh2EbHP~IHOx)HbiN&V7 zEFnQ8Diot(r07{9N;76+T}qlrv<$@{qP2#O{x;D!{R{e{=f(5=o-^~DH=lFPo>G(+ zrio!#A|siRz`?l5;jbLVO1%)XZxi=5X0mA7Ki)VBqp<%;mo)wE_NX~ zE`~`oR5T@FcEnrzOGlaKk{I=NKEq<$vQVn}#GMB+J{&m5VjpwJ1MTXSJ8wUUK=a?< zPW3Vtp;#B!>yMm*&{9F5JfYG9ZPmDMr+iw7-s?7c+*6+otIx<(gORV%3h92M_;VhL zsH|H^_EigV8``*5%%&C%J93(*g1w9vKVp{Q7MFw5Rm0=o15fhBv{ zAe()y{zL80TLs`UO7~$Kl_N&h5hGtg z9Spq7*>JR~KwtI0SkU}Z0<`Fwqo*jDP(v|3ai-}K1iZmm%GJz8St;K(4et7)>4jr@ zz21Ci%@5;gGP2c4T{ED~OwG2EioV1Qo}T*dpyI=IN^ z#d!DKQe;L zf)x?+JrZ`3(EqXhx3G~gC4$O7@JN_4k^LJX2~#1e80OGG4Rysaj8-nkA_z6{>#EL? zzW!sut)#A!KagrjmW}&u`9xK*q?@O1n@~_GIuP%^6lR!#24u3mIlYVE7@ zBYLJ|RU%|dpFj!C(Wq+uxs`8C9I{T3Lz_b^DsJW0hd2q*ue}CI`9oj8=|9Oyxt1bS z{Y{_5Zru`1YK)E!moX6D=U6hX;)m!e-W6UhnMnNRXINTI5jvjf&*y%^1NHN6vAN@= zP!!0BT%@JIn0j&J8kL8t0&b}+`UpY4!f8BBAOiELLroXm3&AkqYD88~IodG^o}zR{ zf}i}Pr@>G>l-v}qxiuF6<;dx-oK+EY_dUSPuLFH*rqrgm z8Xf-WX{m2lE$BLSrpRsxQRAYs`{bYiHkgs35G?@&wOMc7<#BrW~jh# z%TOvg}rizyJVA7cdk8eGg=SI6uJ-0Wm;s0D=FE E0DFa5FaQ7m literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/fit/ARMA2 b/timeseries/tests/data/fit/ARMA2 new file mode 100644 index 0000000000000000000000000000000000000000..53fdae4be045ea489037061d135f19486909a720 GIT binary patch literal 1483 zcmeH{?^n}R9LK+a0&@@qiGT_i5F}=2DjZVqJtgEIhyn)IkpqJ*m`yjfh2R7OCKHe! zgUJTNp__`N2X`W(LRmpzpg`1tj-`kw=phi1Y5WWgLK8qR8WG4y9 zR>pP|!k1hl1hIVaI96n^>f1g)S&)1b_W9yjR0ny7Wylj&i5?sV!A9TC*an}hr z*^Z42=;UBD9Q!S1lA}A->n3+jtH9f@|GIm#y9hl8ZP%ZspM-a^TQ#L65!+-}Iy>3=ElSK`)OjJHo< zF|2C$I%(L2$oG08|$Kk@YrbYyJmePG+q(sLbea0Zz?@wgf^&~C0UW0Gf;?4r2miY zzlDuLn37cf4Ua;Ykh}jNq!4CgwWuL6ebFtK+)+Y*b#}Dn_=J0ufUJymQPfE3KNdA=~NLK zvwO5=Dm6wATzt{)9HXlWZz;-G7GQhRKAYaUJeb@`Gfyixhu)c0ePMl-cyL~{-zF~w zf{M$5#b)HaL%vVx8E0YU;`|O_DS@_Ix-wu|16f*>cq+37J2|t|Kbxdb^MuXv*&<}? z^{#=|6b>EHxh!ZLqiZLEOJVnHWtVyXY3y%3()7d80-P1( wthf3Qqvnk>V3dLQ8+wd<`2YX_ literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/fit/ARMA3 b/timeseries/tests/data/fit/ARMA3 new file mode 100644 index 0000000000000000000000000000000000000000..17160976fdca23f81948729efd1f8cb4ffe6ad73 GIT binary patch literal 1451 zcmeH{>o?m67{`BjVVP66VRWMv#o5-OE}P5vsxoFabL(`~MJA!COCzDvRkpb1q^Kkn zX^gIJbK;UZXX>!5{i#;jajHm;IW}kL=q@(es;KK?bbrHM?0NBgzu$A7^St?-=h@Or zG9oAhL9po@dODZL;?Vwa1T*J!%C2SaYUZw{(Nnkthsk9z2qrgc$FMm}293c=%F?ac zUPmE3b-kj4c~{w8jloE-iZX{Pl-f}fDI@0~Xb!O56cvK?*O?@vMh3>Xi;=&o=?GJp z4@-O4h}d}7NAdi5Jj6ALo%902EADa5_@Bq4DBhHVwty@+b!mEm`2lH1J#pLVge?z}nv%D1fkfZIv<*oV!G|L%){cQn4TJ_%%{ zk)(}Z3AAx1q@$)$uwq5O6r;;4>eJb$4vNv|+F;Rd2k?X&8NC=vRx>>C`OEy(b zs8S4NaAUHvdqa4zH2dxIk2|Iz-}`c)ymOk=>KN&2Ws5?%>-2wI|1De;LQhBK8MK}B z3>}5g-$vJCIyx&T4J7!l>N%4+fjcu1Rzu_MUNig1-)0?m+gZ%atz1M0k!)Vqy z{A#}h_B0R4y`@xq>ld)>>?y!(%=gNH;YNtR%T~1uFTwT0s{5J-34Ym^Ve1zo#<9tX z^yH&bbA?rQqrSO=UFSzvshqSi* z>5yeYEz+E&jwbH8$iG^)DKxCY+^Kd z7boQ~TIfBlI97|S>uydIjRa;H()P5+d046p3w2m6!eFs`Y!Iaqq1rLy!p3qqxj!(B zwvs~~ep2~XCKnN1nw}MNIrIW=SqBZLV8p{yU$|a}jbxJ%1)tT+^bQ}D3{cE;}zX3xlgH`|l literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/fit/ARMA4 b/timeseries/tests/data/fit/ARMA4 new file mode 100644 index 0000000000000000000000000000000000000000..2e5f7cebde1cd4f2f82474f59fc0b8bebeeb6239 GIT binary patch literal 1547 zcmeH{TQJ;r6vzK)tz{Ezu%@aK61T*pE(wFl`6t9JChKlj)Mf2TM8sv470I|S3F3E4U8BLw#-n!)l;4legCWoq=l8#?O<+ zKt_rc)w|afn8T0FNlvL~_P*{hJA*<*IG;6grz#t!F8%%}n^1xr79(iiQfh%In7c;V zibV6T>Jr|&6+w-*s)|ZqA$po*eWu&i3%Q1xmJ)WYk-1I!Xd#~my1`{eN(VF1p@@cDllpYw~*aPaTi7FG-U1ZL*iq*+}22E-k_RXCXvG=R*<1Pb}{VG;uJipB7xazsXxK6KgiO5nDujk0x=cd9ymC<&< zG1Y1xj~WpEB#K_~>q6Uq10>3SRF3n1jQ=T&L`+3a<=Y zi*O8QPiBw{aZIc=xtL#!W0{spW{87-=;gF^>kB8+a5cK2bQc(6B6eEdYSOc+OtPv6 z-rERWR#7dq43xd>_RWK?hGjI{c?ax8^KZm6#c)CYj|O2i59X%c^{=dyL7VfyTSh=W zJnkWoG-E_S8LwBqp;rZ_*A0FNEfYZ4c+%Id9y}22s5wtF3ZVAUYSZ#C!Zgn=Eb3(zp|+Pq*ws-D4DXCWi>pPdR{R8 ztN>a~ZZfRfgb)zk%HMHp1WIt|TBmwB)P)aLcf8|+pH{49W7b_rX`-X)r&+LV391WY zS+H!ZD?}qzaPVX-tvs5ECUU5f*S8vhO1_!1g*)Mv^pBVy}YD_|e;3{+6K{uEr1gH0%ih_jxnnFr9rAe~{wG={<1_A73&!PwV;|Z?Vgx zkX8(F?Pk3YpLss8B_UkLz$!5F6?80jHh}&W#xo129FQM)#hN5wiSp|%7mCM@!YOP= Ku_FqTDg6o8upE!L?VRXnC#L2(}R0-wn zhZ?(|lYtFv4v^T;Z~)A7C~^SP4L%TlLJEY>U=86nL^yCVa08Vbl-`;XF)iES#(^2T z>Q1FQyoz5X7SfUKu!HU4;-s!xbB>G;LCAn@Xh{qezCv9`5zO2rh{A>v)so!|KZjB>)l#+Ea`c=9~dzp zFBdQrG6XOLGUNi|hM_n$u{b_4B_%!p!3<=`11bY4{Rs*h5P&n-!J!2a1cn9J9vBZ4 m2_Wzj#`q6Z05Sn)3Pd@K2Me~JAh&`5B(%XyiFTkLKmY*aNT7}Y literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/fit/SARIMA2 b/timeseries/tests/data/fit/SARIMA2 new file mode 100644 index 0000000000000000000000000000000000000000..68924cbad75f7b6c39d0c793e8b042d120fb399d GIT binary patch literal 1879 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 G5#rxc^k26C literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/fit/SARIMA4 b/timeseries/tests/data/fit/SARIMA4 new file mode 100644 index 0000000000000000000000000000000000000000..da101831a2556783d85168a29e151f28f5b62a4b GIT binary patch literal 1967 zcmeH|`!|(Y7{|AU%$eMa5JhsWnUjQ8VegbmG9e=5B*!{oaGaN1ayhQY9Cx{2nq$P! zT3Rk;)HL=>x{yJvP??Guml#7+3W<4-%bK<3FPI-@|L}aC=h@F*`&sMz?6sFfJT?GH z007&abz%I7A2gvi9N5N4 zg}t}^rPOd3?A*|6)O0@%F1m>~>Pw%)mgZ=9Tv2CVwGfkxi!?%*rh5wD;A3a+c{2ytV6E8X z<{gWzirt#c9hk7_&FfEgJ$ZQZq~l2Q6)u)Y-*R{T^I-T+Wy)tXEg5G!1q?sn7eTKs z#?v5$JZzRFp~V??f*tHT3ugt9SiQb5C|M^H>-tVtSpZL1x%C+LAWr~Wo_D&o6y)QU z1s;(kmV^K7*Q(iTE<^r!{nlh#r97(NZtA=+_yOlca>sNt<&j{0U&y(cIauWSGb?#U zfs}|Xp?`Y&1yV0jnZF%FM=}xvM~bU7Q9<4NXJUu9Bb71Z_KwmBoEh0x+gN%7e-yYW z-TG=2+b6%;RMqkVf1os@*6b1u?4kI-eE%)^XkZP-|>JW=#O-TF$2k5mK6u6YKXJ>r9S2MqYyF5y(ShE%uR>(q8w_Iu-2OE1k0 z7C}(@;hoOiD#ze}$kktE%-E3dvGwzL#KVu4O#2MmL*Tfz_MrGd$zS~sq=@! z@6A+0-vz)E;?6s1YVo*Px@e&PaV`cwL|*Gx&cjTF?_#4%y<)MNK2U|xJy!Za%u zr)jnsNE#$!<hA%Br#r!L zXEGT!|G2imk17+E&Iw0cxaNctEL`HNb}bf$bDNfv04%Lie0kv4O6FGPeeE3puAYcC z4nZ`qa!hh#zE(5>z&f|gf?Did?lc`XU6tRaHFs5)~IXur@hT3{&IR*id8} z#N|`xxcl{wpNORRw(MhQ0L8QXqyeg)8>l|BmyZCsvB$uOM{)FPU`WN>D6z)!2n5j4 f&~D#UDlVOMEKZ_gqB}}XSTEB(%uA;7D_#B#WIS1m literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/fit/nonStat b/timeseries/tests/data/fit/nonStat new file mode 100644 index 0000000000000000000000000000000000000000..3cbc57cc3a5da9b4ed6fbd0dbd21c76ddfb7683c GIT binary patch literal 96 zcmeyTz{vmtTwoGJEa($RWp!X+n32ExsVb1Jc+?q#Y*cY-R@13@j&(Z?tD%P_Sk2V|D-lZ^Rj6 literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/misc/aicScore1 b/timeseries/tests/data/misc/aicScore1 new file mode 100644 index 0000000000000000000000000000000000000000..b8d2a1ae25b57f1df2a84e18c0e182ef6073f522 GIT binary patch literal 68 zcmey*n9R+<%D})-z>vaF$WT(mP@J4!lnN983I1dP5+LvsLLo8!Gk#Yv*76s#JmvrZ DuhtC3 literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/misc/aicScore2 b/timeseries/tests/data/misc/aicScore2 new file mode 100644 index 0000000000000000000000000000000000000000..90981b9a9e28cfe51cf1686cd41428046c39fe90 GIT binary patch literal 68 zcmey*n9R+<%D})-z>vaF$WT(mP@J4!lnN983I1dP5+LvsLLo8!Gk*6twCsvaF$WT(mP@J4!lnN983I1dP5+LvsLLo8!Gk#yo{nRhe;+O*f DxNHs{ literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/misc/aicScore4 b/timeseries/tests/data/misc/aicScore4 new file mode 100644 index 0000000000000000000000000000000000000000..d3339401b64d17b27d4f8c565e78672d6d78308f GIT binary patch literal 68 zcmey*n9R+<%D})-z>vaF$WT(mP@J4!lnN983I1dR5+LvsLLo8!Gkll2@N3@z(_;<* Dy=f0W literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/misc/lagTab1 b/timeseries/tests/data/misc/lagTab1 new file mode 100644 index 0000000000000000000000000000000000000000..13349b34ae838448921a896b780d2e6a13ce2007 GIT binary patch literal 64112 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 Xgqz-@Kujhc+rbAwe=q%C-}L@3{q|Jm literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/misc/lagTab2 b/timeseries/tests/data/misc/lagTab2 new file mode 100644 index 0000000000000000000000000000000000000000..b362ff20154f22bda45cc6e06101dac74d08bf2f GIT binary patch literal 64112 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 U<_)8(9$ob+IgK}c{<`Y_25V-x+W-In literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/misc/stationalityTab1 b/timeseries/tests/data/misc/stationalityTab1 new file mode 100644 index 0000000000000000000000000000000000000000..7241f78f9d48e5ccb9c01959ef090858a581b2f6 GIT binary patch literal 184 zcmey*n9R+<$iTpml30?+ki-Dwu>pCGE^fsoi6smLWr;bZsSF@?W`15`Q6+pCGE^fsoi6smLWr;bZsSF@?W`15`Q6+$lCRKyKc X@>TrAYPP3rP~Q1B%TxI~Sq=aIuZk}M literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/misc/windowTab1 b/timeseries/tests/data/misc/windowTab1 new file mode 100644 index 0000000000000000000000000000000000000000..c4a6649738a7139991b1c664141b63d6b3551774 GIT binary patch literal 231725 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 Ee|8;FApigX literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/misc/windowTab2 b/timeseries/tests/data/misc/windowTab2 new file mode 100644 index 0000000000000000000000000000000000000000..0991ba194167759f3e4f5c40d9028fcc53b74344 GIT binary patch literal 71977 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 literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/pred/predAR1 b/timeseries/tests/data/pred/predAR1 new file mode 100644 index 0000000000000000000000000000000000000000..b666b3f2ef0d059207c4c621b3a8180a98a4c0df GIT binary patch literal 8016 zcmeIy>pK&A00;10lx(q$CRP|BY=#k%dk>Fm9dwyXF*?-hC^B81gIq^0xx`_zc8N6B zT$>nj$dNjoMRs1ji?vY~fqC*g9X z%JI1=N4pn|MOy9`#qWnQ0$!Oz9Q#eGCjBgOJM4Na^Egt?1BHPNx8)YRb|z|SG)LFc zp=<9U#{z7}!%*GvDoBs#{>#k6AB8%@ZTmu3r#w&*_N&iwm#4qXisa5VgoL_8JD!Wp z&!tGXsB!Rf3>s2s8(`+L8vF+Lz{9-{|FyJcU*s`$v%Gb3tMz;`duY7dCfy>&d}6?- zWwXZVZ22{*wSp>`E18aLroDDIUDN2dE7(RTt8CWm>H_! znpdr7W%?vZ=32&Y5Pw^5R6S;EnPNvjFYWzRn2dWRD`6bAFqTR6G?GT|$C5rUrd*>* zAwoI|)1(kY#&d^7XHv`|p;ufsq7VXqj6?J=p^P{fGMXO7HmFB=Wn$NSg@kyj1pm#) zq0W9ozW=3WzgfO&v$(AwkiWSsPCr5#Go3#XCeR|&?j<-cEzUq!OlZxze50haT2qTk z`0r?xNhqfIxwkg+Zk;lhj+r;wJLBf-$UECq1N(6OD)pnY@myj`Y%yf8dNqR7<}W*H zgyGYptU})0xj{ug$4Fxhl^RkL71QfH*v^SNRlS*e+y>vFBwp&gJn_KW*KI5V;!=U+ z%pay)bN(JvsIXNZ5&wIR3VL zy?1UPc~f`MHrs&3%3)C^W(xr=mC)Iy4V$3*8QHV2%b<5KeHca15a%uh7I?n@>G+UH*! z4c9)VU9t3uuBGW+t(9$C%o!WEDpyloU``{ucD}D}k>Q-Zf7v+RR5NcgqN%H7<+>!H zmSuLIGP<}pkFAMvKw2ALDQ)-O>LL|3=`%)j>hBK&|!yFN*$~eSu+zAl&supK3oP zSWHd|$Y&hnYjj7x-j0Td6dS_Rd6c>;T2ALOG6YfE#66dq>q&khne|jfnwP>mZk3l` zmz*Z(Pu?>`MEn>-F$inSd3kAY#**Nqaz7~PZb0flc4vBRTu|AOZ8z%0tVZ8)#^;-D z?nS7dl-vA?yxp|5tTmrYJ}=WUw$5716Mr~>$;uYAK1MFz+5(RwR%`^CWD>*(X3Y5( zrT8GFp-nTljL&gjC~Vm?xO##hVfg?h9hxw8NHuH%CJ$zC#JIRLeHHoJL!DotalHyW z)F?ak9E)}QvDXeLC8bB2gWz0SSr_~7f00e*l5C8%| a00;m9AOHk_01yBIKmZ5;0U+={75E4AmL}i; literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/pred/predAR2 b/timeseries/tests/data/pred/predAR2 new file mode 100644 index 0000000000000000000000000000000000000000..4cf3b5d37b7d611e420f1b90438589143dcb9b40 GIT binary patch literal 8016 zcmeI%?Nd}`7zS`~NfeEjAfpjT(=b$s7k5e#xI}YBa0L}e8e+qRAq7Gttbuc$X8{or z7A6VXB2YsRWd*c#4bX@tC|V{lMT}`sae3WF2xyGi#SYM)(8uR|IbY7qbIzIh&3#|@ zJ+or5+3YnWn!A|=N}iUHZ3M4m*;(Cnp$OO8#oX5stnONAIuyqtVZi5@$&Q2m$;OZ0 z%crn4E@*ADsSMMp6n-hKA(|M1PA>au3;HOKO>8Qz;QZsf5>v_NX5B2a!Ab3${9z%<;^OvZSuh&Ghzya;ZZ zpY`TIP=B!E?`wAnHvI5m!W|C|WoL8#YLiolw&@EF3eS)JB)K{!A&|9?{OKAad`F!s zBR`&pQIr+v{TYwbhTs896TvrLiY0Fg=kIwu*rQL+gr~aA$K9Ubg&i*r#vdk#X!MJn z9406#EU6qzAmK*ApymUoOoFCO0mR1;Qq5^(Eeruqm8fs zrEwl%3kwbxt*1z`4gDf5j>iV|@VS}0k_X& z^XT~gioG(5py{&TOlKsAocc`{s%}sen90BM_orA|;vXax&J9n`39PQ=;c&WgxO)f1 zbst${Z!p0D`6X@I%M^)Av_0B7f|}&j@z(?&KkgAbO$$DZ5vsKTf)AzjgRXYLht@mw z=&TR%=S!z<3qG3OwfJh*hpB&R&PRCO$og3y{Risie0Zis&iN?Z9Wv+R{y+0Ra@F%b zyz=LL6#VDIvSQxHOUwuJ!F+)EVD*F54+>U4SbxR(Dr>QK7{?i4MWEGHV^A*BvWpH5ZA1eEEZ7(1A}FvfqevEmQ9waXtg{KazjF>` zwrE^ZN=cECEQls|C~0A-Bosu=fmFh1V}bDkI|?x>6r)Vb@;CJEoHytFbIxa;?`2-< z>acLeo$QxGiY%~YcACQ65vD2(WH9@!$}D=SgJY&<*pS9><40+IO^N_kRA~6ln@+Z#27doph@_|x9#8AHGxX$bA(pEYw?_Ql zKHy9-n7+|N+(1we(?68&OAwV_9JO_n;qdbDI@L~w_2pfK*Np-?wkaQtb}{Hp*`G!J zKoPOGRApWMpTkR;V%dXAdC41J?qTrEJh zQqsF&fMS{B+2o_w2y%WqCskZwK;QDtlGObWc?PG-?-HyH*xdH0ilAnVtH0n*P&Y+~v(e+VRtBB-rC3+nKWcW1U+P;(u)5-)s4l@a&p98L z+;M_vTqeecP3oLFdp`tyu`-G$tSPK+HHKO3#!=yA3K+-d_T4E(IaW?Sm48; zJ89?RZ=J!;$44KZw(}8`qp|bxdj29Gt$!}^al~bjkB3Q%e5g4eoDa?iI3Ij}@cqHS z_XpQkTwig0#r+5OAKZU%f6o0m_vbvn;`tTNuXw)8^Ie|r^8SPOAH4tI{UGlL|KEP_ EKf2!YF8}}l literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/pred/predAR4 b/timeseries/tests/data/pred/predAR4 new file mode 100644 index 0000000000000000000000000000000000000000..759f7a013ea74753deccb93c9aeaf1a8e1c6a75e GIT binary patch literal 8016 zcmW+*cRbbK|K}4*qKqifpd^a4lX#}cE@YHdX&4nr<&O8dA|fM6GBO%ev`|UeDU?*2 zl&FYQloHABe1HD&aPM``Ij{HY^<3xQbZN2w{dA7{|6gMDUAwOoWKakfGuNK^foe-)(rO`Q1n-yV&u7sj8@gq@Ba4X3H-hVO_=sIRlSw`%fb<+LjCWm#ssxkw zcXkNSnIJte=C2Uf((gKrcrb{Z_QvoKCBo9$aRJkinl2hl7w1sm(_{8-F@yM+eEIwS6e?W&jP3eZcm;b*DRN}sqMv5f z5Kci{LB&&{k&id>a%RT|1(3X{q;!5Xg?pCDI?Ebpe5%q*GU0J(;urcx%8T%GRg|7y zC5P>wT*gmZ!oj4nM(Jh%g^o|*vu+Fs;lH_dSNk{$so&FX7w57leV=sPUW$jdkBjxo z%vpqJossxa!9u2Ug_7e|4u4+XGu!3Dz%V~iH*5lfZ7Y7$>}eXG@7=#JbdwKp<6BPF z5|*D zSp-Lp`n~!;3hv7Hrq~_hA+;;)Y1}Rrb)#(+DqDEamt6Aa@;3&16B36#8(9=CoWeRi zWMJ=Q9DV4e0B^h|4GeA-Az{qWEG>ow|CZiknKBN3Q%xH}IU1@RAKo;Zu}C#E3aIQB zVUAR3LZl6cnGfQo?ogspzP&DOS(6YklLN*V*KrtY?H{RMz{l=x+3-h3q#yt69<7i= z_8Z}5UaBiZ`;|k(VrN+Vq||OkWwN-w(twq$XW-cSVO#cO8il)5?Yj@L_~pAhU3nP; zOS72W%}dBR@-Z;la`jf82dd@?6hf zt;q6t&*AGXzjgQPMc8%9=*0dO7EkIm#wffL!nA9Rr`mT8meaPWehg$Gp>ZazgUr?P z%8H{|WL~E)olt-A5`$~De%0|jA&gI4?+zsAStJ*6=|BUED~~tWY>{P=^XBD<*$fI! z9qMrtX$mSXcXj(EDNq_x>i;xY$Zk2*tMng-_%vn53vC2%+=or(H50tqp^~0)ir_~1 zIMd^WB1FWrUlO|{#zI&2io4-w5w@$9 zlr{emKqw)!a~;z@lSaJlf#K(6q%Nzc{;rH?v3_ib zZLchYSJFzy8@BVY;?nIQqYefYpG&>QP`Hu4E3@Q7BOzYxj@zN9BDcGs}tx&kZUQ zdP^9%+eocix`l@#6Zf^>^%$)4nRIphECvbhtET&@GT^zWgoQ8WAk$+$H=5sI%bDsAhMu=F&lJvAD z25#=UtDNTw@lj!Cf5vJKT-CQ5zE4BZG4!JGlr9aOg<8*}t7ufm)J_=+;gIW{W}ClQ zh`mNS?<1SYI!$THdMpjuicGE9_6#;`Uc|GK6Ja&4;0|VGd`rwDsFXK%i+F%aOtX33{2|gul^M& z#9-Ta&n9aY?zMqS&%GtjCHR!_uX{Wgmxf$3Eo2d9d(PbwK^okFa7ZTXI27i^(qLO#}`6;?xuTFce053?}W~Df?Fpxw#?{h7h+7-qnTgw z8NA5YWqYxL!?cvNgooKI{vF-+H9drZfip8;;!7i^@M`?rQDlATp`b1U3d=iYXJ^b4 z;_CGrb>bld~C4B5F*rheQh`}s=&WJHWFvH~d6TL+& z?i?C+S#efi$hS&^Hf_ti^K4@sE7A5DJ*bnI_!N|0H(iYdBbQ9 z-BoRwhv#t^J@`#%pUhz}?!Gr$&cQ&{PkD6~ha<9UzRUX!Vf}-moI)U9>tVdm4+) z$IJa^4f3%eE0cC`}p>n?spBeqTEQNeHst{iySlP)jBP+quW)F~dM)lUjE>IK*v z8dO9((fEFCfq&~$0dlMN-nH>%@UE*=eE2q*1Fyfg6za&jKVSR0%%h-Sl2TZIRET*+ z<7?#!PD&M*das+tp|>$L@h(SrOJ2tCTnirxYg@8AHj(-GGaThTM}#ZuR^`4;5`tPE zaqdtTi#)ORTe?ICT}a*NS4a3&MY*qRE#b%hGy3@|&xLqbyk+tR83s=@GqMglP^fl| zoE!L#!ThaHtdEhobzHl4qgM=rxwaOOdKW3YTbG?bem)CRA1d`UnWxWz3!?`I87vF( zNaGRKYH-p~CA&T6wE z>xlkoOFv$`m*^vdmiCnoN;z!xIH9S!ia}>*qHFjuwDQy|Q+qfdDlX;xo-$SO{VcnSUmH`){|+sgq=Grj6w7 z$yrKc%YaFYyR8ro=f@1>w^Der-z_h_g+XmQyI~#qKUIDG)lyeEOt+b(rb_TpTEEf7 z!d3*!;2m#n6WlCvD7_W!N5iW=@aE7%4lAWp#_9f~kn-^G_S>a2^u`|CD53m6yfphZ zCX7O7*WV511cwTu)h>G;7eOs0YYIpDJ8pNw-qVj+C@45=a3^~Fdw=o{CxYwm?Pqa6 zB3Q&GjzRYd76~$LQ$Y6b-O=1$Z7;<2I;FaqlLYuN9N^w?lEHZ64lC*x!Lh`B5pFRw z=oOyb^Y-wO@W_2q5z*B;tM!Y!v?=KM%GfjzJ-SrUy=R9ujX$>~KkZmU`m1nWb3D=M zS4JFF%H9$^W+>}qewXOc&59y@OBPe6*hO~{y%`_Tem+xh5SvyW2;I4#g7v)0h}UGF zsv{?LG}f~icYK>lW)cOv8}Izrujeoj(la?`oe0HEji)y88H~7idaNl9fwTC|hw`;V zpD%jer7$eSgz}1q=gu>D_^#YNqn_YKVfTkO*I1kuHO^Nk;$hPCQZ*SPGB;a0zZh>~ z@OMMwi+-}tfw6_brt&ndX-%Es9ZvXArYfY!QGg9@o2J|t&!X|G$K({E*V`ZZ>!~cG z(DGc-Yxy$bC*mbvjJZH#M-u5&qugRgpBrAhGN9koyW6 z`tnv$9+m=qf(Z!)k z$390xfx%e|1*hce6r>2S49uo*wfL0(#&Il??EgGrPBWOSGHCiLgy_tCgYd5F98{79 z2am2}@wn=6gyK>@E-$QDw6upt{-&ZgPo6UPSGePC-yRNb^uxT0SQdljrz0AE6Fr-= zX5vmU4uuO&My3QZu$a-BV_HVzXv1c~NInI#-fgqo)>D|ZYl>XE4~vg=PP?zq62hnM zOh>>Lf*+SJXq_0%;#{IlFz|ayixM&XMo;dO0q_j>Yhtd-@|%6b?&G z*t}x35G#J#wm%^_N-aH+7)JETg2_|goPH!g)z*Yz+bbNdJl|OLwvmFOl>HxRPY!jz z-iv#T5M6R9?_TO2GWX_+`?ULM1czkX1o{(xyz*x8Er!C|nj5nx$Os{k@oqnLl;CWl zvGcp*EEetX3)B2_09_Ra*pV$9yw{GYe%i!`w?XCKxhq**C_1}o@?shr_wAE9>C7Pc zSXo<1A@K$GK7LaF%E3LzttO20k*Jyf?RvHVcSG(e*1lly{jRh5g*~Jnb7n%hCj>)g z?V)1?G>p~$tBWQ2e2L?o7c|jVyS#;`3}suvk{GNc32fkFzUtexD+9Y*X;=clS*)kGgeg^QDPD-Z}DAcDV@mogP1HC+F_d zDSc3-%0OR3=S&|b!1tc#Q@QyxtXi(ceQly~>$SPdoJAbEYHl7L(?NW%OLu8|0S&+B z)`CA}p*TBHXM28*0Gg@GPu2z=#P8mOEzJW&r&}M%8~uZaQCKtFn>lh2#&6`099U=eht zQ)eNW&re=wZyTBN@!{d37werUoDL0IGs#AX%NK2A7iG|x<~-+z)^ehgzbK9QWk>w? z$iL7A3o;MI;#P`o9P+&va63I2h*|je%~(QXt*2Cgt}Bg#wwJ4C6Q33!8Y>+~bf28L z)7vjMDO?HSR>~ZvF|$0T+d;^pIx8T>i|l8;)@?m)qMP1D1{TOFQLrxySnoxA=l-yO zm8Y{gq-^=)y3L1!#`7<-wL2LcdHF-`)kPr$VMDx)H-z}wIqH4icm|JZH(s&`-SGOa!KEIZm@8?`GLjtD@_E(~Dcy6w!T4x&E`)?M1YfV@7}>UZ}EUls#N zI`%{FXn2~YUY7dF$9VJM7G5VGcQ_MXXrTb3*QfkE{xt-FrO$JXWjRRrR_?n(eCra` zL#He%N&cZ3@cG$92G0WxC(pQk5b05t<3E@R@p;vniDgfTZ}Ayg-fqBQgP_A=#}*0} zdKuq0x3gHN-(X`_BfzJUbn$GWo6QxX*SQmZvzL8wiM1y=&v8#3+ssfT(5sUxdIY$& zWYUgKEk4dC9+>MmU4Yu){s)Pg0(@*fY;F8@>nSCPxzC2iH2c+;|Ye~&&8(~zFKT&q37Q@*fL0AYS2YqOd<>M9<6ph&pDdxUtsXUj;FEjYv^gyJ2Zw8 zQd~E#CcZWHnsYJH`Mc&SG!Of;cqw*$`R6YT_DJVHog~3w*T;ex7k3Dum+)oe<2wOl ze z@2?icgl{)LR(POxn#JX+O`YD=|I2ao^LBX9&`es`oJ{me(ZbddWo z5U3~!!N>N8g%?YRe>tljJU^J^aQyo}XmwOqGKgU8R{XC~xD1>(QFg-I}fW%3UU7biD*j?xszvD`L#=B)7 z4|=f(G*z#u&1djwunK0IggCkBTI|a<7GD>t#psi|ojYWn6gQLPv`5@rLtO+IV^`MY z*F@@_?AGukkjCY(b@c2hq(37~VjI&b7+s4FlOVjHJFq;=Adcj8KYke9C3AJp?OJ2% zYXN$$v>l5kI%c!5ReBW5;>^FzD&{^yJP8fHUH66P*Eu1>SI&_af7Aaotw=^q+aQ{)hCcB>GZ3lYNhSkyTj%M>EF;Kt1y@;n1?Z9Q=}ljJhLHqjO%9G<+0>aC3;Iy0!DXM7fmwP7ZX zPR9s-Ybrby5*?vA_+WO&2sv-l#I5n47!()%G%xm}u>Yi7)SsIiEKb*?84#WlTR)bk z6TrvsWx3WZV;S(hn6C!542B;C3`iP~9DI6c2kk-X72$TkpUn9+$$e8PG7psXC#hZ= z3ZZuTXWVDAIJD#s_cWTt=QpW3sqqXZnMXS(C^L}1*p{rYjfT>MiX4@792BgUy{(WC zAm^mp!6@Q4Ok;Pw^dh;}5|{IHZhfJk9w5$?%Tw_EAZdRmf{#5N=S*Vtc<{MnTu1-q zA`>s;pEXoh6ClP$;ogtd>g@*Ovc|99^7!2C)&@m)A zgR%T>-)xdMmudOAzHcCNth;vdg^wg}7jvF>NsfX9-B?<7lZVObpT#aoa(KA7qC|)2 zL1Bs}?WS()Dx7V}Qh4mqSR zoW7kF%^>*jQbKb`imbCl_@Pad#KKkgkhz5;1?`Dvf6bUe;rc5%GYM%5Y?pVq<}DUm z4u)Lnub|N9{@q6*iH3xh%%J0WqDwx@l%J9yens8+c(Nvk%E#NsiFGlsZks!9S&{(% z0;l;OeMxdj)zY=jXBq6bDgM`gR|uZ|Snr&1d{B3{vO*UgcEtVXRuxWinDKYC3}s)jAIBhe==P6fk{2$#uQ2+kI>~<<4~fktdcpI+ag7W^axc(4J}8Iy-;8ydehc3U zA+_0FRh#(DQ7QJDZN)k4ICF_KIO)UVs~n|{iSRu0pTw(XKI(q7-Jke~h1~>p36Ios zNo{`g1)>|(XPEBQt`p!@d9JHs4TmE2txDqGiQm&pQu}a+fmWAgW&J@B_B-gD%R3;% z#A@ed5|$jKGHn8sCJ14;dQYG;@zVkI;WIsno|(R+v9ErH2rx7q>*Gr5u4Zw6V1*E) zP5jl55S;t|JZ$(j$(bq-YW&qAcoW!jU9^n69yk7M==6i6zZ2<_7ILrkcewe+4>HF~ z&hOk`Px?0MtLk69O#)1nUZlLdltOS#XRO>29;BoK%`6_1yuW8f_jH!v;WcH6UyTg> zNM!I} zsO8f6S{`;MZ!9URCVxwwtF9tBfvO_y*=kMjBiBj3+>68BtoDC9)yVxGH#d5a_|{1w zyG8y4CqLLu|8bl=pA+mgX|ct`x7D`QI*cLu(Wl#a+d3NawRMkOKO%zFA(xX{58v1;&_qhk`s}tcg4taXfpFYOZ1}d z<&}!xazel@(6jkT@M>K4+C4`_6!M6yu5RnrxZaXh-rEZ@r?>SJ48T?9gfq5+k`% zU`o0R!K>j*jUr`opW*j>*N+?I-iq5!{kt_F!bPdM5oxn%WZKSbpXI?~lf{c8i_KZw zU%zbUBcfll(kzPY4zPH!2kRv#&@eE(#&JPH>`p&$SGCq8I_H~nQR$-93I_KY5(uxDRU@@y>;n*NKDYW+cUd(eYBLF7JWsba>nKUFNg z>E?Fl&S22qZ&)Tt{QdyGeTcLQ!9SUygp+4EN(7K-+VUgB!!`{ z-Sr#Ded_43w;C2MXK}DQ;^u9F3+K0}Zd2|Jfn8XAyqUHTS5~Pxj`e2YJiUDW1!n>N E4@~ow;Q#;t literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/pred/predARCH1 b/timeseries/tests/data/pred/predARCH1 new file mode 100644 index 0000000000000000000000000000000000000000..a3aa6418f3dfde05b6e7b272528e2958cc32a180 GIT binary patch literal 8016 zcmeyTz{vmtFPOm;!+fp$w@bA|9K+luykrX(aXheo;|c#&B94{IuiuvK7IE}c37@YC zq=lAC9GwBAH{QC)QVXPS?%#F121rZ1f9M6`ul#Ol1fp$j&oKnid@OokzCf4+nD+7& z2h;PPL+H0xAau!P2rUBTvp{JkD9t=dgTrk!93bgrG#p050aU(>ri0ORFq#fV^TB92 zFj@|bmII^Zz-T!zS`Lhs1Eb}@XgM%i4vdxqqvgP8IWSrdjFtnV<-lk;Fj@|bmII^Z Oz-T!zS`N^n8~_09QpPv{ literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/pred/predARCH2 b/timeseries/tests/data/pred/predARCH2 new file mode 100644 index 0000000000000000000000000000000000000000..3710c8ae93718784e677cfb820e46eccbfbff0eb GIT binary patch literal 8016 zcmeIyAr3%L9LI5+&8CZjdk99gxqvFFstHsQf#L$PBX~h|2L})&^WJj`+xL}kfBXGE zk7pa|ou-Zv&-ofuPS_rURcB6^2tN|Cdy$TttXtlb>e*bnOZT;>`ak#w_Mq;A9k2s- sz#ZTK4&VR|-~bNb01n^)4&VR|-~bNb01n^)4&VR|-~bNb!2cb10ru-=asU7T literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/pred/predARIMA1 b/timeseries/tests/data/pred/predARIMA1 new file mode 100644 index 0000000000000000000000000000000000000000..22f3064f87747eca6260381e55a82f388ff511dc GIT binary patch literal 8016 zcmeIy-AmI^7{Kv0tFaLlO5$5{ktD2Wih*H@#}LX~$V$Pa*@7-qkQkOqNMuK1l}Rd^ zrUjxAi^Az_jhrcj+BVa+YQ;(U=xg~hxV~n;2 zb}F<37=<0h=WF^gD$uT-&FjMmyDOtzk1z^P_E(t6IcwK1&8UOytzX=LiB-8jYl4ZZ zGF`_z^8CZ0oVI0RtnIZzP3HHNuE8W?dum&Yk{I8hQEwuSwyU%uM0f3^I*K^0X}81> zFI+Fq+(OJ~o<$MF1YfcLPJ+ab5z2{lzrb@fiz0{E9 z?`(hHH3#$H&_gyi_8_Jr^&-VyJVah zryXGi-V8B2Wlm;&9p8`AanJeq{Bj&O_9{0_$C%4!qz-eBtg{;o26t zcRzB=X1MZsu3hZwg=q_Xo-Uq07j}yIxUgIpFH8~|gr&klVV*EocuaWW9}C1;FZ_Ex zF3uqTec(O7d!YS-djR(U?g9J{paakW=m2y8IshGj4nPN>1JD8J0CWI403Co1KnI`$ N&;jTGbl`vOz#jpJ{x<*s literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/pred/predARIMA2 b/timeseries/tests/data/pred/predARIMA2 new file mode 100644 index 0000000000000000000000000000000000000000..4ad486890822be26d323598b0985064cc0d27c58 GIT binary patch literal 8016 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 M`3IX1viTsz|C9#VmH+?% literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/pred/predARMA4 b/timeseries/tests/data/pred/predARMA4 new file mode 100644 index 0000000000000000000000000000000000000000..2bd30e626c3bb5b0237ab0204a33dba6ef1f7f16 GIT binary patch literal 8016 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 literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/pred/predSARIMA3 b/timeseries/tests/data/pred/predSARIMA3 new file mode 100644 index 0000000000000000000000000000000000000000..072e96a8b5f99abaefdfb6e1ef39fa997fc3043d GIT binary patch literal 8016 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 Jqw*_t_#d*1n%4jT literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/pred/predSARIMA4 b/timeseries/tests/data/pred/predSARIMA4 new file mode 100644 index 0000000000000000000000000000000000000000..ed4884a084a0783a0f25ab2e9d585c811f65bbfe GIT binary patch literal 8016 zcmW-jcRbeb_s8wqzW2WGN+o1Pq7c_1E0U0=5;BTHA5_R}5@kgyDdw%o&Ke+PKOR}V2M5D%;|0MSPjYUG; z=dUO>K1RYT{}w(MB%po`Ur)bRiD*5+L#W`{6SN>i#rvD5B2}!{s_u#B$Rt7{;n9a@ z$OV0(G2}Cm?Bavs)$gy6cgCpqud*Cu7s z&(lIgj@fZep{5uKL{vIjek(tniL4{w>dW#f)XP(|_4ko#H25xJUX5Oj zTx)tGcISRY2}TiDs5ffRQDSC*;n!~{CdI5R@l-7eE)Tae{O|+m23fwawyQ(Iw86%| z?)7M2te&sSL_LaowfpHdfd-TxkLgMiYec8|GW=c`G@{Ks&8r^wno!?Q5&xHpO~{{} zp2OeWgw|As)F=F#(8f=5^4Dsb&{m;#A?|-o$oZ&x+1HcJXt!9~%eNWL2)~ER!y>5} zEwdlo+~(eb2=i|o7pq#(@zh&)I{km4UTrn<-QBGyvJ>mxKG2GOyhy;iUT8z~cC9V_ z|Ft8Amejsm)9t7#q_WnZ+KJBITPFuicOvPLrV+V0(`PO$ct-p6iqO?LKg{7g5R-@RyZLZSyh&Ol zN4%1KPkK=JYg^rdZ#~H9_n6pb^_gAhvBffJ zedrtXq(%(&p~Ddkd6)hA(N=S*2LH}}^nU93$87Qdii~OEI-E0rdj4Wl`U3{gV>?RI zpFabrK(EKDI&A>8%cTDksUAS@ZFc|2A09v!!WpEO*g=#MwBw_c`XHj1*4g>&8ARDn z1_Wgz2hruZE&Cj52a&2hRnuH;2p#BVYAOZ{A!+lVGn36jDD`WXuO@RC0i|A(JAN2Z z8xK%RhK5nD?`eZd@877bN_)+@;Wx53lc`G~jv(9O-Q`x# zh=e1bfj6x*Zx5g)TogULStxFSKr4mYzz@G zz5#pN#!!%QrG1&}I1;@QW&gWs9PL^g7vC*9fwJ%C(GR6gAWPqncZd8Z(Be6hMDg|s zl+v2~RU>HvZSAjcar``iF7{P+`nOJ?p~HoAYk^7BU0v9lD?5oIyyyZ~WhT+)nOU53 zz$9YU)D0f^Jc)S1yF}!1Q)uDoGv{8FDRjcFBcsE13dN@UicbxgLOtPHDf`Q&(5C!R z{K)7Osvdtq(NUX5Wbq_#A=_#6?cPp}rJ!lV_{?aRuboB#gLwqr!fBMdFRI97bQ-ZD z_lIo#I*oo-2_5^{I*oEz)BmDIrcu7orO*)G8I<0s=YgfnpnyB(H!hK9kdSS3nZC{p zI(s?XJ^t(rDjcz!lZ~7~q59F=x8}^C(y4-MzP=f>sxT~_xjuuKe}--E!7PGST5zn> zEaEQr8oc^;7QH^?u&1JD7FqD84rOr9p+l~b$?p5-kbB$?xlr{vq%X1myVbcllrw8_ zp~z?s&7O`uwPH1g>_jh?hMb*4k^S?8iEDGHAx>DNC43Gs#cS)r?#!WQKUB#wALdXU zJ!Mp_Z4TXFBqi<;{EHTzTxj8u{EPIfc3*g4^%p6Y91P+0_>0P|8pHR!{fo|FwPuam z|DxDUBSg}+c{IrCaz1D|k6Mdmud}`95$n{J&DNjh(HusuvL|yM4P@*zQ0kgT#|s)` zb&BUvR^@?&hN^iaO_IiCw9KRMh&KiF*?BbeQYw3qdjZ{5KDS`8Hjmsx9m7fT3+M^r zk2N=0K(8g1B0~HZP=8sEP4mMAbmnkX9jSf+Eu2Zmm<=tU$-c9DI;0j6R^?c&vgINY zxUaV4`DhVYrPVlVeqTf}2SXj1(~D@j*t*+o*AlvSv61YuZ3(rxN06_bTtYr#k@igO zCA5b6+r4#{kO;+cSD@t*Qgc!+C~{vynC1?yGr>zpJ?W?ItIJDB_$c9oUe*%YqqqxS zQMH6hpON!eyvt~hT0v|#Z5h>KBgI4yE~EE5`DMU(}J zF4(QraaV!P1^J)N^th$D0Dc;Vz1hzNxHB$qwDhUqc)M+l*ESIiEFyjLBA?kSf zAujMrv#ULSoC_A-Nr)&rbAjeqc<1Z|F4%xsq{*D)g1jsI59 zEm7RCc=mSs$7F6O(J6Yi{)`*O&%1gYP2u?A42h;vZm{7^1n+O$pn6rj*kza-Bp>q0 zY0q&3Uh3nAKpYQPm-JAYDLimPw$WKll?O_nlJ{OYzyljv&#$Ln;sFz`xCdIddEonj zNynp)dB8xwB>6-U4^$2ddu8SEz%u=OXGT2_G#FGlo0jqbCT+LMD-ORV?H-W-#sedr zbxC`F@j#JfVTXGc57?4k&cw9yfPiP%g-1&~07Y6l(8!hG=AaMj3jnNx@LL}F}}7rwGJmGAKL!JEQ!Hk)yL zARyYeFizzIqwz&i2{Io%{yS0?ugnM0G_tIv1|Qtw!GHT;!3RHHFDn(>@j>Z_R@d!; zd@%9yx%l8MK2X<|+}o1I2MR}{LWT5iW9eV~;D6k~eE&2*ENs>J?M@YdPAjMRG+6-sD*)Es(WB4L3BaQ8r?;*b1i)D#Uc3kpfTw@(1Nn~yz{Bf=msgqq+!)J>h%XWVMoCkoTb%$@#}@s)IUxXf;(@lQ zGXlU@t)HNe#eiJX^1Oiz1|r;LgoaHq(46#W{g({}wug-WF1?I_*ZmS3??+>RXc2Wy z>plk7I@GWwFEF5OQB1oUkAdF-Uz|DvZ>oy5LW4D{>Vyoz39;DFi9FIpclpli>% zeJU3NFP@udU>Y$Hu2hq`u^j`8uyf0es~;X&X9 zy$?EqAcbGdNI5PDvW%x4h5>?LVYZ{o=)NG_T35OA?}Z@T|CZf^uM`CLw8ZCDWrEOk zPC-_nT@b=;g=1LX1VQ_Kz~RO zE}k-2w&oWCuUqDpA4G-V)nJConJq%la0ELws4N8JVqV8gb0G-JvESuqB?R|)?>$5R z3Bk&^n&ZtdA-J5tA5i~B2u^F+-hT2~2*!U16`$-70^!kaljUV0FzDf&ZhT>M2+gI7bAod`)`8Y>UI~)}6 z-zf~odUyDzX$r%si*g+@hlJty&#v}j8(|1_Gp$+(6b8QNfQq{}g<;8G;jU(iF#K3M zdm{IvFerBk?+@!0hV_U3Yemz-@LaPz_=b=OOuntS(kCMV+=31nQJX{{u4H+#X^#k? zdCU8{+eM(}91qXV9U?Futn|!@!?EMJC%@~8KwtCUdB6Q4aP5AzHv$nT&-R)cI3oh@ zTE&=3=S1L$x48cyKM~+97vGc~E&>{N>uBs85qO;5@dCH$1rx%{R|cMh=CbDF1j^P3>YV&@4mMfP%f7LeHSGLpN=PT%LI!-VY>8$ z;teqv{_XHQ@0J*7Tz{DGGhPe~ZaMhrCW^r<`@zM`7%@;_&^6Az7lZ#Y&|prn7+}Vg z(-=R+z?z}=s((lfcBxie3BZU$$#BWyG(YeZaxcK^)Zb z+Y0PG#UVa;Dp28$IFMAeFFRz3Lt|URta*w!C@0L>W*3XYlHbxme6~1Df4Q*d^jmT0 zv5D|4%oXQ+*N=|oRfvN|SVN#qjX2zNnz>$IDh`L1cQ*a*7Y9w$IGX!M90GcBuWnlt z2VG}<*&AdmV5au@MeW4GT&ysZ z7D^3slDYG+;BMSLZ{CW95TDwU(?77#z-46qbDX2|qDhM#Sh$dMdC{f|3ly+^H`x14;7(J!Wrk;2~Aw*js`F zKJyk6k5U}GylHeeydDQ${(=^b?Kt4Av^P69iG%1PJo{7@a3FJ1Ti`Ai9$py61=kVr zFsf@I87Yp3zmoP(B-wai2YL^wknrH$5Z0zo#=|wM)5_%>Rt{x)1+eg7|Dl%=w*e0m zm)ySFGx2aS;&U%q4i85vj!Aj!z=O%`r+m>pcv!qDcuQ^{9&)sJO5R!Ef!w!aw>gJ) z6~W(3?C@Zj#oO?~6%XSfxyK3u@$h=zjk(O*c-T_L*QgbV2mCj6k-0QH+&}U&+B}() z9~EP0^%@VpvRhS0p5bBO;8G?}Iv!SfNGi!3&a8RY9?jP6oc#2Jc{`|%ay=?@jQ)qlGt4@H8`)n;Qsu1ACH(tVleFPZ8 z-?n$wAi#EY1M>+@j&I4*ZR0TN-XESpT>>=MeKfDvBS7k*vFCSm2tZ)U%Z6GIz%gK! z8FrKaQ;2f6@iYM{?mVoz>_UJ6LyeBBz65AJV-Ql|LjZfjch@(E5@7k*M09lw0fGl^ z?T^ghv~OqD#@B*$$SF5kN2yV{y~5^3xg1wDgs;&kCXlUi&HNks@AEN0O>Iy z@>+ES_)=5H6~kd)oNUfm3jwl1GA=!7B|t>^!u9AT&N> z{YWAR$-h0Dl1hZ_k=ZYwClkSrxF%+kLxiEueG+5&M3}w!L*2BF2swJ0En+Q1NUXa! z0KG&=(|%-HI!^@M2No)M<3#Ap4YPIRA^~Qf-yT!WdW3`Oz@-HuND#A*MRC|c{la(t z9}&FRof*5hNwA{$qs?fI2ql|Mv+}VdShIU^HWyEVJ1^gC&!v&zoS(ab7fl`NRVCCi6mV}@W=bP46hdniVi(i z@CzeB_ue?lWDp4s%V59g$CBV}it!R3XWgYXYu@ZG2?mE}?32SukkrT{_%wzDU#5Ly zwm%?2!ilk9r6>|C-BP1Sq>!@NO30nMdo<845P_i-X z`llKaY>h1Vs@y>Wokp_+uOSko+g+hnta0j!9n)^(Cc{zwt-^kyWaxbnoa(=Y3~K{q zYYd$XJEyAe1r{i?PQ3Wl2$U4Bg3T% zlaKZ?WEhMaS>B~ihR09Py0s=5@}Hc*i|LU;^6dFXL*`_l9&9~dV@n4APwOEX98Ou? zh%0m_bLO|8ZQj0QC~v*jav_Qg`{Pe-(uyO)S^4^)=oB&}U~f#$ek4QaUC)0%Uy;F# z^{KR{j0}PjQF+7f$-ueW%h~y4FcNE*Z7v|g>Fdt#VoNwWBeBE!GZ}8X#}qnp^kklg zQ_T-D2xZ47Ti27p`}i~{F@B-e}${=S|USFuWZ-hMKUm_6h)&j6u6+W z@T!nN0fE4zLJt`VQ2tHm;S?xPwP}!@zncQmA++yr%qWm8`s8o-J_@`^`Vz}~i~^;t z7tF4iQb1~^aNqtz6!6$=;WN#lCAW7a@dyRpMUZ6QbK;n(Sszbx^tgOkQ-cEq94n6g zsC1+N&yFa`Zg&bi&B#;@@~6Ol!a*LVdlc|%`!1dkK>@pds=GoW1%3(3y)DV0zo?Gz|%4&}m3QQ+K)uu|V31>Vb1 zzDNJ2zy^Q5_HItx>R!Dcy5khMd+T_oBPY(O;Oa;E1O+;B^$lCrC}2>WJhpe80u4O6 zxoLt_(D`}eQ7MHAb2brg7Nx1M{X%J3j1(34Qn<5Hc2U8*{=VOToZCa49*tG9qJnuz zYGCLID)e}~9Nz9k1)pJ2-k*L{2y3HkQuUz12gkirQa7k@&fsOChc6X)YO3#Ey+#F3 zhgysghidWtb6Fu&xVWK*FnN;-&lI(P8FBP2Pwn2?XexYHI=Oh3!*a*z5L^-!w)NyS zS-hlz)%Q)f$zm$}yAINWg`EE4joBY+sL(JsOOkA*g5~NVX7v~q@cYH|9cQR8;oufn zxlV;QVQzJ2NHpNG;lJ}jh{pNfe$n2`qJh?anWL3N8hj@2o@nRzjz4b-269N$Eclbo zpuxfYeL3`vG>{N6+|WNSyb(bWD~8Q@766w5id+TJw^umL3g!r{paL ztZ3l6OUzvVFby`H^s@1>qrt-?->b6SY2e)Y$H*;&2CwO>I;MAMocU{Z%ZUdxnBAcl z^eBr4k2{@N9?3L_kv=E;_$>|8E_SK@%A|qWuG^OvIlQ3YQcUK=wfzt=qH}zgJ`Zm5 z4>UkO(pA?uTBEAh>3BH}oQ7xny2@yvHzGXwv5p1>uijR~b<$u?*?rHY2^vHwjL_D` zY0!^f_KRPofnZJHMJZuAL~5N+lw#2V8+uD%&lWly^E?o0xq}Wd@|!?!cKnmR@Y&+82d-<;@hibaj?_M(ICGng{xBP^y^MuBSt4Q>M|y zemXej`0N$!paWBV>hNKX@5(2}$KS=-fA@<+h8*8-p{DM{Z%#i4NzWw)=pfkne)HEk zItWdi*IS;Z!})N(&o8(cAh~h!=N~KsbO@5KJ2x^wO?m_u$6?>4#OWIf45+(vlSa^F zz*3!Y@IMmc3Y>_;Civ*~H8-mKe~Ry8XY8B1{MvWjAi*VS=%7vC4M>6DA++^*JESgl5HOOg9cA z%=_k*#hCE#Zb+#P$9L^pwUMGQL2k)fPK2YaL{9uNm1IIAe#3C&W+sfb_&naG#Dwt= z@)^Iin9z|I{`k&8CWv-z$&lvI)WRU#*qRAx%7$ag=a}$dEb7-cUnWRCYEKZo&V;u5 zFrO#)nBZ3SaMUf830n?z#>K@mp}?&8qG%=)V&i5Go=;}NzKM0ON)GLFnhHFhGJ(Fd zW8d8uOgL_QEv)Y)6BbHSsE0YaSLN&h`$8t@be6w~`N-Mld05qS6%*d_Mzlhothu#pRkTsQHXjF9#Mpwzz-_8Pf@_2Yl)!#dgJl*RJ)^VpjQ|_o29Bf|P}#ujl$&?uaQIe{tA->S zl%iA_Njuq45gRa_smq4XJ@sdI9bkjZgI9XJN7%5WVpfxJo(;(+B7Bv$Y~bBT3|IY+ z4fpmIY1BHi;oUHM`X7g;6Y&K}7uaC9>b%C}_{%}b5+z=2u)!`BKlEUOaj=kz#4R>p z?_6C;31UM{=cA{MQEX_g_S`~GW`o@_rqeu!4PS2<%su4rgMf&0a1k2count y 2}[len;]predfn[mdl`params;exog;mdl`pred_dict;;mdl`estresid]/vals + } + + +// ARMA/AR model prediction functionality + +// @private +// @kind function +// @category predictUtility +// @fileoverview prediction function for ARMA model +// @param mdl {dict} contains all information regarding model parameters and required +// residual information +// @param exog {tab} exogenous variables, are additional variables which +// may be accounted for to improve the model +// @param len {integer} the number of data points to be predicted +// @return {num[]} predicted values based on fit ARMA model +ts.i.ARMA.predictFunction:{[mdl;exog;len] + exog:ts.i.predDataCheck[mdl;exog]; + ts.i.predictFunction[mdl;exog;len;ts.i.ARMA.singlePredict] + } + +// @private +// @kind function +// @category predictUtility +// @fileoverview predict a single ARMA value +// @param params {num[]} model parameters retrieved from initial fit model +// @param exog {tab} exogenous variables, are additional variables which +// may be accounted for to improve the model +// @param dict {dict} additional information which can dictate the behaviour +// when making a prediction +// @param pvals {num[]} previously predicted values +// @param estresid {num[]} estimates of the residual errors +// @return {num[]} information required for the prediction of a set of ARMA values +ts.i.ARMA.singlePredict:{[params;exog;dict;pvals;estresid] + exog:exog count pvals 2; + normmat:exog,raze#[neg[dict`p];pvals[0]],pvals[1]; + pred:$[dict`tr; + params[0]+normmat mmu 1_params; + params mmu normmat + ]; + if[count pvals 1; + estvals:exog,pvals[0]; + pvals[1]:(1_pvals[1]),pred-mmu[estresid;estvals] + ]; + ((1_pvals[0]),pred;pvals[1];pvals[2],pred) + } + +// @private +// @kind function +// @category predictUtility +// @fileoverview prediction function for AR model +// @param mdl {dict} contains all information regarding model parameters and required +// residual information +// @param exog {tab} Exogenous variables, are additional variables which +// may be accounted for to improve the model +// @param len {integer} the number of data points to be predicted +// @return {num[]} predicted values based on fit AR model +ts.i.AR.predictFunction:{[mdl;exog;len] + exog:ts.i.predDataCheck[mdl;exog]; + mdl[`pred_dict]:enlist[`p]!enlist count mdl`p_param; + mdl[`estresid]:(); + mdl[`resid]:(); + ts.i.predictFunction[mdl;exog;len;ts.i.AR.singlePredict] + } + +// Predict a single AR value +ts.i.AR.singlePredict:ts.i.ARMA.singlePredict + + +// SARIMA model calculation functionality + +// @private +// @kind function +// @category predictUtility +// @fileoverview prediction function for SARMA model +// @param mdl {dict} contains all information regarding model parameters and required +// residual information +// @param exog {tab} Exogenous variables, are additional variables which +// may be accounted for to improve the model +// @param len {integer} the number of data points to be predicted +// @return {num[]} predicted values based on fit SARMA model +ts.i.SARMA.predictFunction:{[mdl;exog;len] + exog:ts.i.predDataCheck[mdl;exog]; + $[count raze mdl[`pred_dict]; + ts.i.predictFunction[mdl;exog;len;ts.i.SARMA.singlePredict]; + ts.i.AR.predictFunction[mdl;exog;len] + ] + } + +// @private +// @kind function +// @category predictUtility +// @fileoverview predict a single SARMA value +// @param params {num[]} model parameters retrieved from initial fit model +// @param exog {tab} exogenous variables, are additional variables which +// may be accounted for to improve the model +// @param dict {dict} additional information which can dictate the behaviour +// when making a prediction +// @param pvals {num[]} previously predicted values +// @param estresid {num[]} estimates of the residual errors +// @return {num[]} information required for the prediction of SARMA values +ts.i.SARMA.singlePredict:{[params;exog;dict;pvals;estresid]; + exog:exog count pvals 2; + dict,:ts.i.SARMA.preproc[params;dict]; + pred:ts.i.SARMA.predictValue[params;pvals;exog;dict]; + if[count pvals 1; + estvals:exog,neg[dict`n]#pvals 0; + pvals[1]:(1_pvals[1]),pred-mmu[estresid;estvals] + ]; + // append new lag values, for next step calculations + ((1_pvals[0]),pred;pvals[1];pvals[2],pred) + } + +// @private +// @kind function +// @category predictUtility +// @fileoverview Calculate new required lags for SARMA prediction surrounding +// seasonal components +// @param params {dict} model parameters retrieved from initial fit model +// @param dict {dict} additional information which can dictate the behaviour +// in different situations where predictions are being made +// @return {dict} seasonal parameters for prediction in SARMA models +ts.i.SARMA.preproc:{[params;dict] + // 1. Calculate or retrieve all necessary seasonal lagged values for SARMA prediction + // split up the coefficients to their respective p,q,P,Q parts + lagp:(dict[`tr] _params)[til dict`p]; + lagq:((dict[`tr]+dict`p)_params)[til dict`q]; + lagSeasp:((dict[`tr]+sum dict`q`p)_params)[til count[dict`P]]; + lagSeasq:neg[count dict`Q]#params; + // Function to extract additional seasonal multiplied coefficients + // These coefficients multiply p x P vals and q x Q vals + seas_multi:{[x;y;z;d]$[d[x]&min count d upper x;(*/)flip y cross z;2#0f]}; + // append new lags to original dictionary + dictKeys:`add_lag_param`add_resid_param; + dictVals:(seas_multi[`p;lagp;lagSeasp;dict];seas_multi[`q;lagq;lagSeasq;dict]); + dictKeys!dictVals + } + +// @private +// @kind function +// @category predictUtility +// @fileoverview predict a single SARMA value +// @param params {num[]} model parameters retrieved from initial fit model +// @param pvals {num[]} previously predicted values +// @param exog {tab} exogenous variables, are additional variables which +// may be accounted for to improve the model +// @param dict {dict} additional information which can dictate the behaviour +// when making a prediction +// @return {num[]} information required for the prediction of a set of SARMA values +ts.i.SARMA.predictValue:{[params;pvals;exog;dict] + dict[`seas_resid_add]:$[dict[`q]&min count dict`Q; + pvals[1]dict[`seas_add_Q]; + 2#0f + ]; + dict[`seas_lag_add]:$[dict[`p]&min count dict`P; + pvals[0]dict[`seas_add_P]; + 2#0f + ]; + sarmavals:raze#[neg dict`p;pvals 0],#[neg dict`q;pvals 1],pvals[0][dict`P],pvals[1][dict`Q]; + dict[`norm_mat]:exog,sarmavals; + ts.i.SARMA.eval[params;dict] + } + +// @private +// @kind function +// @category predictUtility +// @fileoverview calculate the value of a SARMA prediction based on +// provided params/dictionary +// @param params {num[]} model parameters retrieved from initial fit model +// @param dict {dict} additional information which can dictate the behaviour +// when making a prediction +// @return {num[]} the SARMA prediction values +ts.i.SARMA.eval:{[params;dict] + normVal :mmu[dict`norm_mat;dict[`tr] _params]; + seasResid:mmu[dict`seas_resid_add;dict`add_resid_param]; + seasLag :mmu[dict`seas_lag_add;dict`add_lag_param]; + $[dict`tr;params[0]+;]normVal+seasResid+seasLag + } + + +// @private +// @kind function +// @category predictUtility +// @fileoverview calculate a single ARCH value, +// @param params {dict} model parameters retrieved from initial fit model +// @param pvals {num[]} list of values over which predictions are composed +// @return {num[]} list containing residuals and predicted values +ts.i.ARCH.singlePredict:{[params;pvals] + predict:params[0]+pvals[0] mmu 1_params; + ((1_pvals 0),predict;pvals[1],predict) + } + +// Akaike Information Criterion + +// @private +// @kind function +// @category aicUtility +// @fileoverview calculate the Akaike Information Criterion +// @param true {num[]} true values +// @param pred {num[]} predicted values +// @param params {num[]} list of the lag/residual parameters +// @return {float} Akaike Information Criterion score +ts.i.aicScore:{[true;pred;params] + // Calculate residual sum of squares, normalised for number of values + rss:{wsum[x;x]%y}[true-pred;n:count pred]; + // Number of parameter + k:sum params; + aic:(2*k)+n*log rss; + // if k<40 use the altered aic score + $[k<40;aic+(2*k*k+1)%n-k-1;aic] + } + +// @private +// @kind function +// @category aicUtility +// @fileoverview Fit a model, predict the test, return AIC score +// for a single set of input params +// @param train {dict} training data as a dictionary with endog and exog data +// @param test {dict} testing data as a dictionary with endog and exog data +// @param len {integer} number of steps in the future to be predicted +// @param params {dict} parameters used in prediction +// @return {float} Akaike Information Criterion score +ts.i.aicFitScore:{[train;test;len;params] + // Fit an model using the specified parameters + mdl :ts.ARIMA.fit[train`endog;train`exog;;;;]. params`p`d`q`tr; + // Predict using the fitted model + pred:ts.ARIMA.predict[mdl;test`exog;len]; + // Score the predictions + ts.i.aicScore[len#test`endog;pred;params] + } + + +// Autocorrelation functionality + +// @private +// @kind function +// @category autocorrelationUtility +// @fileoverview Lagged covariance between a dataset at time t and time t-lag +// @param data {num[]} vector on which to calculate the lagged covariance +// @param lag {integer} size of the lag to use when calculating covariance +// @return {float} covariance between a time series and lagged version of itself +ts.i.lagCovariance:{[data;lag] + cov[neg[lag] _ data;lag _ data] + } + +// @private +// @kind function +// @category autocorrelationUtility +// @fileoverview Calculate the autocorrelation between a series +// and lagged version of itself +// @param data {num[]} vector on which to calculate the lagged covariance +// @param lag {integer} size of the lag to use when calculating covariance +// @return {float} autocorrelation between a time series and lagged version of itself +ts.i.autoCorrFunction:{[data;lag] + ts.i.lagCovariance[data;lag]%var data + } + + +// Matrix creation/manipulation functionality + +// @private +// @kind function +// @category matrixUtilities +// @fileoverview create a lagged matrix with each row containing the original +// data as its first element and the remaining 'lag' values as additional row +// elements +// @param data {num[]} vector from which to create the lagged matrix +// @param lag {integer} size of the lag to use when creating lagged matrix +// @return {num[][]} a numeric matrix containing original data augmented with +// lagged versions of the original dataset. +ts.i.lagMatrix:{[data;lag] + data til[count[data]-lag]+\:til lag + } + +// @private +// @kind function +// @category matrixUtilities +// @fileoverview convert a simple table into a matrix +// @param data {tab} simple table to be converted to a matrix representation +// @return {num[][]} matrix representation of the input table in the same 'configuration' +ts.i.tabToMatrix:{[data] + flip value flip data + } + + +// Stationarity functionality used to test if datasets are suitable for application of the ARIMA +// and to facilitate transformation of the data to a more suitable form if relevant + +// @private +// @kind function +// @category stationaryUtilities +// @fileoverview calculate relevant augmented dickey fuller statistics using python +// @param data {dict/tab/num[]} dataset to be testing for stationarity +// @param dtype {short} type of the dataset that's being passed to the function +// @return {num[]/num[][]} all relevant scores from an augmented dickey fuller test +ts.i.stationaryScores:{[data;dtype] + // Calculate the augmented dickey-fuller scores for a dict/tab/vector input + scores:{.ml.fresh.i.adfuller[x]`}@' + $[98h=dtype;flip data; + 99h=dtype;data; + dtype in(6h;7h;8h;9h);enlist data; + '"Inappropriate type provided"]; + flip{x[0 1],(0.05>x 1),value x 4}each$[dtype in 98 99h;value::;]scores + } + +// @private +// @kind function +// @category stationaryUtilities +// @fileoverview Are all of the series provided by a user stationary, +// determined using augmented dickey fuller? +// @param data {dict/tab/num[]} dataset to be testing for stationarity +// @return {bool} indicate if all time series are stationary or not +ts.i.stationary:{[data] + (all/)ts.i.stationaryScores[data;type data][2] + } + + +// Differencing utilities + +// @private +// @kind function +// @category differUtility +// @fileoverview apply time-series differencing and remove first diff elements +// @param data {num[]/num[][]} dataset to apply differencing to +// @param diff {integer} order of time series differencing +// @return {num[]/num[][]} differenced time series +ts.i.diff:{[data;diff] + diffData:diff{deltas x}/data; + diff _ diffData + } + +// @private +// @kind function +// @category differUtility +// @fileoverview apply seasonal differencing and remove first diff elements +// @param diff {integer} how many points in the past does data need to be +// differenced with respect to +// @param data {num[]/num[][]} dataset to apply differencing to +// @return {num[]/num[][]} differenced time series +ts.i.seasonDiff:{[diff;data] + diffData:data - xprev[diff;data]; + diff _ diffData + } + +// @private +// @kind function +// @category differUtility +// @fileoverview revert seasonally differenced data to correct representation +// @param origd {num[]} set of original dataset saved before being differenced +// @param dfdata {num[]} differenced dataset +// @return {num[]} the data reverted back to its original format before differencing +ts.i.reverseSeasonDiff:{[origd;dfdata] + seasd:origd,dfdata; + n:count origd; + [n]_first{x[1]count exog;ts.i.err.len[]]; + // convert exon table to matrix + $[98h~type exog;:"f"$ts.i.tabToMatrix exog;()~exog;:exog;:"f"$exog]; + } + +// @private +// @kind function +// @category dataCheckUtility +// @fileoverview ensure that all required keys are present for the application of +// the various prediction functions +// @param dict {dict} the dictionary parameter to be validated +// @param keyvals {sym[]} list of the keys which should be present in order to +// fully execute the logic of the function +// @param input {string} name of the input dictionary which issue is +// highlighted in +// @return {err/(::)} will error on incorrect inputs otherwise run silently +ts.i.dictCheck:{[dict;keyvals;input] + if[99h<>type dict;'input," must be a dictionary input"]; + validKeys:keyvals in key dict; + if[not all validKeys; + invalid:sv[", ";string[keyvals]where not validKeys]; + '"The following required dictionary keys for '",input,"' are not provided: ",invalid + ]; + } + +// @private +// @kind function +// @category dataCheckUtility +// @fileoverview check that the exogenous data match the expected input when +// predicting data using a the model are consistent, in the case they are not, +// flag an error ensure that the exogenous data is returned as a matrix +// @param mdl {dict} dictionary containing required information to predict +// future values +// @param exog {tab/num[][]} exogenous dataset +// @return {num[][]} exogenous data as a matrix +ts.i.predDataCheck:{[mdl;exog] + // allow null to be provided as exogenous variable + if[exog~(::);exog:()]; + // check that the fit and new params are equivalent + if[not count[mdl`exog_param]~count exog[0];ts.i.err.exog[]]; + // convert exogenous variable to a matrix if required + $[98h~type exog;"f"$ts.i.tabToMatrix exog;()~exog;:exog;"f"$exog] + } + +// @private +// @kind function +// @category dataCheckUtility +// @fileoverview Apply seasonal and non-seasonal time-series differencing, +// error checking stationality of the dataset following application of differencing +// @param endog {num[]} endogenous dataset +// @param diff {integer} non seasonal differencing component (integer) +// @param sdict {dict} dictionary containing relevant seasonal differencing components +// @return {num[]} Seasonal and non-seasonally differenced stationary time-series +ts.i.differ:{[endog;d;s] + // Apply non seasonal differencing if appropriate (handling of AR/ARMA) + if[s~()!();s[`D]:0b]; + I:ts.i.diff[endog;d]; + // Apply seasonal differencing if appropriate + if[s[`D];I:s[`D]ts.i.seasonDiff[s`m]/I]; + // Check stationality + if[not ts.i.stationary[I];ts.i.err.stat[]]; + // Return integrated data + I + } + + +// Feature extraction utilities + +// @private +// @kind function +// @category featureExtractUtilities +// @fileoverview Apply a user defined unary function across a dataset +// using a sliding window of specified length +// Note: this is a modified version of a function provided in qidioms +// using floating point windows instead +// of long windows to increase the diversity of functions that can be applied +// @param func {lambda} unary function to be applied with the data in the sliding window +// @param win {integer} size of the sliding window +// @param data {num[]} data on which the sliding window and associated function +// are to be applied +// @return {num[]} result of the application of the function on each of the sliding window +// components over the data vector +ts.i.slidingWindowFunction:{[func;win;data] + func each{ 1_x,y }\[win#0f;data] + } + + +// Plotting utilities + +// @private +// @kind function +// @category plottingUtility +// @fileoverview Plotting function used in the creation of plots +// for both full and partial autocorrelation graphics +// @param data {num[]} x-axis original dataset +// @param vals {num[]} calculated values +// @param m {num[]} bar plot indices +// @param title {string} title to be given to the plot +// @return {graph} presents a plot to screen associated with relevant analysis +ts.i.plotFunction:{[data;vals;m;title] + plt:.p.import[`matplotlib.pyplot]; + conf:10#1.95%sqrt count data; + plt[`:bar][m;vals;`width pykw 0.5]; + cfgkeys:`linewidth`linestyle`color`label; + cfgvals:3,`dashed`red`conf_interval; + plt[`:plot][m;conf;pykwargs cfgkeys!cfgvals]; + plt[`:legend][]; + plt[`:xlabel][`lags]; + plt[`:ylabel][`acf]; + plt[`:title][title]; + plt[`:show][];} From d07ff71de0a270b5881eddbd6c9ae210eaddd03a Mon Sep 17 00:00:00 2001 From: Conor McCarthy Date: Wed, 9 Sep 2020 17:49:10 +0100 Subject: [PATCH 30/77] update to tests to correctly load data --- timeseries/tests/fit.t | 2 +- timeseries/tests/misc.t | 2 +- timeseries/tests/pred.t | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/timeseries/tests/fit.t b/timeseries/tests/fit.t index 95a56401..e1051645 100644 --- a/timeseries/tests/fit.t +++ b/timeseries/tests/fit.t @@ -20,7 +20,7 @@ residFloat:10000?1000f // Load files fileList:`AR1`AR2`AR3`AR4`ARCH1`ARCH2`ARMA1`ARMA2`ARMA3`ARMA4`ARIMA1`ARIMA2, `ARIMA3`ARIMA4`SARIMA1`SARIMA2`SARIMA3`SARIMA4`nonStat -{load hsym`$":tests/data/fit/",string x}each fileList; +{load hsym`$":timeseries/tests/data/fit/",string x}each fileList; // AR tests .ml.ts.AR.fit[endogInt ;() ;1;0b]~AR1 diff --git a/timeseries/tests/misc.t b/timeseries/tests/misc.t index b409824c..240214ca 100644 --- a/timeseries/tests/misc.t +++ b/timeseries/tests/misc.t @@ -27,7 +27,7 @@ exogMixedFuture :(1000 20#20000?1000),'(1000 20#20000?1000f),'(1000 10#10000?0b) // Load files fileList:`stationalityTab1`stationalityTab2`aicScore1`aicScore2`aicScore3`aicScore4, `windowTab1`windowTab2`lagTab1`lagTab2 -{load hsym`$":tests/data/misc/",string x}each fileList; +{load hsym`$":timeseries/tests/data/misc/",string x}each fileList; // Stationality diff --git a/timeseries/tests/pred.t b/timeseries/tests/pred.t index 0eb248fb..07b0a71b 100644 --- a/timeseries/tests/pred.t +++ b/timeseries/tests/pred.t @@ -13,7 +13,7 @@ exogMixedFuture :(1000 20#20000?1000),'(1000 20#20000?1000f),'(1000 10#10000?0b) // Load files fileList:`AR1`AR2`AR3`AR4`ARCH1`ARCH2`ARMA1`ARMA2`ARMA3`ARMA4`ARIMA1`ARIMA2, `ARIMA3`ARIMA4`SARIMA1`SARIMA2`SARIMA3`SARIMA4 -loadFunc:{load hsym`$":tests/data/",x,string y} +loadFunc:{load hsym`$":timeseries/tests/data/",x,string y} loadFunc["fit/"]each fileList; loadFunc["pred/pred"]each fileList; From dfac85f9826199f3c467bd9641c614373e6bc1a1 Mon Sep 17 00:00:00 2001 From: Conor McCarthy Date: Thu, 10 Sep 2020 09:41:35 +0100 Subject: [PATCH 31/77] update to README to add in reference to optimization, graphing and time series --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 69d9beca..bedefea7 100755 --- a/README.md +++ b/README.md @@ -7,9 +7,12 @@ The machine learning toolkit is at the core of kdb+/q centered machine learning * An implementation of the FRESH (FeatuRe Extraction and Scalable Hypothesis testing) algorithm for use in the extraction of features from time series data and the reduction in the number of features through statistical testing. * Cross validation and grid-search functions allowing for testing of the stability of models to changes in the volume of data or the specific subsets of data used in training. * Clustering algorithms used to group data points and to identify patterns in their distributions. The algorithms make use of a k-dimensional tree to store points and scoring functions to analyze how well they performed. +* Statistical time series models and feature extraction techniques used for the application of machine learning to time series problems. These models allow for the forecasting of the future behaviour of a system under various conditions. +* Numerical optimization techniques used for calculating the optimal parameters for an objective function. +* A graphing and pipeline library for the creation of modularized executable workflow based on a structure described by a mathematical directed graph. * Utility functions relating to areas including statistical analysis, data preprocessing and array manipulation. -These sections are explained in greater depth within the [FRESH](https://code.kx.com/v2/ml/toolkit/fresh/), [Cross Validation](https://code.kx.com/v2/ml/toolkit/xval), [Clustering](https://code.kx.com/v2/ml/toolkit/clustering/algos/) and [Utilities](https://code.kx.com/v2/ml/toolkit/utilities/metric) documentation. +These sections are explained in greater depth within the [FRESH](https://code.kx.com/v2/ml/toolkit/fresh/), [cross validation](https://code.kx.com/v2/ml/toolkit/xval), [clustering](https://code.kx.com/v2/ml/toolkit/clustering/algos/), [time series](https://code.kx.com/v2/ml/toolkit/timeseries), [optimization](https://code.kx.com/v2/ml/toolkit/optimize/), [graph/pipeline](https://code.kx.com/v2/ml/toolkit/graph) and [utilities](https://code.kx.com/v2/ml/toolkit/utilities/metric) documentation. ## Requirements @@ -46,6 +49,7 @@ Examples showing implementations of several components of this toolkit can be fo * Cross validation and grid search capabilities * Results Scoring functionality * Clustering methods applied to datasets +* Time series modeling examples ## Documentation From 86a8409f5a4211265ff3531e3df191282d9eaa19 Mon Sep 17 00:00:00 2001 From: Dianeod Date: Thu, 10 Sep 2020 09:49:56 +0100 Subject: [PATCH 32/77] added README for timeseries --- timeseries/README.md | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 timeseries/README.md diff --git a/timeseries/README.md b/timeseries/README.md new file mode 100644 index 00000000..88f309e4 --- /dev/null +++ b/timeseries/README.md @@ -0,0 +1,40 @@ +# Time Series +In time series analysis, time series forecasting is the use of a model to predict the future values of a dataset based on historical observations. Forecasting can be achieved using a wide range of techniques from simple linear regression to complex neural network constructs. Use cases for time series forecasting vary from its use in the prediction of weather patterns, the forecasting of future product sales and the applications in the stock market. + +## Features +This library contains the following statistical time series forecasting models: +- AutoRegressive (AR) +- AutoRegressive Conditional Heteroskedasticity (ARCH) +- AutoRegressive Moving Average (ARMA) +- AutoRegressive Integrated Moving Average (ARIMA) +- Seasonal AutoRegressive Integrated Moving Average (SARIMA) + +In addition, this library includes feature engineering techniques to create lagged and windowed features from a time series dataset in order to make it suitable to be passed into a traditional machine learning model. + +## Requirements + +- embedPy + +The python dependencies for the Time Series library can be installed by following the instructions laid out in the ML-Toolkit level of this library. + +## Installation + +Place the `ml` library in `$QHOME` and load into a q instance using `ml/ml.q` + +### Load + +The following will load the Time Series functionality into the `.ml` namespace +```q +q)\l ml/ml.q +q).ml.loadfile`:timeseries/init.q +``` + +## Documentation + +Documentation is available on the [timeseries](https://code.kx.com/v2/ml/toolkit/timeseries/) homepage. + +## Status + +The Time Series library is still in development and is available here as a beta release. Further functionality and improvements will be made to the library in the coming months. + +If you have any issues, questions or suggestions, please write to ai@kx.com. From 638adfdf94da2904c40968c2bac2e1bd228277a5 Mon Sep 17 00:00:00 2001 From: Dianeod Date: Thu, 10 Sep 2020 09:56:49 +0100 Subject: [PATCH 33/77] changed time series to lower case --- timeseries/README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/timeseries/README.md b/timeseries/README.md index 88f309e4..c1744b37 100644 --- a/timeseries/README.md +++ b/timeseries/README.md @@ -14,8 +14,7 @@ In addition, this library includes feature engineering techniques to create lagg ## Requirements - embedPy - -The python dependencies for the Time Series library can be installed by following the instructions laid out in the ML-Toolkit level of this library. +The python dependencies for the time series library can be installed by following the instructions laid out in the ML-Toolkit level of this library. ## Installation @@ -23,7 +22,7 @@ Place the `ml` library in `$QHOME` and load into a q instance using `ml/ml.q` ### Load -The following will load the Time Series functionality into the `.ml` namespace +The following will load the time series functionality into the `.ml` namespace ```q q)\l ml/ml.q q).ml.loadfile`:timeseries/init.q @@ -35,6 +34,6 @@ Documentation is available on the [timeseries](https://code.kx.com/v2/ml/toolkit ## Status -The Time Series library is still in development and is available here as a beta release. Further functionality and improvements will be made to the library in the coming months. +The time series library is still in development and is available here as a beta release. Further functionality and improvements will be made to the library in the coming months. If you have any issues, questions or suggestions, please write to ai@kx.com. From 5c412f238544d862e765f889c96b8eac6583910e Mon Sep 17 00:00:00 2001 From: Conor McCarthy Date: Thu, 10 Sep 2020 11:42:34 +0100 Subject: [PATCH 34/77] first pass at optimization tests --- optimize/tests/test.t | 61 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/optimize/tests/test.t b/optimize/tests/test.t index 80854f29..3e9e1441 100644 --- a/optimize/tests/test.t +++ b/optimize/tests/test.t @@ -1,3 +1,60 @@ -// Tests to be populated for this functionality +\l p.q +\l ml.q +\l util/util.q +\l optimize/optim.q + +// Function for the capturing of expected errors +failingTest:{[function;data;applyType;expectedError] + applyType:$[applyType;@;.]; + failureFunction:{[err;ret](`TestFailing;ret;err~ret)}[expectedError;]; + functionReturn:applyType[function;data;failureFunction]; + $[`TestFailing~first functionReturn;last functionReturn;0b] + } + +// Load in data saved as golden copy for this analysis +// Load files +fileList:`quadx0`quadx1`sinex0`sinex1`multix0`multix1`multix1Gtol`multiargs0`multiargs1 +{load hsym`$":data/",string x}each fileList; + +-1"Testing examples of optimization functionality expected to fail"; +c1c2Fail:"When evaluating Wolfe conditions the following must hold 0 < c1 < c2 < 1" +normFail:"ord must be +/- infinity or a long atom" +failingTest[.ml.optimize.BFGS;({x[0]-x 1};1 2f;();``c1!(::; 1.2));0b;c1c2Fail] +failingTest[.ml.optimize.BFGS;({x[0]-x 1};1 2f;();``c2!(::;-1.2));0b;c1c2Fail] +failingTest[.ml.optimize.BFGS;({x[0]-x 1};1 2f;();`c1`c2!0.7 0.2);0b;c1c2Fail] +failingTest[.ml.optimize.BFGS;({x[0]-x 1};1 2f;();``norm!(::;1 2));0b;normFail] +failingTest[.ml.optimize.BFGS;({x[0]-x 1};1 2f;();``norm!(::;1.3));0b;normFail] + +-1"Testing of 1-D quadratic function"; +quadFunc:{xexp[x[0];2]-4*x[0]} +x0quad:enlist 4f +x1quad:enlist[`x]!enlist -2f +.ml.optimize.BFGS[quadFunc;x0quad;();::]~quadx0 +.ml.optimize.BFGS[quadFunc;x1quad;();::]~quadx1 + +-1"Testing of 1-D Sine function with multiple minima"; +sineFunc:{sin x 0} +x0sine:enlist 7f +x1sine:enlist[`x]!enlist 8.5 +.ml.optimize.BFGS[sineFunc;x0sine;();::]~sinex0 +.ml.optimize.BFGS[sineFunc;x1sine;();::]~sinex1 + +-1"Testing of 2-D parabolas with single global minima"; +multiFunc:{xexp[x[0]-1;2]+xexp[x[1]-2.5;2]} +x0multi:10 20f +x1multi:`x`x1!-10 -10f +gtolDict:enlist[`gtol]!enlist 1e-8 +.ml.optimize.BFGS[multiFunc;x0multi;();::]~multix0 +.ml.optimize.BFGS[multiFunc;x1multi;();::]~multix1 +.ml.optimize.BFGS[multiFunc;x1multi;();gtolDict]~multix1Gtol +not .ml.optimize.BFGS[multiFunc;x1multi;();::]~.ml.optimize.BFGS[multiFunc;x1multi;();gtolDict] + + +-1"Testing of 2-D parabolas with single global minima and unchanging additional parameters"; +multiFuncArgList:{y[0]+xexp[x[0]-1;2]+xexp[x[1]-2.5;2]} +multiFuncArgDict:{y[`args0]+xexp[x[0]-1;2]+xexp[x[1]-2.5;2]} +args0:enlist 5f +args1:enlist[`args0]!args0 +.ml.optimize.BFGS[multiFuncArgList;x0multi;args0;::]~multiargs0 +.ml.optimize.BFGS[multiFuncArgDict;x1multi;args1;::]~multiargs1 -1~1 From f4ef1f48fc2b14e13b5519d9e313915442db1f2e Mon Sep 17 00:00:00 2001 From: Conor McCarthy Date: Thu, 10 Sep 2020 11:43:00 +0100 Subject: [PATCH 35/77] addition of datasets for testing --- optimize/tests/data/multiargs0 | Bin 0 -> 77 bytes optimize/tests/data/multiargs1 | Bin 0 -> 77 bytes optimize/tests/data/multix0 | Bin 0 -> 77 bytes optimize/tests/data/multix1 | Bin 0 -> 77 bytes optimize/tests/data/multix1Gtol | Bin 0 -> 77 bytes optimize/tests/data/quadx0 | Bin 0 -> 69 bytes optimize/tests/data/quadx1 | Bin 0 -> 69 bytes optimize/tests/data/sinex0 | Bin 0 -> 69 bytes optimize/tests/data/sinex1 | Bin 0 -> 69 bytes 9 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 optimize/tests/data/multiargs0 create mode 100644 optimize/tests/data/multiargs1 create mode 100644 optimize/tests/data/multix0 create mode 100644 optimize/tests/data/multix1 create mode 100644 optimize/tests/data/multix1Gtol create mode 100644 optimize/tests/data/quadx0 create mode 100644 optimize/tests/data/quadx1 create mode 100644 optimize/tests/data/sinex0 create mode 100644 optimize/tests/data/sinex1 diff --git a/optimize/tests/data/multiargs0 b/optimize/tests/data/multiargs0 new file mode 100644 index 0000000000000000000000000000000000000000..60c60ec2e4af0b72f96697cd5bf8f05887730612 GIT binary patch literal 77 zcmey*n9R+<%)r1<5tf)!%#c=^mmHK@!jM;*>sgXo1QZ9!aWXIgNv3cs#drVT+pBqR X5@TXuarmy<$_Nw|arnssq(A@w?;jD0 literal 0 HcmV?d00001 diff --git a/optimize/tests/data/multiargs1 b/optimize/tests/data/multiargs1 new file mode 100644 index 0000000000000000000000000000000000000000..62c05f80ad84c9c195ddae94401198861deecb4a GIT binary patch literal 77 zcmey*n9R+<%)r1<5tf)!%#c=^mmHK@!jM;*>sgXo1QZ9!aWXIg$$&S!DgDq0ho literal 0 HcmV?d00001 diff --git a/optimize/tests/data/multix0 b/optimize/tests/data/multix0 new file mode 100644 index 0000000000000000000000000000000000000000..359fe0c4ec13e6cf60462caaff8eb87204e6d0bc GIT binary patch literal 77 zcmey*n9R+<%)r1<5tf)!%#c=^mmHK@!jM;*>sgXo1QZ9!aWXIgNv3cs#drVT+pBqR Z5@TXuarl1TK8@Qi=!Wf27N9H$000cR6Tbie literal 0 HcmV?d00001 diff --git a/optimize/tests/data/multix1 b/optimize/tests/data/multix1 new file mode 100644 index 0000000000000000000000000000000000000000..0e42bc69355e901134adfe0a2b064248b2f26299 GIT binary patch literal 77 zcmey*n9R+<%)r1<5tf)!%#c=^mmHK@!jM;*>sgXo1QZ9!aWXIg$$&S!DsgXo1QZ9!aWXIgNyoJefBye}Z?9>{ Z@c;jRW{2++i+!FHewlCclNBfn0swMs7-9eb literal 0 HcmV?d00001 diff --git a/optimize/tests/data/quadx0 b/optimize/tests/data/quadx0 new file mode 100644 index 0000000000000000000000000000000000000000..e46b32a6f552649729c2826c2c57de9a1598e0fb GIT binary patch literal 69 zcmey*n9R+<%)r1<5tf)!%#c=^mmHK@!jM;*>sgXo1QZ9!aWXIhN!5K>d_csgXo1QZ9!aWXIh$%vsgXo1QZ9!aWXIh$%zkiN^5QkIeee< O9|Yd-|H%Rr1pxrM)fPGc literal 0 HcmV?d00001 diff --git a/optimize/tests/data/sinex1 b/optimize/tests/data/sinex1 new file mode 100644 index 0000000000000000000000000000000000000000..3f0d08ce16567029aab1d111036c0f479e11b46c GIT binary patch literal 69 zcmey*n9R+<%)r1<5tf)!%#c=^mmHK@!jM;*>sgXo1QZ9!aWXIh$-vh?Ztwi7>hS%( Q!u$XK|G(e=6C?)$0MP3e{r~^~ literal 0 HcmV?d00001 From c1ba5658e7a9774f99c09d403ddef2ee48d57b9e Mon Sep 17 00:00:00 2001 From: Conor McCarthy Date: Thu, 10 Sep 2020 11:48:04 +0100 Subject: [PATCH 36/77] update to test data path --- optimize/tests/test.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/optimize/tests/test.t b/optimize/tests/test.t index 3e9e1441..b35b8554 100644 --- a/optimize/tests/test.t +++ b/optimize/tests/test.t @@ -14,7 +14,7 @@ failingTest:{[function;data;applyType;expectedError] // Load in data saved as golden copy for this analysis // Load files fileList:`quadx0`quadx1`sinex0`sinex1`multix0`multix1`multix1Gtol`multiargs0`multiargs1 -{load hsym`$":data/",string x}each fileList; +{load hsym`$":optimize/tests/data/",string x}each fileList; -1"Testing examples of optimization functionality expected to fail"; c1c2Fail:"When evaluating Wolfe conditions the following must hold 0 < c1 < c2 < 1" From c3b5cd0133b9ee386eeb60eb577fa1dfbdddf03f Mon Sep 17 00:00:00 2001 From: Conor McCarthy Date: Thu, 10 Sep 2020 11:56:48 +0100 Subject: [PATCH 37/77] update to tests to handle discrepencies between q versions --- optimize/tests/data/sinex0 | Bin 69 -> 0 bytes optimize/tests/test.t | 8 ++++---- 2 files changed, 4 insertions(+), 4 deletions(-) delete mode 100644 optimize/tests/data/sinex0 diff --git a/optimize/tests/data/sinex0 b/optimize/tests/data/sinex0 deleted file mode 100644 index c3c68d9f8405b6dd237bafbfcf0784bd410d0318..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 69 zcmey*n9R+<%)r1<5tf)!%#c=^mmHK@!jM;*>sgXo1QZ9!aWXIh$%zkiN^5QkIeee< O9|Yd-|H%Rr1pxrM)fPGc diff --git a/optimize/tests/test.t b/optimize/tests/test.t index b35b8554..01e8125c 100644 --- a/optimize/tests/test.t +++ b/optimize/tests/test.t @@ -13,7 +13,7 @@ failingTest:{[function;data;applyType;expectedError] // Load in data saved as golden copy for this analysis // Load files -fileList:`quadx0`quadx1`sinex0`sinex1`multix0`multix1`multix1Gtol`multiargs0`multiargs1 +fileList:`quadx0`quadx1`sinex1`multix0`multix1`multix1Gtol`multiargs0`multiargs1 {load hsym`$":optimize/tests/data/",string x}each fileList; -1"Testing examples of optimization functionality expected to fail"; @@ -34,9 +34,9 @@ x1quad:enlist[`x]!enlist -2f -1"Testing of 1-D Sine function with multiple minima"; sineFunc:{sin x 0} -x0sine:enlist 7f -x1sine:enlist[`x]!enlist 8.5 -.ml.optimize.BFGS[sineFunc;x0sine;();::]~sinex0 +x0sine:enlist 8.5 +x1sine:enlist[`x]!x0 +.ml.optimize.BFGS[sineFunc;x0sine;();::]~sinex1 .ml.optimize.BFGS[sineFunc;x1sine;();::]~sinex1 -1"Testing of 2-D parabolas with single global minima"; From 5f65e56d611850643f91ee65076110b69cb6d659 Mon Sep 17 00:00:00 2001 From: Conor McCarthy Date: Thu, 10 Sep 2020 12:09:16 +0100 Subject: [PATCH 38/77] update to optimize test and addition of dictCheck tests --- optimize/tests/test.t | 2 +- timeseries/tests/pred.t | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/optimize/tests/test.t b/optimize/tests/test.t index 01e8125c..6b3a6c94 100644 --- a/optimize/tests/test.t +++ b/optimize/tests/test.t @@ -35,7 +35,7 @@ x1quad:enlist[`x]!enlist -2f -1"Testing of 1-D Sine function with multiple minima"; sineFunc:{sin x 0} x0sine:enlist 8.5 -x1sine:enlist[`x]!x0 +x1sine:enlist[`x]!x0sine .ml.optimize.BFGS[sineFunc;x0sine;();::]~sinex1 .ml.optimize.BFGS[sineFunc;x1sine;();::]~sinex1 diff --git a/timeseries/tests/pred.t b/timeseries/tests/pred.t index 07b0a71b..36328d0f 100644 --- a/timeseries/tests/pred.t +++ b/timeseries/tests/pred.t @@ -66,4 +66,14 @@ failingTest[.ml.ts.SARIMA.predict;(SARIMA2;-1_'exogFloatFuture;1000);0b;"Test ex failingTest[.ml.ts.SARIMA.predict;(SARIMA3;-1_'exogIntFuture ;1000);0b;"Test exog length does not match train exog length"] failingTest[.ml.ts.SARIMA.predict;(ARIMA2 ;exogFloatFuture ;1000);0b;"The following required dictionary keys for 'mdl' are not provided: origs, P_param, Q_param"] +// dictCheck functionality testing +typeCheck:"test1 must be a dictionary input" +keyCheck1:"The following required dictionary keys of 'dict1' are not provided: key1" +keyCheck2:"The following required dictionary keys of 'dict2' are not provided: key1, key2" +test1:til 10 +dict1:`key`key2!1 2 +dict2:enlist[`key]!enlist 1 +failingTest[.ml.ts.i.dictCheck;(test1;`key1`key2;"test1");0b;typeCheck] +failingTest[.ml.ts.i.dictCheck;(dict1;`key`key1`key2;"dict1");0b;keyCheck1] +failingTest[.ml.ts.i.dictCheck;(dict2;`key`key1`key2;"dict2");0b;keyCheck2] From ca80d5412394d9e46b1869e4620805c29617eddb Mon Sep 17 00:00:00 2001 From: Deanna Morgan Date: Thu, 10 Sep 2020 12:18:44 +0100 Subject: [PATCH 39/77] predict updates --- clust/aprop.q | 62 +++++++++++------- clust/dbscan.q | 14 ++-- clust/hierarchical.q | 70 ++++++++++---------- clust/kmeans.q | 12 ++-- clust/score.q | 2 +- clust/tests/clt.t | 153 +++++++++++++++++++++++++++++-------------- clust/util.q | 1 + 7 files changed, 194 insertions(+), 120 deletions(-) diff --git a/clust/aprop.q b/clust/aprop.q index bfd5e2ea..5c84e6ac 100644 --- a/clust/aprop.q +++ b/clust/aprop.q @@ -9,9 +9,12 @@ // @param df {fn} Distance function // @param dmp {float} Damping coefficient // @param diag {fn} Similarity matrix diagonal value function +// @param iter {dict} Max number of overall iterations and iterations without a change in clusters. (::) can be used where the defaults of (`total`nochange!200 15) will be used // @return {long[]} List of clusters -clust.ap.fit:{[data;df;dmp;diag] - clust.i.runap[data;df;dmp;diag;til count data 0;(::)] +clust.ap.fit:{[data;df;dmp;diag;iter] + // update iteration dictionary with user changes + iter:(`run`maxrun`maxmatch!0 200 15),$[iter~(::);();iter]; + clust.i.runap["f"$data;df;dmp;diag;til count data 0;iter] } // @kind function @@ -22,18 +25,19 @@ clust.ap.fit:{[data;df;dmp;diag] // clustered training data // @return {long[]} List of predicted clusters clust.ap.predict:{[data;cfg] - neg[count data 0]#clust.ap.update[data;cfg]`clt + ex:cfg[`data][;distinct cfg`exemplars]; + clust.i.apPredDist[ex;cfg[`inputs]`df]each flip data } // @kind function -// @category clust -// @fileoverview Update AP config including new data points -// @param data {float[][]} Points in `value flip` format -// @param cfg {dict} `data`df`reppts`clt returned from kmeans -// clustered on training data -// @return {dict} Updated model config -clust.ap.update:{[data;cfg] - clust.ap.fit[cfg[`data],'data]. cfg`df`dmp`diag +// @category private +// @fileoverview Predict clusters using AP training exemplars +// @param ex {float[][]} Training cluster centres in `value flip` format +// @param df {fn} Distance function +// @param pt {float[]} Current data point +// @return {long[]} Predicted clusters +clust.i.apPredDist:{[ex;df;pt] + d?max d:clust.i.dists[ex;df;pt]each til count ex 0 } // @kind function @@ -46,15 +50,25 @@ clust.ap.update:{[data;cfg] // @param idxs {long[]} List of indicies to find distances for // @param s0 {float[][]} Old similarity matrix (can be (::) for new run) // @return {long[]} List of clusters -clust.i.runap:{[data;df;dmp;diag;idxs;s0] - // check valid distance function has been given - if[not df in key clust.i.dd;clust.i.err.dd[]]; +clust.i.runap:{[data;df;dmp;diag;idxs;iter] + // check negative euclidean distance has been given + if[not df~`nege2dist;clust.i.err.ap[]]; // calculate distances, availability and responsibility - info0:clust.i.apinit[data;df;diag;idxs;s0]; + info0:clust.i.apinit[data;df;diag;idxs;iter]; // update info to find clusters - info1:{[iter;info]iter>info`matches}[.2*count data]clust.i.apalgo[dmp]/info0; + info1:clust.i.apstop clust.i.apalgo[dmp]/info0; // return config - `data`df`dmp`diag`info0`info1`clt!(data;df;dmp;diag;info0;info1;clust.i.reindex info1`exemplars) + `data`inputs`clt`exemplars! + (data;`df`dmp`diag`iter!(df;dmp;diag;iter);clust.i.reindex info1`exemplars;info1`exemplars) + } + +clust.i.apstop:{[info] + iter:info`iter; + /-1"Run: ",string iter`run; // check -remove when fixed + /-1"Check 1: ",string chk1:iter[`maxrun]>iter`run; + /-1"Check 2: ",string chk2:iter[`maxmatch]>info`matches; + /chk1&chk2 + (iter[`maxrun]>iter`run)&iter[`maxmatch]>info`matches } // @kind function @@ -65,15 +79,13 @@ clust.i.runap:{[data;df;dmp;diag;idxs;s0] // @param diag {fn} Similarity matrix diagonal value function // @return {dict} Similarity, availability and responsibility matrices // and keys for matches and exemplars to be filled during further iterations -clust.i.apinit:{[data;df;diag;idxs;s0] +clust.i.apinit:{[data;df;diag;idxs;iter] // calculate similarity matrix values s:clust.i.dists[data;df;data]each idxs; - // if adding new points, add new similarity onto old - if[not s0~(::);s:(s0,'count[s0]#flip s),s]; // update diagonal s:@[;;:;diag raze s]'[s;k:til n:count data 0]; // create lists/matrices of zeros for other variables - `matches`exemplars`s`a`r!(0;0#0;s),(2;n;n)#0f + `matches`exemplars`s`a`r`iter!(0;0#0;s),((2;n;n)#0f),enlist iter } // @kind function @@ -90,8 +102,12 @@ clust.i.apalgo:{[dmp;info] info[`a]:clust.i.upda[dmp;info]; // find new exemplars ex:imax each sum info`a`r; - // return updated `info` with new exemplars/matches - update exemplars:ex,matches:?[exemplars~ex;matches+1;0]from info + // update `info` with new exemplars/matches + info:update exemplars:ex,matches:?[exemplars~ex;matches+1;0]from info; + // update iter dictionary + info[`iter;`run]+:1; + // return updated info + info } // @kind function diff --git a/clust/dbscan.q b/clust/dbscan.q index 0f212d80..eaaefb63 100644 --- a/clust/dbscan.q +++ b/clust/dbscan.q @@ -14,11 +14,11 @@ clust.dbscan.fit:{[data;df;minpts;eps] // check distance function if[not df in key clust.i.dd;clust.i.err.dd[]]; // create neighbourhood table - t:clust.i.nbhoodtab[data;df;minpts;eps;til count data 0]; + t:clust.i.nbhoodtab[data:"f"$data;df;minpts;eps;til count data 0]; // find cluster for remaining points and return list of clusters clt:-1^exec cluster from t:{[t]any t`corepoint}clust.i.dbalgo/t; // return config dict - `data`df`minpts`eps`clt`t!(data;df;minpts;eps;clt;t) + `data`inputs`clt`t!(data;`df`minpts`eps!(df;minpts;eps);clt;t) } // @kind function @@ -30,19 +30,19 @@ clust.dbscan.fit:{[data;df;minpts;eps] // @return {long[]} List of predicted clusters clust.dbscan.predict:{[data;cfg] // predict new clusters - -1^exec cluster from clust.i.dbscanpredict[data;cfg] + -1^exec cluster from clust.i.dbscanpredict["f"$data;cfg] } // @kind function // @category clust // @fileoverview Update DBSCAN config including new data points // @param data {float[][]} Points in `value flip` format -// @param cfg {dict} `data`df`minpts`eps`clt`nbh returned from DBSCAN +// @param cfg {dict} `data`inputs`clt`nbh returned from DBSCAN // clustered training data // @return {dict} Updated model config clust.dbscan.update:{[data;cfg] // predict new clusters - rtst:clust.i.dbscanpredict[data;cfg]; + rtst:clust.i.dbscanpredict[data:"f"$data;cfg]; rtrn:update corepoint:1b from cfg[`t]where cluster<>0N; // include test points in training neighbourhood rtrn:{[trn;tst;idx] @@ -60,13 +60,13 @@ clust.dbscan.update:{[data;cfg] // @category private // @fileoverview Predict clusters using DBSCAN config // @param data {float[][]} Points in `value flip` format -// @param cfg {dict} `data`df`minpts`eps`clt returned from DBSCAN +// @param cfg {dict} `data`inputs`clt returned from DBSCAN // clustered training data // @return {long[]} Cluster table clust.i.dbscanpredict:{[data;cfg] idx:count[cfg[`data]0]+til count data 0; // create neighbourhood table - t:clust.i.nbhoodtab[cfg[`data],'data;;;;idx]. cfg`df`minpts`eps; + t:clust.i.nbhoodtab[cfg[`data],'data;;;;idx]. cfg[`inputs]`df`minpts`eps; // find which existing clusters new data belongs to update cluster:{x[`clt]first y}[cfg]each nbhood from t where corepoint } diff --git a/clust/hierarchical.q b/clust/hierarchical.q index 7ce6c2a8..b1b26f4d 100644 --- a/clust/hierarchical.q +++ b/clust/hierarchical.q @@ -12,8 +12,8 @@ // @return {table} Dendrogram clust.cure.fit:{[data;df;n;c] if[not df in key clust.i.dd;clust.i.err.dd[]]; - r:clust.hcscc["f"$data;df;`cure;1;n;c;1b]; - r,`data`df`n`c!(data;df;n;c) + dgram:clust.hcscc[data:"f"$data;df;`cure;1;n;c;1b]; + `data`inputs`dgram!(data;`df`n`c!(df;n;c);dgram) } // @kind function @@ -27,27 +27,27 @@ clust.hc.fit:{[data;df;lf] // check distance and linkage functions if[not df in key clust.i.dd;clust.i.err.dd[]]; if[not lf in key clust.i.ld;clust.i.err.ld[]]; - if[lf in`complete`average`ward;r:clust.hccaw["f"$data;df;lf;2;1b]]; - if[lf in`single`centroid;r:clust.hcscc["f"$data;df;lf;1;::;::;1b]]; - r,`data`df`lf!(data;df;lf) + if[lf in`complete`average`ward;dgram:clust.hccaw[data:"f"$data;df;lf;2;1b]]; + if[lf in`single`centroid;dgram:clust.hcscc[data:"f"$data;df;lf;1;::;::;1b]]; + `data`inputs`dgram!(data;`df`lf!(df;lf);dgram) } // @kind function // @category clust -// @fileoverview Convert CURE dendrogram table to k clusters -// @param dgram {table} Dendrogram -// @param k {long} Number of clusters -// @return {long[]} List of clusters +// @fileoverview Convert CURE cfg to k clusters +// @param cfg {dict} Output of .ml.clust.cure.fit +// @param k {long} Number of clusters +// @return {dict} Updated config with clusters added clust.cure.cutk:{[cfg;k] cfg,enlist[`clt]!enlist clust.i.cutdgram[cfg`dgram;k-1] } // @kind function // @category clust -// @fileoverview Convert hierarchical dendrogram table to k clusters -// @param dgram {table} Dendrogram -// @param k {long} Number of clusters -// @return {long[]} List of clusters +// @fileoverview Convert hierarchical cfg to k clusters +// @param cfg {dict} Output of .ml.clust.hc.fit +// @param k {long} Number of clusters +// @return {dict} Updated config with clusters added clust.hc.cutk:clust.cure.cutk // @kind function @@ -56,7 +56,7 @@ clust.hc.cutk:clust.cure.cutk // @param dgram {table} Dendrogram // @param dthresh {float} Cutting distance threshold // @return {long[]} List of clusters -clust.cure.cutdist:{[dgram;dthresh] +clust.cure.cutdist:{[cfg;dthresh] dgram:cfg`dgram; k:0|count[dgram]-exec first i from dgram where dist>dthresh; cfg,enlist[`clt]!enlist clust.i.cutdgram[dgram;k] @@ -64,7 +64,7 @@ clust.cure.cutdist:{[dgram;dthresh] // @kind function // @category clust -// @fileoverview Convert CURE dendrogram to clusters based on distance threshold +// @fileoverview Convert hierarchical dendrogram to clusters based on distance threshold // @param dgram {table} Dendrogram // @param dthresh {float} Cutting distance threshold // @return {long[]} List of clusters @@ -73,9 +73,9 @@ clust.hc.cutdist:clust.cure.cutdist // @kind function // @category private // @fileoverview Predict clusters using hierarchical or CURE config -// @param data {float[][]} Points in `value flip` format -// @param cfg {dict} dict output of .ml.clust.(hc/CURE).(cutk/cutdist) // @param ns {symbol} Namespace to use - `hc or `cure +// @param data {float[][]} Points in `value flip` format +// @param cfg {dict} dict output of .ml.clust.(cutk/cutdist) // @return {long[]} List of predicted clusters clust.i.hccpred:{[ns;data;cfg] // check correct namespace and clusters given @@ -85,25 +85,26 @@ clust.i.hccpred:{[ns;data;cfg] '"Clusters must be contained within cfg - please run .ml.clust.", $[ns~`hc;"hc";"cure"],".(cutk/cutdist)"]; // add namespace and linkage to config dictionary for cure - if[ns~`cure;cfg,:`ns`lf!(ns;`single)]; + if[ns~`cure;cfg[`inputs],:`ns`lf!(ns;`single)]; // recalculate reppts for training clusters reppt:clust.i.getrep[cfg]each value group cfg`clt; // training indicies idxs:til each c:count each reppt[;0]; // return closest clusters to testing points - clust.i.predclosest[data;cfg;reppt;c;idxs]each til count data 0 + clust.i.predclosest["f"$data;cfg;reppt;c;idxs]each til count data 0 } // @kind function // @category private // @fileoverview Recalculate representative points from training clusters -// @param cfg {dict} Dict output of .ml.clust.(hc/CURE).(cutk/cutdist) +// @param cfg {dict} Dict output of .ml.clust.(cutk/cutdist) // @param idxs {long[][]} Training data indices // @return {float[][]} Training data points clust.i.getrep:{[cfg;idxs] - $[cfg[`ns]~`cure; - flip(clust.i.curerep . cfg`df`n`c)::; - cfg[`lf]in`ward`centroid; + `e+1; + $[cfg[`inputs;`ns]~`cure; + flip(clust.i.curerep . cfg[`inputs]`df`n`c)::; + cfg[`inputs;`lf]in`ward`centroid; enlist each avg each;]cfg[`data][;idxs] } @@ -111,7 +112,7 @@ clust.i.getrep:{[cfg;idxs] // @category private // @fileoverview Predict new cluster for given data point // @param data {float[][]} Points in `value flip` format -// @param cfg {dict} dict output of .ml.clust.(hc/CURE).(cutk/cutdist) +// @param cfg {dict} dict output of .ml.clust.(cutk/cutdist) // @param reppt {float[][]} Representative points in matrix format // @param c {long} Number of points in training clusters // @param cltidx {long[][]} Training data indices @@ -119,11 +120,12 @@ clust.i.getrep:{[cfg;idxs] // @return {long[]} List of predicted clusters clust.i.predclosest:{[data;cfg;reppt;c;cltidx;ptidx] // intra cluster distances - dist:.ml.clust.i.dists[;cfg`df;data[;ptidx];]'[reppt;cltidx]; + dist:.ml.clust.i.dists[;cfg[`inputs]`df;data[;ptidx];]'[reppt;cltidx]; // apply linkage - dist:$[`ward~cfg`lf; - 2*clust.i.ld[cfg`lf][1]'[c;dist]; - clust.i.ld[cfg`lf]each dist]; + dist:$[`ward~lf:cfg[`inputs]`lf; + 2*clust.i.ld[lf][1]'[c;dist]; + clust.i.ld[lf]each dist]; + `e+1; // find closest cluster dist?ndst:min dist } @@ -132,7 +134,7 @@ clust.i.predclosest:{[data;cfg;reppt;c;cltidx;ptidx] // @category clust // @fileoverview Predict clusters using CURE config // @param data {float[][]} Points in `value flip` format -// @param cfg {dict} `data`df`n`c`clt returned from .ml.clust.cure.cutk/cutdist +// @param cfg {dict} `data`df`n`c`clt returned from .ml.clust.(cutk/cutdist) // @return {long[]} List of predicted clusters clust.cure.predict:clust.i.hccpred[`cure] @@ -140,7 +142,7 @@ clust.cure.predict:clust.i.hccpred[`cure] // @category clust // @fileoverview Predict clusters using hierarchical config // @param data {float[][]} Points in `value flip` format -// @param cfg {dict} `data`df`lf`clt returned from .ml.clust.hc.cutk/cutdist +// @param cfg {dict} `data`df`lf`clt returned from .ml.clust.(cutk/cutdist) // @return {long[]} List of predicted clusters clust.hc.predict:clust.i.hccpred[`hc] @@ -163,9 +165,7 @@ clust.hccaw:{[data;df;lf;k;dgram] // merge clusters based on chosen algorithm r:{[k;r]k Date: Thu, 10 Sep 2020 13:04:09 +0100 Subject: [PATCH 40/77] revert of prediction functionality as tests are already covered --- timeseries/tests/pred.t | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/timeseries/tests/pred.t b/timeseries/tests/pred.t index 36328d0f..17a7655c 100644 --- a/timeseries/tests/pred.t +++ b/timeseries/tests/pred.t @@ -66,14 +66,3 @@ failingTest[.ml.ts.SARIMA.predict;(SARIMA2;-1_'exogFloatFuture;1000);0b;"Test ex failingTest[.ml.ts.SARIMA.predict;(SARIMA3;-1_'exogIntFuture ;1000);0b;"Test exog length does not match train exog length"] failingTest[.ml.ts.SARIMA.predict;(ARIMA2 ;exogFloatFuture ;1000);0b;"The following required dictionary keys for 'mdl' are not provided: origs, P_param, Q_param"] -// dictCheck functionality testing -typeCheck:"test1 must be a dictionary input" -keyCheck1:"The following required dictionary keys of 'dict1' are not provided: key1" -keyCheck2:"The following required dictionary keys of 'dict2' are not provided: key1, key2" -test1:til 10 -dict1:`key`key2!1 2 -dict2:enlist[`key]!enlist 1 -failingTest[.ml.ts.i.dictCheck;(test1;`key1`key2;"test1");0b;typeCheck] -failingTest[.ml.ts.i.dictCheck;(dict1;`key`key1`key2;"dict1");0b;keyCheck1] -failingTest[.ml.ts.i.dictCheck;(dict2;`key`key1`key2;"dict2");0b;keyCheck2] - From b5c68e1c731abaa78914949d4ee2c4252bcaad3a Mon Sep 17 00:00:00 2001 From: Conor McCarthy Date: Thu, 10 Sep 2020 13:23:04 +0100 Subject: [PATCH 41/77] removal of sobol-seq from from requirements due to lack of conda install and issues with using this and conda --- README.md | 7 +++++++ requirements.txt | 1 - 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bedefea7..9a3405f7 100755 --- a/README.md +++ b/README.md @@ -30,6 +30,13 @@ or via conda: conda install --file requirements.txt ``` +**Note:** + +In order to use the Sobol sequence hyperparameter tuning a user must install the `sobol-seq` python package via: +```bash +pip install sobol-seq +``` + ## Installation Place the `ml` library in `$QHOME` and load into a q instance using `ml/ml.q` diff --git a/requirements.txt b/requirements.txt index 6b1ce8c9..a09f063a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,3 @@ scikit-learn statsmodels matplotlib pandas>=1.0 -sobol-seq \ No newline at end of file From f279a5477fb4bc68f1096123ca6d47469261dfeb Mon Sep 17 00:00:00 2001 From: Conor McCarthy Date: Thu, 10 Sep 2020 13:35:14 +0100 Subject: [PATCH 42/77] reintroduction of requirements file --- README.md | 7 ------- requirements.txt | 1 + 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/README.md b/README.md index 9a3405f7..bedefea7 100755 --- a/README.md +++ b/README.md @@ -30,13 +30,6 @@ or via conda: conda install --file requirements.txt ``` -**Note:** - -In order to use the Sobol sequence hyperparameter tuning a user must install the `sobol-seq` python package via: -```bash -pip install sobol-seq -``` - ## Installation Place the `ml` library in `$QHOME` and load into a q instance using `ml/ml.q` diff --git a/requirements.txt b/requirements.txt index a09f063a..43e1ab11 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,4 @@ scikit-learn statsmodels matplotlib pandas>=1.0 +sobol-seq From 26a032dc90b6ddaac9629d3efa3b064c6664aee4 Mon Sep 17 00:00:00 2001 From: Conor McCarthy Date: Thu, 10 Sep 2020 13:44:21 +0100 Subject: [PATCH 43/77] trigger build --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index bedefea7..44028e18 100755 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ or via conda: conda install --file requirements.txt ``` + ## Installation Place the `ml` library in `$QHOME` and load into a q instance using `ml/ml.q` From b5371e878d842d5554606670d4846bf34337ff8e Mon Sep 17 00:00:00 2001 From: Deanna Morgan Date: Thu, 10 Sep 2020 13:59:09 +0100 Subject: [PATCH 44/77] hc cluster label fixes --- clust/hierarchical.q | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/clust/hierarchical.q b/clust/hierarchical.q index b1b26f4d..66cfc802 100644 --- a/clust/hierarchical.q +++ b/clust/hierarchical.q @@ -87,7 +87,7 @@ clust.i.hccpred:{[ns;data;cfg] // add namespace and linkage to config dictionary for cure if[ns~`cure;cfg[`inputs],:`ns`lf!(ns;`single)]; // recalculate reppts for training clusters - reppt:clust.i.getrep[cfg]each value group cfg`clt; + reppt:clust.i.getrep[cfg]each gc kc:asc key gc:group cfg`clt; // training indicies idxs:til each c:count each reppt[;0]; // return closest clusters to testing points @@ -101,7 +101,6 @@ clust.i.hccpred:{[ns;data;cfg] // @param idxs {long[][]} Training data indices // @return {float[][]} Training data points clust.i.getrep:{[cfg;idxs] - `e+1; $[cfg[`inputs;`ns]~`cure; flip(clust.i.curerep . cfg[`inputs]`df`n`c)::; cfg[`inputs;`lf]in`ward`centroid; @@ -125,7 +124,6 @@ clust.i.predclosest:{[data;cfg;reppt;c;cltidx;ptidx] dist:$[`ward~lf:cfg[`inputs]`lf; 2*clust.i.ld[lf][1]'[c;dist]; clust.i.ld[lf]each dist]; - `e+1; // find closest cluster dist?ndst:min dist } From 001990651efab79b4b75e1b50d24bf885975a9e7 Mon Sep 17 00:00:00 2001 From: Conor McCarthy Date: Thu, 10 Sep 2020 14:05:28 +0100 Subject: [PATCH 45/77] reordering of requirements to trigger build --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 43e1ab11..69db80b8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,5 +3,5 @@ scipy scikit-learn statsmodels matplotlib -pandas>=1.0 sobol-seq +pandas>=1.0 From 3e594e2e19021b06e424ef31a42372e4ee5955ef Mon Sep 17 00:00:00 2001 From: Deanna Morgan Date: Thu, 10 Sep 2020 14:37:03 +0100 Subject: [PATCH 46/77] hc fixes --- clust/hierarchical.q | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clust/hierarchical.q b/clust/hierarchical.q index 66cfc802..8eab6fbe 100644 --- a/clust/hierarchical.q +++ b/clust/hierarchical.q @@ -86,7 +86,7 @@ clust.i.hccpred:{[ns;data;cfg] $[ns~`hc;"hc";"cure"],".(cutk/cutdist)"]; // add namespace and linkage to config dictionary for cure if[ns~`cure;cfg[`inputs],:`ns`lf!(ns;`single)]; - // recalculate reppts for training clusters + // recalculate reppts for training clusters in asc order to ensure correct labels reppt:clust.i.getrep[cfg]each gc kc:asc key gc:group cfg`clt; // training indicies idxs:til each c:count each reppt[;0]; From bcf658c920086f50de8bb526f133f1b6674a8b38 Mon Sep 17 00:00:00 2001 From: Conor McCarthy Date: Thu, 10 Sep 2020 15:50:10 +0100 Subject: [PATCH 47/77] stationality -> stationarity as this is the correct term --- timeseries/misc.q | 6 +++--- timeseries/tests/misc.t | 6 +++--- timeseries/utils.q | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/timeseries/misc.q b/timeseries/misc.q index c2542976..42f96815 100644 --- a/timeseries/misc.q +++ b/timeseries/misc.q @@ -6,13 +6,13 @@ // @kind function // @category misc -// @fileoverview Summary of the stationality of each vector of a multivariate time series +// @fileoverview Summary of the stationarity of each vector of a multivariate time series // or a single vector // @param dset {dict/tab/num[]} a time series of interest, the entries should // in each case be numeric data types. // @return {keytab} informative outputs from the python adfuller test indicating -// the stationality of each vector entry of the relevant dataset -ts.stationality:{[dset] +// the stationarity of each vector entry of the relevant dataset +ts.stationarity:{[dset] dtype:type dset; // Names to be provided to form the key for the return table keyNames:$[99h=dtype;key dset; diff --git a/timeseries/tests/misc.t b/timeseries/tests/misc.t index 240214ca..6629696b 100644 --- a/timeseries/tests/misc.t +++ b/timeseries/tests/misc.t @@ -25,15 +25,15 @@ exogMixedFuture :(1000 20#20000?1000),'(1000 20#20000?1000f),'(1000 10#10000?0b) // Load files -fileList:`stationalityTab1`stationalityTab2`aicScore1`aicScore2`aicScore3`aicScore4, +fileList:`stationarityTab1`stationarityTab2`aicScore1`aicScore2`aicScore3`aicScore4, `windowTab1`windowTab2`lagTab1`lagTab2 {load hsym`$":timeseries/tests/data/misc/",string x}each fileList; // Stationality -.ml.ts.stationality[endogInt ]~stationalityTab1 -.ml.ts.stationality[endogFloat]~stationalityTab2 +.ml.ts.stationarity[endogInt ]~stationarityTab1 +.ml.ts.stationarity[endogFloat]~stationarityTab2 // aicparam diff --git a/timeseries/utils.q b/timeseries/utils.q index 6fa3ce81..79f3982f 100644 --- a/timeseries/utils.q +++ b/timeseries/utils.q @@ -705,7 +705,7 @@ ts.i.predDataCheck:{[mdl;exog] // @kind function // @category dataCheckUtility // @fileoverview Apply seasonal and non-seasonal time-series differencing, -// error checking stationality of the dataset following application of differencing +// error checking stationarity of the dataset following application of differencing // @param endog {num[]} endogenous dataset // @param diff {integer} non seasonal differencing component (integer) // @param sdict {dict} dictionary containing relevant seasonal differencing components @@ -716,7 +716,7 @@ ts.i.differ:{[endog;d;s] I:ts.i.diff[endog;d]; // Apply seasonal differencing if appropriate if[s[`D];I:s[`D]ts.i.seasonDiff[s`m]/I]; - // Check stationality + // Check stationarity if[not ts.i.stationary[I];ts.i.err.stat[]]; // Return integrated data I From a615a6cda4201af9bfecd90b7ea1dd3580fc91a5 Mon Sep 17 00:00:00 2001 From: Conor McCarthy Date: Thu, 10 Sep 2020 16:07:14 +0100 Subject: [PATCH 48/77] renaming of dataset for stationarity --- .../misc/{stationalityTab1 => stationarityTab1} | Bin .../misc/{stationalityTab2 => stationarityTab2} | Bin 2 files changed, 0 insertions(+), 0 deletions(-) rename timeseries/tests/data/misc/{stationalityTab1 => stationarityTab1} (100%) rename timeseries/tests/data/misc/{stationalityTab2 => stationarityTab2} (100%) diff --git a/timeseries/tests/data/misc/stationalityTab1 b/timeseries/tests/data/misc/stationarityTab1 similarity index 100% rename from timeseries/tests/data/misc/stationalityTab1 rename to timeseries/tests/data/misc/stationarityTab1 diff --git a/timeseries/tests/data/misc/stationalityTab2 b/timeseries/tests/data/misc/stationarityTab2 similarity index 100% rename from timeseries/tests/data/misc/stationalityTab2 rename to timeseries/tests/data/misc/stationarityTab2 From 9a9092663e3c5f37d2da5d8e9bd24ff626474211 Mon Sep 17 00:00:00 2001 From: Conor McCarthy Date: Thu, 10 Sep 2020 16:24:58 +0100 Subject: [PATCH 49/77] movement from conda to pip install with docker --- docker/Dockerfile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 9353b821..95a3c623 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -13,6 +13,7 @@ COPY fresh /opt/kx/ml/fresh COPY util /opt/kx/ml/util COPY xval /opt/kx/ml/xval COPY clust /opt/kx/ml/clust +COPY graph /opt/kx/ml/graph COPY timeseries /opt/kx/ml/timeseries COPY optimize /opt/kx/ml/optimize @@ -35,13 +36,14 @@ RUN chown -R kx:kx /opt/kx/ml RUN mkdir /opt/kx/q/ml RUN find /opt/kx/ml -maxdepth 1 -type f -name '*.q' | xargs ln -s -t /opt/kx/q/ml \ && ln -s -t /opt/kx/q/ml /opt/kx/ml/fresh /opt/kx/ml/utils /opt/kx/ml/xval /opt/kx/ml/clust \ - /opt/kx/ml/timeseries /opt/kx/ml/optimize + /opt/kx/ml/graph /opt/kx/ml/timeseries /opt/kx/ml/optimize USER kx RUN . /opt/conda/etc/profile.d/conda.sh \ && conda activate kx \ - && conda install -c conda-forge --file /opt/kx/ml/requirements.txt \ + && pip install pip==9.0.1 \ + && pip install -r /opt/kx/ml/requirements.txt \ && conda clean -y --all USER root From 2e23269ab53f0d5ac98011cd46fc0901b120113d Mon Sep 17 00:00:00 2001 From: Dianeod Date: Fri, 11 Sep 2020 12:26:00 +0100 Subject: [PATCH 50/77] fixed windowed feat function --- timeseries/misc.q | 10 ++++++---- timeseries/tests/data/misc/windowTab1 | Bin 231725 -> 231725 bytes timeseries/tests/data/misc/windowTab2 | Bin 71977 -> 71977 bytes timeseries/utils.q | 7 +++++-- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/timeseries/misc.q b/timeseries/misc.q index c2542976..a6476c3f 100644 --- a/timeseries/misc.q +++ b/timeseries/misc.q @@ -95,9 +95,10 @@ ts.laggedFeatures:{[tab;colNames;lags] // @category misc // @fileoverview Plot and display an autocorrelation plot // @param data {num[]} dataset from which to generate the autocorrelation plot +// @param n {int} number of lags to include in the graph // @return {graph} display to standard out the autocorrelation bar plot -ts.acfPlot:{[data] - acf:ts.i.autoCorrFunction[data;]each m:1_til 11&count[data]; +ts.acfPlot:{[data;n] + acf:ts.i.autoCorrFunction[data;]each m:1_til n&count[data]; ts.i.plotFunction[data;acf;m;"AutoCorrelation"]; } @@ -105,9 +106,10 @@ ts.acfPlot:{[data] // @category misc // @fileoverview Plot and display an autocorrelation plot // @param data {num[]} dataset from which to generate the partial autocorrelation plot +// @param n {int} number of lags to include in the graph // @return {graph} display to standard out the partial autocorrelation bar plot -ts.pacfPlot:{[data] - pacf:.ml.fresh.i.pacf[data;neg[1]+m:11&count data]`; +ts.pacfPlot:{[data;n] + pacf:.ml.fresh.i.pacf[data;neg[1]+m:n&count data]`; ts.i.plotFunction[data;1_pacf;1_til m;"Partial AutoCorrelation"]; } diff --git a/timeseries/tests/data/misc/windowTab1 b/timeseries/tests/data/misc/windowTab1 index c4a6649738a7139991b1c664141b63d6b3551774..ae057cf04c6c9195d8895489efbaeecad0e5181f 100644 GIT binary patch delta 476 zcmZ2GiEr&Bz70?3OrI;sD6skM95Hzwc7~_S3=9ekAh4N#kCHqeCs+i?_3Vj$Z@=08 zwgOC5HBco`RrW6>`RTsX7&$=3aBK$>axfJNAO#GQ`S)l|?`2}**#6Uk@q-+iiQB)2 zGb+dnKrDtB$-q#}JURcD-gMt`pnkwGKN-cgzx~W8AU}O?KNH7vUSXhe zQDG*yXL-Sn+-`2fq#_S8L2P>=kdQ|;VLIQ!DJ6wmNEY_H~FhMO4*2Br?% hS85{!SFhsYSvUQm{r2l#2tlwf7SuXyXUzf%0swwqgAf1! delta 411 zcmZ2GiEr&Bz70?3Y<@dOPJXhtz1(KaJp%HKwv(&kH6i2dYhO2`XD#?>KujbTO}F!z#1oO?va@8ThGL?ozarmbV4A zb4Dmin vals; + plt[`:plot][m;neg conf;pykwargs -1_cfgkeys!cfgvals] + ]; plt[`:legend][]; plt[`:xlabel][`lags]; plt[`:ylabel][`acf]; From ba3c9a6322c34986f3f7508152daf8aa774ed964 Mon Sep 17 00:00:00 2001 From: Deanna Morgan Date: Fri, 11 Sep 2020 14:47:56 +0100 Subject: [PATCH 51/77] convergence function for ap --- clust/aprop.q | 118 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 79 insertions(+), 39 deletions(-) diff --git a/clust/aprop.q b/clust/aprop.q index 5c84e6ac..faf3fb44 100644 --- a/clust/aprop.q +++ b/clust/aprop.q @@ -9,11 +9,15 @@ // @param df {fn} Distance function // @param dmp {float} Damping coefficient // @param diag {fn} Similarity matrix diagonal value function -// @param iter {dict} Max number of overall iterations and iterations without a change in clusters. (::) can be used where the defaults of (`total`nochange!200 15) will be used -// @return {long[]} List of clusters +// @param iter {dict} Max number of overall iterations and iterations +// without a change in clusters. (::) can be passed in where the defaults +// of (`total`nochange!200 15) will be used +// @return {dict} Data, input variables, clusters and exemplars +// (`data`inputs`clt`exemplars) required for the predict method clust.ap.fit:{[data;df;dmp;diag;iter] // update iteration dictionary with user changes iter:(`run`maxrun`maxmatch!0 200 15),$[iter~(::);();iter]; + // cluster data using AP algo clust.i.runap["f"$data;df;dmp;diag;til count data 0;iter] } @@ -21,23 +25,13 @@ clust.ap.fit:{[data;df;dmp;diag;iter] // @category clust // @fileoverview Predict clusters using AP config // @param data {float[][]} Points in `value flip` format -// @param cfg {dict} `data`df`reppts`clt returned from kmeans -// clustered training data +// @param cfg {dict} `data`inputs`clt`exemplars returned by clust.ap.fit // @return {long[]} List of predicted clusters clust.ap.predict:{[data;cfg] + // retrieve cluster centres from training data ex:cfg[`data][;distinct cfg`exemplars]; - clust.i.apPredDist[ex;cfg[`inputs]`df]each flip data - } - -// @kind function -// @category private -// @fileoverview Predict clusters using AP training exemplars -// @param ex {float[][]} Training cluster centres in `value flip` format -// @param df {fn} Distance function -// @param pt {float[]} Current data point -// @return {long[]} Predicted clusters -clust.i.apPredDist:{[ex;df;pt] - d?max d:clust.i.dists[ex;df;pt]each til count ex 0 + // predict testing data clusters + clust.i.appreddist[ex;cfg[`inputs]`df]each flip data } // @kind function @@ -48,27 +42,23 @@ clust.i.apPredDist:{[ex;df;pt] // @param dmp {float} Damping coefficient // @param diag {fn} Similarity matrix diagonal value function // @param idxs {long[]} List of indicies to find distances for -// @param s0 {float[][]} Old similarity matrix (can be (::) for new run) +// @param iter {dict} Max number of overall iterations and iterations +// without a change in clusters. (::) can be passed in where the defaults +// of (`total`nochange!200 15) will be used // @return {long[]} List of clusters clust.i.runap:{[data;df;dmp;diag;idxs;iter] // check negative euclidean distance has been given if[not df~`nege2dist;clust.i.err.ap[]]; - // calculate distances, availability and responsibility - info0:clust.i.apinit[data;df;diag;idxs;iter]; - // update info to find clusters + // calculate distances, availability and responsibility + info0:clust.i.apinit[data;df;diag;idxs]; + // initialize exemplar matrix and convergence boolean + info0,:`emat`conv`iter!((count data 0;iter`maxmatch)#0b;0b;iter); + // run ap algo until maxrun or convergence info1:clust.i.apstop clust.i.apalgo[dmp]/info0; - // return config - `data`inputs`clt`exemplars! - (data;`df`dmp`diag`iter!(df;dmp;diag;iter);clust.i.reindex info1`exemplars;info1`exemplars) - } - -clust.i.apstop:{[info] - iter:info`iter; - /-1"Run: ",string iter`run; // check -remove when fixed - /-1"Check 1: ",string chk1:iter[`maxrun]>iter`run; - /-1"Check 2: ",string chk2:iter[`maxmatch]>info`matches; - /chk1&chk2 - (iter[`maxrun]>iter`run)&iter[`maxmatch]>info`matches + // return data, inputs, clusters and exemplars + inputs:`df`dmp`diag`iter!(df;dmp;diag;iter); + clt:clust.i.reindex ex:info1`exemplars; + `data`inputs`clt`exemplars!(data;inputs;clt;ex) } // @kind function @@ -77,24 +67,25 @@ clust.i.apstop:{[info] // @param data {float[][]} Points in `value flip` format // @param df {fn} Distance function // @param diag {fn} Similarity matrix diagonal value function +// @param idxs {long[]} List of point indices // @return {dict} Similarity, availability and responsibility matrices // and keys for matches and exemplars to be filled during further iterations -clust.i.apinit:{[data;df;diag;idxs;iter] +clust.i.apinit:{[data;df;diag;idxs] // calculate similarity matrix values s:clust.i.dists[data;df;data]each idxs; // update diagonal s:@[;;:;diag raze s]'[s;k:til n:count data 0]; // create lists/matrices of zeros for other variables - `matches`exemplars`s`a`r`iter!(0;0#0;s),((2;n;n)#0f),enlist iter + `matches`exemplars`s`a`r!(0;0#0;s),(2;n;n)#0f } // @kind function // @category private // @fileoverview Run affinity propagation algorithm // @param dmp {float} Damping coefficient -// @param info {dict} Exemplars and matches, similarity, availability and -// responsibility matrices -// @return {dict} Updated info +// @param info {dict} Similarity, availability, responsibility, exemplars, +// matches, iter dictionary, no_conv boolean and iter dict +// @return {dict} Updated inputs clust.i.apalgo:{[dmp;info] // update responsibility matrix info[`r]:clust.i.updr[dmp;info]; @@ -105,9 +96,38 @@ clust.i.apalgo:{[dmp;info] // update `info` with new exemplars/matches info:update exemplars:ex,matches:?[exemplars~ex;matches+1;0]from info; // update iter dictionary - info[`iter;`run]+:1; + .[;(`iter;`run);+[1]]clust.i.apconv info + } + +// @kind function +// @category private +// @fileoverview Check affinity propagation algorithm for convergence +// @param inputs {dict} Info dict, no_conv boolean and iter dict +// @param info {dict} Current info dictionary +// @return {dict} Updated inputs +clust.i.apconv:{[info] + // iteration dictionary + iter:info`iter; + // exemplar matrix + emat:info`emat; + // existing exemplars + ediag:0sum(se=iter`maxmatch)+0=se:sum each emat; + conv:$[(iter[`maxrun]=iter`run)|not[unconv]&sum[ediag]>0;1b;0b]]; // return updated info - info + info,`emat`conv!(emat;conv) + } + +// @kind function +// @category private +// @fileoverview Retrieve diagonal of square matrix +// @param m {any[][]} Square matrix +// @return {any[]} Diagonal +clust.i.diag:{[m] + {x y}'[m;til count m] } // @kind function @@ -142,3 +162,23 @@ clust.i.upda:{[dmp;info] // calculate new availability (dmp*info`a)+a*1-dmp } + +// @kind function +// @category private +// @fileoverview Stopping condition for affinity propagation algorithm +// @param inputs {dict} Info dict, no_conv boolean and iter dict +// @return {bool} Indicates whether to continue or stop running AP (1/0b) +clust.i.apstop:{[info] + (info[`iter;`maxrun]>info[`iter]`run)¬ 1b~info`conv + } + +// @kind function +// @category private +// @fileoverview Predict clusters using AP training exemplars +// @param ex {float[][]} Training cluster centres in `value flip` format +// @param df {fn} Distance function +// @param pt {float[]} Current data point +// @return {long[]} Predicted clusters +clust.i.appreddist:{[ex;df;pt] + d?max d:clust.i.dists[ex;df;pt]each til count ex 0 + } \ No newline at end of file From ee8b3a79c3274d13e74bb00e9105133a12fa948f Mon Sep 17 00:00:00 2001 From: Deanna Morgan Date: Fri, 11 Sep 2020 15:33:06 +0100 Subject: [PATCH 52/77] formatting for ap, dbscan and hc --- clust/aprop.q | 19 ++++++++++--------- clust/dbscan.q | 6 ++++-- clust/hierarchical.q | 28 +++++++++++++++++----------- 3 files changed, 31 insertions(+), 22 deletions(-) diff --git a/clust/aprop.q b/clust/aprop.q index faf3fb44..f1455580 100644 --- a/clust/aprop.q +++ b/clust/aprop.q @@ -102,9 +102,9 @@ clust.i.apalgo:{[dmp;info] // @kind function // @category private // @fileoverview Check affinity propagation algorithm for convergence -// @param inputs {dict} Info dict, no_conv boolean and iter dict -// @param info {dict} Current info dictionary -// @return {dict} Updated inputs +// @param info {dict} Similarity, availability, responsibility, exemplars, +// matches, iter dictionary, no_conv boolean and iter dict +// @return {dict} Updated info dictionary clust.i.apconv:{[info] // iteration dictionary iter:info`iter; @@ -134,8 +134,8 @@ clust.i.diag:{[m] // @category private // @fileoverview Update responsibility matrix // @param dmp {float} Damping coefficient -// @param info {dict} Exemplars and matches, similarity, availability and -// responsibility matrices +// @param info {dict} Similarity, availability, responsibility, exemplars, +// matches, iter dictionary, no_conv boolean and iter dict // @return {float[][]} Updated responsibility matrix clust.i.updr:{[dmp;info] // create matrix with every points max responsibility @@ -150,8 +150,8 @@ clust.i.updr:{[dmp;info] // @category private // @fileoverview Update availability matrix // @param dmp {float} Damping coefficient -// @param info {dict} Exemplars and matches, similarity, availability and -// responsibility matrices +// @param info {dict} Similarity, availability, responsibility, exemplars, +// matches, iter dictionary, no_conv boolean and iter dict // @return {float[][]} Returns updated availability matrix clust.i.upda:{[dmp;info] // sum values in positive availability matrix @@ -166,8 +166,9 @@ clust.i.upda:{[dmp;info] // @kind function // @category private // @fileoverview Stopping condition for affinity propagation algorithm -// @param inputs {dict} Info dict, no_conv boolean and iter dict -// @return {bool} Indicates whether to continue or stop running AP (1/0b) +// @param info {dict} Similarity, availability, responsibility, exemplars, +// matches, iter dictionary, no_conv boolean and iter dict +// @return {bool} Indicates whether to continue or stop running AP (1/0b) clust.i.apstop:{[info] (info[`iter;`maxrun]>info[`iter]`run)¬ 1b~info`conv } diff --git a/clust/dbscan.q b/clust/dbscan.q index eaaefb63..abdadf1e 100644 --- a/clust/dbscan.q +++ b/clust/dbscan.q @@ -9,7 +9,8 @@ // @param df {fn} Distance function // @param minpts {long} Minimum number of points in epsilon radius // @param eps {float} Epsilon radius to search -// @return {long[]} List of clusters +// @return {dict} Data, inputs, clusters and cluster table +// (`data`inputs`clt`t) required for predict and update methods clust.dbscan.fit:{[data;df;minpts;eps] // check distance function if[not df in key clust.i.dd;clust.i.err.dd[]]; @@ -79,7 +80,8 @@ clust.i.dbscanpredict:{[data;cfg] // @param minpts {long} Minimum number of points in epsilon radius // @param eps {float} Epsilon radius to search // @param idx {long[]} Data indices to find neighbourhood for -// @return {table} Neighbourhood table with `nbhood`cluster`corepoint +// @return {table} Neighbourhood table with columns +// `nbhood`cluster`corepoint clust.i.nbhoodtab:{[data;df;minpts;eps;idx] // calculate distances and find all points which are not outliers nbhood:clust.i.nbhood[data;df;eps]each idx; diff --git a/clust/hierarchical.q b/clust/hierarchical.q index 8eab6fbe..f0e7c8ea 100644 --- a/clust/hierarchical.q +++ b/clust/hierarchical.q @@ -9,7 +9,8 @@ // @param df {fn} Distance function // @param n {long} Number of representative points per cluster // @param c {float} Compression factor for representative points -// @return {table} Dendrogram +// @return {dict} Data, input variables and dendrogram +// (`data`inputs`dgram) required for predict method clust.cure.fit:{[data;df;n;c] if[not df in key clust.i.dd;clust.i.err.dd[]]; dgram:clust.hcscc[data:"f"$data;df;`cure;1;n;c;1b]; @@ -22,7 +23,8 @@ clust.cure.fit:{[data;df;n;c] // @param data {float[][]} Points in `value flip` format // @param df {fn} Distance function // @param lf {fn} Linkage function -// @return {table} Dendrogram +// @return {dict} Data, input variables and dendrogram +// (`data`inputs`dgram) required for predict method clust.hc.fit:{[data;df;lf] // check distance and linkage functions if[not df in key clust.i.dd;clust.i.err.dd[]]; @@ -52,10 +54,11 @@ clust.hc.cutk:clust.cure.cutk // @kind function // @category clust -// @fileoverview Convert CURE dendrogram to clusters based on distance threshold -// @param dgram {table} Dendrogram +// @fileoverview Convert CURE dendrogram to clusters based on distance +// threshold +// @param cfg {dict} Output of .ml.clust.cure.fit // @param dthresh {float} Cutting distance threshold -// @return {long[]} List of clusters +// @return {dict} Updated config with clusters added clust.cure.cutdist:{[cfg;dthresh] dgram:cfg`dgram; k:0|count[dgram]-exec first i from dgram where dist>dthresh; @@ -64,10 +67,11 @@ clust.cure.cutdist:{[cfg;dthresh] // @kind function // @category clust -// @fileoverview Convert hierarchical dendrogram to clusters based on distance threshold -// @param dgram {table} Dendrogram +// @fileoverview Convert hierarchical dendrogram to clusters based on distance +// threshold +// @param cfg {dict} Output of .ml.clust.hc.fit // @param dthresh {float} Cutting distance threshold -// @return {long[]} List of clusters +// @return {dict} Updated config with clusters added clust.hc.cutdist:clust.cure.cutdist // @kind function @@ -86,7 +90,7 @@ clust.i.hccpred:{[ns;data;cfg] $[ns~`hc;"hc";"cure"],".(cutk/cutdist)"]; // add namespace and linkage to config dictionary for cure if[ns~`cure;cfg[`inputs],:`ns`lf!(ns;`single)]; - // recalculate reppts for training clusters in asc order to ensure correct labels + // recalc reppts for training clusters in asc order to ensure correct labels reppt:clust.i.getrep[cfg]each gc kc:asc key gc:group cfg`clt; // training indicies idxs:til each c:count each reppt[;0]; @@ -132,7 +136,8 @@ clust.i.predclosest:{[data;cfg;reppt;c;cltidx;ptidx] // @category clust // @fileoverview Predict clusters using CURE config // @param data {float[][]} Points in `value flip` format -// @param cfg {dict} `data`df`n`c`clt returned from .ml.clust.(cutk/cutdist) +// @param cfg {dict} `data`df`n`c`clt returned from +// .ml.clust.(cutk/cutdist) // @return {long[]} List of predicted clusters clust.cure.predict:clust.i.hccpred[`cure] @@ -140,7 +145,8 @@ clust.cure.predict:clust.i.hccpred[`cure] // @category clust // @fileoverview Predict clusters using hierarchical config // @param data {float[][]} Points in `value flip` format -// @param cfg {dict} `data`df`lf`clt returned from .ml.clust.(cutk/cutdist) +// @param cfg {dict} `data`df`lf`clt returned from +// .ml.clust.(cutk/cutdist) // @return {long[]} List of predicted clusters clust.hc.predict:clust.i.hccpred[`hc] From 679e84a528875879c688a05778271bfc6baeea2c Mon Sep 17 00:00:00 2001 From: Deanna Morgan Date: Fri, 11 Sep 2020 16:12:43 +0100 Subject: [PATCH 53/77] added -1 cluster for non convergence --- clust/aprop.q | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/clust/aprop.q b/clust/aprop.q index f1455580..8e2c4ce8 100644 --- a/clust/aprop.q +++ b/clust/aprop.q @@ -28,6 +28,8 @@ clust.ap.fit:{[data;df;dmp;diag;iter] // @param cfg {dict} `data`inputs`clt`exemplars returned by clust.ap.fit // @return {long[]} List of predicted clusters clust.ap.predict:{[data;cfg] + if[-1~first cfg`clt; + '"Clusters = -1. AP fit did not converge. Not possible to predict clusters."]; // retrieve cluster centres from training data ex:cfg[`data][;distinct cfg`exemplars]; // predict testing data clusters @@ -57,7 +59,7 @@ clust.i.runap:{[data;df;dmp;diag;idxs;iter] info1:clust.i.apstop clust.i.apalgo[dmp]/info0; // return data, inputs, clusters and exemplars inputs:`df`dmp`diag`iter!(df;dmp;diag;iter); - clt:clust.i.reindex ex:info1`exemplars; + clt:$[info1`conv;clust.i.reindex ex:info1`exemplars;count[data 0]#-1]; `data`inputs`clt`exemplars!(data;inputs;clt;ex) } From 86d25fed1a871b7790c8404b80bbcb803bdf9c18 Mon Sep 17 00:00:00 2001 From: Deanna Morgan Date: Mon, 14 Sep 2020 13:18:54 +0100 Subject: [PATCH 54/77] test changes --- clust/tests/clt.t | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/clust/tests/clt.t b/clust/tests/clt.t index dd8f3c99..9fb206c0 100644 --- a/clust/tests/clt.t +++ b/clust/tests/clt.t @@ -32,10 +32,8 @@ value[group .ml.clust.ap.fit[d1;`nege2dist;0.9;med;(::)]`clt]~d1clt key[group .ml.clust.ap.fit[d2;`nege2dist;0.95;{[x] -20000.};enlist[`maxsame]!enlist 150]`clt]~til 5 key[group .ml.clust.ap.fit[d2;`nege2dist;0.5;min;(::)]`clt]~til 5 asc[key .ml.clust.ap.fit[d2;`nege2dist;0.5;min;(::)]]~`clt`data`exemplars`inputs - - -// use this as a trap for outliers -/()~.ml.clust.ap.fit[d1tts 0;`nege2dist;0.3;min;`maxrun`maxmatch!100 10]`exemplars +.ml.clust.ap.fit[d2;`nege2dist;0.01;{[x] -10.};(::)][`clt]~200#-1 +.ml.clust.ap.fit[d1tts 0;`nege2dist;0.3;min;`maxrun`maxmatch!100 10][`clt]~45#-1 // Predict .ml.clust.ap.predict[d1tts 1;.ml.clust.ap.fit[d1tts 0;`nege2dist;0.7;min;(::)]]~2 2 2 2 2 2 0 2 0 0 0 2 2 2 2 @@ -44,20 +42,18 @@ asc[key .ml.clust.ap.fit[d2;`nege2dist;0.5;min;(::)]]~`clt`data`exemplars`inputs // K-Means // Fit +.[.ml.clust.kmeans.fit;(d1;`mdist;4;2;1b);1b] value[group .ml.clust.kmeans.fit[d1;`e2dist;4;2;1b]`clt]~d1clt value[group .ml.clust.kmeans.fit[d1;`edist ;4;2;1b]`clt]~d1clt -value[group .ml.clust.kmeans.fit[d2;`e2dist;4;3;1b]`clt]~(til[122]except 41;41 122,.ml.arange[123;180;2];.ml.arange[124;199;2];.ml.arange[181;200;2]) +asc[key .ml.clust.kmeans.fit[d2;`edist ;4;2;1b]]~`clt`data`inputs`reppts // Predict - -.ml.clust.kmeans.predict[d1tts 1;.ml.clust.kmeans.fit[d1tts 0;`e2dist;4;2;1b]]~2 0 0 3 3 0 0 0 0 0 3 0 3 3 -.ml.clust.kmeans.predict[d1tts 1;.ml.clust.kmeans.fit[d1tts 0;`edist;4;2;1b]]~0 2 0 0 2 2 0 0 0 0 0 2 0 2 2 +count[.ml.clust.kmeans.predict[d1tts 1;.ml.clust.kmeans.fit[d1tts 0;`e2dist;4;2;1b]]]~15 +count[.ml.clust.kmeans.predict[d1tts 1;.ml.clust.kmeans.fit[d1tts 0;`edist;4;2;1b]]]~15 // Update - value[group .ml.clust.kmeans.update[d1tts 1;.ml.clust.kmeans.fit[d1tts 0;`e2dist;4;2;1b]]`clt]~d1clt -value[group .ml.clust.kmeans.update[d1tts 1;.ml.clust.kmeans.fit[d1tts 0;`edist;4;2;1b]]`clt]~d1clt -`clt`data`inputs`reppts~.ml.clust.kmeans.update[d1tts 1;.ml.clust.kmeans.fit[d1tts 0;`edist;4;2;1b]] +`clt`data`inputs`reppts~asc key .ml.clust.kmeans.update[d1tts 1;.ml.clust.kmeans.fit[d1tts 0;`edist;4;2;1b]] // DBSCAN @@ -70,13 +66,11 @@ value[group .ml.clust.dbscan.fit[d2;`edist;4;17.2]`clt]~(til[197],198;197 199) value[group .ml.clust.dbscan.fit[d2;`mdist;7;24]`clt]~(til 196;196 197 198 199) // Predict - .ml.clust.dbscan.predict[d1tts 1;.ml.clust.dbscan.fit[d1tts 0;`e2dist;5;5]]~15#-1 .ml.clust.dbscan.predict[d1tts 1;.ml.clust.dbscan.fit[d1tts 0;`edist;5;5]]~15#-1 .ml.clust.dbscan.predict[d1tts 1;.ml.clust.dbscan.fit[d1tts 0;`mdist;5;5]]~15#-1 // Update - value[group .ml.clust.dbscan.update[d1tts 1;.ml.clust.dbscan.fit[d1tts 0;`e2dist;5;5]]`clt]~d1clt value[group .ml.clust.dbscan.update[d1tts 1;.ml.clust.dbscan.fit[d1tts 0;`edist;5;5]]`clt]~d1clt value[group .ml.clust.dbscan.update[d1tts 1;.ml.clust.dbscan.fit[d1tts 0;`mdist;5;5]]`clt]~d1clt @@ -93,10 +87,9 @@ value[group .ml.clust.cure.cutk[.ml.clust.cure.fit[d2;`edist;20;0.2];4]`clt]~(0 value[group .ml.clust.cure.cutk[.ml.clust.cure.fit[d2;`mdist;10;0.1];4]`clt]~(til[122],.ml.arange[122;191;2];.ml.arange[123;194;2];192 194 196 198;195 197 199) // Predict - -.ml.clust.cure.predict[d1tts 1;.ml.clust.cure.cutk[.ml.clust.cure.fit[d1tts 0;`e2dist;5;0];4]]~0 3 0 0 3 3 0 0 0 0 0 3 0 3 3 +.ml.clust.cure.predict[d1tts 1;.ml.clust.cure.cutk[.ml.clust.cure.fit[d1tts 0;`e2dist;5;0];4]]~1 2 1 1 2 2 1 1 1 1 1 2 1 2 2 .ml.clust.cure.predict[d1tts 1;.ml.clust.cure.cutk[.ml.clust.cure.fit[d1tts 0;`edist;10;0.2];4]]~0 3 0 0 3 3 0 0 0 0 0 3 0 3 3 -.ml.clust.cure.predict[d1tts 1;.ml.clust.cure.cutk[.ml.clust.cure.fit[d1tts 0;`mdist;3;0.15];4]]~0 3 0 3 3 3 0 0 0 0 0 3 0 3 3 +.ml.clust.cure.predict[d1tts 1;.ml.clust.cure.cutk[.ml.clust.cure.fit[d1tts 0;`mdist;3;0.15];4]]~1 3 1 3 3 3 1 1 1 1 1 3 1 3 3 // Hierarchical @@ -123,7 +116,6 @@ value[group .ml.clust.hc.cutdist[tab3;500]`clt]~value group fclust[mat tab3`dgra value[group .ml.clust.hc.cutdist[tab4;30]`clt]~value group fclust[mat tab4`dgram;30;`distance]` // Predict - -.ml.clust.hc.predict[d1tts 1;.ml.clust.hc.cutk[.ml.clust.hc.fit[d1tts 0;`e2dist;`single];4]]~hcpred:0 3 0 0 3 3 0 0 0 0 0 3 0 3 3 -.ml.clust.hc.predict[d1tts 1;.ml.clust.hc.cutk[.ml.clust.hc.fit[d1tts 0;`e2dist;`ward];4]]~hcpred -.ml.clust.hc.predict[d1tts 1;.ml.clust.hc.cutk[.ml.clust.hc.fit[d1tts 0;`edist;`centroid];4]]~hcpred +.ml.clust.hc.predict[d1tts 1;.ml.clust.hc.cutk[.ml.clust.hc.fit[d1tts 0;`e2dist;`single];4]]~1 2 1 1 2 2 1 1 1 1 1 2 1 2 2 +.ml.clust.hc.predict[d1tts 1;.ml.clust.hc.cutk[.ml.clust.hc.fit[d1tts 0;`e2dist;`ward];4]]~1 3 1 1 3 3 1 1 1 1 1 3 1 3 3 +.ml.clust.hc.predict[d1tts 1;.ml.clust.hc.cutk[.ml.clust.hc.fit[d1tts 0;`edist;`centroid];4]]~1 3 1 1 3 3 1 1 1 1 1 3 1 3 3 From acd959ed54b6e8a6b4b0ca59f34f3ac2c2999982 Mon Sep 17 00:00:00 2001 From: Conor McCarthy Date: Mon, 14 Sep 2020 13:48:34 +0100 Subject: [PATCH 55/77] full coverage of failing tests for graph library --- graph/tests/graph.t | 41 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/graph/tests/graph.t b/graph/tests/graph.t index 09bfc67c..51a03ba6 100644 --- a/graph/tests/graph.t +++ b/graph/tests/graph.t @@ -37,8 +37,8 @@ g:.ml.addNode[g;`node1]`function`inputs`outputs!({x};"f";"!") nodeConfig:`function`inputs`outputs!({x};"f";"!") // Configuration nodes with various issues -extraKey:`function`inputs`outputs`extrakey!({x};"f";"!";1f) -inputType:`function`inputs`outputs!({x};enlist 1f;"!") +extraKey :`function`inputs`outputs`extrakey!({x};"f";"!";1f) +inputType :`function`inputs`outputs!({x};enlist 1f;"!") outputType:`function`inputs`outputs!({x};"f";enlist 1f) @@ -50,7 +50,7 @@ failingTest[.ml.addCfg;(g;`cfg1;`a`b!1 2);0b;"invalid nodeId"] // Attempt to add a node with a non unique name failingTest[.ml.addNode;(g;`node1;nodeConfig);0b;"invalid nodeId"] -// Attempt to add a node with an addition node configuration key +// Attempt to add a node with an additional node configuration key failingTest[.ml.addNode;(g;`failingNode;extraKey);0b;"invalid node"] // Attempt to add a node with an unsuitable configuration input @@ -88,29 +88,62 @@ failingTest[.ml.disconnectEdge;(g;`node;`input);0b;"invalid edge"] // Attempt to disconnect an edge that doesn't exist on a valid node failingTest[.ml.disconnectEdge;(g;`node1;`test);0b;"invalid edge"] +// Attempt to connect an edge with a non existent source node +failingTest[.ml.connectEdge;(g;`nocfg;`output;`node1;`input);0b;"invalid srcNode"] + +// Attempt to connect an edge from an existent source node but non existent source name +failingTest[.ml.connectEdge;(g;`cfg1;`nosrcName;`node1;`input);0b;"invalid srcName"] + +// Attempt to connect an edge from an non existent destination node +failingTest[.ml.connectEdge;(g;`cfg1;`output;`nosrcnode;`input);0b;"invalid dstNode"] + +// Attempt to connect an edge from an existent destination node but non existent destination name +failingTest[.ml.connectEdge;(g;`cfg1;`output;`node1;`noinput);0b;"invalid dstName"] + + -1"\nTesting delNode"; // Attempt to delete a node that does not exist failingTest[.ml.delNode;(g;`notanode);0b;"invalid nodeId"] +// Add a node to be immediately deleted +g:.ml.addNode[g;`tempNode]`function`inputs`outputs!({x};"f";"!"); +`tempNode in exec nodeId from g[`nodes] +g:.ml.delNode[g;`tempNode] +not `tempNode in exec nodeId from g[`nodes] + +-1"\nTesting valid edge connection"; // Update node1 such that it is valid for connection to cfg1, // but function errors on execution (for pipeline testing) g:.ml.updNode[g;`node1]`function`inputs`outputs!({`e+1};"!";"!") g:.ml.connectEdge[g;`cfg1;`output;`node1;`input] 1b~first exec valid from g[`edges] where dstNode=`node1,dstName=`input + +-1"\nTesting failing pipeline execution without debug mode active"; p:.ml.createPipeline[g] // Execute pipeline with debug functionality inactive (returns a table with non complete rows) not all exec complete from .ml.execPipeline[p] + // Check current debug status, update status and check update success --1"\nTesting graph debug"; +-1"\nTesting graph debug activation functionality"; not .ml.graphDebug .ml.updDebug[] .ml.graphDebug + +-1"\nTesting graph debug mode is triggered appropriately"; // Execute pipeline catching error with debug status activated failingTest[.ml.execPipeline;p;1b;"type"] +-1"\nTesting pipeline generation error being appropriately triggered on disconnected edges"; +// disconnect a valid edge, test this causes failure in pipeline generation and reconnect edge +g:.ml.disconnectEdge[g;`node1;`input] +failingTest[.ml.createPipeline;g;1b;"disconnected edges"] +g:.ml.connectEdge[g;`cfg1;`output;`node1;`input] + +-1"\nTesting fully valid graph execution"; +// Test that a valid graph operates as expected g:.ml.updNode[g;`node1]`function`inputs`outputs!({x};"!";"!") p1:.ml.createPipeline[g] all exec complete from .ml.execPipeline[p1] From 77688a8b63063f88ef4c8880123300239c90ee3f Mon Sep 17 00:00:00 2001 From: Dianeod Date: Wed, 16 Sep 2020 15:57:52 +0100 Subject: [PATCH 56/77] updated appveyor files --- build/buildscript.bat | 17 +++++------------ build/getembedpy.q | 11 ----------- build/getkdb.bat | 7 +++---- 3 files changed, 8 insertions(+), 27 deletions(-) delete mode 100644 build/getembedpy.q diff --git a/build/buildscript.bat b/build/buildscript.bat index 06fa90b8..65ea8c88 100644 --- a/build/buildscript.bat +++ b/build/buildscript.bat @@ -4,27 +4,20 @@ if "%APPVEYOR_REPO_TAG%"=="true" ( set ML_VERSION=%APPVEYOR_REPO_BRANCH%_%APPVEYOR_REPO_COMMIT% ) set PATH=C:\Perl;%PATH% -perl -p -i.bak -e s/TOOLKITVERSION/`\$\"%ML_VERSION%\"/g ml.q +perl -p -i.bak -e s/MLVERSION/`\$\"%ML_VERSION%\"/g ml.q + if not defined QLIC_KC ( goto :nokdb ) -call "build\getkdb.bat" || goto :error -set PATH=C:\Miniconda3-x64;C:\Miniconda3-x64\Scripts;%PATH% -mkdir embedpy -cd embedpy -echo getembedpy"latest" | q ..\build\getembedpy.q -q || goto :error -cd .. -echo p)print('embedpy runs') | q -q || goto :error -cd clust/build -call "build.bat" 2017 -cd ../.. +set PATH=C:\Miniconda3-x64;C:\Miniconda3-x64\Scripts;%PATH% +conda config --set always_yes yes --set changeps1 no +call "build\getkdb.bat" || goto :error exit /b 0 - :error echo failed with error exit /b diff --git a/build/getembedpy.q b/build/getembedpy.q deleted file mode 100644 index f247d9f9..00000000 --- a/build/getembedpy.q +++ /dev/null @@ -1,11 +0,0 @@ -qhome:hsym`$$[not count u:getenv`QHOME;[-2"QHOME not defined";exit 1];u]; -dl:{[s;url]$[s;;`/:]system"curl -u ",getenv[`GH_APIREAD]," -s -L ",url,$[s;" -J -O";""]} -download:{ - assets:.j.k[dl[0b]"https://api.github.com/repos/KxSystems/embedPy/releases/",$[not[count x]|x~"latest";"latest";"tags/",x]]`assets; - relurl:first exec browser_download_url from assets where name like{"*",x,"*"}(`m64`l64`w64!string`osx`linux`windows).z.o; - $[count relurl;-1"downloading embedpy from ",relurl;'"release not found"]; - dl[1b]relurl;last ` vs hsym`$relurl} -extract:{system$[x like"*.tgz";"tar -zxf";x like"*.zip";$[.z.o~`w64;"7z x -y";"unzip"];'"not zip or tgz"]," ",string x} -install:{{(` sv qhome,x)1:read1 x}each`p.k`p.q,`${$[x~"w64";x,"/p.dll";x,"/p.so"]}string .z.o} -getembedpy:{@[x;y;{-2"ERROR: ",x;exit 1}]}{install extract download x} - diff --git a/build/getkdb.bat b/build/getkdb.bat index aa8ca23e..65cf23df 100755 --- a/build/getkdb.bat +++ b/build/getkdb.bat @@ -1,9 +1,8 @@ -curl -fsSOJL %W64% +conda install -c kx embedPy mkdir q -7z x w64.zip -oq +xcopy /E C:\Miniconda3-x64\q q echo|set /P =%QLIC_KC% >q\kc.lic.enc certutil -decode q\kc.lic.enc q\kc.lic set QHOME=%cd%\q set PATH=%QHOME%\w64;%PATH% -where q -echo .z.K | q -q +echo show .z.K;show .z.k;exit 0 | q -q From 01fc07687d790b71a9596b18aa4ffbec292762df Mon Sep 17 00:00:00 2001 From: Conor McCarthy Date: Tue, 22 Sep 2020 17:05:02 +0100 Subject: [PATCH 57/77] addition of width parameter to autocorrelation plot --- timeseries/misc.q | 8 ++++---- timeseries/utils.q | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/timeseries/misc.q b/timeseries/misc.q index 03d5cde2..0a21f7a1 100644 --- a/timeseries/misc.q +++ b/timeseries/misc.q @@ -97,9 +97,9 @@ ts.laggedFeatures:{[tab;colNames;lags] // @param data {num[]} dataset from which to generate the autocorrelation plot // @param n {int} number of lags to include in the graph // @return {graph} display to standard out the autocorrelation bar plot -ts.acfPlot:{[data;n] - acf:ts.i.autoCorrFunction[data;]each m:1_til n&count[data]; - ts.i.plotFunction[data;acf;m;"AutoCorrelation"]; +ts.acfPlot:{[data;n;width] + acf:ts.i.autoCorrFunction[data;]each n; + ts.i.plotFunction[data;acf;n;width;"AutoCorrelation"]; } // @kind function @@ -110,6 +110,6 @@ ts.acfPlot:{[data;n] // @return {graph} display to standard out the partial autocorrelation bar plot ts.pacfPlot:{[data;n] pacf:.ml.fresh.i.pacf[data;neg[1]+m:n&count data]`; - ts.i.plotFunction[data;1_pacf;1_til m;"Partial AutoCorrelation"]; + ts.i.plotFunction[data;1_pacf;1_til m;1;"Partial AutoCorrelation"]; } diff --git a/timeseries/utils.q b/timeseries/utils.q index 3bf387ba..e9b20e96 100644 --- a/timeseries/utils.q +++ b/timeseries/utils.q @@ -756,10 +756,10 @@ ts.i.slidingWindowFunction:{[func;win;data] // @param m {num[]} bar plot indices // @param title {string} title to be given to the plot // @return {graph} presents a plot to screen associated with relevant analysis -ts.i.plotFunction:{[data;vals;m;title] +ts.i.plotFunction:{[data;vals;m;width;title] plt:.p.import[`matplotlib.pyplot]; conf:count[m]#1.95%sqrt count data; - plt[`:bar][m;vals;`width pykw 0.5]; + plt[`:bar][m;vals;`width pykw width%2]; cfgkeys:`linewidth`linestyle`color`label; cfgvals:3,`dashed`red`conf_interval; plt[`:plot][m;conf;pykwargs cfgkeys!cfgvals]; From 17299e11ab73b6e64dfef5efa59d801b472cbcca Mon Sep 17 00:00:00 2001 From: Dianeod Date: Tue, 29 Sep 2020 15:43:22 +0100 Subject: [PATCH 58/77] addition of timeseries and optimization tests --- clust/tests/util.t | 2 +- optimize/optim.q | 2 +- optimize/tests/test.t | 14 ++++++++++++++ timeseries/tests/data/{ => linux}/fit/AR1 | Bin timeseries/tests/data/{ => linux}/fit/AR2 | Bin timeseries/tests/data/{ => linux}/fit/AR3 | Bin timeseries/tests/data/{ => linux}/fit/AR4 | Bin timeseries/tests/data/{ => linux}/fit/ARCH1 | Bin timeseries/tests/data/{ => linux}/fit/ARCH2 | Bin timeseries/tests/data/{ => linux}/fit/ARIMA1 | Bin timeseries/tests/data/{ => linux}/fit/ARIMA2 | Bin timeseries/tests/data/{ => linux}/fit/ARIMA3 | Bin timeseries/tests/data/{ => linux}/fit/ARIMA4 | Bin timeseries/tests/data/{ => linux}/fit/ARMA1 | Bin timeseries/tests/data/{ => linux}/fit/ARMA2 | Bin timeseries/tests/data/{ => linux}/fit/ARMA3 | Bin timeseries/tests/data/{ => linux}/fit/ARMA4 | Bin timeseries/tests/data/{ => linux}/fit/SARIMA1 | Bin timeseries/tests/data/{ => linux}/fit/SARIMA2 | Bin timeseries/tests/data/{ => linux}/fit/SARIMA3 | Bin timeseries/tests/data/{ => linux}/fit/SARIMA4 | Bin timeseries/tests/data/{ => linux}/fit/nonStat | Bin timeseries/tests/data/{ => linux}/pred/predAR1 | Bin timeseries/tests/data/{ => linux}/pred/predAR2 | Bin timeseries/tests/data/{ => linux}/pred/predAR3 | Bin timeseries/tests/data/{ => linux}/pred/predAR4 | Bin timeseries/tests/data/{ => linux}/pred/predARCH1 | Bin timeseries/tests/data/{ => linux}/pred/predARCH2 | Bin timeseries/tests/data/{ => linux}/pred/predARIMA1 | Bin timeseries/tests/data/{ => linux}/pred/predARIMA2 | Bin timeseries/tests/data/{ => linux}/pred/predARIMA3 | Bin timeseries/tests/data/{ => linux}/pred/predARIMA4 | Bin timeseries/tests/data/{ => linux}/pred/predARMA1 | Bin timeseries/tests/data/{ => linux}/pred/predARMA2 | Bin timeseries/tests/data/{ => linux}/pred/predARMA3 | Bin timeseries/tests/data/{ => linux}/pred/predARMA4 | Bin .../tests/data/{ => linux}/pred/predSARIMA1 | Bin .../tests/data/{ => linux}/pred/predSARIMA2 | Bin .../tests/data/{ => linux}/pred/predSARIMA3 | Bin .../tests/data/{ => linux}/pred/predSARIMA4 | Bin timeseries/tests/fit.t | 5 ++++- timeseries/tests/pred.t | 10 +++++++--- 42 files changed, 27 insertions(+), 6 deletions(-) rename timeseries/tests/data/{ => linux}/fit/AR1 (100%) rename timeseries/tests/data/{ => linux}/fit/AR2 (100%) rename timeseries/tests/data/{ => linux}/fit/AR3 (100%) rename timeseries/tests/data/{ => linux}/fit/AR4 (100%) rename timeseries/tests/data/{ => linux}/fit/ARCH1 (100%) rename timeseries/tests/data/{ => linux}/fit/ARCH2 (100%) rename timeseries/tests/data/{ => linux}/fit/ARIMA1 (100%) rename timeseries/tests/data/{ => linux}/fit/ARIMA2 (100%) rename timeseries/tests/data/{ => linux}/fit/ARIMA3 (100%) rename timeseries/tests/data/{ => linux}/fit/ARIMA4 (100%) rename timeseries/tests/data/{ => linux}/fit/ARMA1 (100%) rename timeseries/tests/data/{ => linux}/fit/ARMA2 (100%) rename timeseries/tests/data/{ => linux}/fit/ARMA3 (100%) rename timeseries/tests/data/{ => linux}/fit/ARMA4 (100%) rename timeseries/tests/data/{ => linux}/fit/SARIMA1 (100%) rename timeseries/tests/data/{ => linux}/fit/SARIMA2 (100%) rename timeseries/tests/data/{ => linux}/fit/SARIMA3 (100%) rename timeseries/tests/data/{ => linux}/fit/SARIMA4 (100%) rename timeseries/tests/data/{ => linux}/fit/nonStat (100%) rename timeseries/tests/data/{ => linux}/pred/predAR1 (100%) rename timeseries/tests/data/{ => linux}/pred/predAR2 (100%) rename timeseries/tests/data/{ => linux}/pred/predAR3 (100%) rename timeseries/tests/data/{ => linux}/pred/predAR4 (100%) rename timeseries/tests/data/{ => linux}/pred/predARCH1 (100%) rename timeseries/tests/data/{ => linux}/pred/predARCH2 (100%) rename timeseries/tests/data/{ => linux}/pred/predARIMA1 (100%) rename timeseries/tests/data/{ => linux}/pred/predARIMA2 (100%) rename timeseries/tests/data/{ => linux}/pred/predARIMA3 (100%) rename timeseries/tests/data/{ => linux}/pred/predARIMA4 (100%) rename timeseries/tests/data/{ => linux}/pred/predARMA1 (100%) rename timeseries/tests/data/{ => linux}/pred/predARMA2 (100%) rename timeseries/tests/data/{ => linux}/pred/predARMA3 (100%) rename timeseries/tests/data/{ => linux}/pred/predARMA4 (100%) rename timeseries/tests/data/{ => linux}/pred/predSARIMA1 (100%) rename timeseries/tests/data/{ => linux}/pred/predSARIMA2 (100%) rename timeseries/tests/data/{ => linux}/pred/predSARIMA3 (100%) rename timeseries/tests/data/{ => linux}/pred/predSARIMA4 (100%) diff --git a/clust/tests/util.t b/clust/tests/util.t index cd0d1c3e..cea94622 100644 --- a/clust/tests/util.t +++ b/clust/tests/util.t @@ -28,7 +28,7 @@ info:.ml.clust.i.apinit[d1;`e2dist;max] .ml.clust.kd.newtree[d2;3][`parent]~0N 0 0 .ml.clust.kd.newtree[d2;3][`idxs]~(0#0;til 5;5+til 5) .ml.clust.kd.nn[tree;d1;`edist;0;d2[;2]][`closestPoint]~2 -.ml.clust.kd.nn[tree;d1;`mdist;1 2 3 4;d1[;1]][`closestPoint`closestDist]~(0;2f) +.ml.clust.kd.nn[tree;d1;`mdist;1 2 3 4;d1[;1]][`closestPoint`closestDist]=(0;2f) .ml.clust.kd.nn[tree;d1;`mdist;1;7 9f][`closestPoint`closestDist]~(4;8f) .ml.clust.kd.nn[tree2;d2;`edist;1 2 3;d1[;1]][`closestPoint]~0 .ml.clust.kd.nn[tree2;d2;`edist;1 5 2;d1[;3]][`closestPoint]~3 diff --git a/optimize/optim.q b/optimize/optim.q index b9347e78..d832e790 100644 --- a/optimize/optim.q +++ b/optimize/optim.q @@ -547,7 +547,7 @@ i.wolfeParamCheck:{[dict] // @param x0 {dict/num/num[]} initial values of x to be optimized // @returns {num[]} the initial values of x converted into a suitable numerical list format i.dataFormat:{[x0] - $[99h=type x0;value x0;0h >type x0;enlist x0; x0] + "f"$$[99h=type x0;raze value x0;0h >type x0;enlist x0; x0] } diff --git a/optimize/tests/test.t b/optimize/tests/test.t index 6b3a6c94..a792eee2 100644 --- a/optimize/tests/test.t +++ b/optimize/tests/test.t @@ -16,6 +16,11 @@ failingTest:{[function;data;applyType;expectedError] fileList:`quadx0`quadx1`sinex1`multix0`multix1`multix1Gtol`multiargs0`multiargs1 {load hsym`$":optimize/tests/data/",string x}each fileList; +-1"Warning: These tests may cause varying results for Linux vs Windows users"; +os:$[.z.o like "w*";"windows/";"linux/"]; +fileList2:`rosenx0`rosenx1 +{load hsym`$":optimize/tests/data/",x,string y}[os]each fileList2; + -1"Testing examples of optimization functionality expected to fail"; c1c2Fail:"When evaluating Wolfe conditions the following must hold 0 < c1 < c2 < 1" normFail:"ord must be +/- infinity or a long atom" @@ -58,3 +63,12 @@ args1:enlist[`args0]!args0 .ml.optimize.BFGS[multiFuncArgList;x0multi;args0;::]~multiargs0 .ml.optimize.BFGS[multiFuncArgDict;x1multi;args1;::]~multiargs1 +/S 42 +precisionFunc:{all 1e-5>abs x-y} +-1"Testing of Rosenbrock function of N variables"; +rosenFunc:{(sum(100*(_[1;x] - _[-1;x]xexp 2.0)xexp 2) + (1 - _[-1;x])xexp 2)} +x0rosen:10?10 +x1rosen:enlist[`x]!enlist 10?10f +precisionFunc[.ml.optimize.BFGS[rosenFunc;x0rosen;();::]`xVals;rosenx0`xVals] +precisionFunc[.ml.optimize.BFGS[rosenFunc;x1rosen;();::]`xVals;rosenx1`xVals] + diff --git a/timeseries/tests/data/fit/AR1 b/timeseries/tests/data/linux/fit/AR1 similarity index 100% rename from timeseries/tests/data/fit/AR1 rename to timeseries/tests/data/linux/fit/AR1 diff --git a/timeseries/tests/data/fit/AR2 b/timeseries/tests/data/linux/fit/AR2 similarity index 100% rename from timeseries/tests/data/fit/AR2 rename to timeseries/tests/data/linux/fit/AR2 diff --git a/timeseries/tests/data/fit/AR3 b/timeseries/tests/data/linux/fit/AR3 similarity index 100% rename from timeseries/tests/data/fit/AR3 rename to timeseries/tests/data/linux/fit/AR3 diff --git a/timeseries/tests/data/fit/AR4 b/timeseries/tests/data/linux/fit/AR4 similarity index 100% rename from timeseries/tests/data/fit/AR4 rename to timeseries/tests/data/linux/fit/AR4 diff --git a/timeseries/tests/data/fit/ARCH1 b/timeseries/tests/data/linux/fit/ARCH1 similarity index 100% rename from timeseries/tests/data/fit/ARCH1 rename to timeseries/tests/data/linux/fit/ARCH1 diff --git a/timeseries/tests/data/fit/ARCH2 b/timeseries/tests/data/linux/fit/ARCH2 similarity index 100% rename from timeseries/tests/data/fit/ARCH2 rename to timeseries/tests/data/linux/fit/ARCH2 diff --git a/timeseries/tests/data/fit/ARIMA1 b/timeseries/tests/data/linux/fit/ARIMA1 similarity index 100% rename from timeseries/tests/data/fit/ARIMA1 rename to timeseries/tests/data/linux/fit/ARIMA1 diff --git a/timeseries/tests/data/fit/ARIMA2 b/timeseries/tests/data/linux/fit/ARIMA2 similarity index 100% rename from timeseries/tests/data/fit/ARIMA2 rename to timeseries/tests/data/linux/fit/ARIMA2 diff --git a/timeseries/tests/data/fit/ARIMA3 b/timeseries/tests/data/linux/fit/ARIMA3 similarity index 100% rename from timeseries/tests/data/fit/ARIMA3 rename to timeseries/tests/data/linux/fit/ARIMA3 diff --git a/timeseries/tests/data/fit/ARIMA4 b/timeseries/tests/data/linux/fit/ARIMA4 similarity index 100% rename from timeseries/tests/data/fit/ARIMA4 rename to timeseries/tests/data/linux/fit/ARIMA4 diff --git a/timeseries/tests/data/fit/ARMA1 b/timeseries/tests/data/linux/fit/ARMA1 similarity index 100% rename from timeseries/tests/data/fit/ARMA1 rename to timeseries/tests/data/linux/fit/ARMA1 diff --git a/timeseries/tests/data/fit/ARMA2 b/timeseries/tests/data/linux/fit/ARMA2 similarity index 100% rename from timeseries/tests/data/fit/ARMA2 rename to timeseries/tests/data/linux/fit/ARMA2 diff --git a/timeseries/tests/data/fit/ARMA3 b/timeseries/tests/data/linux/fit/ARMA3 similarity index 100% rename from timeseries/tests/data/fit/ARMA3 rename to timeseries/tests/data/linux/fit/ARMA3 diff --git a/timeseries/tests/data/fit/ARMA4 b/timeseries/tests/data/linux/fit/ARMA4 similarity index 100% rename from timeseries/tests/data/fit/ARMA4 rename to timeseries/tests/data/linux/fit/ARMA4 diff --git a/timeseries/tests/data/fit/SARIMA1 b/timeseries/tests/data/linux/fit/SARIMA1 similarity index 100% rename from timeseries/tests/data/fit/SARIMA1 rename to timeseries/tests/data/linux/fit/SARIMA1 diff --git a/timeseries/tests/data/fit/SARIMA2 b/timeseries/tests/data/linux/fit/SARIMA2 similarity index 100% rename from timeseries/tests/data/fit/SARIMA2 rename to timeseries/tests/data/linux/fit/SARIMA2 diff --git a/timeseries/tests/data/fit/SARIMA3 b/timeseries/tests/data/linux/fit/SARIMA3 similarity index 100% rename from timeseries/tests/data/fit/SARIMA3 rename to timeseries/tests/data/linux/fit/SARIMA3 diff --git a/timeseries/tests/data/fit/SARIMA4 b/timeseries/tests/data/linux/fit/SARIMA4 similarity index 100% rename from timeseries/tests/data/fit/SARIMA4 rename to timeseries/tests/data/linux/fit/SARIMA4 diff --git a/timeseries/tests/data/fit/nonStat b/timeseries/tests/data/linux/fit/nonStat similarity index 100% rename from timeseries/tests/data/fit/nonStat rename to timeseries/tests/data/linux/fit/nonStat diff --git a/timeseries/tests/data/pred/predAR1 b/timeseries/tests/data/linux/pred/predAR1 similarity index 100% rename from timeseries/tests/data/pred/predAR1 rename to timeseries/tests/data/linux/pred/predAR1 diff --git a/timeseries/tests/data/pred/predAR2 b/timeseries/tests/data/linux/pred/predAR2 similarity index 100% rename from timeseries/tests/data/pred/predAR2 rename to timeseries/tests/data/linux/pred/predAR2 diff --git a/timeseries/tests/data/pred/predAR3 b/timeseries/tests/data/linux/pred/predAR3 similarity index 100% rename from timeseries/tests/data/pred/predAR3 rename to timeseries/tests/data/linux/pred/predAR3 diff --git a/timeseries/tests/data/pred/predAR4 b/timeseries/tests/data/linux/pred/predAR4 similarity index 100% rename from timeseries/tests/data/pred/predAR4 rename to timeseries/tests/data/linux/pred/predAR4 diff --git a/timeseries/tests/data/pred/predARCH1 b/timeseries/tests/data/linux/pred/predARCH1 similarity index 100% rename from timeseries/tests/data/pred/predARCH1 rename to timeseries/tests/data/linux/pred/predARCH1 diff --git a/timeseries/tests/data/pred/predARCH2 b/timeseries/tests/data/linux/pred/predARCH2 similarity index 100% rename from timeseries/tests/data/pred/predARCH2 rename to timeseries/tests/data/linux/pred/predARCH2 diff --git a/timeseries/tests/data/pred/predARIMA1 b/timeseries/tests/data/linux/pred/predARIMA1 similarity index 100% rename from timeseries/tests/data/pred/predARIMA1 rename to timeseries/tests/data/linux/pred/predARIMA1 diff --git a/timeseries/tests/data/pred/predARIMA2 b/timeseries/tests/data/linux/pred/predARIMA2 similarity index 100% rename from timeseries/tests/data/pred/predARIMA2 rename to timeseries/tests/data/linux/pred/predARIMA2 diff --git a/timeseries/tests/data/pred/predARIMA3 b/timeseries/tests/data/linux/pred/predARIMA3 similarity index 100% rename from timeseries/tests/data/pred/predARIMA3 rename to timeseries/tests/data/linux/pred/predARIMA3 diff --git a/timeseries/tests/data/pred/predARIMA4 b/timeseries/tests/data/linux/pred/predARIMA4 similarity index 100% rename from timeseries/tests/data/pred/predARIMA4 rename to timeseries/tests/data/linux/pred/predARIMA4 diff --git a/timeseries/tests/data/pred/predARMA1 b/timeseries/tests/data/linux/pred/predARMA1 similarity index 100% rename from timeseries/tests/data/pred/predARMA1 rename to timeseries/tests/data/linux/pred/predARMA1 diff --git a/timeseries/tests/data/pred/predARMA2 b/timeseries/tests/data/linux/pred/predARMA2 similarity index 100% rename from timeseries/tests/data/pred/predARMA2 rename to timeseries/tests/data/linux/pred/predARMA2 diff --git a/timeseries/tests/data/pred/predARMA3 b/timeseries/tests/data/linux/pred/predARMA3 similarity index 100% rename from timeseries/tests/data/pred/predARMA3 rename to timeseries/tests/data/linux/pred/predARMA3 diff --git a/timeseries/tests/data/pred/predARMA4 b/timeseries/tests/data/linux/pred/predARMA4 similarity index 100% rename from timeseries/tests/data/pred/predARMA4 rename to timeseries/tests/data/linux/pred/predARMA4 diff --git a/timeseries/tests/data/pred/predSARIMA1 b/timeseries/tests/data/linux/pred/predSARIMA1 similarity index 100% rename from timeseries/tests/data/pred/predSARIMA1 rename to timeseries/tests/data/linux/pred/predSARIMA1 diff --git a/timeseries/tests/data/pred/predSARIMA2 b/timeseries/tests/data/linux/pred/predSARIMA2 similarity index 100% rename from timeseries/tests/data/pred/predSARIMA2 rename to timeseries/tests/data/linux/pred/predSARIMA2 diff --git a/timeseries/tests/data/pred/predSARIMA3 b/timeseries/tests/data/linux/pred/predSARIMA3 similarity index 100% rename from timeseries/tests/data/pred/predSARIMA3 rename to timeseries/tests/data/linux/pred/predSARIMA3 diff --git a/timeseries/tests/data/pred/predSARIMA4 b/timeseries/tests/data/linux/pred/predSARIMA4 similarity index 100% rename from timeseries/tests/data/pred/predSARIMA4 rename to timeseries/tests/data/linux/pred/predSARIMA4 diff --git a/timeseries/tests/fit.t b/timeseries/tests/fit.t index e1051645..31eee812 100644 --- a/timeseries/tests/fit.t +++ b/timeseries/tests/fit.t @@ -7,6 +7,7 @@ \l fresh/extract.q \l timeseries/tests/failMessage.q +-1"Warning: These tests may cause varying results for Linux vs Windows users"; \S 42 endogInt :10000?1000 @@ -17,10 +18,12 @@ exogMixed :(10000 20#20000?1000),'(10000 20#20000?1000f),'(10000 10#10000?0b) residInt :10000?1000 residFloat:10000?1000f +os:$[.z.o like "w*";"windows/";"linux/"]; + // Load files fileList:`AR1`AR2`AR3`AR4`ARCH1`ARCH2`ARMA1`ARMA2`ARMA3`ARMA4`ARIMA1`ARIMA2, `ARIMA3`ARIMA4`SARIMA1`SARIMA2`SARIMA3`SARIMA4`nonStat -{load hsym`$":timeseries/tests/data/fit/",string x}each fileList; +{load hsym`$":timeseries/tests/data/",y,"fit/",string x}[;os]each fileList; // AR tests .ml.ts.AR.fit[endogInt ;() ;1;0b]~AR1 diff --git a/timeseries/tests/pred.t b/timeseries/tests/pred.t index 17a7655c..df2d2dad 100644 --- a/timeseries/tests/pred.t +++ b/timeseries/tests/pred.t @@ -5,17 +5,21 @@ \l timeseries/predict.q \l timeseries/tests/failMessage.q +-1"Warning: These tests may cause varying results for Linux vs Windows users"; + \S 42 exogIntFuture :1000 50#5000?1000 exogFloatFuture :1000 50#5000?1000f exogMixedFuture :(1000 20#20000?1000),'(1000 20#20000?1000f),'(1000 10#10000?0b) +os:$[.z.o like "w*";"windows/";"linux/"]; + // Load files fileList:`AR1`AR2`AR3`AR4`ARCH1`ARCH2`ARMA1`ARMA2`ARMA3`ARMA4`ARIMA1`ARIMA2, `ARIMA3`ARIMA4`SARIMA1`SARIMA2`SARIMA3`SARIMA4 -loadFunc:{load hsym`$":timeseries/tests/data/",x,string y} -loadFunc["fit/"]each fileList; -loadFunc["pred/pred"]each fileList; +loadFunc:{load hsym`$":timeseries/tests/data/",x,y,string z} +loadFunc[os;"fit/"]each fileList; +loadFunc[os;"pred/pred"]each fileList; // AR tests From 9d291db6ea3c737911f0dc90c795b7c275317927 Mon Sep 17 00:00:00 2001 From: Dianeod Date: Tue, 29 Sep 2020 15:48:30 +0100 Subject: [PATCH 59/77] included optimization files and fixed cluster test --- clust/tests/util.t | 2 +- optimize/tests/data/linux/rosenx0 | Bin 0 -> 141 bytes optimize/tests/data/linux/rosenx1 | Bin 0 -> 141 bytes 3 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 optimize/tests/data/linux/rosenx0 create mode 100644 optimize/tests/data/linux/rosenx1 diff --git a/clust/tests/util.t b/clust/tests/util.t index cea94622..cfe43e4f 100644 --- a/clust/tests/util.t +++ b/clust/tests/util.t @@ -28,7 +28,7 @@ info:.ml.clust.i.apinit[d1;`e2dist;max] .ml.clust.kd.newtree[d2;3][`parent]~0N 0 0 .ml.clust.kd.newtree[d2;3][`idxs]~(0#0;til 5;5+til 5) .ml.clust.kd.nn[tree;d1;`edist;0;d2[;2]][`closestPoint]~2 -.ml.clust.kd.nn[tree;d1;`mdist;1 2 3 4;d1[;1]][`closestPoint`closestDist]=(0;2f) +all .ml.clust.kd.nn[tree;d1;`mdist;1 2 3 4;d1[;1]][`closestPoint`closestDist]=(0;2f) .ml.clust.kd.nn[tree;d1;`mdist;1;7 9f][`closestPoint`closestDist]~(4;8f) .ml.clust.kd.nn[tree2;d2;`edist;1 2 3;d1[;1]][`closestPoint]~0 .ml.clust.kd.nn[tree2;d2;`edist;1 5 2;d1[;3]][`closestPoint]~3 diff --git a/optimize/tests/data/linux/rosenx0 b/optimize/tests/data/linux/rosenx0 new file mode 100644 index 0000000000000000000000000000000000000000..43ac9114d4b4113a05e6b156f9b8f571ca709a59 GIT binary patch literal 141 zcmey*n9R+<%)r1<5tf)!%#c=^mmHK@!jM;*>sgXo1QZ9!aWZfLNnah83;+MWx1XG% ze-=a+CT=(eqOTw4+X$j_HFku7=(joBdj0|FgJNode}VLD4w;|7fb{On_M2aU^oQEk Xkk>%^`sgXo1QZ9!aWZfL$@ss$5B~puZ=aTV z?G%WPxN&L)i2k>rGZRF2wE2Ge2c#J+{tJO Date: Tue, 29 Sep 2020 16:06:17 +0100 Subject: [PATCH 60/77] added saved input files for optim func --- optimize/tests/data/x0rosen | Bin 0 -> 96 bytes optimize/tests/data/x1rosen | Bin 0 -> 103 bytes optimize/tests/test.t | 4 +--- 3 files changed, 1 insertion(+), 3 deletions(-) create mode 100644 optimize/tests/data/x0rosen create mode 100644 optimize/tests/data/x1rosen diff --git a/optimize/tests/data/x0rosen b/optimize/tests/data/x0rosen new file mode 100644 index 0000000000000000000000000000000000000000..6c1b1c0c59587c8048826b914f700847e5c2e67f GIT binary patch literal 96 ncmeyTz|H^yTws!cfdfi2LTOGY%?hPipfnpA4O0)(4^s~SdeQ-9 literal 0 HcmV?d00001 diff --git a/optimize/tests/data/x1rosen b/optimize/tests/data/x1rosen new file mode 100644 index 0000000000000000000000000000000000000000..6a252337951e15f2cdb896f8a4252b47fd521972 GIT binary patch literal 103 zcmey*n9R+<$iTo*0mLAhlYt9JGB7ASDvxJ!U|?8KHFK>rkmgXoe?xU&ER_fzY>sMQEf0m5J(#wb>S2T(is)}YsDP^Cgv9k literal 0 HcmV?d00001 diff --git a/optimize/tests/test.t b/optimize/tests/test.t index a792eee2..105849dd 100644 --- a/optimize/tests/test.t +++ b/optimize/tests/test.t @@ -13,7 +13,7 @@ failingTest:{[function;data;applyType;expectedError] // Load in data saved as golden copy for this analysis // Load files -fileList:`quadx0`quadx1`sinex1`multix0`multix1`multix1Gtol`multiargs0`multiargs1 +fileList:`quadx0`quadx1`sinex1`multix0`multix1`multix1Gtol`multiargs0`multiargs1`x0rosen`x1rosen {load hsym`$":optimize/tests/data/",string x}each fileList; -1"Warning: These tests may cause varying results for Linux vs Windows users"; @@ -67,8 +67,6 @@ args1:enlist[`args0]!args0 precisionFunc:{all 1e-5>abs x-y} -1"Testing of Rosenbrock function of N variables"; rosenFunc:{(sum(100*(_[1;x] - _[-1;x]xexp 2.0)xexp 2) + (1 - _[-1;x])xexp 2)} -x0rosen:10?10 -x1rosen:enlist[`x]!enlist 10?10f precisionFunc[.ml.optimize.BFGS[rosenFunc;x0rosen;();::]`xVals;rosenx0`xVals] precisionFunc[.ml.optimize.BFGS[rosenFunc;x1rosen;();::]`xVals;rosenx1`xVals] From 805717335bb52db58a5150043eef1eb045226832 Mon Sep 17 00:00:00 2001 From: Deanna Morgan Date: Thu, 1 Oct 2020 10:29:08 +0100 Subject: [PATCH 61/77] restructure --- optimize/tests/data/multiargs0 | Bin 77 -> 0 bytes optimize/tests/data/multiargs1 | Bin 77 -> 0 bytes optimize/tests/data/multix0 | Bin 77 -> 0 bytes optimize/tests/data/multix1 | Bin 77 -> 0 bytes optimize/tests/data/multix1Gtol | Bin 77 -> 0 bytes optimize/tests/data/quadx0 | Bin 69 -> 0 bytes optimize/tests/data/quadx1 | Bin 69 -> 0 bytes optimize/tests/data/sinex1 | Bin 69 -> 0 bytes optimize/tests/data/x0rosen | Bin 96 -> 0 bytes optimize/tests/data/x1rosen | Bin 103 -> 0 bytes 10 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 optimize/tests/data/multiargs0 delete mode 100644 optimize/tests/data/multiargs1 delete mode 100644 optimize/tests/data/multix0 delete mode 100644 optimize/tests/data/multix1 delete mode 100644 optimize/tests/data/multix1Gtol delete mode 100644 optimize/tests/data/quadx0 delete mode 100644 optimize/tests/data/quadx1 delete mode 100644 optimize/tests/data/sinex1 delete mode 100644 optimize/tests/data/x0rosen delete mode 100644 optimize/tests/data/x1rosen diff --git a/optimize/tests/data/multiargs0 b/optimize/tests/data/multiargs0 deleted file mode 100644 index 60c60ec2e4af0b72f96697cd5bf8f05887730612..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 77 zcmey*n9R+<%)r1<5tf)!%#c=^mmHK@!jM;*>sgXo1QZ9!aWXIgNv3cs#drVT+pBqR X5@TXuarmy<$_Nw|arnssq(A@w?;jD0 diff --git a/optimize/tests/data/multiargs1 b/optimize/tests/data/multiargs1 deleted file mode 100644 index 62c05f80ad84c9c195ddae94401198861deecb4a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 77 zcmey*n9R+<%)r1<5tf)!%#c=^mmHK@!jM;*>sgXo1QZ9!aWXIg$$&S!DgDq0ho diff --git a/optimize/tests/data/multix0 b/optimize/tests/data/multix0 deleted file mode 100644 index 359fe0c4ec13e6cf60462caaff8eb87204e6d0bc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 77 zcmey*n9R+<%)r1<5tf)!%#c=^mmHK@!jM;*>sgXo1QZ9!aWXIgNv3cs#drVT+pBqR Z5@TXuarl1TK8@Qi=!Wf27N9H$000cR6Tbie diff --git a/optimize/tests/data/multix1 b/optimize/tests/data/multix1 deleted file mode 100644 index 0e42bc69355e901134adfe0a2b064248b2f26299..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 77 zcmey*n9R+<%)r1<5tf)!%#c=^mmHK@!jM;*>sgXo1QZ9!aWXIg$$&S!DsgXo1QZ9!aWXIgNyoJefBye}Z?9>{ Z@c;jRW{2++i+!FHewlCclNBfn0swMs7-9eb diff --git a/optimize/tests/data/quadx0 b/optimize/tests/data/quadx0 deleted file mode 100644 index e46b32a6f552649729c2826c2c57de9a1598e0fb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 69 zcmey*n9R+<%)r1<5tf)!%#c=^mmHK@!jM;*>sgXo1QZ9!aWXIhN!5K>d_csgXo1QZ9!aWXIh$%vsgXo1QZ9!aWXIh$-vh?Ztwi7>hS%( Q!u$XK|G(e=6C?)$0MP3e{r~^~ diff --git a/optimize/tests/data/x0rosen b/optimize/tests/data/x0rosen deleted file mode 100644 index 6c1b1c0c59587c8048826b914f700847e5c2e67f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 96 ncmeyTz|H^yTws!cfdfi2LTOGY%?hPipfnpA4O0)(4^s~SdeQ-9 diff --git a/optimize/tests/data/x1rosen b/optimize/tests/data/x1rosen deleted file mode 100644 index 6a252337951e15f2cdb896f8a4252b47fd521972..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 103 zcmey*n9R+<$iTo*0mLAhlYt9JGB7ASDvxJ!U|?8KHFK>rkmgXoe?xU&ER_fzY>sMQEf0m5J(#wb>S2T(is)}YsDP^Cgv9k From 31e1b441b49e03afe2f7eedd9c259bdd54fb099b Mon Sep 17 00:00:00 2001 From: Deanna Morgan Date: Thu, 1 Oct 2020 10:29:43 +0100 Subject: [PATCH 62/77] restructure and addition of windows data --- optimize/tests/data/linux/multiargs0 | Bin 0 -> 77 bytes optimize/tests/data/linux/multiargs1 | Bin 0 -> 77 bytes optimize/tests/data/linux/multix0 | Bin 0 -> 77 bytes optimize/tests/data/linux/multix1 | Bin 0 -> 77 bytes optimize/tests/data/linux/multix1Gtol | Bin 0 -> 77 bytes optimize/tests/data/linux/quadx0 | Bin 0 -> 69 bytes optimize/tests/data/linux/quadx1 | Bin 0 -> 69 bytes optimize/tests/data/linux/sinex1 | Bin 0 -> 69 bytes optimize/tests/data/linux/x0rosen | Bin 0 -> 96 bytes optimize/tests/data/linux/x1rosen | Bin 0 -> 103 bytes optimize/tests/data/windows/multiargs0 | Bin 0 -> 77 bytes optimize/tests/data/windows/multiargs1 | Bin 0 -> 77 bytes optimize/tests/data/windows/multix0 | Bin 0 -> 77 bytes optimize/tests/data/windows/multix1 | Bin 0 -> 77 bytes optimize/tests/data/windows/multix1Gtol | Bin 0 -> 77 bytes optimize/tests/data/windows/quadx0 | Bin 0 -> 69 bytes optimize/tests/data/windows/quadx1 | Bin 0 -> 69 bytes optimize/tests/data/windows/rosenx0 | Bin 0 -> 141 bytes optimize/tests/data/windows/rosenx1 | Bin 0 -> 141 bytes optimize/tests/data/windows/sinex1 | Bin 0 -> 69 bytes optimize/tests/data/windows/x0rosen | Bin 0 -> 96 bytes optimize/tests/data/windows/x1rosen | Bin 0 -> 103 bytes 22 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 optimize/tests/data/linux/multiargs0 create mode 100644 optimize/tests/data/linux/multiargs1 create mode 100644 optimize/tests/data/linux/multix0 create mode 100644 optimize/tests/data/linux/multix1 create mode 100644 optimize/tests/data/linux/multix1Gtol create mode 100644 optimize/tests/data/linux/quadx0 create mode 100644 optimize/tests/data/linux/quadx1 create mode 100644 optimize/tests/data/linux/sinex1 create mode 100644 optimize/tests/data/linux/x0rosen create mode 100644 optimize/tests/data/linux/x1rosen create mode 100644 optimize/tests/data/windows/multiargs0 create mode 100644 optimize/tests/data/windows/multiargs1 create mode 100644 optimize/tests/data/windows/multix0 create mode 100644 optimize/tests/data/windows/multix1 create mode 100644 optimize/tests/data/windows/multix1Gtol create mode 100644 optimize/tests/data/windows/quadx0 create mode 100644 optimize/tests/data/windows/quadx1 create mode 100644 optimize/tests/data/windows/rosenx0 create mode 100644 optimize/tests/data/windows/rosenx1 create mode 100644 optimize/tests/data/windows/sinex1 create mode 100644 optimize/tests/data/windows/x0rosen create mode 100644 optimize/tests/data/windows/x1rosen diff --git a/optimize/tests/data/linux/multiargs0 b/optimize/tests/data/linux/multiargs0 new file mode 100644 index 0000000000000000000000000000000000000000..60c60ec2e4af0b72f96697cd5bf8f05887730612 GIT binary patch literal 77 zcmey*n9R+<%)r1<5tf)!%#c=^mmHK@!jM;*>sgXo1QZ9!aWXIgNv3cs#drVT+pBqR X5@TXuarmy<$_Nw|arnssq(A@w?;jD0 literal 0 HcmV?d00001 diff --git a/optimize/tests/data/linux/multiargs1 b/optimize/tests/data/linux/multiargs1 new file mode 100644 index 0000000000000000000000000000000000000000..62c05f80ad84c9c195ddae94401198861deecb4a GIT binary patch literal 77 zcmey*n9R+<%)r1<5tf)!%#c=^mmHK@!jM;*>sgXo1QZ9!aWXIg$$&S!DgDq0ho literal 0 HcmV?d00001 diff --git a/optimize/tests/data/linux/multix0 b/optimize/tests/data/linux/multix0 new file mode 100644 index 0000000000000000000000000000000000000000..359fe0c4ec13e6cf60462caaff8eb87204e6d0bc GIT binary patch literal 77 zcmey*n9R+<%)r1<5tf)!%#c=^mmHK@!jM;*>sgXo1QZ9!aWXIgNv3cs#drVT+pBqR Z5@TXuarl1TK8@Qi=!Wf27N9H$000cR6Tbie literal 0 HcmV?d00001 diff --git a/optimize/tests/data/linux/multix1 b/optimize/tests/data/linux/multix1 new file mode 100644 index 0000000000000000000000000000000000000000..0e42bc69355e901134adfe0a2b064248b2f26299 GIT binary patch literal 77 zcmey*n9R+<%)r1<5tf)!%#c=^mmHK@!jM;*>sgXo1QZ9!aWXIg$$&S!DsgXo1QZ9!aWXIgNyoJefBye}Z?9>{ Z@c;jRW{2++i+!FHewlCclNBfn0swMs7-9eb literal 0 HcmV?d00001 diff --git a/optimize/tests/data/linux/quadx0 b/optimize/tests/data/linux/quadx0 new file mode 100644 index 0000000000000000000000000000000000000000..e46b32a6f552649729c2826c2c57de9a1598e0fb GIT binary patch literal 69 zcmey*n9R+<%)r1<5tf)!%#c=^mmHK@!jM;*>sgXo1QZ9!aWXIhN!5K>d_csgXo1QZ9!aWXIh$%vsgXo1QZ9!aWXIh$-vh?Ztwi7>hS%( Q!u$XK|G(e=6C?)$0MP3e{r~^~ literal 0 HcmV?d00001 diff --git a/optimize/tests/data/linux/x0rosen b/optimize/tests/data/linux/x0rosen new file mode 100644 index 0000000000000000000000000000000000000000..6c1b1c0c59587c8048826b914f700847e5c2e67f GIT binary patch literal 96 ncmeyTz|H^yTws!cfdfi2LTOGY%?hPipfnpA4O0)(4^s~SdeQ-9 literal 0 HcmV?d00001 diff --git a/optimize/tests/data/linux/x1rosen b/optimize/tests/data/linux/x1rosen new file mode 100644 index 0000000000000000000000000000000000000000..6a252337951e15f2cdb896f8a4252b47fd521972 GIT binary patch literal 103 zcmey*n9R+<$iTo*0mLAhlYt9JGB7ASDvxJ!U|?8KHFK>rkmgXoe?xU&ER_fzY>sMQEf0m5J(#wb>S2T(is)}YsDP^Cgv9k literal 0 HcmV?d00001 diff --git a/optimize/tests/data/windows/multiargs0 b/optimize/tests/data/windows/multiargs0 new file mode 100644 index 0000000000000000000000000000000000000000..548c7a602ed9d03d7f1862c587430d2ca518f4db GIT binary patch literal 77 zcmey*n9R+<%)r1<5tf)!%#c=^mmHK@!jM;*>sgXo1QZ9!aWXIg$?)256O9=@*x&uO YqxsW+W{2;~UYh`gMI3&z04Wdv09wWtRR910 literal 0 HcmV?d00001 diff --git a/optimize/tests/data/windows/multiargs1 b/optimize/tests/data/windows/multiargs1 new file mode 100644 index 0000000000000000000000000000000000000000..d95f1584bc8fea236a727bfe883f70dcd568bde9 GIT binary patch literal 77 zcmey*n9R+<%)r1<5tf)!%#c=^mmHK@!jM;*>sgXo1QZ9!aWXIg$qB3N&TaYs-d^b@ X^ASM?7KiUr7c_ywA`U-6YCr%0FT@k# literal 0 HcmV?d00001 diff --git a/optimize/tests/data/windows/multix0 b/optimize/tests/data/windows/multix0 new file mode 100644 index 0000000000000000000000000000000000000000..5b476839eb47b868242791db4466689b2e475f36 GIT binary patch literal 77 zcmey*n9R+<%)r1<5tf)!%#c=^mmHK@!jM;*>sgXo1QZ9!aWXIg$?)256O9=@*x&uO ZqxsW+W{2;}&kxvKF4eO8$pVxG0RV1b7xDlA literal 0 HcmV?d00001 diff --git a/optimize/tests/data/windows/multix1 b/optimize/tests/data/windows/multix1 new file mode 100644 index 0000000000000000000000000000000000000000..ac78fac5346662431502b0a6baaaeb14ecc0e29d GIT binary patch literal 77 zcmey*n9R+<%)r1<5tf)!%#c=^mmHK@!jM;*>sgXo1QZ9!aWXIg$qB3N&TaYs-d^b@ Y^ASM?7KiT(3gYc~U#Qsq1gQZ507YvRfB*mh literal 0 HcmV?d00001 diff --git a/optimize/tests/data/windows/multix1Gtol b/optimize/tests/data/windows/multix1Gtol new file mode 100644 index 0000000000000000000000000000000000000000..eebbaedc9830de71050fae1a425b3676e25adffc GIT binary patch literal 77 zcmey*n9R+<%)r1<5tf)!%#c=^mmHK@!jM;*>sgXo1QZ9!aWXIgNe}L4zyJS#Z=d$) a$G`vonH|16EZ>+p>COV1pR7Px5C8zDWg2Jz literal 0 HcmV?d00001 diff --git a/optimize/tests/data/windows/quadx0 b/optimize/tests/data/windows/quadx0 new file mode 100644 index 0000000000000000000000000000000000000000..c5aa4b257616ea3ab3b1b8d6c79dc71013bdd918 GIT binary patch literal 69 zcmey*n9R+<%)r1<5tf)!%#c=^mmHK@!jM;*>sgXo1QZ9!aWXIh$+2X Mz<=N;6Oao60Ehe&m;e9( literal 0 HcmV?d00001 diff --git a/optimize/tests/data/windows/quadx1 b/optimize/tests/data/windows/quadx1 new file mode 100644 index 0000000000000000000000000000000000000000..78ddfdd8de66f5b98ddad4b5a7fffe04d581c82f GIT binary patch literal 69 zcmey*n9R+<%)r1<5tf)!%#c=^mmHK@!jM;*>sgXo1QZ9!aWXIh$!+(p{|5s5??5&X L2psqck^=z%q5BfC literal 0 HcmV?d00001 diff --git a/optimize/tests/data/windows/rosenx0 b/optimize/tests/data/windows/rosenx0 new file mode 100644 index 0000000000000000000000000000000000000000..d7c433742b01a86c917bce48187fe3c8f3eb9a19 GIT binary patch literal 141 zcmey*n9R+<%)r1<5tf)!%#c=^mmHK@!jM;*>sgXo1QZ9!aWZg$$Z1z@{{R2p-ZDq@ zB8cwV^YsgXo1QZ9!aWZfL$)j=nK=OnA(rGOJ z{{Me(|DfOf5s2mynzs!^ztLUb4WdsSDtP`6NdNqx*8LYq8;asgXo1QZ9!aWXIh$-vh?Ztwi7>hS%( Q!u$XK|G(e=6C?)$0MP3e{r~^~ literal 0 HcmV?d00001 diff --git a/optimize/tests/data/windows/x0rosen b/optimize/tests/data/windows/x0rosen new file mode 100644 index 0000000000000000000000000000000000000000..6c1b1c0c59587c8048826b914f700847e5c2e67f GIT binary patch literal 96 ncmeyTz|H^yTws!cfdfi2LTOGY%?hPipfnpA4O0)(4^s~SdeQ-9 literal 0 HcmV?d00001 diff --git a/optimize/tests/data/windows/x1rosen b/optimize/tests/data/windows/x1rosen new file mode 100644 index 0000000000000000000000000000000000000000..6a252337951e15f2cdb896f8a4252b47fd521972 GIT binary patch literal 103 zcmey*n9R+<$iTo*0mLAhlYt9JGB7ASDvxJ!U|?8KHFK>rkmgXoe?xU&ER_fzY>sMQEf0m5J(#wb>S2T(is)}YsDP^Cgv9k literal 0 HcmV?d00001 From 70a5cd6c297d174d63c51d1cd34d1c13bf872b2d Mon Sep 17 00:00:00 2001 From: Deanna Morgan Date: Thu, 1 Oct 2020 10:30:06 +0100 Subject: [PATCH 63/77] restructure and addition of windows data --- timeseries/tests/data/windows/fit/AR1 | Bin 0 -> 109 bytes timeseries/tests/data/windows/fit/AR2 | Bin 0 -> 973 bytes timeseries/tests/data/windows/fit/AR3 | Bin 0 -> 949 bytes timeseries/tests/data/windows/fit/AR4 | Bin 0 -> 981 bytes timeseries/tests/data/windows/fit/ARCH1 | Bin 0 -> 152 bytes timeseries/tests/data/windows/fit/ARCH2 | Bin 0 -> 104 bytes timeseries/tests/data/windows/fit/ARIMA1 | Bin 0 -> 327 bytes timeseries/tests/data/windows/fit/ARIMA2 | Bin 0 -> 1479 bytes timeseries/tests/data/windows/fit/ARIMA3 | Bin 0 -> 1535 bytes timeseries/tests/data/windows/fit/ARIMA4 | Bin 0 -> 1519 bytes timeseries/tests/data/windows/fit/ARMA1 | Bin 0 -> 299 bytes timeseries/tests/data/windows/fit/ARMA2 | Bin 0 -> 1483 bytes timeseries/tests/data/windows/fit/ARMA3 | Bin 0 -> 1451 bytes timeseries/tests/data/windows/fit/ARMA4 | Bin 0 -> 1547 bytes timeseries/tests/data/windows/fit/SARIMA1 | Bin 0 -> 591 bytes timeseries/tests/data/windows/fit/SARIMA2 | Bin 0 -> 1879 bytes timeseries/tests/data/windows/fit/SARIMA3 | Bin 0 -> 2607 bytes timeseries/tests/data/windows/fit/SARIMA4 | Bin 0 -> 1967 bytes timeseries/tests/data/windows/fit/nonStat | Bin 0 -> 96 bytes timeseries/tests/data/windows/pred/predAR1 | Bin 0 -> 8016 bytes timeseries/tests/data/windows/pred/predAR2 | Bin 0 -> 8016 bytes timeseries/tests/data/windows/pred/predAR3 | Bin 0 -> 8016 bytes timeseries/tests/data/windows/pred/predAR4 | Bin 0 -> 8016 bytes timeseries/tests/data/windows/pred/predARCH1 | Bin 0 -> 8016 bytes timeseries/tests/data/windows/pred/predARCH2 | Bin 0 -> 8016 bytes timeseries/tests/data/windows/pred/predARIMA1 | Bin 0 -> 8016 bytes timeseries/tests/data/windows/pred/predARIMA2 | Bin 0 -> 8016 bytes timeseries/tests/data/windows/pred/predARIMA3 | Bin 0 -> 8016 bytes timeseries/tests/data/windows/pred/predARIMA4 | Bin 0 -> 8016 bytes timeseries/tests/data/windows/pred/predARMA1 | Bin 0 -> 8016 bytes timeseries/tests/data/windows/pred/predARMA2 | Bin 0 -> 8016 bytes timeseries/tests/data/windows/pred/predARMA3 | Bin 0 -> 8016 bytes timeseries/tests/data/windows/pred/predARMA4 | Bin 0 -> 8016 bytes timeseries/tests/data/windows/pred/predSARIMA1 | Bin 0 -> 8016 bytes timeseries/tests/data/windows/pred/predSARIMA2 | Bin 0 -> 8016 bytes timeseries/tests/data/windows/pred/predSARIMA3 | Bin 0 -> 8016 bytes timeseries/tests/data/windows/pred/predSARIMA4 | Bin 0 -> 8016 bytes 37 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 timeseries/tests/data/windows/fit/AR1 create mode 100644 timeseries/tests/data/windows/fit/AR2 create mode 100644 timeseries/tests/data/windows/fit/AR3 create mode 100644 timeseries/tests/data/windows/fit/AR4 create mode 100644 timeseries/tests/data/windows/fit/ARCH1 create mode 100644 timeseries/tests/data/windows/fit/ARCH2 create mode 100644 timeseries/tests/data/windows/fit/ARIMA1 create mode 100644 timeseries/tests/data/windows/fit/ARIMA2 create mode 100644 timeseries/tests/data/windows/fit/ARIMA3 create mode 100644 timeseries/tests/data/windows/fit/ARIMA4 create mode 100644 timeseries/tests/data/windows/fit/ARMA1 create mode 100644 timeseries/tests/data/windows/fit/ARMA2 create mode 100644 timeseries/tests/data/windows/fit/ARMA3 create mode 100644 timeseries/tests/data/windows/fit/ARMA4 create mode 100644 timeseries/tests/data/windows/fit/SARIMA1 create mode 100644 timeseries/tests/data/windows/fit/SARIMA2 create mode 100644 timeseries/tests/data/windows/fit/SARIMA3 create mode 100644 timeseries/tests/data/windows/fit/SARIMA4 create mode 100644 timeseries/tests/data/windows/fit/nonStat create mode 100644 timeseries/tests/data/windows/pred/predAR1 create mode 100644 timeseries/tests/data/windows/pred/predAR2 create mode 100644 timeseries/tests/data/windows/pred/predAR3 create mode 100644 timeseries/tests/data/windows/pred/predAR4 create mode 100644 timeseries/tests/data/windows/pred/predARCH1 create mode 100644 timeseries/tests/data/windows/pred/predARCH2 create mode 100644 timeseries/tests/data/windows/pred/predARIMA1 create mode 100644 timeseries/tests/data/windows/pred/predARIMA2 create mode 100644 timeseries/tests/data/windows/pred/predARIMA3 create mode 100644 timeseries/tests/data/windows/pred/predARIMA4 create mode 100644 timeseries/tests/data/windows/pred/predARMA1 create mode 100644 timeseries/tests/data/windows/pred/predARMA2 create mode 100644 timeseries/tests/data/windows/pred/predARMA3 create mode 100644 timeseries/tests/data/windows/pred/predARMA4 create mode 100644 timeseries/tests/data/windows/pred/predSARIMA1 create mode 100644 timeseries/tests/data/windows/pred/predSARIMA2 create mode 100644 timeseries/tests/data/windows/pred/predSARIMA3 create mode 100644 timeseries/tests/data/windows/pred/predSARIMA4 diff --git a/timeseries/tests/data/windows/fit/AR1 b/timeseries/tests/data/windows/fit/AR1 new file mode 100644 index 0000000000000000000000000000000000000000..47e50d0e669681291e42401606ddf4cd063fc7a3 GIT binary patch literal 109 zcmey*n9R+<%D})-kXV$MTg*^W6c1)Gq*mmoLzx9oIwvu`7^o7Ynv;PMNY=meO0#=Z UwVxBn28lsPR2dKt3?$ke09nx)_y7O^ literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/windows/fit/AR2 b/timeseries/tests/data/windows/fit/AR2 new file mode 100644 index 0000000000000000000000000000000000000000..bdd39803c93efc1ef970cf6efda351c528379d43 GIT binary patch literal 973 zcmey*n9R+<%D})-kXV$MTg*^W6c1)Gq*mmoLzx9oIwvu`7^o7Ynv=l{Ncyj@T)6sa zox`oZJCE0YuiL-sfR?Y{g=YJPZ>j=)U2XRB^4ANb>J-}VyChvzlia*N^sn=-X48uO z6)K-JWNnM~Ka|LF-OXFF|D9W%ho4)k{h~c}uQt?8+VA)@F5rD{$Nnbg7m30r%l8*B zpW;hh-)+A`Rgh0b0%)$eeC&xG6ZdaUJlS;VSCf6%z5PbTXR_?qssB-%($cV>Y3YO` z_v}0Er!w4BZdu)DpR`4*D_S*TznH;P(ac*_`z`Woj1nYE>=&oX9Er6nwm&Us+``SQ zzF)-d==Ou&1@?31Ch_|15=Lt5!+de*f}0g?;6f_S5(0cp4gY+6PXky0}n0*Dul=RQ&@`uY|0BcX zTRwTo`==Co{Hbkj-`}!AW_zlx_x`Hyd}~@)w(pObf4_*!%EtcPq~|qWYSsHce>oVd zVw=1F{wJyFpH&*|?UD=Bwkf3TH}snpm?mGdpOb+RDb_d{jDWn+xEow?$H~A9j1}B5 T2$BVZ2Q3a@ngdEpv^xL*Lv*w; literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/windows/fit/AR3 b/timeseries/tests/data/windows/fit/AR3 new file mode 100644 index 0000000000000000000000000000000000000000..1e9da8eb53befa8ff0af2de986d2ac74b5c9096d GIT binary patch literal 949 zcmey*n9R+<%D})-kXV$MTg*^W6c1)Gq*mmoLzx9oIwvu`7^o7Ynv=m4Nb*UGb|q>x zIv94%Kd?c4n!WLoXJuNVZT2@dTmN|xRt0s@X4J6ClQ^H+6sGqt8}+dL8z^UU|Re z-r2VQs-4fj%@V!#EE&lQAG=N1-@o_J)SaJF?3b}@Elc1|w6BYAXl>t6zF+X_g7aT4 zOt9Z{?8(y|8Fl+Z%?;mKU#s1(z#Y7%fV;u|aPpD#Lodqq$DBJ@tX12z-}9TW6kCju z{Tp*#%dh8J?4v}M&0sNXwdZV{B<2&|xc|oS>Q8-%J^SCS`^fkAYTN!p^RF!GVoTWn z@6by2lQVnv>odGDTk;{*e#Z35Z}G7$`?n_BR*0tvSoD7UevBk+?1mumz+2D#ZP6j4mtYF3*NR;7# Sz@?|{Jq`>FT8!%ZD;xl_55sBz literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/windows/fit/AR4 b/timeseries/tests/data/windows/fit/AR4 new file mode 100644 index 0000000000000000000000000000000000000000..51d34ef1c906d728c95d7fb06c3488794c8c3579 GIT binary patch literal 981 zcmey*n9R+<%D})-kXV$MTg*^W6c1)Gq*mmoLzx9oIwvu`7^o7Ynv=l{NN(BwmWfAq zq5YxH38Jap^XwgWPJFyAu+_d{*~|TRwoI^(Typ>8f!jUyFD0KWnLBHmebdF?yI;MU zXzygX_iIu}yZyrL>^%ILi|t!aE@Y|_n_+*VNcv>V#5wl;=ZfaLcuccD@r%ReW>Sa! znu~8&zU-fF-`{2;VwJGa{?fkx#`(`C+m~9ZOFJ4*wofVK{+IN)z<#0U1782u-2KMW zH0Me6%(0)6_Mgd1YnJ_?znH3+<)_Wqy2nsd$xzC8tuQRh|b-Ae1`oi=j@0{X({_BH~K&O zP`kpuSGcC-?AsQ5?t{;sN9)C8yynp)U|0SFbKez<#7X1`+NS__Mnd_OF!}b)lCAyb{ z92h3IP5&ax;c#hkm`vO?b_b=8=PYhEsW=#v1y$_OQFS=|T-r5atAWE6<5qE=SF#Rn ztHq)M?oYR`e-YJY6*|MdCveW@ms^(DFG@_hp=sT0&k2k(P}~`f#@(QcJ5B}`V5|_1 oL69njhQ&X^=XN_VI22#17wU3gI3RH8X?u?YLxUEh`u++B0FVX0JOBUy literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/windows/fit/ARCH1 b/timeseries/tests/data/windows/fit/ARCH1 new file mode 100644 index 0000000000000000000000000000000000000000..79bfe798a4e2326f0c3b9eb47ae4578450f73b94 GIT binary patch literal 152 zcmey*n9R+gF& CSu)N5 literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/windows/fit/ARCH2 b/timeseries/tests/data/windows/fit/ARCH2 new file mode 100644 index 0000000000000000000000000000000000000000..27d88e56f34996351482193759456494dc3f4240 GIT binary patch literal 104 zcmey*n9R+gx>OzQ(+x*@^=qUXtpZGR_B zOmvv>W>ajgZxT=xq-VR!G3M_-&hO_~)-r!%(1ZPvm7bd|JYMVv1_wyGfT0i=7C;7w V^AqGC5P&oOGq5u-0tFsJWdHy^W@P{X literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/windows/fit/ARIMA2 b/timeseries/tests/data/windows/fit/ARIMA2 new file mode 100644 index 0000000000000000000000000000000000000000..56af381433491ea7c11489d2e11cfde42d6acfae GIT binary patch literal 1479 zcmeH{>oeSU6vlrpnU+YDMAl?Fb&F<2nWa<*Gl!vxRD;q*5|v%+XKiflH7ts?yIHrb zEW7AsqC#96WmBm(M(UtVnOr1tX{SPWH8B;J#I0!=m9AU=hQ8=|@qM0W&Uen6&zbYw zgteWpBnX1f6f!>$5n|zqbw`jA9(&XBH&w8yTqavY2uTqqnIJ{t4Z|0b$tRLIEHS|o za@fhnfonr82w&sd6|kH&N2m2>jl0h#X*a{+~2?L&yc~hi?&lghcIA-dh|D%XCNxY z6QOOs9_aerTjqCvDTK-XgE93^CFr2=jgA@QA>Hjgs-{5&`p1CB6KCSmzks##COL(>?qI1@S%Ej*EI>q zF1EfDOv%GDms)e<>;>3A+R(0}YGBwUu4Z9B6AdeR#zJg0o|e0F-!xZ2vm$Kotf>Z+ z9ZX3UR}Cb$wMBAL4p(dFL|Uy3EXgb0?w~<^3)Apbh!}#N_(gXQXp!lEb5(XG6HPr9 zqqK4bRx6YhgYi;S6b6OA?!pH$xNtrqUx3HvJl?gd%fx)z$IjfBC-BcLP!@!@5!?T` z{#&>#2oocfEod8Q3!MdFYV2)PK+n2Llw zixtq`-TBU!CdJUUKQw7(ycpz);u(8|8k3vCyd8BRa5&d#HP4X&?T(jovpX|TVxBoo zUSPpMa7V|;iVUT zRGm};=`}BoHNIK^=@YJ5T_fqBJsR;>-M#nW!0yis;a{ZS4W)y(zE}yZw31~Tt`v9o zQ%fBNrLc2K)Yr!;LcL)`$9qx$rjL00e){MXMjp{ka=l_;)r{dCBB+GGXS@iK!o5(o5VfTdMQYALG<)f8J8t( uxjej&$6uS-)>}G`#&c|D#Kb2A##?MOYhh^({965Mo}0X|z6}pIvHt_`&t|v)$hwsbs$XTTJs|Dby|0=7v{f!8h~=edpd~W3Pg%Ys!@nkOWIy zj}X)G6R!um*PJe3!D`sviT;b2ewSs)vSr}Nvg0&|)w3WSn~^)+W8(u=-eW7jN-#@G z3tBI{fXzsRGH zQ+KXGV7B{qRd5?#e+`a+KGb6TAK!lq9|7rWsr(y60@BmIzD7wv2HFbom71WE95Nzv zMpAbP$f(O~?ot-SxwxNh>=l9w`o%G*&v0^k} z^q8Rn90gJPEm{*G;84@F!c7huJEGX;c`BSZ*$`fuRtN*iFp&|u3^dycF*Q{L0W+~> zAMI4aeG50Qn5IlHBr>@`%>&_;+^Yh5F(~BCK6R=>SRI`)lext}shQesVnPnio5M!@ zq|2abN%Z`FrVNHurBYF=2%?U!v8akv5Nqs2_2U^(ZyGwGUMhfktDE_j4MJ$06S7YI5y0wTy@!DIiHM(L$mvphA+X-E4>JeV*KSaCPB>(^b literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/windows/fit/ARIMA4 b/timeseries/tests/data/windows/fit/ARIMA4 new file mode 100644 index 0000000000000000000000000000000000000000..e025ec69405753ae3d72f86a1cb3d89a3259943e GIT binary patch literal 1519 zcmeH{*;A8e6vjVUri2tl5@8$)vNV>Zb_$in6n(!6Scw6lBC=Ts1OW{tWT8+xYzd1D z0fJ#|6;PJ4lqv-VSv(PtLK&tSp&-NN6bc1~DB=KvPWnaZP5**k^jy5pbI#0rZhq$+ z)t+!Nri)=1m&Ien@G$`|-IRnm5U>AaTQPIF2*tL~Vo|qK(Y)?h!vlpFTKyw9*3KEwnYY^T!Bi+} z=&4QcG?F2fr`wH(E*Fp@-)}3X{0wTSWNgP@%trZOIN4pD2J{&U&0ypjB9r>+i>?Tf ztmfvDv6~n<1{>E8y-tODAD-y0aSVYp(!48YG8<7=9Rrmg1)>r1K&qo#DYPv3`sJ(A z;P@ZYJLUOO6f?Be5^5@iJ9WM@S|bH;bmghL87~ta^!-BH&L~4plAiOW7uc|_B!goT zADk^*59dfcpmVRK(n^&9Va(5qHtv>zt)mTxT$PV9`^{VCev$xP;d+*1Sc00<-Z%7? z+=Nqh{B^oj8QR#%+w`gcwp_t$PdHlT$Sun;>Ow%Rg@p{h6yD}R2g>J!Z=O{GRET|4}k)kHKL0snGSFrCTB~D~1 zLq9m&FLiikiTGxZ9t{<<(92GG{-~x8@+Nted7ex`TE<^uiIus?H^Wyf_(BNQ!5&ef z(E{KHu)>y{;vqvfuU<(LqN(#WnoDP;ATDtkO_azWeB8V-nUM`7ZeeI@cM1AU!+(O> z9tNX3_DqAp%dmJ$s`O~ef&|*}j`TGdoId9@RJ~ISpEA>KIZYIyjo$r=@1!@NVq&Ji zHt#yh`E|O$yQ2y$=Cnfu@!(f?P4$*(__1>8UtQtv;tO00XG)O~zEjT)msc)7k}AuAWh%m((q=U*)U8^Q9c Aq5uE@ literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/windows/fit/ARMA1 b/timeseries/tests/data/windows/fit/ARMA1 new file mode 100644 index 0000000000000000000000000000000000000000..8e859e20d0be527b14978df8d3d1d0e3d4626833 GIT binary patch literal 299 zcmey*n9R+<$-ux+kXV$MTg*^W6c1)Gq*mmoLzx9ox)4g|B&HWL6r~ntrZA)ymp~W= zMX4$ADVfP7K%+p$aWb#~$qy6m9xXdn?@&B%+S#s*o%`=Rcx5#ER`!0!1rz<0c5k=; z4pRWq0@B0?Bw>m<8JK`PbS<0=%pj#8@Sw#3OmjeKiFOBwo;b&q7Kiv+96D?6>hh|B zL_vD~C3>!0Dbi%$-==VTrE>ay)yMA^rQWXE4-5d1bOA#l(Dy(Fi1QQV5D@qYasvqb GX9NH`OkUyu literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/windows/fit/ARMA2 b/timeseries/tests/data/windows/fit/ARMA2 new file mode 100644 index 0000000000000000000000000000000000000000..17e5259853f8d29b06f214656b18cb2349cc2b1b GIT binary patch literal 1483 zcmeH{>rc~X6vuynI%_o$7=<;UsEAA^+6fyouy2(O1}Jjb7&B2Q&;c3H(pID(FriXG zDYr^X=?b(mPy=a#3^IhO3^$nEX6P=;B~|CbC|(c>jL@~n-s~^fi+x`_-|u7>Z+q97#&ghHN0av&(GF<{f$Y4@(u86ZXi@`B%f^@Y20fpBmIYjdJ(! z6X8$gu{Tu77f|N&`T(V8>*oe^A9W&WIs%|))n z?emzL7LF=3H5k^t)1sbIK>LE4roJPGzn|{r_q<&6irb#J8Oy*g^^ADbUIxpkGs$#v zHo6K1)m926oZK4bdZ|&G<)hstMK#6P+Ztv)bfpl(zhrF4D6WH}!C@q1q#8c;*&kaM zCgG~I$xmdZM0k?-44wZKnw_b8iH$-`43yjZ&Z-cf6(M+@Qw#AI2J*}HayX{(Iwgh@ zL@q6D^V?E|xtUX+9=uTsTRDg7Jy?Oku<^=<-DQ}OHVut+i?LAWDpeWeFl_aqsj@>5 zxhdeVez6v|fvm}vTNlt!@ufgXlVaV;mN;HxAzT6vJ#n481huQ4T<~oTm^KT}E4p$_ zRtM1+2I6sQNk+StuY<rD++x7SHuwp{*1Z`o*(>`;)}na^%rX*x17*#LlmGw# literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/windows/fit/ARMA3 b/timeseries/tests/data/windows/fit/ARMA3 new file mode 100644 index 0000000000000000000000000000000000000000..63248b87111f324ca322ed7eee8c054b047e7518 GIT binary patch literal 1451 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 BU6=p> literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/windows/fit/SARIMA4 b/timeseries/tests/data/windows/fit/SARIMA4 new file mode 100644 index 0000000000000000000000000000000000000000..2c0bfa6f4f3512fdff387f456fa34835d6977afb GIT binary patch literal 1967 zcmeH|`#02S9LFEUGAo)Pnp`5+BE*E&rspZ!DN0B#rD8HdZes@PZZH^G{@@igjy6Cn?m>ZKG&eTlPDQv*+wzus`hc!{_z+e7>LO`+UxMf1dNq$oMM) zB>(`M$zg_ZK@`V#)&l{t5rI;dEoG#Xu2Q;58OjXg0!{!om<0m3QF9KP6TtFi1^Y*V z2u^SyYu0kfV33&xQ>jT#t2E$6;#dH9NT* zN3efE@Ai~~`7rI8_p7HS@p#X!FK3U&B|(Fz{ii)dHLyP3Imu5W1M{?^KRN1#Lt5cz zh8OZ;Opp2((teqnYDzrFMRghdMr1}Q9RzQwPC)*4J@ z>(WxpG-#QoD->g4Q=ZSLHI>L1;2rdAQbXwMAzjtvPdMwd(=l~5b!7iU=j-1LreWGu z8*xFkI-$8Ct0zH85nb3Cxm@m?1#*tg-nMYu5Ghn9y9pEY(3F_Qq5hDB!!%#>&(>VV zrgxw8UN#@b2MSF4svG<9AEajFnw+5kE0X`q_urC_0v3>}{98l{kRh}4Qc_4?a{VMZ zsjQF!7S3j?Han`t5CF7YDOs0-0PkJAGB=%|05$UK^!eLEyvq;0?3Q=xR=^$0cS0x>MX5dC{GTIA@gQI?KY4;#)#?-Tg$Qpf@HvF@lH- zU)JA!TbYJL*;S{z>Hg@Z_qtuhxrr?zJ_@YyhjPjx<7QV3hRt@!y90%+c^)2P`H z0e4Pr(2g6AggfN-m{N2za4)r@ueY-VzpqU`)2kuEeYE50sWri{bvZO)_h#Y+g2K$v zz;ygr{{cgZA;6~b2k2}|^1u6>Jx>XX-~wTTO&2{MSLeyYdJ`^ec=l_TB`pu1_iXa9 zP)Wkv8rh$G%67t=!GuS>`ceFJo3dUhrx=cnvSMFb3$Tk>+a>0D0d8JZer8#E9&Wwc zkxEl3$KLtBh>bxST;uV;MWwC^5-x2kGrwZny`mFtDUZB5>clYG z=XOr{rKLpfx%2lOdKM$|&WpD0wNy0Z%G$C!UZ0rEUhR-Lu@XH@K9xTzT!jiYEglZH zHATh!mG7!I86x@Gp`)q7cD#0KM9$#AGko6u{_7(42qqsM$w0C}Bya_8AdGx_05^ci z^<}bHzOMhcZXld2lax+LMqIL{Boi!I@=`k=OTwuovq=DEq?Df>`1z8lxp|+z2Y_cH zV~k@F1q#Y*cY-R@13@j&(Z?tD%P_Sk2V|D-lZ^Rj6 literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/windows/pred/predAR1 b/timeseries/tests/data/windows/pred/predAR1 new file mode 100644 index 0000000000000000000000000000000000000000..94f75b72533eb89bf8a1773053bb4db3a172a977 GIT binary patch literal 8016 zcmeIy{Xf$Q00;0P#9|(rSYd|6=5ds#dF-XLJmvXmous3I&!(Kb;oMc8xNuJ8OS9kx!{qX(#@cI1y{^28otAM^os_^fC=vBeRj}o-+ zN0Y&_7C(l2d=ux6c077uH?+mvNWRA{5Ipc_Lg=A;iS_hIS688HG_OI~1Q*l)B)wK8 z;++I5ecoQ7>y;+pzsL`IX$is>AZc@c)}?om12$z$k&X-RXj}c1HM?(KO)^O`uyJlR z3BZz}ckj^l&d@-Ehn*B_0zdKe$9k2c)+OGM+<*^y!wyY7>$gt7A!^IIk=+ml1IJ<1vk zZTCMQiajWtw${zfcD3+>Hr^P*InmGqUWVpNaaB>Eb*xZis7;(~?c4vcNaB%TRozU0 zXm+e{&!1wA^_z8$a1T~%>T7(-JmGD9cx@C-zYgKGhF!T;~yruf!Mhl7l(!3xd2*t7-F>A$YDXCaHP+z=vh2+?ttkivh=!n0qS^QLFHW z2E>e{#D9jfCzoG*k0U`*;l=E1Y(vfJpJ69 z1*tbSm*aIA6^6@WvK_pbS*RrDaP2cVKBgjJqdSVBgNeobLY*`GylmFzhCE*{mJYoe z$&wkQnH3}GbQ0a>60B9e5NhBzJyD|$WziJJ`)|+3S?OTla=EWI)P-IrG5X5!SFtKfOZ)+Tlx z(EhtErPPx*SXua(vN751ul07a&%0?&T;ZkKRVCRtskxp#pqESkSpG=yxOxwjs6lI! zA{3@~mQ#jdTB>u^5KDuT`J-2ju1)@y`IMW*RbPl<#}ghqlF7~7SdSME)&9ao2{6Q< ztA|h=?ST`ob~P?`uWa*9m0@nsl1Tll;H|Bu4%W{(rH;B!go}sOz_4tEyLB}+CHmoL zqq$}iRZ>`DBm&<~lHs39ZP2bNpRmNHpq!E7%PF@~Lnxs;J?jMYcHfyId`7QV1>}r! z*g*^SzNC#pw8ORj9Ndi-2Jw;-p$@+1oF(uA z3QQ8UgrLPml;vWs5RwrUjE18a0?9O(a%Y`b6~;)Ym&nNO(rPlXk48c88)$h#|el3f}_e2W_>-9_>mkMwjUVV61 z8pG{>EE*c^2u5AEZGY%b5HHJL_1kF%-`jn<5(7ohdqpm7F$~u=w(eE71Ur|M+_By! zAa(Z+ji(L6s~I82`@Rf{p7i7IY!KjD64BhUh2lwO7)1cS@Hj+K0leD)_bb zH3TSC#*jRMB29eyzBY!M?W*Ks;RGXf^_O*vDJ+Ig?%m@+F&gHO*ae3BW3%53P86`v zN?tohFF>6<(OwrtARXA6WO_lMkywOU>nN^$r8;RiL*Y1hvyKiE9F8>RHG5DLnxB^o zpoohr7`=Lr!u_X@Vq4t>G@Va*(x?&pw65n=oLFyjBk~4T$eK zon2_sGFUp_vG1Hukv`wgWrH1sp<32Cm_wn-F*eW2CQu#^Jy+5}Q4`tQvZ9Q_yYngg zBjP9=C$5E;^^0|{FRiy%GZ^<;dMN&4*p;7o!nBn^?l{~U^g#UYKxom*4gm!0khg`JP$l8Hby z`_#cqH-_DIVYvy>1VQ?NuS;VYjwG&DXNY~ia?ud3ep!o;FAkMgHZYW4aMy?ZOE4z6 z6&v!afVpdzuPrv$Li*zQ{`@}$6eWdMrG7_XQR9&GsENX?*YJtc5wS16{&&xPN|5{G zW&4Z>ieJk8CT=MOoUL4Qq4;}-EHljy3I&5QTM-m0-s_N<5>Qe`FzbuLfwqkdS3K3V zotr4snoHFQjtn2ntL~_-plI0tR?JnAkMeiDoW?{x0+hP9{Y5@PY#y{UiG0+^;}1>p zVe#clBX>nU8g^Qqo#aE>Ju=0I?8KnkBp>x3R!s4+BtbdFNA8x*Q+%}lJvOKpxjyIq h75A^Wf5rV>?(cGcm**cm|KRxt&j)!v`2X_3{{Zy#g{uGn literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/windows/pred/predAR3 b/timeseries/tests/data/windows/pred/predAR3 new file mode 100644 index 0000000000000000000000000000000000000000..c1ad311677c7cd22aed3fa580fdda0ad24f70c8c GIT binary patch literal 8016 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 DIf$Bp literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/windows/pred/predARCH1 b/timeseries/tests/data/windows/pred/predARCH1 new file mode 100644 index 0000000000000000000000000000000000000000..a3aa6418f3dfde05b6e7b272528e2958cc32a180 GIT binary patch literal 8016 zcmeyTz{vmtFPOm;!+fp$w@bA|9K+luykrX(aXheo;|c#&B94{IuiuvK7IE}c37@YC zq=lAC9GwBAH{QC)QVXPS?%#F121rZ1f9M6`ul#Ol1fp$j&oKnid@OokzCf4+nD+7& z2h;PPL+H0xAau!P2rUBTvp{JkD9t=dgTrk!93bgrG#p050aU(>ri0ORFq#fV^TB92 zFj@|bmII^Zz-T!zS`Lhs1Eb}@XgM%i4vdxqqvgP8IWSrdjFtnV<-lk;Fj@|bmII^Z Oz-T!zS`N^n8~_09QpPv{ literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/windows/pred/predARCH2 b/timeseries/tests/data/windows/pred/predARCH2 new file mode 100644 index 0000000000000000000000000000000000000000..598733c352412833a61bd0cf15cba1b3a1b87298 GIT binary patch literal 8016 zcmeIyAr3%L9LI5+&8BOLdkDq>T!1L5stHsI0>uGzMM{RhFbn-AytJ-hks?7N_m%jhQ* z&XD}3L9xiO;=T+!`8uz**`I|*u#U50vI*=(mu$G zF*=oK7&F*0x^=Kj(e1)0V~>5{lNY1UfdZ4tiBYKvhqZNKG~JlDy@~9p4s|G(-p456 zy-p!LAZIhKtnI@nM_N3Q)q_!`_ei|w9!4=qfpQx;?_U2)Kh#V1B4?Ih;?c~xS|xEV z%GmajJRjd{YMv!(x}GbvWNuS?oSTW4QkompL|vU;yNo#4s*$3JF6$d@9PzEbwIhLe z?0jL`Dk8ctfmRaNjV8HQ5PkIn>9NF6VL=7mZ;wBlM=i)Nx6yg3UNTbS)I}ERPG89| zbxW>1Ks`~lqmO#-Ye_rxdgka=YX11l9qMcUbPx4LAi0~m;njR6_1ehPZR)~Jt%ItZ z{l@mUo$h7s>c7k8MV*KlnNq{-j+$hSxn*qcVcj!!u8wdp-_`T)?OW?*=V9q7bG7U- zGf{FegD?7-Em3Z!^$g!%Zsb;s@_Ee~uI3~+TH|5+p{?BSzxh1q2RxmQ>w)Cmp3{la2lzR)Bz3bTZ{|ClY#X5rs+ zjyQw-_ks5S?}7FU?g88bxCih*fDS+hpaakW=m2y8IshGj4nPN>1JD8J0CWI403Co1 OKnI`$(1HK81AhQ!yzj99 literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/windows/pred/predARIMA2 b/timeseries/tests/data/windows/pred/predARIMA2 new file mode 100644 index 0000000000000000000000000000000000000000..04524dff20419690d3026ca871c9f563ce9ff5b9 GIT binary patch literal 8016 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 P8DoNjH>WQ=Wy1dey!G+= literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/windows/pred/predARIMA3 b/timeseries/tests/data/windows/pred/predARIMA3 new file mode 100644 index 0000000000000000000000000000000000000000..f2d50d79089439016b936809b2808794fd4f65d7 GIT binary patch literal 8016 zcmeI%=~Go@90zb>zhr=@+<-8MQ3?bUfglCH0z!ibT}UVv0ga_An@lbU=R6Oxy3|NS z;snJOu_!wz3^;m0aBxD3!OSoT6pXy6qcxB$oqOTG=s9nmH)qZ~=Q-yyzirV>q@uVU zsVNtg0*gr>%e55ImmiTbxIu3@ehRIcLQ^>SDP5kH5ar0%(+F31v4?Pn~ z+>cZE+goa;4ifCnzw2z&!NWsObvhuJ;!pef)?8bP#*+Cj_j^-ls%`Xh@Z%v9XF5zv zIY`fWK8>B^!By6kw263(b?;BQkjNvvvPkb>0!69GDOubh4z?jqaebo{Js6Mn_P1~0F=%akKO%T+Ie*23@dEE@?HI{V{>^fO@6(4g18p2J8Kch}XChebqL zr+koy!Tr?Yi@F4{K{VDlB^`!)L|u`FJVZumbH~CsJiCAI!O0kkAKwf7EGLD)vgk^I zNic_5iSCly48i%RQ5|j#fyLM+d1V(xWcZ!h=%3TD{)d9q&#i^?7;dnb`<6%J&5EkZ zCJsFx*>%PAP;5DRJtvXyu&N#!aM?^Tua@z=x}PG~?e;B^Jw>09^{TTkIQU4DCpjaE z>imZ)p=JcmjsrdCT!jDDAyaD8N-#1Je06d><-k?Rel7ht9`gZOpte@ zPU~VLhtul$e{9=G;Up<CPe8)Vx!w`z}TIHG1pK`E;~fX%tG$DSpvxX|GsF$38Fl_Zq_aTj#FP*e>|!`THyV z2;upu(XxJh!G}0>YB*5v(agW&pztwflbNURp;so~s_+plIc%%&QK9Ff^pWRYU##%q zoe!lCO{ddJAOG&nQu;_~cU1at57@2rab~7M>Er#Q%RVkGXiqDAycl2hF`TjN!*9jM zwiO?4D?S!`mwiO9`0!;um=ERy%m<4fEPhb1_`&ikmS1sT`4y`_SpC6))gP=rXZ1O& j&sqP9^{-g}iuHF{f0y-l+5CgeKiK?(%?JNa^TGcB@-Q8p literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/windows/pred/predARIMA4 b/timeseries/tests/data/windows/pred/predARIMA4 new file mode 100644 index 0000000000000000000000000000000000000000..91ef0dc63747652054c0cc8ec205ec88e9a0b636 GIT binary patch literal 8016 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 literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/windows/pred/predARMA3 b/timeseries/tests/data/windows/pred/predARMA3 new file mode 100644 index 0000000000000000000000000000000000000000..99e7b5e3209f35db749f1c298a65e3ff3c340730 GIT binary patch literal 8016 zcmeI%`%hD67zc1fJ5af7AZ#N9$c8Koxy%)$u#7LD4mF*cEY2;fTttz{C4h+J+}`3K zLmFVl!Lo?~AzTC7N-4-dRwc|rMF$E~7%s??adDE0%B8Ti(Ci2QgylWIoSdKDlQ;R~ zdA`rH;<3@f^!&Ac^<~kcm$@iT2&gK5He9tRU_>&~?JZ}K)?@gn<1H4hy2N*bTNorq zJJ!ge2tvm~U^&dV_FlR%&f)r#k;r@{hm&?=yNZi>WGrsz ztErX4@h3~|fQZMsv~w6#|~V?lzsu z@@OP&dLcxPvtLagEc#52x%jf^rCNeQ)!3l}+X#+c>mCvjg7B5shI;;FaXNozrHUi) zD-88iS?8cE;`0V&mw+4kb1i-OJf1k)Y9g8$_}Zs_Tc_c`OwXKo_<)0jBB+5&U@)9J zHgcqchdSx!jG5g8L+(+fY%Y&(Nv~QK%pxMJKt5{xUeqeK+&WJ%Wvfjo^WfoD$>=m3 z2cMjk&V^n9#rbt?-1`JO9^BvR(=H%0)OP=O5d>M=913)g1>84irJG^|6l8b#DqrO> ze|%x^>1iJQ$;W#V+jxZ6%0nl0JoI6V`eXtJ%a#43E`9=Rngl0}Lcs6qgRQ%*3C=f^ zr*o}50Dyzv&nO{JY%OcH@-)p%C5 zg2gG3Ce~nJplR$JvXv4@oZ~dz0R+rmg>6*}0h_d9Z7{0f_NyPUVv4G-N6l$9B$SX z4%872ueg30rORdErMy0!ca&f-%h934c;8}4y;fwrx9wjz`o9-0AkAie@QR8<$sc># zpZ61Z?Q3Q7TnRkx&L#y#3ph8r+n>3@Vy=RZ*ZHt$(zzG*|HxoDOqy@MhafXcAAi^A zL$XjUI&AcjW)Z1kj6PDGnUsa+GRK4v}Ee0Yzo`RMt3&4-iON1)lq;tM`J%|3$7J|4f|BiZc3+3X{b z`k+3jkN@fi^+D?gtsexme$f7k_E$Ne{T0n0G=IoJ^9Rl6G@o;z`JB$L==_S#ujqW2 X&Ufj2m+n94{)6s6=zj43zaRV;jgIO= literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/windows/pred/predARMA4 b/timeseries/tests/data/windows/pred/predARMA4 new file mode 100644 index 0000000000000000000000000000000000000000..1b6b1d33288313dacada4e8ed701b9c88e7fd587 GIT binary patch literal 8016 zcmW-mcRbeb_s8v#S=o{mm4uSj(6Kj#QY51k840Dh-{VH2lq6duLMci@3w^SMM3EF( z4GM`O@jKsN|9Heb-q&@V^LjmB=USBGW%=K~FYN#SgXLMZ9<@o3hUsMwXyHi;7QMz@ z_$6p)XwvfANs};IY|0sDO2H}_X{U?sBs_flxl>w)0;iK(4o6o}uvU5EeEl&JK56C% zJpV(1N=S$ON*)^AcFDar`apuznZd(zrwF*y7H4N@LBopmC9=(l6u`2`=#g0hR`JYH z)yWh*5FU^}H$cHFzp$?UEDDnAj($0GjRv{bdbt_>1mw3)iyP*V(D`_>ugr*q)9lw~ zdvytLklbVQFNTDS)$@`)88k>LLXuXujJs zlMqV51@ql1_ioZKcQ3LohlL42-+syp>e67C5;7a2M?wid-W?C=1rz_BS$G%oicN|poWC0`|4W)^9b1C(y$nCg@80o zEB3PsBsABRa_PKfz#Od;#A#2!x>1cL-dhATT1a5KjJ(Jc{q>y$j?t}a&Kc40WtT(Jk|hn#p0_;PtVqMV&QRZ*_ehY5 zPik1$O@rme8FhbC0!;gye!RXwLip?G-vQVc^CHvR(n?5puwI$6l^~&HP(d){8=iM( z`|Vrv1YDZ1V8#rRU>Ld0(drfrE%Ey9Y;F`pW`Ff(Z6Lt?>zH(JMpy`Pddno3Y0wSUx$HJFB%CGGJG+_(>n zzlMpDBn(f^R1&vH$kn9nIt*!ucInLak)$E$u-pT0MG9Qy<>I*IDG;-4?c8I5=fbx1 zNUe|rqg^d--I>_ux#Q|ThiDkn>^^exG!04)VLpfWDEMtrw1w+60ot|sFJ`WvV(CrAi%tKwwG^~@K6;^-<8?!DN0&J+9ko6hGK zsqZK_I=w&cQXvCmrMA=*@K6w}`d@WS84dFEFaMBY8obCBj~ks7n00+0NO*yt+a}PJ zevyWyr_FO!1P$7={jym%ND#}q{qiv@32x4P<}FJ!d}JOeunH!j|B}$p`>10P|4QF> z7gKOtS84C`J_fwx5N0l7zaHTevPc-g=kt<3!~ltNr*UVm2Q8v1cbR~r7^T9ILMyR)r0kP)pbCB)dm9gdR86n=467Dy_?H>k_2U01J=Gr zsE2kp3h%9>z-1_Wa1r(P;T?_vJ?vZmb?j*?Vksz#Z4CSm>s|22l3VN#?31D8`qu&| z(1}yFlsbiVv0L`?`vw}UOV6}UXA;23=ixn%`sC@C;g+!u`@H(DRMvVLbbNgkuAu%- zZZGtk+Cjs66|pELAJ$>!NuD513WoM>O!H@r0NQoO+l}os?6_2P^z9S_gi_op<>P4h zE)?OljUa)%tsT^Ye!;S>MP3Q(W3*d9*a*M>W$eH@FFW*sZjBOWGwhe>*3-&P6j+EJ z5ex?kQp*zzr%*p%_J`M4qrQp;I6GwP(9n3%bz32x=TW5!u?y@J9BK_wmEA`|BFmHt z+ieo|QYp`SoJbgR7M722!}m8%KDp|F^&Z7jD2D!{l5ni#oFxUjma=xudNSbY-JdBl zX9>vRVgxqfdColP&^$rXusY+^8uuc6-iac+suj4;fyKs6@l0^!nA~GsK*IN|iF|um z3iQ2P^4pd(pp;`l?$SvDni5(M2w~k2Ek1?prZn*H|K6x&MMBpq4wYimkI>4Bs|y1**k^0)cId4EBs(AkKPf{=R;l5nHpQOGM{eZB0S8(5ehQ#`xm$;>9;A}kYdR&SLkms#3bccp3 zJ_jT-1JDN#3;LJuqd~B8@4TKK1wL|{hiF#RH_>sefOlA*rPghAcSult&n7*E&&Bxs z;)km;3E4IwsY#n?sA}9eYKpq$Rk^bAT0aA7$Nef2mq>73De|W!k$^RvmVa$bu-^?+ z18CH{CD*J0P3(J))t9XPYBRw*zv;po9~zoN<8!#M{$7{fPI-rZKCq|#L1;Vy>GdJA zkzzEE&r7xQuurSEC+Nvu%}*1BA0ik8fTgAaebgl}79&^h~fQmRl2W(R!x$ z3+iC{x;w1F_&Jsfn<`Qkv5v~3Uc5zqDf+XQxw4T2m&ZEKJ8@ro9&OIZG9)49d{h1T zuN1V=0^8a`Xt*7Frm?A!0-Lf8PU5Hs&)tdFqZRo2LW!}FKQwd)$XPfDQ(*tKNh^$- zh7c1*)U7xQ&VFl`jmo6KWH3NNxSN74HF=xMkUR8F%*|UKqG8!>UYGh#0@MQoSDODo zpWkxz)2j&zRy>ec(BeaV`Xd(_B|<}?uaL4a^3T_bQr`j}0@~j;FpZEGb=WwQr)e5y z6@?p@W1aQeN{pRSV*)RI)#}tS0u+1S6iOkN#HNh2=VIOcHQgWPjOW+!ic|WcEd}rI z2bM+W;yzzIvziP2@RAtIofdp<^V?iAS|v2Zn~!a~qe8%M>mAl^UIeh4>(?z9(@?!= zoYb?NhBt?Vw1Z;^$U4|=x%W5&7I_U9cq>T|7`2vukLNY&d}-hGaSCc=hl)=|QsDl5 zF8f0p0estcukr67fx*KxAwBW?ln2L#o{>;2u<9p`=aFIF+Z%`eO}+g1q&OXYSRwS? zaTXdf!fu_6pCf@)`+|bbS?rG;%p(n&$YE0K$AYJ@AA+p4?SJ9MP~_aSVc9>} z=WJ<@XEo7>Btizx8+zcnQ2;OHt~znsE^)r|1Gg1XW00ZnPg-kXNM&4{zA^uF@Jux_9O|?e*Hm7*C;q5 z)s`YsOMvZ_n15R8G)ywGSelU=f@OA;_91_W==O4Pp+ER?)J#S-qu$#{O}k*7-R3>_ z-s?T;6Oa1ixh@*aMkN&Y8&j~ZJLiF)Fah%MRvSwH;ko85lUwUc!oKokcX#xi7}b+k z&x8^n%3ON*3t#{7td;AUY+TR6G-0-jfV+PUy6+>$pU)lV?W0LZjpuF{M=sJA7TM;F zoZ6ua*W*;Mua+msOEn>PCg{|jOr;^Hllj_oTj2lS-B`Hen&!-;d&D~beR z!`oL+kI=y5evG9Y>pZRbj?ZqaPm5>qF_~DGQM$=TE~1XvcsYu+Ab+`pojT--KF#ek zbHCG=g!fl{&Odle!f!KI*;gDi_}P5wW5arI*>zO>68ghX_th0D zRiGN6F5^H!c)r`~jEy9esmpt`(-efjdu4zva`HD{x&lFN9aKBi}qM zerA^tLPGlfvc-MK&0LYsE9*2!c>BD5!W+4W^ZoKve$=g$!HY+71JOsjPkrUb^V#>d zO+`h5hOpb}i6sw7s9C7sH!Gqblkcv%Xb=rOnkkX&m_O4AwdW5Iz@5gYTrq*1drOk{#S8(?X_KD9s6XuO>79G3 zDCijBd3b?=oM-emHo=*OrjpYi9Z>g6@7w>U#Y;h@R`&&I~?=@~KCTzZA z-94E}Y{Xo%`^*l<$zL>V6%Mrw<)Y!&g8qe<`RK2lkG~~mFratAX4vv62~Ew`>JL^? z;P^bD_;3OZ37+x%Pu|j?_|T+f5jm}|K6`Br7ZbVQO#fX4ag58qHn zHYNVLFO@;S(}g;zhgf%p(Az$N`K7SSMw5!iyk8^cEQ1$p$LO6h$vhdHqhT@QL3I)U~2a#p-ol!hOdP4%82r){+{ z_dC;t^_h5M;t2AdM#busq&5n^K2p*;Q%1ts7t#+-L=uqET=wS~a^yx6nPmQ0oXh;X z;b*&_2C>urB3GXgAZ7Dtu|AxFFRp%R?#PSwN*XO{8Z@LWitQcVjoc8B^jjbKLo-C_ zi9B)w%L)J3VQJ*U?}-6a1qBUb4=OGRA~ct+ogtZyo-8AV>!5r`lkDFLL=uyXs?~uhQVSY~7tg zYbF#qc#WSCCn2-tkaY+4v*UFy7Hi~U)jES2n@{zZiMQva==GiFS`s4R0kba}omW03O`5{v!Y`_E? zQ-I>5xc-F5Y)Nh=>}bhnOyYS?Z9eRF_!9{qXM?xiiJ)Mak3j1!5gMij#C$?sDNrbu zuNK4qA5-AU7XcbnB3i@Mao%uRtXbI_xvu|bgn$U@4^OtdS^Rnu^n^Ez>0o`{6?gjV z=Zf6jH_-FkfP%tlLB;H|6s&ZL7fpYU96IZ@$)hnGQn~79zCR%0Z{yLJ0|U6uH6P4| zFh6uSnh5>Hxr(pXWTKTc`fBsvwGD4bu!x$_RZk9gIq|FgFQ$}s!*rfL_*aVIDdNO-0*L(5Yt0JtEB*+%|pIEL;!=_7o!y zzm6wCzGC8?i#XupE|H&r4I5~=9l_2#(Yd*6XLuu z-H-Dehk#g>RvK<-6$(21L*8CLv{nG;QlBh&Tw8HIuu-X%%bf+E!%_9M@LR0EQ<)Bz z^+||3{MlLU2=WER^I{J^&yw_o{p$_Te+{FoHefxg^RVpNBTmDqpG#)rO!QS@iU0nW z_i`urBt@=MFp+kq<5M;bs^QD7O~UpDtb1H(%m9 zyyFrkK8Hj3tP^+XNd`nG9THIiT<2@Y8Z+#V;lC$>I`v54O_f@69d%jEj4h=R=aIsq zF$=jN$N`oM$w@fZxv6n2!5#av{9;*I69pb=M~ zJ~yPNrd`2)hzQ_V_8I%`*y~?s-B2F~mks)vBfnTHt$tVhih%3-1z{qg$nTk)L4v|0 zJo(hV&832d-Z1&ol~+hG`*YuqLcQkpEO%eE4|8U+Z_=ba=Gl>s$cwy8@UinTzlPki zS>uhaQ#kTV0kvs-1n21-Wdc6vckv0&a)j4XAoTa&<2^B~}?@}}3*2T@<# zI>P_0!gIc}sk;Vq{EfX`E*4HCta~xQJ%jU9qnO**8_|~+$7;2&>^rfO7tPyrl+KIX2Kh;5Z5!C&bD#B_Uxk6b<;Z$Ba=3qyo!NfTRTr;*^ zl5rrxKw{&{lU@|C*>B@`vy29ZDA~*BuHrnWdD;6yRvOy;EBG?KDL6KtFLuwDfPNOq z^Uc`bk?wtLRcRDtG_1T4dx3%xZhp=-Bh(E+@2YSH1x;>lwkeqVEd|OCaih;KynV`3 zZiMr%yI=I%aDQG7f608Eig`!h&W$~m23mLf>ZE_z|IVL>hw%3)3gW@R>J;QD{VG3* z^NRt&mfCyB<83N8Sp2YW+#ChCw2+T@Etl^+7)OIl%;{Qx3(Q{!1hz+G4svW%lrH>; zd1*?)N_sEmyT*F`ZK#7;td|EW*|83b13%hho&j1kkEf7=+y|#vpXp*gdu#7ug8g17 z)N=)pUt60DJgy^edHhj`{j$J-&MPJs4>5me&n+3u9l$!ZIqq`=xz1d0MBe%*=I-wP z*3X#ZeT(KfMv`b)Y_0e%iG4f1g<({K`QfRCYJQv>1*3DkeAD>7_V3k7<)SH&-OeRz zF-3xTRo9&W|xYCVZa_8Pvmiuz$o$9vz}! zO|X%tH};Xj)~N7Z`1>Lm@#ppY1oZw`btYsb=0ll~@4Be(;-AjFJbn>#{#?VtnV$?u z=jSh3%B0}2ah1MPA_XZw_5WJo>$^Id4Kr}xApR05X@okSn5`M|h=jC5!wZ7H3HX$9 zOenyPh5)}AvCeDgqor3o>Lr+9cuZk9Ru<<|XO>G_7;OVw_ zDTH~{F)8Dq=urZMJx=(qPom((g2&KrR^*Mvnb2v>yVvUtxJ6N4(ruzuN0`_TiIW!9 z2k`uNO*y>5I-AN>8wx_M+c()@z`(jpchfU^9E<$Kwy9ipA{-_bf`{k+65w&vNx7FB z_sd_ezEm82%TwQ28v9l^`;2W7evVqPx?XQ|iTgy3K!Ud@vW< zv=4O}R^r_5XGi$NJkHfe#8r^fuw=^eP%|DyU?Jgkt{9 zO?pth8t*}_Rc|_alZN77T88JvnXsX%eJLvs=cQ%~9%_oXk9^(9g;+l#$JH%6(O>oD z`42OCF!%THHtY<*dG2U}VNo6hY6oUb3ae=7-+lGRrW^u%9h|O=p-#KK5_#XHhV!L5 z^{QdiAy%<3$+vD|&TFs?bD2T^p*=VKiml$sR`e@< zb_nx7&jLZpRvSz7Dy; z!Qg(*vfp@qrJo~|zY$O@o7Jf*MnTLSlN!+gygyt1C(0S;IFFt;y^ciR5pt`_-hfEi`N=#EK?Q;yQ(iUM;M@4<`509PSb@CU8z?9`D;s zH;66MT#NnxD9|dzjDXD64X+&WeBYM0MKKOyo(TB1VHfV}+WC?PE9`MjU$kFKP6hiQ z^Qo=BD(Xt0jYGQ&-k)}B|LDOv$i=5hf#oIGpA~PORiZ9cm>dqA%*8z8qgE7v`s(Lv z9ALT;{D%DCOJ5PJlAJD7c-hBA}&bi#ny=UJ)VG#s<8;B>Yfz~8|P++kX$|0y7S#ktfBe7|gSQD!5yNvkP-{X;xckaWu&Wm0 z_MF7#4h`#c#9t3nG}zJlyzN?S^ke5#A@)vbm6gnc#fGOu=lh8JXRv-9P49>%rOuzs zwQ1(l#H7Qf7BYWQ+$kqn=4}xZ(~r8siLVY!PSN$cfgqA?_TFX0<&UNVi3?KyMMRz7 zngHU#TC0S3tw2ielcf?-8#CkSbzMcBRFh`~oww)AQRTC4YQ1hFU00pA(7e*l)~C-- z(%g3HD>cy2LsgVbQB6^7{Y2{+&2Ps>sgXM;s5#@z(tNhAsQf_Z6T{5KsUKtgeZcbNto)3cZPiO;+1SWdXY{9MM2yw1$BG7C&hjhX4HVa8l#*6`=)uV&fCy}{M; z>uPw`aJO)SxW~9@+$e4WH;Jp_ZsqR&$6Y+{;QF#pC7=8B%|Z74;61>5AUz1@0L}rN z1Na|64?qt<4?qt<4?qt<4?qt<4?qt<4?qt<4?qt<4?qt<4?qt<4?qw6uO9ddLb(WN literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/windows/pred/predSARIMA2 b/timeseries/tests/data/windows/pred/predSARIMA2 new file mode 100644 index 0000000000000000000000000000000000000000..5c352f4b8ef46a6ba8ac7cced5e732721e74ab2c GIT binary patch literal 8016 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 Date: Thu, 1 Oct 2020 10:31:19 +0100 Subject: [PATCH 64/77] separate windows and linux folders --- optimize/tests/test.t | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/optimize/tests/test.t b/optimize/tests/test.t index 105849dd..e6c027b7 100644 --- a/optimize/tests/test.t +++ b/optimize/tests/test.t @@ -4,22 +4,13 @@ \l optimize/optim.q // Function for the capturing of expected errors -failingTest:{[function;data;applyType;expectedError] - applyType:$[applyType;@;.]; - failureFunction:{[err;ret](`TestFailing;ret;err~ret)}[expectedError;]; - functionReturn:applyType[function;data;failureFunction]; - $[`TestFailing~first functionReturn;last functionReturn;0b] - } +failingTest:{[function;data;applyType;expectedError] applyType:$[applyType;@;.]; failureFunction:{[err;ret](`TestFailing;ret;err~ret)}[expectedError;]; functionReturn:applyType[function;data;failureFunction]; $[`TestFailing~first functionReturn;last functionReturn;0b] } // Load in data saved as golden copy for this analysis // Load files -fileList:`quadx0`quadx1`sinex1`multix0`multix1`multix1Gtol`multiargs0`multiargs1`x0rosen`x1rosen -{load hsym`$":optimize/tests/data/",string x}each fileList; - --1"Warning: These tests may cause varying results for Linux vs Windows users"; os:$[.z.o like "w*";"windows/";"linux/"]; -fileList2:`rosenx0`rosenx1 -{load hsym`$":optimize/tests/data/",x,string y}[os]each fileList2; +fileList:`quadx0`quadx1`sinex1`multix0`multix1`multix1Gtol`multiargs0`multiargs1`x0rosen`x1rosen`rosenx0`rosenx1 +{load hsym`$":optimize/tests/data/",x,string y}[os]each fileList; -1"Testing examples of optimization functionality expected to fail"; c1c2Fail:"When evaluating Wolfe conditions the following must hold 0 < c1 < c2 < 1" From 751552083f05eee6ab44d51d8f89ffbb621cdea9 Mon Sep 17 00:00:00 2001 From: Dianeod Date: Thu, 1 Oct 2020 15:19:01 +0100 Subject: [PATCH 65/77] updated optim tests for windows/linux --- optimize/tests/data/{linux => }/multiargs0 | Bin optimize/tests/data/{linux => }/multiargs1 | Bin optimize/tests/data/{linux => }/multix0 | Bin optimize/tests/data/{linux => }/multix1 | Bin optimize/tests/data/{linux => }/multix1Gtol | Bin optimize/tests/data/{linux => }/quadx0 | Bin optimize/tests/data/{linux => }/quadx1 | Bin optimize/tests/data/{linux => }/sinex1 | Bin optimize/tests/data/windows/multiargs0 | Bin 77 -> 0 bytes optimize/tests/data/windows/multiargs1 | Bin 77 -> 0 bytes optimize/tests/data/windows/multix0 | Bin 77 -> 0 bytes optimize/tests/data/windows/multix1 | Bin 77 -> 0 bytes optimize/tests/data/windows/multix1Gtol | Bin 77 -> 0 bytes optimize/tests/data/windows/quadx0 | Bin 69 -> 0 bytes optimize/tests/data/windows/quadx1 | Bin 69 -> 0 bytes optimize/tests/data/windows/sinex1 | Bin 69 -> 0 bytes optimize/tests/data/windows/x0rosen | Bin 96 -> 0 bytes optimize/tests/data/windows/x1rosen | Bin 103 -> 0 bytes optimize/tests/data/{linux => }/x0rosen | Bin optimize/tests/data/{linux => }/x1rosen | Bin optimize/tests/test.t | 15 ++++++++++++--- 21 files changed, 12 insertions(+), 3 deletions(-) rename optimize/tests/data/{linux => }/multiargs0 (100%) rename optimize/tests/data/{linux => }/multiargs1 (100%) rename optimize/tests/data/{linux => }/multix0 (100%) rename optimize/tests/data/{linux => }/multix1 (100%) rename optimize/tests/data/{linux => }/multix1Gtol (100%) rename optimize/tests/data/{linux => }/quadx0 (100%) rename optimize/tests/data/{linux => }/quadx1 (100%) rename optimize/tests/data/{linux => }/sinex1 (100%) delete mode 100644 optimize/tests/data/windows/multiargs0 delete mode 100644 optimize/tests/data/windows/multiargs1 delete mode 100644 optimize/tests/data/windows/multix0 delete mode 100644 optimize/tests/data/windows/multix1 delete mode 100644 optimize/tests/data/windows/multix1Gtol delete mode 100644 optimize/tests/data/windows/quadx0 delete mode 100644 optimize/tests/data/windows/quadx1 delete mode 100644 optimize/tests/data/windows/sinex1 delete mode 100644 optimize/tests/data/windows/x0rosen delete mode 100644 optimize/tests/data/windows/x1rosen rename optimize/tests/data/{linux => }/x0rosen (100%) rename optimize/tests/data/{linux => }/x1rosen (100%) diff --git a/optimize/tests/data/linux/multiargs0 b/optimize/tests/data/multiargs0 similarity index 100% rename from optimize/tests/data/linux/multiargs0 rename to optimize/tests/data/multiargs0 diff --git a/optimize/tests/data/linux/multiargs1 b/optimize/tests/data/multiargs1 similarity index 100% rename from optimize/tests/data/linux/multiargs1 rename to optimize/tests/data/multiargs1 diff --git a/optimize/tests/data/linux/multix0 b/optimize/tests/data/multix0 similarity index 100% rename from optimize/tests/data/linux/multix0 rename to optimize/tests/data/multix0 diff --git a/optimize/tests/data/linux/multix1 b/optimize/tests/data/multix1 similarity index 100% rename from optimize/tests/data/linux/multix1 rename to optimize/tests/data/multix1 diff --git a/optimize/tests/data/linux/multix1Gtol b/optimize/tests/data/multix1Gtol similarity index 100% rename from optimize/tests/data/linux/multix1Gtol rename to optimize/tests/data/multix1Gtol diff --git a/optimize/tests/data/linux/quadx0 b/optimize/tests/data/quadx0 similarity index 100% rename from optimize/tests/data/linux/quadx0 rename to optimize/tests/data/quadx0 diff --git a/optimize/tests/data/linux/quadx1 b/optimize/tests/data/quadx1 similarity index 100% rename from optimize/tests/data/linux/quadx1 rename to optimize/tests/data/quadx1 diff --git a/optimize/tests/data/linux/sinex1 b/optimize/tests/data/sinex1 similarity index 100% rename from optimize/tests/data/linux/sinex1 rename to optimize/tests/data/sinex1 diff --git a/optimize/tests/data/windows/multiargs0 b/optimize/tests/data/windows/multiargs0 deleted file mode 100644 index 548c7a602ed9d03d7f1862c587430d2ca518f4db..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 77 zcmey*n9R+<%)r1<5tf)!%#c=^mmHK@!jM;*>sgXo1QZ9!aWXIg$?)256O9=@*x&uO YqxsW+W{2;~UYh`gMI3&z04Wdv09wWtRR910 diff --git a/optimize/tests/data/windows/multiargs1 b/optimize/tests/data/windows/multiargs1 deleted file mode 100644 index d95f1584bc8fea236a727bfe883f70dcd568bde9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 77 zcmey*n9R+<%)r1<5tf)!%#c=^mmHK@!jM;*>sgXo1QZ9!aWXIg$qB3N&TaYs-d^b@ X^ASM?7KiUr7c_ywA`U-6YCr%0FT@k# diff --git a/optimize/tests/data/windows/multix0 b/optimize/tests/data/windows/multix0 deleted file mode 100644 index 5b476839eb47b868242791db4466689b2e475f36..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 77 zcmey*n9R+<%)r1<5tf)!%#c=^mmHK@!jM;*>sgXo1QZ9!aWXIg$?)256O9=@*x&uO ZqxsW+W{2;}&kxvKF4eO8$pVxG0RV1b7xDlA diff --git a/optimize/tests/data/windows/multix1 b/optimize/tests/data/windows/multix1 deleted file mode 100644 index ac78fac5346662431502b0a6baaaeb14ecc0e29d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 77 zcmey*n9R+<%)r1<5tf)!%#c=^mmHK@!jM;*>sgXo1QZ9!aWXIg$qB3N&TaYs-d^b@ Y^ASM?7KiT(3gYc~U#Qsq1gQZ507YvRfB*mh diff --git a/optimize/tests/data/windows/multix1Gtol b/optimize/tests/data/windows/multix1Gtol deleted file mode 100644 index eebbaedc9830de71050fae1a425b3676e25adffc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 77 zcmey*n9R+<%)r1<5tf)!%#c=^mmHK@!jM;*>sgXo1QZ9!aWXIgNe}L4zyJS#Z=d$) a$G`vonH|16EZ>+p>COV1pR7Px5C8zDWg2Jz diff --git a/optimize/tests/data/windows/quadx0 b/optimize/tests/data/windows/quadx0 deleted file mode 100644 index c5aa4b257616ea3ab3b1b8d6c79dc71013bdd918..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 69 zcmey*n9R+<%)r1<5tf)!%#c=^mmHK@!jM;*>sgXo1QZ9!aWXIh$+2X Mz<=N;6Oao60Ehe&m;e9( diff --git a/optimize/tests/data/windows/quadx1 b/optimize/tests/data/windows/quadx1 deleted file mode 100644 index 78ddfdd8de66f5b98ddad4b5a7fffe04d581c82f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 69 zcmey*n9R+<%)r1<5tf)!%#c=^mmHK@!jM;*>sgXo1QZ9!aWXIh$!+(p{|5s5??5&X L2psqck^=z%q5BfC diff --git a/optimize/tests/data/windows/sinex1 b/optimize/tests/data/windows/sinex1 deleted file mode 100644 index 3f0d08ce16567029aab1d111036c0f479e11b46c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 69 zcmey*n9R+<%)r1<5tf)!%#c=^mmHK@!jM;*>sgXo1QZ9!aWXIh$-vh?Ztwi7>hS%( Q!u$XK|G(e=6C?)$0MP3e{r~^~ diff --git a/optimize/tests/data/windows/x0rosen b/optimize/tests/data/windows/x0rosen deleted file mode 100644 index 6c1b1c0c59587c8048826b914f700847e5c2e67f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 96 ncmeyTz|H^yTws!cfdfi2LTOGY%?hPipfnpA4O0)(4^s~SdeQ-9 diff --git a/optimize/tests/data/windows/x1rosen b/optimize/tests/data/windows/x1rosen deleted file mode 100644 index 6a252337951e15f2cdb896f8a4252b47fd521972..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 103 zcmey*n9R+<$iTo*0mLAhlYt9JGB7ASDvxJ!U|?8KHFK>rkmgXoe?xU&ER_fzY>sMQEf0m5J(#wb>S2T(is)}YsDP^Cgv9k diff --git a/optimize/tests/data/linux/x0rosen b/optimize/tests/data/x0rosen similarity index 100% rename from optimize/tests/data/linux/x0rosen rename to optimize/tests/data/x0rosen diff --git a/optimize/tests/data/linux/x1rosen b/optimize/tests/data/x1rosen similarity index 100% rename from optimize/tests/data/linux/x1rosen rename to optimize/tests/data/x1rosen diff --git a/optimize/tests/test.t b/optimize/tests/test.t index e6c027b7..51e12f15 100644 --- a/optimize/tests/test.t +++ b/optimize/tests/test.t @@ -4,13 +4,22 @@ \l optimize/optim.q // Function for the capturing of expected errors -failingTest:{[function;data;applyType;expectedError] applyType:$[applyType;@;.]; failureFunction:{[err;ret](`TestFailing;ret;err~ret)}[expectedError;]; functionReturn:applyType[function;data;failureFunction]; $[`TestFailing~first functionReturn;last functionReturn;0b] } +failingTest:{[function;data;applyType;expectedError] + applyType:$[applyType;@;.]; + failureFunction:{[err;ret](`TestFailing;ret;err~ret)}[expectedError;]; + functionReturn:applyType[function;data;failureFunction]; + $[`TestFailing~first functionReturn;last functionReturn;0b] + } // Load in data saved as golden copy for this analysis // Load files +fileList:`quadx0`quadx1`sinex1`multix0`multix1`multix1Gtol`multiargs0`multiargs1`x0rosen`x1rosen +{load hsym`$":optimize/tests/data/",string x}each fileList; + +-1"Warning: These tests may cause varying results for Linux vs Windows users"; os:$[.z.o like "w*";"windows/";"linux/"]; -fileList:`quadx0`quadx1`sinex1`multix0`multix1`multix1Gtol`multiargs0`multiargs1`x0rosen`x1rosen`rosenx0`rosenx1 -{load hsym`$":optimize/tests/data/",x,string y}[os]each fileList; +fileListOS:`rosenx0`rosenx1 +{load hsym`$":optimize/tests/data/",x,string y}[os]each fileListOS; -1"Testing examples of optimization functionality expected to fail"; c1c2Fail:"When evaluating Wolfe conditions the following must hold 0 < c1 < c2 < 1" From 7b54583a94958244f26c32109dc5811f7dc6d571 Mon Sep 17 00:00:00 2001 From: Conor McCarthy Date: Mon, 5 Oct 2020 20:43:00 +0100 Subject: [PATCH 66/77] update to clustering code to add kmeans early stop and cleanup --- clust/aprop.q | 72 +++++---- clust/dbscan.q | 83 ++++++---- clust/hierarchical.q | 360 +++++++++++++++++++++++++------------------ clust/init.q | 20 ++- clust/kdtree.q | 15 +- clust/kmeans.q | 142 ++++++++++++----- clust/score.q | 35 ++--- clust/tests/clt.t | 22 +-- clust/tests/score.t | 33 ++-- clust/util.q | 30 ++-- 10 files changed, 479 insertions(+), 333 deletions(-) diff --git a/clust/aprop.q b/clust/aprop.q index 8e2c4ce8..a91a09be 100644 --- a/clust/aprop.q +++ b/clust/aprop.q @@ -5,44 +5,52 @@ // @kind function // @category clust // @fileoverview Fit affinity propagation algorithm -// @param data {float[][]} Points in `value flip` format -// @param df {fn} Distance function +// @param data {float[][]} Data in matrix format, each column is an individual datapoint +// @param df {symbol} Distance function name within '.ml.clust.df' // @param dmp {float} Damping coefficient -// @param diag {fn} Similarity matrix diagonal value function +// @param diag {func} Function applied to the similarity matrix diagonal // @param iter {dict} Max number of overall iterations and iterations -// without a change in clusters. (::) can be passed in where the defaults +// without a change in clusters. (::) can be passed in which case the defaults // of (`total`nochange!200 15) will be used // @return {dict} Data, input variables, clusters and exemplars // (`data`inputs`clt`exemplars) required for the predict method clust.ap.fit:{[data;df;dmp;diag;iter] + data:clust.i.floatConversion[data]; + defaultDict:`run`total`nochange!0 200 15; + if[iter~(::);iter:()!()]; + if[99h<>type iter;'"iter must be (::) or a dictionary"]; // update iteration dictionary with user changes - iter:(`run`maxrun`maxmatch!0 200 15),$[iter~(::);();iter]; + updDict:defaultDict,iter; // cluster data using AP algo - clust.i.runap["f"$data;df;dmp;diag;til count data 0;iter] + clust.i.runap[data;df;dmp;diag;til count data 0;updDict] } // @kind function // @category clust // @fileoverview Predict clusters using AP config -// @param data {float[][]} Points in `value flip` format +// @param data {float[][]} Data in matrix format, each column is an individual datapoint // @param cfg {dict} `data`inputs`clt`exemplars returned by clust.ap.fit // @return {long[]} List of predicted clusters clust.ap.predict:{[data;cfg] + data:clust.i.floatConversion[data]; if[-1~first cfg`clt; - '"Clusters = -1. AP fit did not converge. Not possible to predict clusters."]; + '"'.ml.clust.ap.fit' did not converge, all clusters returned -1. Cannot predict new data."]; // retrieve cluster centres from training data ex:cfg[`data][;distinct cfg`exemplars]; // predict testing data clusters - clust.i.appreddist[ex;cfg[`inputs]`df]each flip data + clust.i.appreddist[ex;cfg[`inputs]`df]each $[0h=type data;flip;enlist]data } + +// Utilities + // @kind function // @category private // @fileoverview Run affinity propagation algorithm -// @param data {float[][]} Points in `value flip` format -// @param df {fn} Distance function +// @param data {float[][]} Data in matrix format, each column is an individual datapoint +// @param df {symbol} Distance function name within '.ml.clust.df' // @param dmp {float} Damping coefficient -// @param diag {fn} Similarity matrix diagonal value function +// @param diag {func} Function applied to the similarity matrix diagonal // @param idxs {long[]} List of indicies to find distances for // @param iter {dict} Max number of overall iterations and iterations // without a change in clusters. (::) can be passed in where the defaults @@ -50,25 +58,26 @@ clust.ap.predict:{[data;cfg] // @return {long[]} List of clusters clust.i.runap:{[data;df;dmp;diag;idxs;iter] // check negative euclidean distance has been given - if[not df~`nege2dist;clust.i.err.ap[]]; + if[df<>`nege2dist;clust.i.err.ap[]]; // calculate distances, availability and responsibility info0:clust.i.apinit[data;df;diag;idxs]; // initialize exemplar matrix and convergence boolean - info0,:`emat`conv`iter!((count data 0;iter`maxmatch)#0b;0b;iter); - // run ap algo until maxrun or convergence + info0,:`emat`conv`iter!((count data 0;iter`nochange)#0b;0b;iter); + // run ap algo until maximum number of iterations completed or convergence info1:clust.i.apstop clust.i.apalgo[dmp]/info0; // return data, inputs, clusters and exemplars inputs:`df`dmp`diag`iter!(df;dmp;diag;iter); - clt:$[info1`conv;clust.i.reindex ex:info1`exemplars;count[data 0]#-1]; - `data`inputs`clt`exemplars!(data;inputs;clt;ex) + exemplars:info1`exemplars; + clt:$[info1`conv;clust.i.reindex exemplars;count[data 0]#-1]; + `data`inputs`clt`exemplars!(data;inputs;clt;exemplars) } // @kind function // @category private // @fileoverview Initialize matrices -// @param data {float[][]} Points in `value flip` format -// @param df {fn} Distance function -// @param diag {fn} Similarity matrix diagonal value function +// @param data {float[][]} Data in matrix format, each column is an individual datapoint +// @param df {symbol} Distance function name within '.ml.clust.df' +// @param diag {func} Function applied to the similarity matrix diagonal // @param idxs {long[]} List of point indices // @return {dict} Similarity, availability and responsibility matrices // and keys for matches and exemplars to be filled during further iterations @@ -98,7 +107,7 @@ clust.i.apalgo:{[dmp;info] // update `info` with new exemplars/matches info:update exemplars:ex,matches:?[exemplars~ex;matches+1;0]from info; // update iter dictionary - .[;(`iter;`run);+[1]]clust.i.apconv info + .[clust.i.apconv info;(`iter;`run);+[1]] } // @kind function @@ -114,20 +123,20 @@ clust.i.apconv:{[info] emat:info`emat; // existing exemplars ediag:0sum(se=iter`maxmatch)+0=se:sum each emat; - conv:$[(iter[`maxrun]=iter`run)|not[unconv]&sum[ediag]>0;1b;0b]]; + if[iter[`nochange]<=iter`run; + unconv:count[info`s]<>sum(se=iter`nochange)+0=se:sum each emat; + conv:$[(iter[`total]=iter`run)|not[unconv]&sum[ediag]>0;1b;0b]]; // return updated info info,`emat`conv!(emat;conv) } // @kind function // @category private -// @fileoverview Retrieve diagonal of square matrix +// @fileoverview Retrieve diagonal from a square matrix // @param m {any[][]} Square matrix -// @return {any[]} Diagonal +// @return {any[]} Matrix diagonal clust.i.diag:{[m] {x y}'[m;til count m] } @@ -172,16 +181,17 @@ clust.i.upda:{[dmp;info] // matches, iter dictionary, no_conv boolean and iter dict // @return {bool} Indicates whether to continue or stop running AP (1/0b) clust.i.apstop:{[info] - (info[`iter;`maxrun]>info[`iter]`run)¬ 1b~info`conv + (info[`iter;`total]>info[`iter]`run)¬ 1b~info`conv } // @kind function // @category private // @fileoverview Predict clusters using AP training exemplars -// @param ex {float[][]} Training cluster centres in `value flip` format -// @param df {fn} Distance function +// @param ex {float[][]} Training cluster centres in matrix format, +// each column is an individual datapoint +// @param df {symbol} Distance function name within '.ml.clust.df' // @param pt {float[]} Current data point // @return {long[]} Predicted clusters clust.i.appreddist:{[ex;df;pt] d?max d:clust.i.dists[ex;df;pt]each til count ex 0 - } \ No newline at end of file + } diff --git a/clust/dbscan.q b/clust/dbscan.q index abdadf1e..83fce63c 100644 --- a/clust/dbscan.q +++ b/clust/dbscan.q @@ -5,19 +5,22 @@ // @kind function // @category clust // @fileoverview Fit DBSCAN algorithm to data -// @param data {float[][]} Points in `value flip` format -// @param df {fn} Distance function -// @param minpts {long} Minimum number of points in epsilon radius +// @param data {float[][]} Data in matrix format, each column is an individual datapoint +// @param df {symbol} Distance function name within '.ml.clust.df' +// @param minpts {long} Minimum number of points with the epsilon radius // @param eps {float} Epsilon radius to search // @return {dict} Data, inputs, clusters and cluster table // (`data`inputs`clt`t) required for predict and update methods clust.dbscan.fit:{[data;df;minpts;eps] + data:clust.i.floatConversion[data]; // check distance function - if[not df in key clust.i.dd;clust.i.err.dd[]]; + if[not df in key clust.i.df;clust.i.err.df[]]; // create neighbourhood table - t:clust.i.nbhoodtab[data:"f"$data;df;minpts;eps;til count data 0]; + t:clust.i.nbhoodtab[data;df;minpts;eps;til count data 0]; + // apply the density based clustering algorithm over the neighbourhood table + t:{[t]any t`corepoint}clust.i.dbalgo/t; // find cluster for remaining points and return list of clusters - clt:-1^exec cluster from t:{[t]any t`corepoint}clust.i.dbalgo/t; + clt:-1^exec cluster from t; // return config dict `data`inputs`clt`t!(data;`df`minpts`eps!(df;minpts;eps);clt;t) } @@ -25,49 +28,62 @@ clust.dbscan.fit:{[data;df;minpts;eps] // @kind function // @category clust // @fileoverview Predict clusters using DBSCAN config -// @param data {float[][]} Points in `value flip` format +// @param data {float[][]} Data in matrix format, each column is an individual datapoint // @param cfg {dict} `data`df`minpts`eps`clt returned from DBSCAN // clustered training data // @return {long[]} List of predicted clusters clust.dbscan.predict:{[data;cfg] + data:clust.i.floatConversion[data]; // predict new clusters - -1^exec cluster from clust.i.dbscanpredict["f"$data;cfg] + -1^exec cluster from clust.i.dbscanpredict[data;cfg] } // @kind function // @category clust // @fileoverview Update DBSCAN config including new data points -// @param data {float[][]} Points in `value flip` format -// @param cfg {dict} `data`inputs`clt`nbh returned from DBSCAN -// clustered training data +// @param data {float[][]} Data in matrix format, each column is an individual datapoint +// @param cfg {dict} `data`inputs`clt`nbh returned from DBSCAN clustered training data // @return {dict} Updated model config clust.dbscan.update:{[data;cfg] + data:clust.i.floatConversion[data]; + // original data prior to addition of new points, with core points set + orig:update corepoint:1b from cfg[`t]where cluster<>0N; // predict new clusters - rtst:clust.i.dbscanpredict[data:"f"$data;cfg]; - rtrn:update corepoint:1b from cfg[`t]where cluster<>0N; - // include test points in training neighbourhood - rtrn:{[trn;tst;idx] - update nbhood:{x,'y}[nbhood;idx]from trn where i in tst`nbhood - }/[rtrn;rtst;count[rtrn]+til count rtst]; - // update clusters - t:{[t]any t`corepoint}.ml.clust.i.dbalgo/rtrn,rtst; - // start clusters from 0 + new:clust.i.dbscanpredict[data;cfg]; + // include new data points in training neighbourhood + orig:clust.i.updnbhood/[orig;new;count[orig]+til count new]; + // fit model with new data included to update model + t:{[t]any t`corepoint}.ml.clust.i.dbalgo/orig,new; + // reindex the clusters t:update{(d!til count d:distinct x)x}cluster from t where cluster<>0N; // return updated config cfg,`data`t`clt!(cfg[`data],'data;t;-1^exec cluster from t) } + +// Utilities + +// @kind function +// @category private +// @fileoverview Update the neighbourhood of a previously fit original dbscan model based on new data +// @param orig {tab} Original table of data with all points set as core points +// @param new {tab} Table generated from new data with the previously generated model +// @param idx {long[]} Indices used to update the neighbourhood of the original table +// @return {tab} Table with neighbourhood updated appropriately for the newly introduced data +clust.i.updnbhood:{[orig;new;idx] + update nbhood:{x,'y}[nbhood;idx]from orig where i in new`nbhood + } + // @kind function // @category private // @fileoverview Predict clusters using DBSCAN config -// @param data {float[][]} Points in `value flip` format -// @param cfg {dict} `data`inputs`clt returned from DBSCAN -// clustered training data -// @return {long[]} Cluster table +// @param data {float[][]} Data in matrix format, each column is an individual datapoint +// @param cfg {dict} `data`inputs`clt returned from DBSCAN clustered training data +// @return {tab} Cluster table clust.i.dbscanpredict:{[data;cfg] idx:count[cfg[`data]0]+til count data 0; // create neighbourhood table - t:clust.i.nbhoodtab[cfg[`data],'data;;;;idx]. cfg[`inputs]`df`minpts`eps; + t:clust.i.nbhoodtab[cfg[`data],'data;;;;idx]. cfg[`inputs;`df`minpts`eps]; // find which existing clusters new data belongs to update cluster:{x[`clt]first y}[cfg]each nbhood from t where corepoint } @@ -75,13 +91,12 @@ clust.i.dbscanpredict:{[data;cfg] // @kind function // @category private // @fileoverview Create neighbourhood table for points at indices provided -// @param data {float[][]} Points in `value flip` format -// @param df {fn} Distance function -// @param minpts {long} Minimum number of points in epsilon radius +// @param data {float[][]} Data in matrix format, each column is an individual datapoint +// @param df {symbol} Distance function name within '.ml.clust.df' +// @param minpts {long} Minimum number of points with the epsilon radius // @param eps {float} Epsilon radius to search // @param idx {long[]} Data indices to find neighbourhood for -// @return {table} Neighbourhood table with columns -// `nbhood`cluster`corepoint +// @return {table} Neighbourhood table with columns `nbhood`cluster`corepoint clust.i.nbhoodtab:{[data;df;minpts;eps;idx] // calculate distances and find all points which are not outliers nbhood:clust.i.nbhood[data;df;eps]each idx; @@ -92,13 +107,13 @@ clust.i.nbhoodtab:{[data;df;minpts;eps;idx] // @kind function // @category private // @fileoverview Find all points which are not outliers -// @param data {float[][]} Points in `value flip` format -// @param df {fn} Distance function +// @param data {float[][]} Data in matrix format, each column is an individual datapoint +// @param df {symbol} Distance function name within '.ml.clust.df' // @param eps {float} Epsilon radius to search // @param idx {long} Index of current point // @return {long[]} Indices of points within the epsilon radius clust.i.nbhood:{[data;df;eps;idx] - where eps>@[;idx;:;0w]clust.i.dd[df]data-data[;idx] + where eps>@[;idx;:;0w]clust.i.df[df]data-data[;idx] } // @kind function @@ -115,7 +130,7 @@ clust.i.dbalgo:{[t] // @category private // @fileoverview Find indices in each points neighborhood // @param t {table} Cluster info table -// @param idxs {long[]} Indices to search neighborhood of +// @param idxs {long[]} Indices to search the neighborhood of // @return {long[]} Indices in neighborhood clust.i.nbhoodidxs:{[t;idxs] nbh:exec nbhood from t[distinct idxs,raze t[idxs]`nbhood]where corepoint; diff --git a/clust/hierarchical.q b/clust/hierarchical.q index f0e7c8ea..1df3d122 100644 --- a/clust/hierarchical.q +++ b/clust/hierarchical.q @@ -5,32 +5,37 @@ // @kind function // @category clust // @fileoverview Fit CURE algorithm to data -// @param data {float[][]} Points in `value flip` format -// @param df {fn} Distance function +// @param data {float[][]} Data in matrix format, each column is an individual datapoint +// @param df {symbol} Distance function name within '.ml.clust.df' // @param n {long} Number of representative points per cluster // @param c {float} Compression factor for representative points // @return {dict} Data, input variables and dendrogram // (`data`inputs`dgram) required for predict method clust.cure.fit:{[data;df;n;c] - if[not df in key clust.i.dd;clust.i.err.dd[]]; - dgram:clust.hcscc[data:"f"$data;df;`cure;1;n;c;1b]; + data:clust.i.floatConversion[data]; + if[not df in key clust.i.df;clust.i.err.df[]]; + dgram:clust.i.hcscc[data;df;`cure;1;n;c;1b]; `data`inputs`dgram!(data;`df`n`c!(df;n;c);dgram) } // @kind function // @category clust -// @fileoverview Fit Hierarchical algorithms to data -// @param data {float[][]} Points in `value flip` format -// @param df {fn} Distance function -// @param lf {fn} Linkage function +// @fileoverview Fit Hierarchical algorithm to data +// @param data {float[][]} Data in matrix format, each column is an individual datapoint +// @param df {symbol} Distance function name within '.ml.clust.df' +// @param lf {symbol} Linkage function name within '.ml.clust.lf' // @return {dict} Data, input variables and dendrogram // (`data`inputs`dgram) required for predict method clust.hc.fit:{[data;df;lf] // check distance and linkage functions - if[not df in key clust.i.dd;clust.i.err.dd[]]; - if[not lf in key clust.i.ld;clust.i.err.ld[]]; - if[lf in`complete`average`ward;dgram:clust.hccaw[data:"f"$data;df;lf;2;1b]]; - if[lf in`single`centroid;dgram:clust.hcscc[data:"f"$data;df;lf;1;::;::;1b]]; + data:clust.i.floatConversion[data]; + if[not df in key clust.i.df;clust.i.err.df[]]; + dgram:$[lf in`complete`average`ward; + clust.i.hccaw[data;df;lf;2;1b]; + lf in`single`centroid; + clust.i.hcscc[data;df;lf;1;::;::;1b]; + clust.i.err.lf[] + ]; `data`inputs`dgram!(data;`df`lf!(df;lf);dgram) } @@ -39,7 +44,7 @@ clust.hc.fit:{[data;df;lf] // @fileoverview Convert CURE cfg to k clusters // @param cfg {dict} Output of .ml.clust.cure.fit // @param k {long} Number of clusters -// @return {dict} Updated config with clusters added +// @return {dict} Updated config with clusters labels added clust.cure.cutk:{[cfg;k] cfg,enlist[`clt]!enlist clust.i.cutdgram[cfg`dgram;k-1] } @@ -74,92 +79,39 @@ clust.cure.cutdist:{[cfg;dthresh] // @return {dict} Updated config with clusters added clust.hc.cutdist:clust.cure.cutdist -// @kind function -// @category private -// @fileoverview Predict clusters using hierarchical or CURE config -// @param ns {symbol} Namespace to use - `hc or `cure -// @param data {float[][]} Points in `value flip` format -// @param cfg {dict} dict output of .ml.clust.(cutk/cutdist) -// @return {long[]} List of predicted clusters -clust.i.hccpred:{[ns;data;cfg] - // check correct namespace and clusters given - if[not ns in`hc`cure; - '"Incorrect namespace - please use `hc or `cure"]; - if[not`clt in key cfg; - '"Clusters must be contained within cfg - please run .ml.clust.", - $[ns~`hc;"hc";"cure"],".(cutk/cutdist)"]; - // add namespace and linkage to config dictionary for cure - if[ns~`cure;cfg[`inputs],:`ns`lf!(ns;`single)]; - // recalc reppts for training clusters in asc order to ensure correct labels - reppt:clust.i.getrep[cfg]each gc kc:asc key gc:group cfg`clt; - // training indicies - idxs:til each c:count each reppt[;0]; - // return closest clusters to testing points - clust.i.predclosest["f"$data;cfg;reppt;c;idxs]each til count data 0 - } - -// @kind function -// @category private -// @fileoverview Recalculate representative points from training clusters -// @param cfg {dict} Dict output of .ml.clust.(cutk/cutdist) -// @param idxs {long[][]} Training data indices -// @return {float[][]} Training data points -clust.i.getrep:{[cfg;idxs] - $[cfg[`inputs;`ns]~`cure; - flip(clust.i.curerep . cfg[`inputs]`df`n`c)::; - cfg[`inputs;`lf]in`ward`centroid; - enlist each avg each;]cfg[`data][;idxs] - } - -// @kind function -// @category private -// @fileoverview Predict new cluster for given data point -// @param data {float[][]} Points in `value flip` format -// @param cfg {dict} dict output of .ml.clust.(cutk/cutdist) -// @param reppt {float[][]} Representative points in matrix format -// @param c {long} Number of points in training clusters -// @param cltidx {long[][]} Training data indices -// @param ptidx {long[][]} Index of current data point -// @return {long[]} List of predicted clusters -clust.i.predclosest:{[data;cfg;reppt;c;cltidx;ptidx] - // intra cluster distances - dist:.ml.clust.i.dists[;cfg[`inputs]`df;data[;ptidx];]'[reppt;cltidx]; - // apply linkage - dist:$[`ward~lf:cfg[`inputs]`lf; - 2*clust.i.ld[lf][1]'[c;dist]; - clust.i.ld[lf]each dist]; - // find closest cluster - dist?ndst:min dist - } - // @kind function // @category clust // @fileoverview Predict clusters using CURE config -// @param data {float[][]} Points in `value flip` format -// @param cfg {dict} `data`df`n`c`clt returned from -// .ml.clust.(cutk/cutdist) +// @param data {float[][]} Data in matrix format, each column is an individual datapoint +// @param cfg {dict} `data`df`n`c`clt returned from .ml.clust.(cutk/cutdist) // @return {long[]} List of predicted clusters -clust.cure.predict:clust.i.hccpred[`cure] +clust.cure.predict:{[data;cfg] + clust.i.hccpred[`cure;data;cfg] + } // @kind function // @category clust // @fileoverview Predict clusters using hierarchical config -// @param data {float[][]} Points in `value flip` format -// @param cfg {dict} `data`df`lf`clt returned from -// .ml.clust.(cutk/cutdist) +// @param data {float[][]} Data in matrix format, each column is an individual datapoint +// @param cfg {dict} `data`df`lf`clt returned from .ml.clust.(cutk/cutdist) // @return {long[]} List of predicted clusters -clust.hc.predict:clust.i.hccpred[`hc] +clust.hc.predict:{[data;cfg] + clust.i.hccpred[`hc;data;cfg] + } + + +// Utilities // @kind function -// @category clust +// @category private // @fileoverview Complete, Average, Ward (CAW) Linkage -// @param data {float[][]} Points in `value flip` format -// @param df {fn} Distance function -// @param lf {fn} Linkage function +// @param data {float[][]} Data in matrix format, each column is an individual datapoint +// @param df {symbol} Distance function name within '.ml.clust.df' +// @param lf {symbol} Linkage function name within '.ml.clust.lf' // @param k {long} Number of clusters // @param dgram {bool} Generate dendrogram or not (1b/0b) // @return {table/long[]} Dendrogram or list of clusters -clust.hccaw:{[data;df;lf;k;dgram] +clust.i.hccaw:{[data;df;lf;k;dgram] // check distance function for ward if[(not df~`e2dist)&lf=`ward;clust.i.err.ward[]]; // create initial cluster table @@ -173,28 +125,27 @@ clust.hccaw:{[data;df;lf;k;dgram] } // @kind function -// @category clust +// @category private // @fileoverview Single, Centroid, Cure (SCC) Linkage -// @param data {float[][]} Points in `value flip` format -// @param df {fn} Distance function +// @param data {float[][]} Data in matrix format, each column is an individual datapoint +// @param df {symbol} Distance function name within '.ml.clust.df' // @param lf {fn} Linkage function // @param k {long} Number of clusters // @param n {long} Number of representative points per cluster // @param c {float} Compression factor for representative points // @param dgram {bool} Generate dendrogram or not (1b/0b) // @return {long[]} List of clusters -clust.hcscc:{[data;df;lf;k;n;c;dgram] +clust.i.hcscc:{[data;df;lf;k;n;c;dgram] if[(not df in`edist`e2dist)&lf=`centroid;clust.i.err.centroid[]]; clustinit:clust.i.initscc[data;df;k;n;c;dgram]; r:(count[data 0]-k).[clust.i.algoscc[data;df;lf]]/clustinit; vres:select from r[1]where valid; $[dgram; clust.i.dgramidx last[r]0; - enlist @[;;:;]/[count[data 0]#0N;vres`points;til count vres]] + enlist @[;;:;]/[count[data 0]#0N;vres`points;til count vres] + ] } -// Utilities - // @kind function // @category private // @fileoverview Update dendrogram for CAW with final cluster of all the points @@ -206,11 +157,69 @@ clust.i.upddgram:{[t;m] m } +// @kind function +// @category private +// @fileoverview Predict clusters using hierarchical or CURE config +// @param ns {symbol} Namespace to use - `hc or `cure +// @param data {float[][]} Data in matrix format, each column is an individual datapoint +// @param cfg {dict} dict output of .ml.clust.(cutk/cutdist) +// @return {long[]} List of predicted clusters +clust.i.hccpred:{[ns;data;cfg] + data:clust.i.floatConversion[data]; + // check correct namespace and clusters given + if[not ns in`hc`cure;'"Incorrect namespace - please use `hc or `cure"]; + if[not`clt in key cfg; + '"Clusters must be contained within cfg - please run .ml.clust.", + $[ns~`hc;"hc";"cure"],".(cutk/cutdist)"]; + // add namespace and linkage to config dictionary for cure + if[ns~`cure;cfg[`inputs],:`ns`lf!(ns;`single)]; + // recalc reppts for training clusters in asc order to ensure correct labels + reppt:clust.i.getrep[cfg]each gc kc:asc key gc:group cfg`clt; + // training indicies + idxs:til each c:count each reppt[;0]; + // return closest clusters to testing points + clust.i.predclosest[data;cfg;reppt;c;idxs]each til count data 0 + } + +// @kind function +// @category private +// @fileoverview Recalculate representative points from training clusters +// @param cfg {dict} Dict output of .ml.clust.(cutk/cutdist) +// @param idxs {long[][]} Training data indices +// @return {float[][]} Training data points +clust.i.getrep:{[cfg;idxs] + $[cfg[`inputs;`ns]~`cure; + flip(clust.i.curerep . cfg[`inputs;`df`n`c])::; + cfg[`inputs;`lf]in`ward`centroid; + enlist each avg each;]cfg[`data][;idxs] + } + +// @kind function +// @category private +// @fileoverview Predict new cluster for given data point +// @param data {float[][]} Data in matrix format, each column is an individual datapoint +// @param cfg {dict} dict output of .ml.clust.(cutk/cutdist) +// @param reppt {float[][]} Representative points in matrix format +// @param c {long} Number of points in training clusters +// @param cltidx {long[][]} Training data indices +// @param ptidx {long[][]} Index of current data point +// @return {long[]} List of predicted clusters +clust.i.predclosest:{[data;cfg;reppt;c;cltidx;ptidx] + // intra cluster distances + dist:.ml.clust.i.dists[;cfg[`inputs]`df;data[;ptidx];]'[reppt;cltidx]; + // apply linkage + dist:$[`ward~lf:cfg[`inputs]`lf; + 2*clust.i.lf[lf][1]'[c;dist]; + clust.i.lf[lf]each dist]; + // find closest cluster + dist?ndst:min dist + } + // @kind function // @category private // @fileoverview Initialize cluster table -// @param data {float[][]} Points in `value flip` format -// @param df {fn} Distance function +// @param data {float[][]} Data in matrix format, each column is an individual datapoint +// @param df {symbol} Distance function name within '.ml.clust.df' // @return {table} Distances, neighbors, clusters and representatives clust.i.initcaw:{[data;df] // create table with distances and nearest neighhbors noted @@ -222,11 +231,11 @@ clust.i.initcaw:{[data;df] // @kind function // @category private // @fileoverview Find nearest neighbour index and distance -// @param data {float[][]} Points in `value flip` format -// @param df {fn} Distance function +// @param data {float[][]} Data in matrix format, each column is an individual datapoint +// @param df {symbol} Distance function name within '.ml.clust.df' // @param pt {float[][]} Points in `value flip` format -// @param idxs {long} Index of point in pt to find nn for -// @return {dict} Index of and distance to nn +// @param idxs {long} Index of point in 'pt' to find nearest neighbour for +// @return {dict} Index of and distance to nearest neighbour clust.i.nncaw:{[data;df;pt;idxs] `nni`nnd!(d?m;m:min d:@[;idxs;:;0w]clust.i.dists[data;df;pt;idxs]) } @@ -234,9 +243,9 @@ clust.i.nncaw:{[data;df;pt;idxs] // @kind function // @category private // @fileoverview CAW algo -// @param data {float[][]} Points in `value flip` format -// @param df {fn} Distance function -// @param lf {fn} Linkage function +// @param data {float[][]} Data in matrix format, each column is an individual datapoint +// @param df {symbol} Distance function name within '.ml.clust.df' +// @param lf {symbol} Linkage function name within '.ml.clust.lf' // @param l {(table;float[][])} List with cluster table and linkage matrix // @return {(table;float[][])} Updated l clust.i.algocaw:{[data;df;lf;l] @@ -261,16 +270,14 @@ clust.i.algocaw:{[data;df;lf;l] // @category private // @fileoverview Complete linkage // @param cpts {float[][]} Points in each cluster -// @param df {fn} Distance function -// @param lf {fn} Linkage function +// @param df {symbol} Distance function name within '.ml.clust.df' +// @param lf {symbol} Linkage function name within '.ml.clust.lf' // @param t {table} Cluster table // @param chk {long[]} Points to check // @return {table} Updated cluster table clust.i.hcupd.complete:{[cpts;df;lf;t;chk] // calculate cluster distances using complete method - dsts:{[df;lf;x;y] - clust.i.ld[lf]raze clust.i.dd[df]x[`pts]-\:'y`pts - }[df;lf;cpts chk]each cpts _ chk; + dsts:clust.i.completedist[df;lf;cpts;chk]; // find nearest neighbors nidx:dsts?ndst:min dsts; // update cluster table @@ -281,8 +288,8 @@ clust.i.hcupd.complete:{[cpts;df;lf;t;chk] // @category private // @fileoverview Average linkage // @param cpts {float[][]} Points in each cluster -// @param df {fn} Distance function -// @param lf {fn} Linkage function +// @param df {symbol} Distance function name within '.ml.clust.df' +// @param lf {symbol} Linkage function name within '.ml.clust.lf' // @param t {table} Cluster table // @param chk {long[]} Points to check // @return {table} Updated cluster table @@ -292,40 +299,64 @@ clust.i.hcupd.average:clust.i.hcupd.complete // @category private // @fileoverview Ward linkage // @param cpts {float[][]} Points in each cluster -// @param df {fn} Distance function -// @param lf {fn} Linkage function +// @param df {symbol} Distance function name within '.ml.clust.df' +// @param lf {symbol} Linkage function name within '.ml.clust.lf' // @param t {table} Cluster table // @param chk {long[]} Points to check // @return {table} Updated cluster table clust.i.hcupd.ward:{[cpts;df;lf;t;chk] // calculate distances using ward method - dsts:{[df;lf;x;y] - 2*clust.i.ld[lf][x`n;y`n]clust.i.dd[df]x[`reppt]-y`reppt - }[df;lf;cpts chk]each cpts _ chk; + dsts:clust.i.warddist[df;lf;cpts;chk]; // find nearest neighbors nidx:dsts?ndst:min dsts; // update cluster table and rep pts update nni:nidx,nnd:ndst from t where clt=chk} +// @kind function +// @category private +// @fileoverview Calculate distances between points based on specified +// linkage and distance functions +// @param df {symbol} Distance function name within '.ml.clust.df' +// @param lf {symbol} Linkage function name within '.ml.clust.lf' +// @param data {float[][]} Points in each cluster +// @param idxs {long[]} Indices for which to produce distances +// @return {float[]} list of distances between all data points and those in idxs +clust.i.completedist:{[df;lf;data;idxs] + {[df;lf;xdata;ydata] + dists:raze clust.i.df[df]xdata[`pts]-\:'ydata`pts; + clust.i.lf[lf]dists}[df;lf;data idxs]each data _ idxs + } + +// @kind function +// @category private +// @fileoverview Calculate distances between points based on ward linkage and +// specified distance function +// @param df {symbol} Distance function name within '.ml.clust.df' +// @param lf {symbol} Linkage function name within '.ml.clust.lf' +// @param data {float[][]} Points in each cluster +// @param idxs {long[]} Indices for which to produce distances +// @return {float[]} list of distances between all data points and those in idxs +clust.i.warddist:{[df;lf;data;idxs] + {[df;lf;xdata;ydata] + dists:clust.i.df[df]xdata[`reppt]-ydata`reppt; + 2*clust.i.lf[lf][xdata`n;ydata`n;dists]}[df;lf;data idxs]each data _ idxs + } + // @kind function // @category private // @fileoverview Initialize SCC clusters -// @param data {float[][]} Points in `value flip` format -// @param df {fn} Distance function -// @param k {long} Number of clusters -// @param n {long} Number of representative points per -// cluster -// @param c {float} Compression factor for -// representative points +// @param data {float[][]} Data in matrix format, each column is an individual datapoint +// @param df {symbol} Distance function name within '.ml.clust.df' +// @param k {long} Number of clusters +// @param n {long} Number of representative points per cluster +// @param c {float} Compression factor for representative points // @return {(dict;long[];table;table)} Parameters, clusters, representative // points and the kdtree clust.i.initscc:{[data;df;k;n;c;dgram] // build kdtree kdtree:clust.kd.newtree[data]1000&ceiling .01*nd:count data 0; - // create distance table with closest clusters identified - dists:update closestClust:closestPoint from{[kdtree;data;df;i] - clust.kd.nn[kdtree;data;df;i;data[;i]] - }[kdtree;data;df]each til nd; + // generate distance table with closest clusters identified + dists:clust.i.gendisttab[kdtree;data;df;nd]; lidx:select raze idxs,self:self where count each idxs from kdtree where leaf; r2l:exec self idxs?til count i from lidx; // create cluster table @@ -341,6 +372,24 @@ clust.i.initscc:{[data;df;k;n;c;dgram] (params;clusts;reppts;kdtree;(lnkmat;dgram)) } + +// @kind function +// @category private +// @fileoverview Generate distance table indicating closest cluster +// @param kdtree {tab} initial representation of the k-d tree +// @param data {float[][]} Data in matrix format, each column is an individual datapoint +// @param df {symbol} Distance function name within '.ml.clust.df' +// @param npts {long} Number of points in the dataset +// @return {tab} Distance table containing an indication of the closest cluster +clust.i.gendisttab:{[kdtree;data;df;npts] + // generate the distance table + gentab:{[kdtree;data;df;idx] + clust.kd.nn[kdtree;data;df;idx;data[;idx]] + }[kdtree;data;df]each til npts; + // update naming convention + update closestClust:closestPoint from gentab + } + // @kind function // @category private // @fileoverview Representative points for Centroid linkage @@ -353,14 +402,14 @@ clust.i.centrep:{[p] // @kind function // @category private // @fileoverview Representative points for CURE -// @param df {fn} Distance function +// @param df {symbol} Distance function name within '.ml.clust.df' // @param n {long} Number of representative points per cluster // @param c {float} Compression factor for representative points // @param p {float[][]} List of data points // @return {float[][]} List of representative points clust.i.curerep:{[df;n;c;p] rpts:1_first(n&count p 0).[{[df;rpts;p] - i:imax min clust.i.dd[df]each p-/:neg[1|-1+count rpts]#rpts; + i:imax min clust.i.df[df]each p-/:neg[1|-1+count rpts]#rpts; rpts,:enlist p[;i]; (rpts;.[p;(::;i);:;0n]) }[df]]/(enlist avgpt:avg each p;p); @@ -420,74 +469,77 @@ clust.i.extractclt:{[clts;cntt;inds] // @kind function // @category private // @fileoverview SCC algo -// @param data {float[][]} Points in `value flip` format -// @param df {fn} Distance function -// @param lf {fn} Linkage function -// @param params {dict} Parameters - k (no. clusts), n -// (no. reppts per clust), reppts, kdtree -// @param clusts {table} Cluster table -// @param reppts {float[][]} Representative points and -// associated info -// @param kdtree {table} k-dimensional tree storing -// points and distances +// @param data {float[][]} Data in matrix format, each column is +// an individual datapoint +// @param df {symbol} Distance function name within '.ml.clust.df' +// @param lf {symbol} Linkage function name within '.ml.clust.lf' +// @param params {dict} Parameters - k (no. clusts), n (no. reppts per clust), reppts, kdtree +// @param clusts {table} Cluster table +// @param reppts {float[][]} Representative points and associated info +// @param kdtree {table} k-dimensional tree storing points and distances // @return {(dict;long[];float[][];table)} Parameters dict, clusters, // representative points and kdtree tables clust.i.algoscc:{[data;df;lf;params;clusts;reppts;kdtree;lnkmat] - // merge closest clusters clust0:exec clust{x?min x}closestDist from clusts where valid; newmrg:clusts clust0,clust1:clusts[clust0]`closestClust; newmrg:update valid:10b,reppts:(raze reppts;0#0),points:(raze points;0#0)from newmrg; - // make dendrogram if required if[lnkmat 1; m:lnkmat 0; m,:newmrg[`clusti],fnew[`closestDist],count(fnew:first newmrg)`points; - lnkmat[0]:m]; - + lnkmat[0]:m + ]; // keep track of old reppts oldrep:reppts newmrg[0]`reppts; // find reps in new cluster $[sgl:lf~`single; // for single new reps=old reps -> no new points calculated newrep:select reppt,clust:clust0 from oldrep; - // if centroid reps=avg, if cure=calc reps - [newrep:flip params[`rpcols]!flip$[lf~`centroid;clust.i.centrep;clust.i.curerep[df;params`n;params`c]]data[;newmrg[0]`points]; + [ + // generate new representative points table (centroid -> reps=avg; cure -> calc reps) + newrepfunc:$[lf~`centroid;clust.i.centrep;clust.i.curerep[df;params`n;params`c]]; + newrepkeys:params[`rpcols]; + newrepvals:flip newrepfunc[data[;newmrg[0]`points]]; + newrep:flip newrepkeys!newrepvals; newrep:update clust:clust0,reppt:count[i]#newmrg[0]`reppts from newrep; // new rep leaves newrep[`leaf]:(clust.kd.findleaf[kdtree;;kdtree 0]each flip newrep params`rpcols)`self; newmrg[0;`reppts]:newrep`reppt; // delete old points from leaf and update new point to new rep leaf kdtree:.[kdtree;(oldrep`leaf;`idxs);except;oldrep`reppt]; - kdtree:.[kdtree;(newrep`leaf;`idxs);union ;newrep`reppt]]]; + kdtree:.[kdtree;(newrep`leaf;`idxs);union ;newrep`reppt] + ] + ]; // update clusters and reppts clusts:@[clusts;newmrg`clust;,;delete clust from newmrg]; reppts:@[reppts;newrep`reppt;,;delete reppt from newrep]; - updrep:reppts newrep`reppt; // nneighbour to clust if[sgl;updrep:select from updrep where closestClust in newmrg`clust]; - updrep:updrep,'clust.kd.nn[kdtree;reppts params`rpcols;df;newmrg[0]`points]each flip updrep params`rpcols; + // calculate and append to representative point table the nearest neighbours + // of columns containing representative points + updrepdata:flip updrep params`rpcols; + updrepdatann:clust.kd.nn[kdtree;reppts params`rpcols;df;newmrg[0]`points] each updrepdata; + updrep:updrep,'updrepdatann; updrep:update closestClust:reppts[closestPoint;`clust]from updrep; - if[sgl; reppts:@[reppts;updrep`reppt;,;select closestDist,closestClust from updrep]; updrep:reppts newrep`reppt]; // update nneighbour of new clust updrep@:raze imin updrep`closestDist; clusts:@[clusts;updrep`clust;,;`closestDist`closestClust#updrep]; - $[sgl; // single - nneighbour=new clust - [clusts:update closestClust:clust0 from clusts where valid,closestClust=clust1; - reppts:update closestClust:clust0 from reppts where closestClust=clust1]; + [clusts:update closestClust:clust0 from clusts where valid,closestClust=clust1; + reppts:update closestClust:clust0 from reppts where closestClust=clust1]; // else do nneighbour search if[count updcls:select from clusts where valid,closestClust in(clust0;clust1); - updcls:updcls,'{x imin x`closestDist}each clust.kd.nn[kdtree;reppts params`rpcols;df]/:' - [updcls`reppts;flip each reppts[updcls`reppts]@\:params`rpcols]; - updcls[`closestClust]:reppts[updcls`closestPoint]`clust; - clusts:@[clusts;updcls`clust;,;select closestDist,closestClust from updcls]]]; - + updcls:updcls,'{x imin x`closestDist}each clust.kd.nn[kdtree;reppts params`rpcols;df]/:' + [updcls`reppts;flip each reppts[updcls`reppts]@\:params`rpcols]; + updcls[`closestClust]:reppts[updcls`closestPoint]`clust; + clusts:@[clusts;updcls`clust;,;select closestDist,closestClust from updcls] + ] + ]; (params;clusts;reppts;kdtree;lnkmat) - } diff --git a/clust/init.q b/clust/init.q index aed79f39..e8e69e36 100644 --- a/clust/init.q +++ b/clust/init.q @@ -1,7 +1,13 @@ -.ml.loadfile`:clust/util.q -.ml.loadfile`:clust/kdtree.q -.ml.loadfile`:clust/kmeans.q -.ml.loadfile`:clust/aprop.q -.ml.loadfile`:clust/dbscan.q -.ml.loadfile`:clust/hierarchical.q -.ml.loadfile`:clust/score.q +\d .ml + +// required for use of .ml.confmat in score.q +loadfile`:util/init.q + +// load clustering files +loadfile`:clust/util.q +loadfile`:clust/kdtree.q +loadfile`:clust/kmeans.q +loadfile`:clust/aprop.q +loadfile`:clust/dbscan.q +loadfile`:clust/hierarchical.q +loadfile`:clust/score.q diff --git a/clust/kdtree.q b/clust/kdtree.q index d21ba1f2..97a50918 100644 --- a/clust/kdtree.q +++ b/clust/kdtree.q @@ -5,7 +5,7 @@ // @kind function // @category clust // @fileoverview Create new k-d tree -// @param data {float[][]} Points in `value flip` format +// @param data {float[][]} Data in matrix format, each column is an individual datapoint // @param leafsz {long} Number of points per leaf (<2*number of reppts) // @return {table} k-d tree clust.kd.newtree:{[data;leafsz] @@ -17,8 +17,8 @@ clust.kd.newtree:{[data;leafsz] // @category clust // @fileoverview Find nearest neighhbors in k-d tree // @param tree {table} k-d tree -// @param data {float[][]} Points in `value flip` format -// @param df {fn} Distance function +// @param data {float[][]} Data in matrix format, each column is an individual datapoint +// @param df {symbol} Distance function name within '.ml.clust.df' // @param xidxs {long[][]} Points to exclude in search // @param pt {long[]} Point to find nearest neighbor for // @return {dict} Nearest neighbor dictionary with closest point, @@ -71,7 +71,7 @@ clust.kd.i.nncheck:{[tree;data;df;xidxs;pt;nninfo] ] ]; if[not null childidx:first nninfo[`node;`children]except nninfo`xnodes; - nndist:clust.i.dd[df]pt[nninfo[`node]`axis]-nninfo[`node]`midval; + nndist:clust.i.df[df]pt[nninfo[`node]`axis]-nninfo[`node]`midval; childidx:$[(nninfo`closestDist)type cfg;'"cfg must be (::) or a dictionary"]; + // update iteration dictionary with user changes + updDict:defaultDict,cfg; // fit algo to data - r:clust.i.kmeans[data:"f"$data;df;k;iter;kpp]; + r:clust.i.kmeans[data;df;k;updDict]; // return config with new clusters - r,`data`inputs!(data;`df`k`iter`kpp!(df;k;iter;kpp)) + r,`data`inputs!(data;`df`k`iter`kpp!(df;k;updDict`iter;updDict`init)) } // @kind function // @category clust // @fileoverview Predict clusters using k-means config -// @param data {float[][]} Points in `value flip` format -// @param cfg {dict} `data`df`reppts`clt returned from kmeans -// clustered training data +// @param data {float[][]} Data in matrix format, each column is an individual datapoint +// @param df {symbol} Distance function name within '.ml.clust.df' +// @param cfg {dict} `data`df`reppts`clt returned from kmeans clustered training data // @return {long[]} List of predicted clusters clust.kmeans.predict:{[data;cfg] + data:clust.i.floatConversion[data]; // get new clusters based on latest config - clust.i.getclust["f"$data;cfg[`inputs]`df;cfg`reppts] + clust.i.getclust[data;cfg[`inputs]`df;cfg`reppts] } // @kind function // @category clust // @fileoverview Update kmeans config including new data points -// @param data {float[][]} Points in `value flip` format -// @param cfg {dict} `data`df`reppts`clt returned from kmeans -// clustered on training data +// @param data {float[][]} Data in matrix format, each column is an individual datapoint +// @param cfg {dict} `data`df`reppts`clt returned from kmeans clustered on training data // @return {dict} Updated model config clust.kmeans.update:{[data;cfg] + data:clust.i.floatConversion[data]; // update data to include new points - cfg[`data]:cfg[`data],'"f"$data; + cfg[`data]:cfg[`data],'data; // update k means - cfg[`reppts]:clust.i.updcentres[cfg`data;cfg[`inputs]`df;cfg`reppts]; + cfg[`reppts]:clust.i.updcenters[cfg`data;cfg[`inputs]`df;()!();cfg`reppts]; // get updated clusters based on new means cfg[`clt]:clust.i.getclust[cfg`data;cfg[`inputs]`df;cfg`reppts]; // return updated config cfg } + +// Utilities + // @kind function -// @category clust +// @category private // @fileoverview K-Means algorithm -// @param data {float[][]} Points in `value flip` format -// @param df {fn} Distance function +// @param data {float[][]} Data in matrix format, each column is an individual datapoint +// @param df {symbol} Distance function name within '.ml.clust.df' // @param k {long} Number of clusters -// @param iter {long} Number of iterations -// @param kpp {bool} Use kmeans++ or random initialization (1/0b) +// @param cfg {dict} Configuration information containing the maximum iterations `iter, +// initialisation type `init and threshold for smallest distance +// to move between the previous and new run `thresh // @return {dict} Clusters or reppts depending on rep -clust.i.kmeans:{[data;df;k;iter;kpp] +clust.i.kmeans:{[data;df;k;cfg] // check distance function if[not df in`e2dist`edist;clust.i.err.kmeans[]]; // initialize representative points - reppts0:$[kpp;clust.i.initkpp df;clust.i.initrdm][data;k]; - // run algo `iter` times - reppts1:iter clust.i.updcentres[data;df]/reppts0; + initreppts:$[cfg`init;clust.i.initkpp df;clust.i.initrdm][data;k]; + // run algo until maximum number of iterations reached or convergence + reppts0:`idx`reppts`notconv!(0;initreppts;1b); + reppts1:clust.i.kmeansConverge[cfg] clust.i.updcenters[data;df;cfg]/reppts0; // return representative points and clusters - `reppts`clt!(reppts1;clust.i.getclust[data;df;reppts1]) + `reppts`clt!(reppts1`reppts;clust.i.getclust[data;df;reppts1`reppts]) } // @kind function // @category private -// @fileoverview Update cluster centres -// @param data {float[][]} Points in `value flip` format -// @param df {fn} Distance function -// @return {float[][]} Updated representative points -clust.i.updcentres:{[data;df;reppt] - {[data;j] - avg each data[;j] - }[data]each value group clust.i.getclust[data;df;reppt] +// @fileoverview Check to see if cluster centers are stable or +// if the maximum number of iterations allowable have been reached +// @param cfg {dict} Configuration information containing the maximum iterations `iter, +// initialisation type `init and threshold for smallest distance +// to move between the previous and new run `thresh +// @param algorun {dict} Information about the current run of the algorithm which can have an +// impact on early or on time stopping i.e. have the maximum number of iterations been exceeded +// or have the cluster centers not moved more than the threshold i.e. 'stationary' +// @return {bool} 0b indicates number of iterations has exceeded maximum and +clust.i.kmeansConverge:{[cfg;algorun] + check1:cfg[`iter]>algorun`idx; + check2:algorun`notconv; + check1 & check2 } +// @kind function +// @category private +// @fileoverview Update cluster centers +// @param data {float[][]} Data in matrix format, each column is an individual datapoint +// @param df {symbol} Distance function name within '.ml.clust.df' +// @param cfg {dict} Configuration information containing the maximum iterations `iter, +// initialisation type `init and threshold for smallest distance +// to move between the previous and new run `thresh +// @param reppts {float[][]/dict} Information relating to the representative points, in the case of +// fitting the model this is a dictionary containing the current iteration index and if the data +// has converged in addition to the representative points. In an individual update this is just +// the representative points for the k means centers. +// @return {float[][]} Updated representative points +clust.i.updcenters:{[data;df;cfg;reppts] + // projection used for calculation of representative points + repptFunc:clust.i.newreppts[data;df;]; + if[99h=type reppts; + reppts[`idx]+:1; + prevpoint:reppts`reppts; + reppts[`reppts]:repptFunc reppts`reppts; + reppts[`notconv]:cfg[`thresh] Date: Tue, 6 Oct 2020 11:33:18 +0100 Subject: [PATCH 67/77] fix to failing cluster util tests --- clust/tests/util.t | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/clust/tests/util.t b/clust/tests/util.t index cfe43e4f..9618bae6 100644 --- a/clust/tests/util.t +++ b/clust/tests/util.t @@ -6,9 +6,13 @@ d1:(til 5;3 2 5 1 4) d2:(2#"F";",")0:`:clust/tests/data/sample1.csv +idxs1:til count d1 0 +idxs2:til count d2 0 tree:.ml.clust.kd.newtree[d1;1] tree2:.ml.clust.kd.newtree[d2;2] -info:.ml.clust.i.apinit[d1;`e2dist;max] +iter:`run`total`nochange!0 200 15 +info:.ml.clust.i.apinit[d1;`e2dist;max;idxs1] +info,:`emat`conv`iter!((count d1 0;iter`nochange)#0b;0b;iter) // k-d Tree using C @@ -66,10 +70,10 @@ all .ml.clust.kd.nn[tree;d1;`mdist;1 2 3 4;d1[;1]][`closestPoint`closestDist]=(0 // Affinity Propagation -.ml.clust.i.apinit[d2;`e2dist;med][`matches]~0 -.ml.clust.i.apinit[d1;`e2dist;min][`s]~(0 2 8 13 17;2 0 10 5 13;8 10 0 17 5 ;13 5 17 0 10;17 13 5 10 0) -.ml.clust.i.apinit[d1;`e2dist;min][`a]~5 5#0f -.ml.clust.i.apinit[d1;`e2dist;max][`r]~5 5#0f +.ml.clust.i.apinit[d2;`e2dist;med;idxs2][`matches]~0 +.ml.clust.i.apinit[d1;`e2dist;min;idxs1][`s]~(0 2 8 13 17;2 0 10 5 13;8 10 0 17 5 ;13 5 17 0 10;17 13 5 10 0) +.ml.clust.i.apinit[d1;`e2dist;min;idxs1][`a]~5 5#0f +.ml.clust.i.apinit[d1;`e2dist;max;idxs1][`r]~5 5#0f .ml.clust.i.apalgo[0.1;info][`exemplars]~0 1 2 2 0 .ml.clust.i.apalgo[0.1;info][`s]~(17 2 8 13 17;2 17 10 5 13;8 10 17 17 5;13 5 17 17 10;17 13 5 10 17) .ml.clust.i.apalgo[0.1;info][`a]~"f"$(3.24 0 0 0 0;0 0 0 0 0;0 0 3.24 0 0;0 0 0 0 0;0 0 0 0 0) From 798aca3e1931630fede8b1a681cee75a3dd54ad4 Mon Sep 17 00:00:00 2001 From: Deanna Morgan Date: Tue, 6 Oct 2020 16:25:56 +0100 Subject: [PATCH 68/77] change of testing format --- clust/aprop.q | 2 +- clust/tests/clt.t | 197 ++++++++++++++++++++++++++++---------------- clust/tests/score.t | 46 ++++++----- clust/tests/util.t | 152 +++++++++++++++++++++------------- 4 files changed, 246 insertions(+), 151 deletions(-) diff --git a/clust/aprop.q b/clust/aprop.q index a91a09be..1fc46dce 100644 --- a/clust/aprop.q +++ b/clust/aprop.q @@ -38,7 +38,7 @@ clust.ap.predict:{[data;cfg] // retrieve cluster centres from training data ex:cfg[`data][;distinct cfg`exemplars]; // predict testing data clusters - clust.i.appreddist[ex;cfg[`inputs]`df]each $[0h=type data;flip;enlist]data + clust.i.appreddist[ex;cfg[`inputs]`df]each$[0h=type data;flip;enlist]data } diff --git a/clust/tests/clt.t b/clust/tests/clt.t index 9317a6a0..8423a338 100644 --- a/clust/tests/clt.t +++ b/clust/tests/clt.t @@ -1,123 +1,174 @@ \l ml.q +.ml.loadfile`:clust/tests/passfail.q .ml.loadfile`:clust/init.q .ml.loadfile`:util/init.q +// Initialize datasets + \S 10 // Python imports lnk :.p.import[`scipy.cluster.hierarchy]`:linkage fclust:.p.import[`scipy.cluster.hierarchy]`:fcluster -// q utilities -mat:{"f"$flip value flip x} +// q Utilities +mat :{"f"$flip value flip x} +clusterIdxs:{value group(x . y)`clt} +clusterKeys:{key group(x . y)`clt} +clusterAdd1:{1+(x . y)`clt} +qDendrogram:{asc each x(y . z)`dgram} +algoOutputs:{asc key x . y} +countOutput:{count x . y} +pythonRes :{[fclust;mat;t;clt;param]value group fclust[mat t`dgram;clt;param]`}[fclust;mat] +pythonDgram:{[lnk;d;lf;df]asc each lnk[flip d;lf;df]`}[lnk] +qDgramDists:{(x . y)[`dgram]`dist} // Datasets d1:flip(60#"F";",")0:`:clust/tests/data/ss5.csv d1tts:flip each(0;45)_flip d1 d2:@[;`AnnualIncome`SpendingScore]("SSIII";(),",")0:`:clust/tests/data/Mall_Customers.csv -// Results -d1clt:(15*til 4)+\:til 15 -tab1:.ml.clust.hc.fit[d1;`mdist;`single] -tab2:.ml.clust.hc.fit[d1;`e2dist;`average] -tab3:.ml.clust.hc.fit[d2;`e2dist;`centroid] -tab4:.ml.clust.hc.fit[d2;`edist;`complete] +// Configurations +kMeansCfg:enlist[`iter]!enlist 2 + // Affinity Propagation +// Expected Results +d1clt:(15*til 4)+\:til 15 +APclt:2 2 2 2 2 2 0 2 0 0 0 2 2 2 2 + // Fit -.[.ml.clust.ap.fit;(d1;`e2dist;0.7;min;(::));1b] -value[group .ml.clust.ap.fit[d1;`nege2dist;0.7;min;(::)]`clt]~d1clt -value[group .ml.clust.ap.fit[d1;`nege2dist;0.9;med;(::)]`clt]~d1clt -key[group .ml.clust.ap.fit[d2;`nege2dist;0.95;{[x] -20000.};enlist[`maxsame]!enlist 150]`clt]~til 5 -key[group .ml.clust.ap.fit[d2;`nege2dist;0.5;min;(::)]`clt]~til 5 -asc[key .ml.clust.ap.fit[d2;`nege2dist;0.5;min;(::)]]~`clt`data`exemplars`inputs -.ml.clust.ap.fit[d2;`nege2dist;0.01;{[x] -10.};(::)][`clt]~200#-1 -.ml.clust.ap.fit[d1tts 0;`nege2dist;0.3;min;`maxrun`maxmatch!100 10][`clt]~45#-1 +passingTest[clusterIdxs[.ml.clust.ap.fit];(d1;`nege2dist;0.7;min;(::));1b;d1clt] +passingTest[clusterIdxs[.ml.clust.ap.fit];(d1;`nege2dist;0.9;med;(::));1b;d1clt] +passingTest[clusterIdxs[.ml.clust.ap.fit];(d2;`nege2dist;0.01;{[x] -10.};(::));1b;enlist til 200] +passingTest[clusterIdxs[.ml.clust.ap.fit];(d1tts 0;`nege2dist;0.3;min;`maxrun`maxmatch!100 10);1b;enlist til 45] +passingTest[clusterKeys[.ml.clust.ap.fit];(d2;`nege2dist;0.95;{[x] -20000.};enlist[`maxsame]!enlist 150);1b;til 5] +passingTest[clusterKeys[.ml.clust.ap.fit];(d2;`nege2dist;0.5;min;(::));1b;til 5] +passingTest[algoOutputs[.ml.clust.ap.fit];(d2;`nege2dist;0.5;min;(::));1b;`clt`data`exemplars`inputs] +failingTest[.ml.clust.ap.fit;(d1;`e2dist;0.7;min;(::));0b;"AP must be used with nege2dist"] +failingTest[.ml.clust.ap.fit;(d1;`nege2dist;0.7;min;100);0b;"iter must be (::) or a dictionary"] +failingTest[.ml.clust.ap.fit;(d1;`nege2dist;0.7;min;([]total:10,();nochange:5,()));0b;"iter must be (::) or a dictionary"] +failingTest[.ml.clust.ap.fit;(100?`8;`nege2dist;0.7;min;(::));0b;"Dataset not suitable for clustering. Must be convertible to floats."] // Predict -.ml.clust.ap.predict[d1tts 1;.ml.clust.ap.fit[d1tts 0;`nege2dist;0.7;min;(::)]]~2 2 2 2 2 2 0 2 0 0 0 2 2 2 2 -.ml.clust.ap.predict[d1tts 1;.ml.clust.ap.fit[d1tts 0;`nege2dist;0.7;med;`maxrun`maxmatch!100 10]]~2 2 2 2 2 2 0 2 0 0 0 2 2 2 2 +passingTest[.ml.clust.ap.predict;(d1tts 1;.ml.clust.ap.fit[d1tts 0;`nege2dist;0.7;min;(::)]);0b;APclt] +passingTest[.ml.clust.ap.predict;(d1tts 1;.ml.clust.ap.fit[d1tts 0;`nege2dist;0.7;med;`maxrun`maxmatch!100 10]);0b;APclt] +failingTest[.ml.clust.ap.predict;(100?`7;enlist[`clt]!enlist -1);0b;"Dataset not suitable for clustering. Must be convertible to floats."] +failingTest[.ml.clust.ap.predict;(d1tts 1;enlist[`clt]!enlist -1);0b;"'.ml.clust.ap.fit' did not converge, all clusters returned -1. Cannot predict new data."] + // K-Means +/`iter`init`thresh!(100;1b;1e-5) + // Fit -kMeansCfg:enlist[`iter]!enlist 2 -.[.ml.clust.kmeans.fit;(d1;`mdist;4;kMeansCfg);1b] -value[group .ml.clust.kmeans.fit[d1;`e2dist;4;kMeansCfg]`clt]~d1clt -value[group .ml.clust.kmeans.fit[d1;`edist ;4;kMeansCfg]`clt]~d1clt -asc[key .ml.clust.kmeans.fit[d2;`edist ;4;kMeansCfg]]~`clt`data`inputs`reppts +passingTest[clusterIdxs[.ml.clust.kmeans.fit];(d1;`e2dist;4;kMeansCfg);1b;d1clt] +passingTest[clusterIdxs[.ml.clust.kmeans.fit];(d1;`edist;4;kMeansCfg);1b;d1clt] +passingTest[clusterKeys[.ml.clust.kmeans.fit];(d1;`edist;4;kMeansCfg);1b;til 4] +passingTest[clusterKeys[.ml.clust.kmeans.fit];(d1;`e2dist;7;kMeansCfg);1b;til 7] +passingTest[algoOutputs[.ml.clust.kmeans.fit];(d2;`edist;4;kMeansCfg);1b;`clt`data`inputs`reppts] +failingTest[.ml.clust.kmeans.fit;(d1;`mdist;4;kMeansCfg);0b;"kmeans must be used with edist/e2dist"] +failingTest[.ml.clust.kmeans.fit;(d1;`nege2dist;4;74);0b;"cfg must be (::) or a dictionary"] +failingTest[.ml.clust.kmeans.fit;(d1;`nege2dist;4;([]total:28,();nochange:100,()));0b;"cfg must be (::) or a dictionary"] +failingTest[.ml.clust.kmeans.fit;(1000?`a`b`c;`edist;4;kMeansCfg);0b;"Dataset not suitable for clustering. Must be convertible to floats."] // Predict -count[.ml.clust.kmeans.predict[d1tts 1;.ml.clust.kmeans.fit[d1tts 0;`e2dist;4;kMeansCfg]]]~15 -count[.ml.clust.kmeans.predict[d1tts 1;.ml.clust.kmeans.fit[d1tts 0;`edist;4;kMeansCfg]]]~15 +passingTest[countOutput[.ml.clust.kmeans.predict];(d1tts 1;.ml.clust.kmeans.fit[d1tts 0;`e2dist;4;kMeansCfg]);1b;15] +passingTest[countOutput[.ml.clust.kmeans.predict];(d1tts 1;.ml.clust.kmeans.fit[d1tts 0;`edist;4;kMeansCfg]);1b;15] +failingTest[.ml.clust.kmeans.predict;(100?`4;()!());0b;"Dataset not suitable for clustering. Must be convertible to floats."] // Update -value[group .ml.clust.kmeans.update[d1tts 1;.ml.clust.kmeans.fit[d1tts 0;`e2dist;4;kMeansCfg]]`clt]~d1clt -`clt`data`inputs`reppts~asc key .ml.clust.kmeans.update[d1tts 1;.ml.clust.kmeans.fit[d1tts 0;`edist;4;kMeansCfg]] +passingTest[clusterIdxs[.ml.clust.kmeans.update];(d1tts 1;.ml.clust.kmeans.fit[d1tts 0;`e2dist;4;kMeansCfg]);1b;d1clt] +passingTest[algoOutputs[.ml.clust.kmeans.update];(d1tts 1;.ml.clust.kmeans.fit[d1tts 0;`edist;4;kMeansCfg]);1b;`clt`data`inputs`reppts] +failingTest[.ml.clust.kmeans.update;(1000?`2;()!());0b;"Dataset not suitable for clustering. Must be convertible to floats."] + // DBSCAN // Fit -value[group .ml.clust.dbscan.fit[d1;`e2dist;5;5]`clt]~d1clt -value[group .ml.clust.dbscan.fit[d1;`edist;5;5]`clt]~d1clt -value[group .ml.clust.dbscan.fit[d1;`mdist;5;5]`clt]~d1clt -value[group .ml.clust.dbscan.fit[d2;`e2dist;4;300]`clt]~(til[197],198;197 199) -value[group .ml.clust.dbscan.fit[d2;`edist;4;17.2]`clt]~(til[197],198;197 199) -value[group .ml.clust.dbscan.fit[d2;`mdist;7;24]`clt]~(til 196;196 197 198 199) +passingTest[clusterIdxs[.ml.clust.dbscan.fit];(d1;`e2dist;5;5);1b;d1clt] +passingTest[clusterIdxs[.ml.clust.dbscan.fit];(d1;`edist;5;5);1b;d1clt] +passingTest[clusterIdxs[.ml.clust.dbscan.fit];(d1;`mdist;5;5);1b;d1clt] +passingTest[clusterIdxs[.ml.clust.dbscan.fit];(d2;`e2dist;4;300);1b;(til[197],198;197 199)] +passingTest[clusterIdxs[.ml.clust.dbscan.fit];(d2;`edist;4;17.2);1b;(til[197],198;197 199)] +passingTest[clusterIdxs[.ml.clust.dbscan.fit];(d2;`mdist;7;24);1b;(til 196;196 197 198 199)] // Predict -.ml.clust.dbscan.predict[d1tts 1;.ml.clust.dbscan.fit[d1tts 0;`e2dist;5;5]]~15#-1 -.ml.clust.dbscan.predict[d1tts 1;.ml.clust.dbscan.fit[d1tts 0;`edist;5;5]]~15#-1 -.ml.clust.dbscan.predict[d1tts 1;.ml.clust.dbscan.fit[d1tts 0;`mdist;5;5]]~15#-1 +passingTest[.ml.clust.dbscan.predict;(d1tts 1;.ml.clust.dbscan.fit[d1tts 0;`e2dist;5;5]);0b;15#-1] +passingTest[.ml.clust.dbscan.predict;(d1tts 1;.ml.clust.dbscan.fit[d1tts 0;`edist;5;5]);0b;15#-1] +passingTest[.ml.clust.dbscan.predict;(d1tts 1;.ml.clust.dbscan.fit[d1tts 0;`mdist;5;5]);0b;15#-1] // Update -value[group .ml.clust.dbscan.update[d1tts 1;.ml.clust.dbscan.fit[d1tts 0;`e2dist;5;5]]`clt]~d1clt -value[group .ml.clust.dbscan.update[d1tts 1;.ml.clust.dbscan.fit[d1tts 0;`edist;5;5]]`clt]~d1clt -value[group .ml.clust.dbscan.update[d1tts 1;.ml.clust.dbscan.fit[d1tts 0;`mdist;5;5]]`clt]~d1clt -`clt`data`inputs`t~asc key .ml.clust.dbscan.update[d1tts 1;.ml.clust.dbscan.fit[d1tts 0;`mdist;5;5]] +passingTest[clusterIdxs[.ml.clust.dbscan.update];(d1tts 1;.ml.clust.dbscan.fit[d1tts 0;`e2dist;5;5]);1b;d1clt] +passingTest[clusterIdxs[.ml.clust.dbscan.update];(d1tts 1;.ml.clust.dbscan.fit[d1tts 0;`edist;5;5]);1b;d1clt] +passingTest[clusterIdxs[.ml.clust.dbscan.update];(d1tts 1;.ml.clust.dbscan.fit[d1tts 0;`mdist;5;5]);1b;d1clt] +passingTest[algoOutputs[.ml.clust.dbscan.update];(d1tts 1;.ml.clust.dbscan.fit[d1tts 0;`mdist;5;5]);1b;`clt`data`inputs`t] + // CURE +// Expected Results +cured2clt1:((til 192),193 195 197;192 194 196;enlist 198;enlist 199) +cured2clt2:(0 1 3 4,.ml.arange[5;16;2],16 17 18 19 20 21 23 25 26 27 28 29 31 33 35,(37+til 86),124 126 132 142 146 160;2 6 8 10 12 14 22 24 30 32 34 36;.ml.arange[123;200;2];128 130 134 136 138 140 144,.ml.arange[148;199;2]except 160) +cured2clt3:(til[122],.ml.arange[122;191;2];.ml.arange[123;194;2];192 194 196 198;195 197 199) +cured1pred1:1 2 1 1 2 2 1 1 1 1 1 2 1 2 2 +cured1pred2:0 3 0 0 3 3 0 0 0 0 0 3 0 3 3 +cured1pred3:1 3 1 3 3 3 1 1 1 1 1 3 1 3 3 + // Fit -value[group .ml.clust.cure.cutk[.ml.clust.cure.fit[d1;`e2dist;5;0];4]`clt]~d1clt -value[group .ml.clust.cure.cutk[.ml.clust.cure.fit[d1;`edist;10;0.2];4]`clt]~d1clt -value[group .ml.clust.cure.cutk[.ml.clust.cure.fit[d1;`mdist;3;0.15];4]`clt]~d1clt -value[group .ml.clust.cure.cutk[.ml.clust.cure.fit[d2;`e2dist;20;0];4]`clt]~((til 192),193 195 197;192 194 196;enlist 198;enlist 199) -value[group .ml.clust.cure.cutk[.ml.clust.cure.fit[d2;`edist;20;0.2];4]`clt]~(0 1 3 4,.ml.arange[5;16;2],16 17 18 19 20 21 23 25 26 27 28 29 31 33 35,(37+til 86),124 126 132 142 146 160;2 6 8 10 12 14 22 24 30 32 34 36;.ml.arange[123;200;2];128 130 134 136 138 140 144,.ml.arange[148;199;2]except 160) -value[group .ml.clust.cure.cutk[.ml.clust.cure.fit[d2;`mdist;10;0.1];4]`clt]~(til[122],.ml.arange[122;191;2];.ml.arange[123;194;2];192 194 196 198;195 197 199) +passingTest[clusterIdxs[.ml.clust.cure.cutk];(.ml.clust.cure.fit[d1;`e2dist;5;0];4);1b;d1clt] +passingTest[clusterIdxs[.ml.clust.cure.cutk];(.ml.clust.cure.fit[d1;`edist;10;0.2];4);1b;d1clt] +passingTest[clusterIdxs[.ml.clust.cure.cutk];(.ml.clust.cure.fit[d1;`mdist;3;0.15];4);1b;d1clt] +passingTest[clusterIdxs[.ml.clust.cure.cutk];(.ml.clust.cure.fit[d2;`e2dist;20;0];4);1b;cured2clt1] +passingTest[clusterIdxs[.ml.clust.cure.cutk];(.ml.clust.cure.fit[d2;`edist;20;0.2];4);1b;cured2clt2] +passingTest[clusterIdxs[.ml.clust.cure.cutk];(.ml.clust.cure.fit[d2;`mdist;10;0.1];4);1b;cured2clt3] // Predict -.ml.clust.cure.predict[d1tts 1;.ml.clust.cure.cutk[.ml.clust.cure.fit[d1tts 0;`e2dist;5;0];4]]~1 2 1 1 2 2 1 1 1 1 1 2 1 2 2 -.ml.clust.cure.predict[d1tts 1;.ml.clust.cure.cutk[.ml.clust.cure.fit[d1tts 0;`edist;10;0.2];4]]~0 3 0 0 3 3 0 0 0 0 0 3 0 3 3 -.ml.clust.cure.predict[d1tts 1;.ml.clust.cure.cutk[.ml.clust.cure.fit[d1tts 0;`mdist;3;0.15];4]]~1 3 1 3 3 3 1 1 1 1 1 3 1 3 3 +passingTest[.ml.clust.cure.predict;(d1tts 1;.ml.clust.cure.cutk[.ml.clust.cure.fit[d1tts 0;`e2dist;5;0];4]);0b;cured1pred1] +passingTest[.ml.clust.cure.predict;(d1tts 1;.ml.clust.cure.cutk[.ml.clust.cure.fit[d1tts 0;`edist;10;0.2];4]);0b;cured1pred2] +passingTest[.ml.clust.cure.predict;(d1tts 1;.ml.clust.cure.cutk[.ml.clust.cure.fit[d1tts 0;`mdist;3;0.15];4]);0b;cured1pred3] + // Hierarchical +// Expected Results +hcResSingle:((til 195),196;195 197;enlist 198;enlist 199) +hcResWard:(.ml.arange[0;43;2],(43+(til 80)),124 126 132 142 146 160;(.ml.arange[1;43;2]);.ml.arange[123;200;2];(.ml.arange[128;200;2])except 132 142 146 160) +hcResCentroid:((til 123);.ml.arange[123;194;2];.ml.arange[124;199;2];195 197 199) +hcResComplete:(.ml.arange[0;43;2],(43+(til 80));(.ml.arange[1;43;2]);.ml.arange[123;200;2];.ml.arange[124;200;2]) +hcResAverage:(.ml.arange[0;27;2],27,.ml.arange[28;43;2],43+(til 80);(.ml.arange[1;27;2],.ml.arange[29;42;2]);.ml.arange[123;200;2];.ml.arange[124;200;2]) +tab1:.ml.clust.hc.fit[d1;`mdist ;`single] +tab2:.ml.clust.hc.fit[d1;`e2dist;`average] +tab3:.ml.clust.hc.fit[d2;`e2dist;`centroid] +tab4:.ml.clust.hc.fit[d2;`edist ;`complete] +hct1fit:"j"$fclust[mat tab1`dgram;4;`maxclust]` +hcd1pred1:1 2 1 1 2 2 1 1 1 1 1 2 1 2 2 +hcd1pred2:1 3 1 1 3 3 1 1 1 1 1 3 1 3 3 +hcd1pred3:1 3 1 1 3 3 1 1 1 1 1 3 1 3 3 +pyDgramDists:(lnk[flip d2;`single;`sqeuclidean]`)[;2] + // Fit -(asc each mat .ml.clust.hc.fit[d1;`e2dist;`single]`dgram)~asc each lnk[flip d1;`single;`sqeuclidean]` -(asc each mat .ml.clust.hc.fit[d1;`mdist;`complete]`dgram)~asc each lnk[flip d1;`complete;`cityblock]` -(asc each mat .ml.clust.hc.fit[d1;`edist;`centroid]`dgram)~asc each lnk[flip d1;`centroid;`euclidean]` -(asc each mat .ml.clust.hc.fit[d1;`mdist;`average]`dgram)~asc each lnk[flip d1;`average;`cityblock]` -.ml.clust.hc.fit[d2;`e2dist;`single][`dgram][`dist]~(lnk[flip d2;`single;`sqeuclidean]`)[;2] - -value[group .ml.clust.hc.cutk[.ml.clust.hc.fit[d2;`e2dist;`single];4]`clt]~((til 195),196;195 197;enlist 198;enlist 199) -value[group .ml.clust.hc.cutk[.ml.clust.hc.fit[d2;`e2dist;`ward];4]`clt]~(.ml.arange[0;43;2],(43+(til 80)),124 126 132 142 146 160;(.ml.arange[1;43;2]);.ml.arange[123;200;2];(.ml.arange[128;200;2])except 132 142 146 160) -value[group .ml.clust.hc.cutk[.ml.clust.hc.fit[d2;`edist;`centroid];4]`clt]~((til 123);.ml.arange[123;194;2];.ml.arange[124;199;2];195 197 199) -value[group .ml.clust.hc.cutk[.ml.clust.hc.fit[d2;`edist;`complete];4]`clt]~(.ml.arange[0;43;2],(43+(til 80));(.ml.arange[1;43;2]);.ml.arange[123;200;2];.ml.arange[124;200;2]) -value[group .ml.clust.hc.cutk[.ml.clust.hc.fit[d2;`mdist;`average];4]`clt]~(.ml.arange[0;27;2],27,.ml.arange[28;43;2],43+(til 80);(.ml.arange[1;27;2],.ml.arange[29;42;2]);.ml.arange[123;200;2];.ml.arange[124;200;2]) -value[group .ml.clust.hc.cutk[tab2;4]`clt]~value group fclust[mat tab2`dgram;4;`maxclust]` -value[group .ml.clust.hc.cutk[tab3;4]`clt]~value group fclust[mat tab3`dgram;4;`maxclust]` -value[group .ml.clust.hc.cutk[tab4;4]`clt]~value group fclust[mat tab4`dgram;4;`maxclust]` -(1+.ml.clust.hc.cutk[tab1;4]`clt)~"j"$fclust[mat tab1`dgram;4;`maxclust]` - -value[group .ml.clust.hc.cutdist[tab1;.45]`clt]~value group fclust[mat tab1`dgram;.45;`distance]` -value[group .ml.clust.hc.cutdist[tab2;4]`clt]~value group fclust[mat tab2`dgram;34;`distance]` -value[group .ml.clust.hc.cutdist[tab3;500]`clt]~value group fclust[mat tab3`dgram;500;`distance]` -value[group .ml.clust.hc.cutdist[tab4;30]`clt]~value group fclust[mat tab4`dgram;30;`distance]` +passingTest[clusterAdd1[.ml.clust.hc.cutk ];(tab1;4);1b;hct1fit] +passingTest[clusterIdxs[.ml.clust.hc.cutk ];(.ml.clust.hc.fit[d2;`e2dist;`single];4);1b;hcResSingle] +passingTest[clusterIdxs[.ml.clust.hc.cutk ];(.ml.clust.hc.fit[d2;`e2dist;`ward];4);1b;hcResWard] +passingTest[clusterIdxs[.ml.clust.hc.cutk ];(.ml.clust.hc.fit[d2;`edist;`centroid];4);1b;hcResCentroid] +passingTest[clusterIdxs[.ml.clust.hc.cutk ];(.ml.clust.hc.fit[d2;`edist;`complete];4);1b;hcResComplete] +passingTest[clusterIdxs[.ml.clust.hc.cutk ];(.ml.clust.hc.fit[d2;`mdist;`average];4);1b;hcResAverage] +passingTest[clusterIdxs[.ml.clust.hc.cutk ];(tab2;4);1b;pythonRes[tab2;4;`maxclust]] +passingTest[clusterIdxs[.ml.clust.hc.cutk ];(tab3;4);1b;pythonRes[tab3;4;`maxclust]] +passingTest[clusterIdxs[.ml.clust.hc.cutk ];(tab4;4);1b;pythonRes[tab4;4;`maxclust]] +passingTest[clusterIdxs[.ml.clust.hc.cutdist];(tab1;.45);1b;pythonRes[tab1;.45;`distance]] +passingTest[clusterIdxs[.ml.clust.hc.cutdist];(tab2;4);1b;pythonRes[tab2;34;`distance]] +passingTest[clusterIdxs[.ml.clust.hc.cutdist];(tab3;500);1b;pythonRes[tab3;500;`distance]] +passingTest[clusterIdxs[.ml.clust.hc.cutdist];(tab4;30);1b;pythonRes[tab4;30;`distance]] +passingTest[qDendrogram[mat;.ml.clust.hc.fit];(d1;`e2dist;`single);1b;pythonDgram[d1;`single;`sqeuclidean]] +passingTest[qDendrogram[mat;.ml.clust.hc.fit];(d1;`mdist;`complete);1b;pythonDgram[d1;`complete;`cityblock]] +passingTest[qDendrogram[mat;.ml.clust.hc.fit];(d1;`edist;`centroid);1b;pythonDgram[d1;`centroid;`euclidean]] +passingTest[qDendrogram[mat;.ml.clust.hc.fit];(d1;`mdist;`average);1b;pythonDgram[d1;`average;`cityblock]] +passingTest[qDgramDists[.ml.clust.hc.fit ];(d2;`e2dist;`single);1b;pyDgramDists] // Predict -.ml.clust.hc.predict[d1tts 1;.ml.clust.hc.cutk[.ml.clust.hc.fit[d1tts 0;`e2dist;`single];4]]~1 2 1 1 2 2 1 1 1 1 1 2 1 2 2 -.ml.clust.hc.predict[d1tts 1;.ml.clust.hc.cutk[.ml.clust.hc.fit[d1tts 0;`e2dist;`ward];4]]~1 3 1 1 3 3 1 1 1 1 1 3 1 3 3 -.ml.clust.hc.predict[d1tts 1;.ml.clust.hc.cutk[.ml.clust.hc.fit[d1tts 0;`edist;`centroid];4]]~1 3 1 1 3 3 1 1 1 1 1 3 1 3 3 - +passingTest[.ml.clust.hc.predict;(d1tts 1;.ml.clust.hc.cutk[.ml.clust.hc.fit[d1tts 0;`e2dist;`single];4]);0b;hcd1pred1] +passingTest[.ml.clust.hc.predict;(d1tts 1;.ml.clust.hc.cutk[.ml.clust.hc.fit[d1tts 0;`e2dist;`ward];4]);0b;hcd1pred2] +passingTest[.ml.clust.hc.predict;(d1tts 1;.ml.clust.hc.cutk[.ml.clust.hc.fit[d1tts 0;`edist;`centroid];4]);0b;hcd1pred3] diff --git a/clust/tests/score.t b/clust/tests/score.t index e8ae5424..79dda546 100644 --- a/clust/tests/score.t +++ b/clust/tests/score.t @@ -1,46 +1,52 @@ \l ml.q +.ml.loadfile`:clust/tests/passfail.q .ml.loadfile`:clust/init.q .ml.loadfile`:util/init.q +// Initialize datasets + \S 10 +// Python imports pydb: .p.import[`sklearn.metrics]`:davies_bouldin_score pysil: .p.import[`sklearn.metrics]`:silhouette_score hscore:.p.import[`sklearn.metrics]`:homogeneity_score +// q Utilities +applyScoring:{ceiling y*x . z} + +// Datasets d1:flip(60#"F";",")0:`:clust/tests/data/ss5.csv d2:@[;`AnnualIncome`SpendingScore]("SSIII";(),",")0:`:clust/tests/data/Mall_Customers.csv + +// Expected Results clt1:.ml.clust.hc.cutk[.ml.clust.hc.fit[d1;`edist;`single];4] clt2:.ml.clust.hc.cutk[.ml.clust.hc.fit[d2;`e2dist;`ward];4] clt3:.ml.clust.hc.cutk[.ml.clust.cure.fit[d2;`edist;20;0.2];4] rnd1:count[flip d1]?4 -rnd2:count [flip d2]?4 +rnd2:count[flip d2]?4 // Dave Bouldin Score -.ml.clust.daviesbouldin[d1;clt1`clt]~pydb[flip d1;clt1`clt]` -.ml.clust.daviesbouldin[d2;clt2`clt]~pydb[flip d2;clt2`clt]` -.ml.clust.daviesbouldin[d2;clt3`clt]~pydb[flip d2;clt3`clt]` +passingTest[.ml.clust.daviesbouldin;(d1;clt1`clt);0b;pydb[flip d1;clt1`clt]`] +passingTest[.ml.clust.daviesbouldin;(d2;clt2`clt);0b;pydb[flip d2;clt2`clt]`] +passingTest[.ml.clust.daviesbouldin;(d2;clt3`clt);0b;pydb[flip d2;clt3`clt]`] // Silhouette Score - -.ml.clust.silhouette[d1;`edist;clt1`clt;1b]~pysil[flip d1;clt1`clt]` -.ml.clust.silhouette[d2;`edist;clt2`clt;1b]~pysil[flip d2;clt2`clt]` -.ml.clust.silhouette[d2;`edist;clt3`clt;1b]~pysil[flip d2;clt3`clt]` +passingTest[.ml.clust.silhouette;(d1;`edist;clt1`clt;1b);0b;pysil[flip d1;clt1`clt]`] +passingTest[.ml.clust.silhouette;(d2;`edist;clt2`clt;1b);0b;pysil[flip d2;clt2`clt]`] +passingTest[.ml.clust.silhouette;(d2;`edist;clt3`clt;1b);0b;pysil[flip d2;clt3`clt]`] // Dunn Score - -ceiling[.ml.clust.dunn[d1;`e2dist;clt1`clt]]~20 -ceiling[.ml.clust.dunn[d2;`edist;clt2`clt]*100]~13 -ceiling[.ml.clust.dunn[d2;`mdist;clt3`clt]*100]~10 +passingTest[applyScoring[.ml.clust.dunn;1 ];(d1;`e2dist;clt1`clt);1b;20] +passingTest[applyScoring[.ml.clust.dunn;100];(d2;`edist;clt2`clt);1b;13] +passingTest[applyScoring[.ml.clust.dunn;100];(d2;`mdist;clt3`clt);1b;10] // Elbow Scoring - -ceiling[.ml.clust.elbow[d1;`e2dist;2]]~enlist 548 -ceiling[.ml.clust.elbow[d2;`e2dist;2]]~enlist 183654 -ceiling[.ml.clust.elbow[d2;`e2dist;2]]~enlist 186363 +passingTest[applyScoring[.ml.clust.elbow;1];(d1;`e2dist;2);1b;enlist 548] +passingTest[applyScoring[.ml.clust.elbow;1];(d2;`e2dist;2);1b;enlist 183654] +passingTest[applyScoring[.ml.clust.elbow;1];(d2;`e2dist;2);1b;enlist 186363] // Homogeneity Score - -.ml.clust.homogeneity[clt1`clt;rnd1]~hscore[rnd1;clt1`clt]` -.ml.clust.homogeneity[clt2`clt;rnd2]~hscore[rnd2;clt2`clt]` -.ml.clust.homogeneity[clt3`clt;rnd2]~hscore[rnd2;clt3`clt]` +passingTest[.ml.clust.homogeneity;(clt1`clt;rnd1);0b;hscore[rnd1;clt1`clt]`] +passingTest[.ml.clust.homogeneity;(clt2`clt;rnd2);0b;hscore[rnd2;clt2`clt]`] +passingTest[.ml.clust.homogeneity;(clt3`clt;rnd2);0b;hscore[rnd2;clt3`clt]`] diff --git a/clust/tests/util.t b/clust/tests/util.t index 9618bae6..eed272d7 100644 --- a/clust/tests/util.t +++ b/clust/tests/util.t @@ -1,83 +1,121 @@ \l ml.q +.ml.loadfile`:clust/tests/passfail.q .ml.loadfile`:clust/init.q .ml.loadfile`:util/init.q +// Initialize Datasets + \S 10 +// Datasets d1:(til 5;3 2 5 1 4) d2:(2#"F";",")0:`:clust/tests/data/sample1.csv + +// Indices idxs1:til count d1 0 idxs2:til count d2 0 + +// K-D trees tree:.ml.clust.kd.newtree[d1;1] tree2:.ml.clust.kd.newtree[d2;2] + +// Configurations iter:`run`total`nochange!0 200 15 info:.ml.clust.i.apinit[d1;`e2dist;max;idxs1] info,:`emat`conv`iter!((count d1 0;iter`nochange)#0b;0b;iter) -// k-d Tree using C - -.ml.clust.i.dists[d1;`e2dist;4 2;1 2 3]~9 13 2 -.ml.clust.i.dists[d1;`e2dist;8 2;til 5]~65 49 45 26 20 -.ml.clust.i.closest[d1;`e2dist;1 2;til 5]~`point`distance!(1;0) -.ml.clust.i.closest[d2;`e2dist;3 6;reverse til 5][`point]~2 -.ml.clust.kd.newtree[d1;2][`left]~010b -.ml.clust.kd.newtree[d1;2][`leaf]~011b -.ml.clust.kd.newtree[d1;2][`midval]~2 0n 0n -.ml.clust.kd.newtree[d1;2][`parent]~0N 0 0 -.ml.clust.kd.newtree[d1;2][`idxs]~(0#0;0 1;2 3 4) -.ml.clust.kd.newtree[d1;2][`axis]~0 0N 0N -.ml.clust.kd.newtree[d2;3][`left]~010b -.ml.clust.kd.newtree[d2;3][`leaf]~011b -.ml.clust.kd.newtree[d2;3][`idxs]~(0#0;til 5;5 6 7 8 9) -.ml.clust.kd.newtree[d2;3][`parent]~0N 0 0 -.ml.clust.kd.newtree[d2;3][`idxs]~(0#0;til 5;5+til 5) -.ml.clust.kd.nn[tree;d1;`edist;0;d2[;2]][`closestPoint]~2 -all .ml.clust.kd.nn[tree;d1;`mdist;1 2 3 4;d1[;1]][`closestPoint`closestDist]=(0;2f) -.ml.clust.kd.nn[tree;d1;`mdist;1;7 9f][`closestPoint`closestDist]~(4;8f) -.ml.clust.kd.nn[tree2;d2;`edist;1 2 3;d1[;1]][`closestPoint]~0 -.ml.clust.kd.nn[tree2;d2;`edist;1 5 2;d1[;3]][`closestPoint]~3 -.ml.clust.kd.nn[tree2;d2;`edist;0;d2[;2]][`closestPoint`closestDist]~(2;0f) -.ml.clust.kd.findleaf[tree;d1[;1];tree 0]~(`leaf`left`self`parent`children`axis`midval`idxs!(1b;0b;3;1;0#0;0N;0n;enlist 1)) -.ml.clust.kd.findleaf[tree;d2[;4];tree 2]~(`leaf`left`self`parent`children`axis`midval`idxs!(1b;1b;2;1;0#0;0N;0n;enlist 0)) -.ml.clust.kd.findleaf[tree2;d2[;1];tree2 1]~(`leaf`left`self`parent`children`axis`midval`idxs!(1b;0b;3;1;0#0;0N;0n;1 3 4)) -.ml.clust.kd.findleaf[tree2;d1[;0];tree2 2]~(`leaf`left`self`parent`children`axis`midval`idxs!(1b;1b;2;1;0#0;0N;0n;0 2)) - -// k-d Tree using q +// q Utilities +specificRes :{(x . z)y} +closestPoint:specificRes[.ml.clust.i.closest;`point] +newTreeRes :specificRes[.ml.clust.kd.newtree] +nnRes :specificRes[.ml.clust.kd.nn] + + +// K-D Tree using C + +// Expected Results +kdKey:`leaf`left`self`parent`children`axis`midval`idxs +kdRes1:kdKey!(1b;0b;3;1;0#0;0N;0n;enlist 1) +kdRes2:kdKey!(1b;1b;2;1;0#0;0N;0n;enlist 0) +kdRes3:kdKey!(1b;0b;3;1;0#0;0N;0n;1 3 4) +kdRes4:kdKey!(1b;1b;2;1;0#0;0N;0n;0 2) + +passingTest[.ml.clust.i.dists ;(d1;`e2dist;4 2;1 2 3);0b;9 13 2] +passingTest[.ml.clust.i.dists ;(d1;`e2dist;8 2;til 5);0b;65 49 45 26 20] +passingTest[.ml.clust.i.closest;(d1;`e2dist;1 2;til 5);0b;`point`distance!(1;0)] +passingTest[closestPoint ;(d2;`e2dist;3 6;reverse til 5);1b;2] +passingTest[newTreeRes`left ;(d1;2);1b;010b] +passingTest[newTreeRes`leaf ;(d1;2);1b;011b] +passingTest[newTreeRes`midval;(d1;2);1b;2 0n 0n] +passingTest[newTreeRes`parent;(d1;2);1b;0N 0 0] +passingTest[newTreeRes`idxs ;(d1;2);1b;(0#0;0 1;2 3 4)] +passingTest[newTreeRes`axis ;(d1;2);1b;0 0N 0N] +passingTest[newTreeRes`left ;(d2;3);1b;010b] +passingTest[newTreeRes`leaf ;(d2;3);1b;011b] +passingTest[newTreeRes`idxs ;(d2;3);1b;(0#0;til 5;5 6 7 8 9)] +passingTest[newTreeRes`parent;(d2;3);1b;0N 0 0] +passingTest[newTreeRes`idxs ;(d2;3);1b;(0#0;til 5;5+til 5)] +passingTest[nnRes`closestPoint;(tree;d1;`edist;0;d2[;2]);1b;2] +passingTest[nnRes`closestPoint;(tree2;d2;`edist;1 2 3;d1[;1]);1b;0] +passingTest[nnRes`closestPoint;(tree2;d2;`edist;1 5 2;d1[;3]);1b;3] +passingTest[nnRes`closestPoint`closestDist;(tree;d1;`mdist;1;7 9f);1b;(4;8f)] +passingTest[nnRes`closestPoint`closestDist;(tree2;d2;`edist;0;d2[;2]);1b;(2;0f)] +passingTest[.ml.clust.kd.findleaf;(tree;d1[;1];tree 0);0b;kdRes1] +passingTest[.ml.clust.kd.findleaf;(tree;d2[;4];tree 2);0b;kdRes2] +passingTest[.ml.clust.kd.findleaf;(tree2;d2[;1];tree2 1);0b;kdRes3] +passingTest[.ml.clust.kd.findleaf;(tree2;d1[;0];tree2 2);0b;kdRes4] + + +// K-D Tree using q + +// Expected Results +kdRes5:kdKey!(1b;0b;3;1;0#0;0N;0n;enlist 1) +kdRes6:kdKey!(1b;1b;2;1;0#0;0N;0n;enlist 0) +kdRes7:kdKey!(1b;0b;3;1;0#0;0N;0n;1 3 4) +kdRes8:kdKey!(1b;1b;2;1;0#0;0N;0n;0 2) + +// Change to q implementation .ml.clust.kd.qC[1b]; -.ml.clust.kd.nn[tree;d1;`edist;0;d2[;2]][`closestPoint]~2 -.ml.clust.kd.nn[tree;d1;`mdist;1 2 3 4;d1[;1]][`closestPoint`closestDist]~(0;2) -.ml.clust.kd.nn[tree;d1;`mdist;1;7 9f][`closestPoint`closestDist]~(4;8f) -.ml.clust.kd.nn[tree2;d2;`edist;1 2 3;d1[;1]][`closestPoint]~0 -.ml.clust.kd.nn[tree2;d2;`edist;1 5 2;d1[;3]][`closestPoint]~3 -.ml.clust.kd.nn[tree2;d2;`edist;0;d2[;2]][`closestPoint`closestDist]~(2;0f) -.ml.clust.kd.findleaf[tree;d1[;1];tree 0]~(`leaf`left`self`parent`children`axis`midval`idxs!(1b;0b;3;1;0#0;0N;0n;enlist 1)) -.ml.clust.kd.findleaf[tree;d2[;4];tree 2]~(`leaf`left`self`parent`children`axis`midval`idxs!(1b;1b;2;1;0#0;0N;0n;enlist 0)) -.ml.clust.kd.findleaf[tree2;d2[;1];tree2 1]~(`leaf`left`self`parent`children`axis`midval`idxs!(1b;0b;3;1;0#0;0N;0n;1 3 4)) -.ml.clust.kd.findleaf[tree2;d1[;0];tree2 2]~(`leaf`left`self`parent`children`axis`midval`idxs!(1b;1b;2;1;0#0;0N;0n;0 2)) + +passingTest[nnRes`closestPoint;(tree;d1;`edist;0;d2[;2]);1b;2] +passingTest[nnRes`closestPoint;(tree2;d2;`edist;1 2 3;d1[;1]);1b;0] +passingTest[nnRes`closestPoint;(tree2;d2;`edist;1 5 2;d1[;3]);1b;3] +passingTest[nnRes`closestPoint`closestDist;(tree;d1;`mdist;1 2 3 4;d1[;1]);1b;0 2] +passingTest[nnRes`closestPoint`closestDist;(tree;d1;`mdist;1;7 9f);1b;(4;8f)] +passingTest[nnRes`closestPoint`closestDist;(tree2;d2;`edist;0;d2[;2]);1b;(2;0f)] +passingTest[.ml.clust.kd.findleaf;(tree;d1[;1];tree 0);0b;kdRes5] +passingTest[.ml.clust.kd.findleaf;(tree;d2[;4];tree 2);0b;kdRes6] +passingTest[.ml.clust.kd.findleaf;(tree2;d2[;1];tree2 1);0b;kdRes7] +passingTest[.ml.clust.kd.findleaf;(tree2;d1[;0];tree2 2);0b;kdRes8] + // K-Means -.ml.clust.i.getclust[d2;`e2dist;flip d2[;1 2]]~1 0 1 0 0 0 0 0 0 0 -.ml.clust.i.getclust[d2;`e2dist;flip d2[;1 2 3]]~1 0 1 2 2 2 2 2 2 2 -.ml.clust.i.getclust[d1;`e2dist;flip d1[;2 3]]~0 1 0 1 0 -.ml.clust.i.getclust[d1;`edist;flip d1[;3 4]]~0 0 1 0 1 +passingTest[.ml.clust.i.getclust;(d2;`e2dist;flip d2[;1 2]);0b;1 0 1 0 0 0 0 0 0 0] +passingTest[.ml.clust.i.getclust;(d2;`e2dist;flip d2[;1 2 3]);0b;1 0 1 2 2 2 2 2 2 2] +passingTest[.ml.clust.i.getclust;(d1;`e2dist;flip d1[;2 3]);0b;0 1 0 1 0] +passingTest[.ml.clust.i.getclust;(d1;`edist;flip d1[;3 4]);0b;0 0 1 0 1] + // DBSCAN -.ml.clust.i.nbhood["f"$d1;`edist;10;4]~0 1 2 3 -.ml.clust.i.nbhood[d2;`e2dist;0.1;1]~ 0 3 -.ml.clust.i.nbhood[d2;`edist;0.3;3]~0 1 +passingTest[.ml.clust.i.nbhood;("f"$d1;`edist;10;4);0b;0 1 2 3] +passingTest[.ml.clust.i.nbhood;(d2;`e2dist;0.1;1);0b; 0 3] +passingTest[.ml.clust.i.nbhood;(d2;`edist;0.3;3);0b;0 1] + // Affinity Propagation -.ml.clust.i.apinit[d2;`e2dist;med;idxs2][`matches]~0 -.ml.clust.i.apinit[d1;`e2dist;min;idxs1][`s]~(0 2 8 13 17;2 0 10 5 13;8 10 0 17 5 ;13 5 17 0 10;17 13 5 10 0) -.ml.clust.i.apinit[d1;`e2dist;min;idxs1][`a]~5 5#0f -.ml.clust.i.apinit[d1;`e2dist;max;idxs1][`r]~5 5#0f -.ml.clust.i.apalgo[0.1;info][`exemplars]~0 1 2 2 0 -.ml.clust.i.apalgo[0.1;info][`s]~(17 2 8 13 17;2 17 10 5 13;8 10 17 17 5;13 5 17 17 10;17 13 5 10 17) -.ml.clust.i.apalgo[0.1;info][`a]~"f"$(3.24 0 0 0 0;0 0 0 0 0;0 0 3.24 0 0;0 0 0 0 0;0 0 0 0 0) -.ml.clust.i.updr[0.2;info]~(0 -12 -7.2 -3.2 0;-12 3.2 -5.6 -9.6 -3.2;-7.2 -5.6 0 0 -9.6;-3.2 -9.6 3.2 0 -5.6;3.2 -3.2 -9.6 -5.6 0) -.ml.clust.i.updr[0.1;info]~(0 -13.5 -8.1 -3.6 0;-13.5 3.6 -6.3 -10.8 -3.6;-8.1 -6.3 0 0 -10.8;-3.6 -10.8 3.6 0 -6.3;3.6 -3.6 -10.8 -6.3 0) -.ml.clust.i.upda[0.5;info]~5 5#0f -.ml.clust.i.upda[0.9;info]~5 5#0f +// Expected Results +d1S:(0 2 8 13 17;2 0 10 5 13;8 10 0 17 5 ;13 5 17 0 10;17 13 5 10 0) +s01:(17 2 8 13 17;2 17 10 5 13;8 10 17 17 5;13 5 17 17 10;17 13 5 10 17) +a01:"f"$(3.24 0 0 0 0;0 0 0 0 0;0 0 3.24 0 0;0 0 0 0 0;0 0 0 0 0) +AP1:(0 -12 -7.2 -3.2 0;-12 3.2 -5.6 -9.6 -3.2;-7.2 -5.6 0 0 -9.6;-3.2 -9.6 3.2 0 -5.6;3.2 -3.2 -9.6 -5.6 0) +AP2:(0 -13.5 -8.1 -3.6 0;-13.5 3.6 -6.3 -10.8 -3.6;-8.1 -6.3 0 0 -10.8;-3.6 -10.8 3.6 0 -6.3;3.6 -3.6 -10.8 -6.3 0) + +passingTest[specificRes[.ml.clust.i.apinit;`s`a`r`matches];(d1;`e2dist;min;idxs1);1b;(d1S;5 5#0f;5 5#0f;0)] +passingTest[specificRes[.ml.clust.i.apalgo;`exemplars`s`a];(.1;info);1b;(0 1 2 2 0;s01;a01)] +passingTest[.ml.clust.i.updr;(.2;info);0b;AP1] +passingTest[.ml.clust.i.updr;(.1;info);0b;AP2] +passingTest[.ml.clust.i.upda;(.5;info);0b;5 5#0f] +passingTest[.ml.clust.i.upda;(.9;info);0b;5 5#0f] From 5c81ce388a0779a89d90b59e513028d39618906d Mon Sep 17 00:00:00 2001 From: Deanna Morgan Date: Tue, 6 Oct 2020 16:26:11 +0100 Subject: [PATCH 69/77] new testing script --- clust/tests/passfail.q | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 clust/tests/passfail.q diff --git a/clust/tests/passfail.q b/clust/tests/passfail.q new file mode 100644 index 00000000..b1e60543 --- /dev/null +++ b/clust/tests/passfail.q @@ -0,0 +1,41 @@ +// The following utilities are used to test that a function is returning the expected +// error message or data, these functions will likely be provided in some form within +// the test.q script provided as standard for the testing of q and embedPy code + +// @kind function +// @category tests +// @fileoverview Ensure that a test that is expected to fail, +// does so with an appropriate message +// @param function {(func;proj)} The function or projection to be tested +// @param data {any} The data to be applied to the function as an individual item for +// unary functions or a list of variables for multivariant functions +// @param applyType {boolean} Is the function to be applied unary(1b) or multivariant(0b) +// @param expectedError {string} The expected error message on failure of the function +// @return {boolean} Function errored with appropriate message (1b), function failed +// inappropriately or passed (0b) +failingTest:{[function;data;applyType;expectedError] + // Is function to be applied unary or multivariant + applyType:$[applyType;@;.]; + failureFunction:{[err;ret](`TestFailing;ret;err~ret)}[expectedError;]; + functionReturn:applyType[function;data;failureFunction]; + $[`TestFailing~first functionReturn;last functionReturn;0b] + } + +// @kind function +// @category tests +// @fileoverview Ensure that a test that is expected to pass, +// does so with an appropriate return +// @param function {(func;proj)} The function or projection to be tested +// @param data {any} The data to be applied to the function as an individual item for +// unary functions or a list of variables for multivariant functions +// @param applyType {boolean} Is the function to be applied unary(1b) or multivariant(0b) +// @param expectedReturn {string} The data expected to be returned on +// execution of the function with the supplied data +// @return {boolean} Function returned the appropriate output (1b), function failed +// or executed with incorrect output (0b) +passingTest:{[function;data;applyType;expectedReturn] + // Is function to be applied unary or multivariant + applyType:$[applyType;@;.]; + functionReturn:applyType[function;data]; + expectedReturn~functionReturn + } \ No newline at end of file From d7b442b75f1b9e0208982cd2026d24c5827a57b5 Mon Sep 17 00:00:00 2001 From: Deanna Morgan Date: Tue, 6 Oct 2020 16:43:20 +0100 Subject: [PATCH 70/77] change to seed --- clust/tests/clt.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clust/tests/clt.t b/clust/tests/clt.t index 8423a338..1dd338d5 100644 --- a/clust/tests/clt.t +++ b/clust/tests/clt.t @@ -5,7 +5,7 @@ // Initialize datasets -\S 10 +\S 42 // Python imports lnk :.p.import[`scipy.cluster.hierarchy]`:linkage From bea876bfc51596227217b61116fea8e711514922 Mon Sep 17 00:00:00 2001 From: Deanna Morgan Date: Tue, 6 Oct 2020 17:25:22 +0100 Subject: [PATCH 71/77] addition of failing tests --- clust/tests/clt.t | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/clust/tests/clt.t b/clust/tests/clt.t index 1dd338d5..37e9fdff 100644 --- a/clust/tests/clt.t +++ b/clust/tests/clt.t @@ -31,7 +31,6 @@ d2:@[;`AnnualIncome`SpendingScore]("SSIII";(),",")0:`:clust/tests/data/Mall_Cust // Configurations kMeansCfg:enlist[`iter]!enlist 2 - // Affinity Propagation // Expected Results @@ -57,13 +56,13 @@ passingTest[.ml.clust.ap.predict;(d1tts 1;.ml.clust.ap.fit[d1tts 0;`nege2dist;0. failingTest[.ml.clust.ap.predict;(100?`7;enlist[`clt]!enlist -1);0b;"Dataset not suitable for clustering. Must be convertible to floats."] failingTest[.ml.clust.ap.predict;(d1tts 1;enlist[`clt]!enlist -1);0b;"'.ml.clust.ap.fit' did not converge, all clusters returned -1. Cannot predict new data."] - // K-Means -/`iter`init`thresh!(100;1b;1e-5) - // Fit passingTest[clusterIdxs[.ml.clust.kmeans.fit];(d1;`e2dist;4;kMeansCfg);1b;d1clt] +passingTest[clusterIdxs[.ml.clust.kmeans.fit];(d1;`e2dist;4;kMeansCfg,enlist[`iter]!enlist 3);1b;d1clt] +passingTest[clusterIdxs[.ml.clust.kmeans.fit];(d1;`e2dist;4;kMeansCfg,enlist[`init]!enlist 0b);1b;d1clt] +passingTest[clusterIdxs[.ml.clust.kmeans.fit];(d1;`e2dist;4;kMeansCfg,enlist[`thresh]!enlist 1e-3);1b;d1clt] passingTest[clusterIdxs[.ml.clust.kmeans.fit];(d1;`edist;4;kMeansCfg);1b;d1clt] passingTest[clusterKeys[.ml.clust.kmeans.fit];(d1;`edist;4;kMeansCfg);1b;til 4] passingTest[clusterKeys[.ml.clust.kmeans.fit];(d1;`e2dist;7;kMeansCfg);1b;til 7] @@ -83,7 +82,6 @@ passingTest[clusterIdxs[.ml.clust.kmeans.update];(d1tts 1;.ml.clust.kmeans.fit[d passingTest[algoOutputs[.ml.clust.kmeans.update];(d1tts 1;.ml.clust.kmeans.fit[d1tts 0;`edist;4;kMeansCfg]);1b;`clt`data`inputs`reppts] failingTest[.ml.clust.kmeans.update;(1000?`2;()!());0b;"Dataset not suitable for clustering. Must be convertible to floats."] - // DBSCAN // Fit @@ -93,18 +91,21 @@ passingTest[clusterIdxs[.ml.clust.dbscan.fit];(d1;`mdist;5;5);1b;d1clt] passingTest[clusterIdxs[.ml.clust.dbscan.fit];(d2;`e2dist;4;300);1b;(til[197],198;197 199)] passingTest[clusterIdxs[.ml.clust.dbscan.fit];(d2;`edist;4;17.2);1b;(til[197],198;197 199)] passingTest[clusterIdxs[.ml.clust.dbscan.fit];(d2;`mdist;7;24);1b;(til 196;196 197 198 199)] +failingTest[.ml.clust.dbscan.fit;(50?`x`y;`edist;4;300);0b;"Dataset not suitable for clustering. Must be convertible to floats."] +failingTest[.ml.clust.dbscan.fit;(d1;`euclidean;5;5);0b;"invalid distance metric"] // Predict passingTest[.ml.clust.dbscan.predict;(d1tts 1;.ml.clust.dbscan.fit[d1tts 0;`e2dist;5;5]);0b;15#-1] passingTest[.ml.clust.dbscan.predict;(d1tts 1;.ml.clust.dbscan.fit[d1tts 0;`edist;5;5]);0b;15#-1] passingTest[.ml.clust.dbscan.predict;(d1tts 1;.ml.clust.dbscan.fit[d1tts 0;`mdist;5;5]);0b;15#-1] +failingTest[.ml.clust.dbscan.predict;(50?`x`y;());0b;"Dataset not suitable for clustering. Must be convertible to floats."] // Update passingTest[clusterIdxs[.ml.clust.dbscan.update];(d1tts 1;.ml.clust.dbscan.fit[d1tts 0;`e2dist;5;5]);1b;d1clt] passingTest[clusterIdxs[.ml.clust.dbscan.update];(d1tts 1;.ml.clust.dbscan.fit[d1tts 0;`edist;5;5]);1b;d1clt] passingTest[clusterIdxs[.ml.clust.dbscan.update];(d1tts 1;.ml.clust.dbscan.fit[d1tts 0;`mdist;5;5]);1b;d1clt] passingTest[algoOutputs[.ml.clust.dbscan.update];(d1tts 1;.ml.clust.dbscan.fit[d1tts 0;`mdist;5;5]);1b;`clt`data`inputs`t] - +failingTest[.ml.clust.dbscan.update;(50?`x`y;());0b;"Dataset not suitable for clustering. Must be convertible to floats."] // CURE @@ -123,12 +124,19 @@ passingTest[clusterIdxs[.ml.clust.cure.cutk];(.ml.clust.cure.fit[d1;`mdist;3;0.1 passingTest[clusterIdxs[.ml.clust.cure.cutk];(.ml.clust.cure.fit[d2;`e2dist;20;0];4);1b;cured2clt1] passingTest[clusterIdxs[.ml.clust.cure.cutk];(.ml.clust.cure.fit[d2;`edist;20;0.2];4);1b;cured2clt2] passingTest[clusterIdxs[.ml.clust.cure.cutk];(.ml.clust.cure.fit[d2;`mdist;10;0.1];4);1b;cured2clt3] +passingTest[clusterIdxs[.ml.clust.cure.cutdist];(.ml.clust.cure.fit[d1;`e2dist;5;0];2.);1b;d1clt] +passingTest[clusterIdxs[.ml.clust.cure.cutdist];(.ml.clust.cure.fit[d1;`edist;10;0.2];2.);1b;d1clt] +passingTest[clusterIdxs[.ml.clust.cure.cutdist];(.ml.clust.cure.fit[d1;`mdist;3;0.15];2.);1b;d1clt] +passingTest[algoOutputs[.ml.clust.cure.fit];(d1;`e2dist;5;0);1b;`data`dgram`inputs] +failingTest[.ml.clust.cure.fit;(821?`2;`e2dist;5;0);0b;"Dataset not suitable for clustering. Must be convertible to floats."] +failingTest[.ml.clust.cure.fit;(d1;`newmetric;5;0);0b;"invalid distance metric"] // Predict passingTest[.ml.clust.cure.predict;(d1tts 1;.ml.clust.cure.cutk[.ml.clust.cure.fit[d1tts 0;`e2dist;5;0];4]);0b;cured1pred1] passingTest[.ml.clust.cure.predict;(d1tts 1;.ml.clust.cure.cutk[.ml.clust.cure.fit[d1tts 0;`edist;10;0.2];4]);0b;cured1pred2] passingTest[.ml.clust.cure.predict;(d1tts 1;.ml.clust.cure.cutk[.ml.clust.cure.fit[d1tts 0;`mdist;3;0.15];4]);0b;cured1pred3] - +failingTest[.ml.clust.cure.predict;(182?`5;());0b;"Dataset not suitable for clustering. Must be convertible to floats."] +failingTest[.ml.clust.cure.predict;(2 10#20?5.;()!());0b;"Clusters must be contained within cfg - please run .ml.clust.cure.(cutk/cutdist)"] // Hierarchical @@ -167,8 +175,12 @@ passingTest[qDendrogram[mat;.ml.clust.hc.fit];(d1;`mdist;`complete);1b;pythonDgr passingTest[qDendrogram[mat;.ml.clust.hc.fit];(d1;`edist;`centroid);1b;pythonDgram[d1;`centroid;`euclidean]] passingTest[qDendrogram[mat;.ml.clust.hc.fit];(d1;`mdist;`average);1b;pythonDgram[d1;`average;`cityblock]] passingTest[qDgramDists[.ml.clust.hc.fit ];(d2;`e2dist;`single);1b;pyDgramDists] +failingTest[.ml.clust.hc.fit;(821?`2;`e2dist;`ward);0b;"Dataset not suitable for clustering. Must be convertible to floats."] +failingTest[.ml.clust.hc.fit;(d1;`mdist;`ward);0b;"ward must be used with e2dist"] +failingTest[.ml.clust.hc.fit;(d1;`mdist;`linkage);0b;"invalid linkage"] // Predict passingTest[.ml.clust.hc.predict;(d1tts 1;.ml.clust.hc.cutk[.ml.clust.hc.fit[d1tts 0;`e2dist;`single];4]);0b;hcd1pred1] passingTest[.ml.clust.hc.predict;(d1tts 1;.ml.clust.hc.cutk[.ml.clust.hc.fit[d1tts 0;`e2dist;`ward];4]);0b;hcd1pred2] passingTest[.ml.clust.hc.predict;(d1tts 1;.ml.clust.hc.cutk[.ml.clust.hc.fit[d1tts 0;`edist;`centroid];4]);0b;hcd1pred3] +failingTest[.ml.clust.hc.predict;(2 10#20?5.;()!());0b;"Clusters must be contained within cfg - please run .ml.clust.hc.(cutk/cutdist)"] From 1fed4b2c2c06f0505f50f23dbe4e51b0612cc969 Mon Sep 17 00:00:00 2001 From: Deanna Morgan Date: Tue, 6 Oct 2020 17:30:49 +0100 Subject: [PATCH 72/77] scoring failing tests --- clust/score.q | 2 +- clust/tests/score.t | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/clust/score.q b/clust/score.q index 03b1730c..df058a16 100644 --- a/clust/score.q +++ b/clust/score.q @@ -53,7 +53,7 @@ clust.silhouette:{[data;df;clt;isavg] // @return {float} Homogeneity score for true clust.homogeneity:{[pred;true] if[count[pred]<>n:count true; - '`$"distinct lengths - lenght of lists has to be the same"]; + '`$"pred and true must have equal lengths"]; if[not e:clust.i.entropy true;:1.]; cm:value confmat[pred;true]; nm:(*\:/:).((count each group@)each(pred;true))@\:til count cm; diff --git a/clust/tests/score.t b/clust/tests/score.t index 79dda546..500d5616 100644 --- a/clust/tests/score.t +++ b/clust/tests/score.t @@ -45,8 +45,10 @@ passingTest[applyScoring[.ml.clust.dunn;100];(d2;`mdist;clt3`clt);1b;10] passingTest[applyScoring[.ml.clust.elbow;1];(d1;`e2dist;2);1b;enlist 548] passingTest[applyScoring[.ml.clust.elbow;1];(d2;`e2dist;2);1b;enlist 183654] passingTest[applyScoring[.ml.clust.elbow;1];(d2;`e2dist;2);1b;enlist 186363] +failingTest[.ml.clust.elbow;(d2;`mdist;3);0b;"kmeans must be used with edist/e2dist"] // Homogeneity Score passingTest[.ml.clust.homogeneity;(clt1`clt;rnd1);0b;hscore[rnd1;clt1`clt]`] passingTest[.ml.clust.homogeneity;(clt2`clt;rnd2);0b;hscore[rnd2;clt2`clt]`] passingTest[.ml.clust.homogeneity;(clt3`clt;rnd2);0b;hscore[rnd2;clt3`clt]`] +failingTest[.ml.clust.homogeneity;(100?0b;10?0b);0b;"pred and true must have equal lengths"] From 93be66af55f77823f01c14c4d71aa9dd61fa2561 Mon Sep 17 00:00:00 2001 From: Deanna Morgan Date: Tue, 6 Oct 2020 17:33:32 +0100 Subject: [PATCH 73/77] change of testing format --- clust/tests/util.t | 5 ----- 1 file changed, 5 deletions(-) diff --git a/clust/tests/util.t b/clust/tests/util.t index eed272d7..1d8514a7 100644 --- a/clust/tests/util.t +++ b/clust/tests/util.t @@ -30,7 +30,6 @@ closestPoint:specificRes[.ml.clust.i.closest;`point] newTreeRes :specificRes[.ml.clust.kd.newtree] nnRes :specificRes[.ml.clust.kd.nn] - // K-D Tree using C // Expected Results @@ -65,7 +64,6 @@ passingTest[.ml.clust.kd.findleaf;(tree;d2[;4];tree 2);0b;kdRes2] passingTest[.ml.clust.kd.findleaf;(tree2;d2[;1];tree2 1);0b;kdRes3] passingTest[.ml.clust.kd.findleaf;(tree2;d1[;0];tree2 2);0b;kdRes4] - // K-D Tree using q // Expected Results @@ -88,7 +86,6 @@ passingTest[.ml.clust.kd.findleaf;(tree;d2[;4];tree 2);0b;kdRes6] passingTest[.ml.clust.kd.findleaf;(tree2;d2[;1];tree2 1);0b;kdRes7] passingTest[.ml.clust.kd.findleaf;(tree2;d1[;0];tree2 2);0b;kdRes8] - // K-Means passingTest[.ml.clust.i.getclust;(d2;`e2dist;flip d2[;1 2]);0b;1 0 1 0 0 0 0 0 0 0] @@ -96,14 +93,12 @@ passingTest[.ml.clust.i.getclust;(d2;`e2dist;flip d2[;1 2 3]);0b;1 0 1 2 2 2 2 2 passingTest[.ml.clust.i.getclust;(d1;`e2dist;flip d1[;2 3]);0b;0 1 0 1 0] passingTest[.ml.clust.i.getclust;(d1;`edist;flip d1[;3 4]);0b;0 0 1 0 1] - // DBSCAN passingTest[.ml.clust.i.nbhood;("f"$d1;`edist;10;4);0b;0 1 2 3] passingTest[.ml.clust.i.nbhood;(d2;`e2dist;0.1;1);0b; 0 3] passingTest[.ml.clust.i.nbhood;(d2;`edist;0.3;3);0b;0 1] - // Affinity Propagation // Expected Results From 1df2700cfc82c872974f1635076a6fa0127526fb Mon Sep 17 00:00:00 2001 From: Deanna Morgan Date: Tue, 6 Oct 2020 18:04:16 +0100 Subject: [PATCH 74/77] util test fix --- clust/tests/util.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clust/tests/util.t b/clust/tests/util.t index 1d8514a7..a75f1eba 100644 --- a/clust/tests/util.t +++ b/clust/tests/util.t @@ -78,7 +78,7 @@ kdRes8:kdKey!(1b;1b;2;1;0#0;0N;0n;0 2) passingTest[nnRes`closestPoint;(tree;d1;`edist;0;d2[;2]);1b;2] passingTest[nnRes`closestPoint;(tree2;d2;`edist;1 2 3;d1[;1]);1b;0] passingTest[nnRes`closestPoint;(tree2;d2;`edist;1 5 2;d1[;3]);1b;3] -passingTest[nnRes`closestPoint`closestDist;(tree;d1;`mdist;1 2 3 4;d1[;1]);1b;0 2] +passingTest[nnRes`closestPoint`closestDist;(tree;d1;`mdist;1 2 3 4;d1[;1]);1b;(0;2f)] passingTest[nnRes`closestPoint`closestDist;(tree;d1;`mdist;1;7 9f);1b;(4;8f)] passingTest[nnRes`closestPoint`closestDist;(tree2;d2;`edist;0;d2[;2]);1b;(2;0f)] passingTest[.ml.clust.kd.findleaf;(tree;d1[;1];tree 0);0b;kdRes5] From 6e97d86c4647701413c93c22922b8ff8d93e19f9 Mon Sep 17 00:00:00 2001 From: Deanna Morgan Date: Mon, 12 Oct 2020 16:44:00 +0100 Subject: [PATCH 75/77] conflict fixes --- timeseries/tests/data/linux/fit/SARIMA3 | Bin 0 -> 2607 bytes timeseries/tests/data/linux/fit/SARIMA4 | Bin 0 -> 1967 bytes timeseries/tests/data/linux/pred/predSARIMA3 | Bin 0 -> 8016 bytes timeseries/tests/data/linux/pred/predSARIMA4 | Bin 0 -> 8016 bytes timeseries/tests/data/windows/fit/SARIMA3 | Bin 0 -> 2607 bytes timeseries/tests/data/windows/fit/SARIMA4 | Bin 0 -> 1967 bytes timeseries/tests/data/windows/pred/predSARIMA3 | Bin 0 -> 8016 bytes timeseries/tests/data/windows/pred/predSARIMA4 | Bin 0 -> 8016 bytes 8 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 timeseries/tests/data/linux/fit/SARIMA3 create mode 100644 timeseries/tests/data/linux/fit/SARIMA4 create mode 100644 timeseries/tests/data/linux/pred/predSARIMA3 create mode 100644 timeseries/tests/data/linux/pred/predSARIMA4 create mode 100644 timeseries/tests/data/windows/fit/SARIMA3 create mode 100644 timeseries/tests/data/windows/fit/SARIMA4 create mode 100644 timeseries/tests/data/windows/pred/predSARIMA3 create mode 100644 timeseries/tests/data/windows/pred/predSARIMA4 diff --git a/timeseries/tests/data/linux/fit/SARIMA3 b/timeseries/tests/data/linux/fit/SARIMA3 new file mode 100644 index 0000000000000000000000000000000000000000..a4cbc49a35c8864e7f24d93ff51c11872c5e6e35 GIT binary patch literal 2607 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 C4(vt% literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/linux/pred/predSARIMA4 b/timeseries/tests/data/linux/pred/predSARIMA4 new file mode 100644 index 0000000000000000000000000000000000000000..ce0fee96d93d0c8e58d80ad4b681022d95ab5b00 GIT binary patch literal 8016 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 JJ4$%uzX4jZV1@ty literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/windows/fit/SARIMA4 b/timeseries/tests/data/windows/fit/SARIMA4 new file mode 100644 index 0000000000000000000000000000000000000000..7f59648885841bd51ecd901d368505bae859521f GIT binary patch literal 1967 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 literal 0 HcmV?d00001 diff --git a/timeseries/tests/data/windows/pred/predSARIMA4 b/timeseries/tests/data/windows/pred/predSARIMA4 new file mode 100644 index 0000000000000000000000000000000000000000..e27b8bbefc5416dd85113d4689dcb4404c7ce111 GIT binary patch literal 8016 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 literal 0 HcmV?d00001 From 6ec16e405f8462960a93890815698fcff08f6946 Mon Sep 17 00:00:00 2001 From: Deanna Morgan Date: Mon, 12 Oct 2020 16:49:17 +0100 Subject: [PATCH 76/77] conflict fixes --- clust/tests/util.t | 1 - 1 file changed, 1 deletion(-) diff --git a/clust/tests/util.t b/clust/tests/util.t index 050f9f35..a75f1eba 100644 --- a/clust/tests/util.t +++ b/clust/tests/util.t @@ -18,7 +18,6 @@ idxs2:til count d2 0 // K-D trees tree:.ml.clust.kd.newtree[d1;1] tree2:.ml.clust.kd.newtree[d2;2] -<<<<<<< HEAD // Configurations iter:`run`total`nochange!0 200 15 From 726c30c972856c6fa51ebc8e08c18e58203a936f Mon Sep 17 00:00:00 2001 From: Deanna Morgan Date: Mon, 12 Oct 2020 17:04:59 +0100 Subject: [PATCH 77/77] conflict fixes --- clust/tests/util.t | 1 - 1 file changed, 1 deletion(-) diff --git a/clust/tests/util.t b/clust/tests/util.t index a75f1eba..0b078fe7 100644 --- a/clust/tests/util.t +++ b/clust/tests/util.t @@ -78,7 +78,6 @@ kdRes8:kdKey!(1b;1b;2;1;0#0;0N;0n;0 2) passingTest[nnRes`closestPoint;(tree;d1;`edist;0;d2[;2]);1b;2] passingTest[nnRes`closestPoint;(tree2;d2;`edist;1 2 3;d1[;1]);1b;0] passingTest[nnRes`closestPoint;(tree2;d2;`edist;1 5 2;d1[;3]);1b;3] -passingTest[nnRes`closestPoint`closestDist;(tree;d1;`mdist;1 2 3 4;d1[;1]);1b;(0;2f)] passingTest[nnRes`closestPoint`closestDist;(tree;d1;`mdist;1;7 9f);1b;(4;8f)] passingTest[nnRes`closestPoint`closestDist;(tree2;d2;`edist;0;d2[;2]);1b;(2;0f)] passingTest[.ml.clust.kd.findleaf;(tree;d1[;1];tree 0);0b;kdRes5]