[
  {
    "path": ".circleci/config.yml",
    "content": "version: 2\njobs:\n  build:\n    docker:\n      - image: circleci/golang:1.14\n    steps:\n      - checkout\n      - run:\n          name: Tests\n          command: |\n            go fmt ./...\n            go vet ./...\n            go test -v ./...\n"
  },
  {
    "path": ".editorconfig",
    "content": "# http://editorconfig.org\nroot = true\n\n[*]\nindent_size = 2\nindent_style =  space\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[*.go]\nindent_style = tab\n\n[*.{js,jsx,json,html}]\nindent_size = 4\n\n[Makefile]\nindent_style = tab\n\n[*.md]\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".gitignore",
    "content": "vendor/\nbin/\ndata/\n.DS_Store\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n    \"go.testFlags\": [\"-v\", \"-count=1\"]\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright 2017-2020 Datasweet <http://www.datasweet.fr>\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "\n# datatable\n[![Go Report Card](https://goreportcard.com/badge/github.com/datasweet/datatable)](https://goreportcard.com/report/github.com/datasweet/datatable) [![GoDoc](https://godoc.org/github.com/datasweet/datatable?status.png)](https://godoc.org/github.com/datasweet/datatable) [![GitHub stars](https://img.shields.io/github/stars/datasweet/datatable.svg)](https://github.com/datasweet/datatable/stargazers)\n[![GitHub license](https://img.shields.io/github/license/datasweet/datatable.svg)](https://github.com/datasweet/datatable/blob/master/LICENSE)\n\n[![datasweet-logo](https://www.datasweet.fr/wp-content/uploads/2019/02/datasweet-black.png)](http://www.datasweet.fr)\n\ndatatable is a Go package to manipulate tabular data, like an excel spreadsheet. \ndatatable is inspired by the pandas python package and the data.frame R structure.\nAlthough it's production ready, be aware that we're still working on API improvements\n\n## Installation\n```\ngo get github.com/datasweet/datatable\n```\n\n## Features\n- Create custom Series (ie custom columns). Currently available, serie.Int, serie.String, serie.Time, serie.Float64. \n- Apply expressions\n- Selects (head, tail, subset)\n- Sorting\n- InnerJoin, LeftJoin, RightJoin, OuterJoin, Concats\n- Aggregate\n- Import from CSV\n- Export to map, slice\n\n\n### Creating a DataTable\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/datasweet/datatable\"\n)\n\nfunc main() {\n\tdt := datatable.New(\"test\")\n\tdt.AddColumn(\"champ\", datatable.String, datatable.Values(\"Malzahar\", \"Xerath\", \"Teemo\"))\n\tdt.AddColumn(\"champion\", datatable.String, datatable.Expr(\"upper(`champ`)\"))\n\tdt.AddColumn(\"win\", datatable.Int, datatable.Values(10, 20, 666))\n\tdt.AddColumn(\"loose\", datatable.Int, datatable.Values(6, 5, 666))\n\tdt.AddColumn(\"winRate\", datatable.Float64, datatable.Expr(\"`win` * 100 / (`win` + `loose`)\"))\n\tdt.AddColumn(\"winRate %\", datatable.String, datatable.Expr(\" `winRate` ~ \\\" %\\\"\"))\n\tdt.AddColumn(\"sum\", datatable.Float64, datatable.Expr(\"sum(`win`)\"))\n\n\tfmt.Println(dt)\n}\n\n/*\nCHAMP <NULLSTRING>      CHAMPION <NULLSTRING>   WIN <NULLINT>   LOOSE <NULLINT> WINRATE <NULLFLOAT64>   WINRATE % <NULLSTRING>  SUM <NULLFLOAT64> \nMalzahar                MALZAHAR                10              6               62.5                    62.5 %                  696              \nXerath                  XERATH                  20              5               80                      80 %                    696              \nTeemo                   TEEMO                   666             666             50                      50 %                    696    \n*/\n```\n\n\n### Reading a CSV and aggregate\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/datasweet/datatable\"\n\t\"github.com/datasweet/datatable/import/csv\"\n)\n\nfunc main() {\n\tdt, err := csv.Import(\"csv\", \"phone_data.csv\",\n\t\tcsv.HasHeader(true),\n\t\tcsv.AcceptDate(\"02/01/06 15:04\"),\n\t\tcsv.AcceptDate(\"2006-01\"),\n\t)\n\tif err != nil {\n\t\tlog.Fatalf(\"reading csv: %v\", err)\n\t}\n\n\tdt.Print(os.Stdout, datatable.PrintMaxRows(24))\n\n\tdt2, err := dt.Aggregate(datatable.AggregateBy{Type: datatable.Count, Field: \"index\"})\n\tif err != nil {\n\t\tlog.Fatalf(\"aggregate COUNT('index'): %v\", err)\n\t}\n\tfmt.Println(dt2)\n\n\tgroups, err := dt.GroupBy(datatable.GroupBy{\n\t\tName: \"year\",\n\t\tType: datatable.Int,\n\t\tKeyer: func(row datatable.Row) (interface{}, bool) {\n\t\t\tif d, ok := row[\"date\"]; ok {\n\t\t\t\tif tm, ok := d.(time.Time); ok {\n\t\t\t\t\treturn tm.Year(), true\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil, false\n\t\t},\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"GROUP BY 'year': %v\", err)\n\t}\n\tdt3, err := groups.Aggregate(\n\t\tdatatable.AggregateBy{Type: datatable.Sum, Field: \"duration\"},\n\t\tdatatable.AggregateBy{Type: datatable.CountDistinct, Field: \"network\"},\n\t)\n\tif err != nil {\n\t\tlog.Fatalf(\"Aggregate SUM('duration'), COUNT_DISTINCT('network') GROUP BY 'year': %v\", err)\n\t}\n\tfmt.Println(dt3)\n}\n```\n\n### Creating a custom serie\n\nTo create a custom serie you must provide:\n- a caster function, to cast a generic value to your serie value. The signature must be func(i interface{}) T\n- a comparator, to compare your serie value. The signature must be func(a, b T) int\n\nExample with a NullInt\n\n```go\n// IntN is an alis to create the custom Serie to manage IntN\nfunc IntN(v ...interface{}) Serie {\n\ts, _ := New(NullInt{}, asNullInt, compareNullInt)\n\tif len(v) > 0 {\n\t\ts.Append(v...)\n\t}\n\treturn s\n}\n\ntype NullInt struct {\n\tInt   int\n\tValid bool\n}\n\n// Interface() to render the current struct as a value.\n// If not provided, the serie.All() or serie.Get() wills returns the embedded value\n// IE: NullInt{}\nfunc (i NullInt) Interface() interface{} {\n\tif i.Valid {\n\t\treturn i.Int\n\t}\n\treturn nil\n}\n\n// asNullInt is our caster function\nfunc asNullInt(i interface{}) NullInt {\n\tvar ni NullInt\n\tif i == nil {\n\t\treturn ni\n\t}\n\n\tif v, ok := i.(NullInt); ok {\n\t\treturn v\n\t}\n\n\tif v, err := cast.ToIntE(i); err == nil {\n\t\tni.Int = v\n\t\tni.Valid = true\n\t}\n\treturn ni\n}\n\n// compareNullInt is our comparator function\n// used to sort\nfunc compareNullInt(a, b NullInt) int {\n\tif !b.Valid {\n\t\tif !a.Valid {\n\t\t\treturn Eq\n\t\t}\n\t\treturn Gt\n\t}\n\tif !a.Valid {\n\t\treturn Lt\n  }\n  if a.Int == b.Int {\n\t\treturn Eq\n\t}\n\tif a.Int < b.Int {\n\t\treturn Lt\n\t}\n\treturn Gt\n}\n```\n\n## Who are we ?\nWe are Datasweet, a french startup providing full service (big) data solutions.\n\n## Questions ? problems ? suggestions ?\nIf you find a bug or want to request a feature, please create a [GitHub Issue](https://github.com/datasweet/datatable/issues/new).\n\n## Contributors\n<table>\n <tr>\n  <td align=\"center\"><a href=\"https://github.com/constantoine\"><img src=\"https://avatars0.githubusercontent.com/u/13930958?s=100&v=4\" width=\"100\" /><br><sub><b>Cléo Rebert</b></a></td>\n </tr>\n</table>\n\n\n## License\n```\nThis software is licensed under the Apache License, version 2 (\"ALv2\"), quoted below.\n\nCopyright 2017-2020 Datasweet <http://www.datasweet.fr>\n\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not\nuse this file except in compliance with the License. You may obtain a copy of\nthe License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\nWARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\nLicense for the specific language governing permissions and limitations under\nthe License.\n```\n"
  },
  {
    "path": "aggregate.go",
    "content": "package datatable\n\nimport (\n\t\"bytes\"\n\t\"encoding/gob\"\n\t\"fmt\"\n\n\t\"github.com/cespare/xxhash\"\n\t\"github.com/datasweet/datatable/serie\"\n\t\"github.com/pkg/errors\"\n)\n\n// GroupBy defines the group by  configuration\n// Name is the name of the output column\n// Type is the type of the output column\n// Keyer is our main function to aggregate\ntype GroupBy struct {\n\tName  string\n\tType  ColumnType\n\tKeyer func(row Row) (interface{}, bool)\n}\n\n// AggregationType defines the avalaible aggregation\ntype AggregationType uint8\n\nconst (\n\tAvg AggregationType = iota\n\tCount\n\tCountDistinct\n\tCusum\n\tMax\n\tMin\n\tMedian\n\tStddev\n\tSum\n\tVariance\n)\n\nfunc (a AggregationType) String() string {\n\tswitch a {\n\tcase Avg:\n\t\treturn \"avg\"\n\tcase Count:\n\t\treturn \"count\"\n\tcase CountDistinct:\n\t\treturn \"count_distinct\"\n\tcase Cusum:\n\t\treturn \"cusum\"\n\tcase Max:\n\t\treturn \"max\"\n\tcase Min:\n\t\treturn \"min\"\n\tcase Median:\n\t\treturn \"median\"\n\tcase Stddev:\n\t\treturn \"stddev\"\n\tcase Sum:\n\t\treturn \"sum\"\n\tcase Variance:\n\t\treturn \"variance\"\n\tdefault:\n\t\tpanic(\"unkwown aggregation type\")\n\t}\n}\n\n// AggregateBy defines the aggregation\ntype AggregateBy struct {\n\tType  AggregationType\n\tField string\n\tAs    string\n}\n\n// GroupBy splits our datatable by group\nfunc (dt *DataTable) GroupBy(by ...GroupBy) (*Groups, error) {\n\tif len(by) == 0 {\n\t\treturn nil, ErrNoGroupBy\n\t}\n\n\tvar groups []*group\n\tgindex := make(map[uint64]int)\n\n\tfor pos := 0; pos < dt.nrows; pos++ {\n\t\trow := dt.Row(pos)\n\t\tbuf := bytes.NewBuffer(nil)\n\t\tenc := gob.NewEncoder(buf)\n\n\t\tbuckets := make([]interface{}, len(by))\n\n\t\tfor i, k := range by {\n\t\t\tk := &k\n\t\t\tif v, ok := k.Keyer(row); ok {\n\t\t\t\tbuckets[i] = v\n\t\t\t\tenc.Encode(v)\n\t\t\t}\n\t\t}\n\n\t\thash := xxhash.Sum64(buf.Bytes())\n\n\t\tif at, ok := gindex[hash]; ok {\n\t\t\tgroups[at].Rows = append(groups[at].Rows, pos)\n\t\t} else {\n\t\t\tgindex[hash] = len(groups)\n\t\t\tgroups = append(groups, &group{\n\t\t\t\tKey:     hash,\n\t\t\t\tBuckets: buckets,\n\t\t\t\tRows:    []int{pos},\n\t\t\t})\n\t\t}\n\t}\n\treturn &Groups{dt: dt, groups: groups, by: by}, nil\n}\n\n// Aggregate aggregates some field\nfunc (dt *DataTable) Aggregate(by ...AggregateBy) (*DataTable, error) {\n\tg := &Groups{\n\t\tdt: dt,\n\t\tgroups: []*group{\n\t\t\t&group{TakeAll: true},\n\t\t},\n\t}\n\treturn g.Aggregate(by...)\n}\n\n// Groups\ntype Groups struct {\n\tdt     *DataTable\n\tby     []GroupBy\n\tgroups []*group\n}\n\ntype group struct {\n\tKey     uint64\n\tBuckets []interface{}\n\tRows    []int\n\tTakeAll bool\n}\n\n// Aggregate our groups\nfunc (g *Groups) Aggregate(aggs ...AggregateBy) (*DataTable, error) {\n\tif g == nil {\n\t\treturn nil, ErrNoGroups\n\t}\n\n\tif g.dt == nil {\n\t\treturn nil, ErrNilDatatable\n\t}\n\n\t// check cols\n\tseries := make(map[string]serie.Serie)\n\tfor _, agg := range aggs {\n\t\tcol := g.dt.Column(agg.Field)\n\t\tif col == nil {\n\t\t\terr := errors.Errorf(\"column '%s' not found\", agg.Field)\n\t\t\treturn nil, errors.Wrap(err, ErrColumnNotFound.Error())\n\t\t}\n\t\tswitch agg.Type {\n\t\tcase Avg, Count, CountDistinct, Cusum, Max, Min, Median, Stddev, Sum, Variance:\n\t\t\tseries[agg.Field] = col.(*column).serie\n\t\tdefault:\n\t\t\treturn nil, ErrUnknownAgg\n\t\t}\n\t}\n\n\tout := New(g.dt.name)\n\n\t// create columns\n\tfor _, by := range g.by {\n\t\ttyp := by.Type\n\t\tif len(typ) == 0 {\n\t\t\ttyp = Raw\n\t\t}\n\t\tif err := out.AddColumn(by.Name, typ); err != nil {\n\t\t\terr = errors.Wrapf(err, \"can't add column '%s'\", by.Name)\n\t\t\treturn nil, errors.Wrap(err, ErrCantAddColumn.Error())\n\t\t}\n\t}\n\tfor _, agg := range aggs {\n\t\tname := agg.As\n\t\tif len(name) == 0 {\n\t\t\tname = fmt.Sprintf(\"%s %s\", agg.Type, agg.Field)\n\t\t}\n\t\ttyp := Float64\n\t\tswitch agg.Type {\n\t\tcase Count, CountDistinct:\n\t\t\ttyp = Int64\n\t\tdefault:\n\t\t}\n\t\tif err := out.AddColumn(name, typ); err != nil {\n\t\t\terr = errors.Wrapf(err, \"can't add column '%s'\", name)\n\t\t\treturn nil, errors.Wrap(err, ErrCantAddColumn.Error())\n\t\t}\n\t}\n\n\t// aggregate the series\n\tfor _, group := range g.groups {\n\t\tvalues := make([]interface{}, 0, len(group.Buckets)+len(aggs))\n\t\tvalues = append(values, group.Buckets...)\n\n\t\tfor _, agg := range aggs {\n\t\t\tserie := series[agg.Field]\n\n\t\t\tif !group.TakeAll {\n\t\t\t\tserie = serie.Pick(group.Rows...)\n\t\t\t}\n\n\t\t\tswitch agg.Type {\n\t\t\tcase Avg:\n\t\t\t\tvalues = append(values, serie.Avg())\n\t\t\tcase Count:\n\t\t\t\tvalues = append(values, serie.Count())\n\t\t\tcase CountDistinct:\n\t\t\t\tvalues = append(values, serie.CountDistinct())\n\t\t\tcase Cusum:\n\t\t\t\tvalues = append(values, serie.Cusum())\n\t\t\tcase Max:\n\t\t\t\tvalues = append(values, serie.Max())\n\t\t\tcase Min:\n\t\t\t\tvalues = append(values, serie.Min())\n\t\t\tcase Median:\n\t\t\t\tvalues = append(values, serie.Median())\n\t\t\tcase Stddev:\n\t\t\t\tvalues = append(values, serie.Stddev())\n\t\t\tcase Sum:\n\t\t\t\tvalues = append(values, serie.Sum())\n\t\t\tcase Variance:\n\t\t\t\tvalues = append(values, serie.Variance())\n\t\t\t}\n\t\t}\n\t\tout.AppendRow(values...)\n\t}\n\n\treturn out, nil\n}\n"
  },
  {
    "path": "aggregate_test.go",
    "content": "package datatable_test\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/datasweet/datatable\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestAggregate(t *testing.T) {\n\tcustomers, orders := sampleForJoin()\n\n\tdt, err := customers.LeftJoin(orders, datatable.On(\"[Customers].[id]\", \"[Orders].[user_id]\"))\n\tassert.NoError(t, err)\n\tassert.NotNil(t, dt)\n\tfmt.Println(dt)\n\n\t// Aggregate by SUM\n\tout, err := dt.Aggregate(datatable.AggregateBy{datatable.Sum, \"prix_total\", \"sum_prix_total\"})\n\tassert.NoError(t, err)\n\tassert.NotNil(t, out)\n\tfmt.Println(out)\n\n\t// Aggregate by SUM(prix_total), COUNT_DISTINCT(ville)\n\tout, err = dt.Aggregate(datatable.AggregateBy{datatable.Sum, \"prix_total\", \"sum_prix_total\"}, datatable.AggregateBy{datatable.CountDistinct, \"ville\", \"uniq_count_ville\"})\n\tassert.NoError(t, err)\n\tassert.NotNil(t, out)\n\tfmt.Println(out)\n\n\tgroups, err := dt.GroupBy(\n\t\tdatatable.GroupBy{\n\t\t\tName: \"Year\",\n\t\t\tType: datatable.Int64,\n\t\t\tKeyer: func(row datatable.Row) (interface{}, bool) {\n\t\t\t\tt, ok := row[\"date_achat\"].(time.Time)\n\t\t\t\tif !ok {\n\t\t\t\t\treturn 0, false\n\t\t\t\t}\n\t\t\t\treturn t.Year(), true\n\t\t\t},\n\t\t},\n\t\tdatatable.GroupBy{\n\t\t\tName: \"Month\",\n\t\t\tType: datatable.Int,\n\t\t\tKeyer: func(row datatable.Row) (interface{}, bool) {\n\t\t\t\tt, ok := row[\"date_achat\"].(time.Time)\n\t\t\t\tif !ok {\n\t\t\t\t\treturn 0, false\n\t\t\t\t}\n\t\t\t\treturn int(t.Month()), true\n\t\t\t},\n\t\t},\n\t)\n\tassert.NoError(t, err)\n\tassert.NotNil(t, groups)\n\n\tgdt, err := groups.Aggregate(datatable.AggregateBy{datatable.Sum, \"prix_total\", \"sum_prix_total\"})\n\tassert.NoError(t, err)\n\tfmt.Println(gdt)\n}\n"
  },
  {
    "path": "column.go",
    "content": "package datatable\n\nimport (\n\t\"reflect\"\n\t\"strings\"\n\n\t\"github.com/datasweet/datatable/serie\"\n\t\"github.com/datasweet/expr\"\n\t\"github.com/pkg/errors\"\n)\n\n// ColumnType defines the valid column type in datatable\ntype ColumnType string\n\nconst (\n\tBool   ColumnType = \"bool\"\n\tString ColumnType = \"string\"\n\tInt    ColumnType = \"int\"\n\t// Int8     ColumnType = \"int8\"\n\t// Int16    ColumnType = \"int16\"\n\tInt32 ColumnType = \"int32\"\n\tInt64 ColumnType = \"int64\"\n\t// Uint  ColumnType = \"uint\"\n\t// Uint8     ColumnType = \"uint8\"\n\t// Uint16    ColumnType = \"uint16\"\n\t// Uint32    ColumnType = \"uint32\"\n\t// Uint64    ColumnType = \"uint64\"\n\tFloat32 ColumnType = \"float32\"\n\tFloat64 ColumnType = \"float64\"\n\tTime    ColumnType = \"time\"\n\tRaw     ColumnType = \"raw\"\n)\n\n// ColumnOptions describes options to be apply on a column\ntype ColumnOptions struct {\n\tHidden      bool\n\tExpr        string\n\tValues      []interface{}\n\tTimeFormats []string\n}\n\n// ColumnOption sets column options\ntype ColumnOption func(opts *ColumnOptions)\n\n// ColumnHidden sets the visibility\nfunc ColumnHidden(v bool) ColumnOption {\n\treturn func(opts *ColumnOptions) {\n\t\topts.Hidden = v\n\t}\n}\n\n// Expr sets the expr for the column\n// <!> Incompatible with ColumnValues\nfunc Expr(v string) ColumnOption {\n\treturn func(opts *ColumnOptions) {\n\t\topts.Expr = v\n\t}\n}\n\n// Values fills the column with the values\n// <!> Incompatible with ColumnExpr\nfunc Values(v ...interface{}) ColumnOption {\n\treturn func(opts *ColumnOptions) {\n\t\topts.Values = v\n\t}\n}\n\n// TimeFormats sets the valid time formats.\n// <!> Only for Time Column\nfunc TimeFormats(v ...string) ColumnOption {\n\treturn func(opts *ColumnOptions) {\n\t\topts.TimeFormats = append(opts.TimeFormats, v...)\n\t}\n}\n\n// ColumnSerier to create a serie from column options\ntype ColumnSerier func(ColumnOptions) serie.Serie\n\n// ctypes is our column type registry\nvar ctypes map[ColumnType]ColumnSerier\n\nfunc init() {\n\tctypes = make(map[ColumnType]ColumnSerier)\n\tRegisterColumnType(Bool, func(opts ColumnOptions) serie.Serie {\n\t\treturn serie.BoolN(opts.Values...)\n\t})\n\tRegisterColumnType(String, func(opts ColumnOptions) serie.Serie {\n\t\treturn serie.StringN(opts.Values...)\n\t})\n\tRegisterColumnType(Int, func(opts ColumnOptions) serie.Serie {\n\t\treturn serie.IntN(opts.Values...)\n\t})\n\tRegisterColumnType(Int32, func(opts ColumnOptions) serie.Serie {\n\t\treturn serie.Int32N(opts.Values...)\n\t})\n\tRegisterColumnType(Int64, func(opts ColumnOptions) serie.Serie {\n\t\treturn serie.Int64N(opts.Values...)\n\t})\n\tRegisterColumnType(Float32, func(opts ColumnOptions) serie.Serie {\n\t\treturn serie.Float32N(opts.Values...)\n\t})\n\tRegisterColumnType(Float64, func(opts ColumnOptions) serie.Serie {\n\t\treturn serie.Float64N(opts.Values...)\n\t})\n\tRegisterColumnType(Time, func(opts ColumnOptions) serie.Serie {\n\t\tsr := serie.TimeN(opts.TimeFormats...)\n\t\tif len(opts.Values) > 0 {\n\t\t\tsr.Append(opts.Values...)\n\t\t}\n\t\treturn sr\n\t})\n\tRegisterColumnType(Raw, func(opts ColumnOptions) serie.Serie {\n\t\treturn serie.Raw(opts.Values...)\n\t})\n}\n\n// RegisterColumnType to extends the known type\nfunc RegisterColumnType(name ColumnType, serier ColumnSerier) error {\n\tname = ColumnType(strings.TrimSpace(string(name)))\n\tif len(name) == 0 {\n\t\treturn ErrEmptyName\n\t}\n\tif serier == nil {\n\t\treturn ErrNilFactory\n\t}\n\tif _, ok := ctypes[name]; ok {\n\t\terr := errors.Errorf(\"type '%s' already exists\", name)\n\t\treturn errors.Wrap(err, ErrTypeAlreadyExists.Error())\n\t}\n\tctypes[name] = serier\n\treturn nil\n}\n\n// ColumnTypes to list all column type\nfunc ColumnTypes() []ColumnType {\n\tctyp := make([]ColumnType, 0, len(ctypes))\n\tfor k := range ctypes {\n\t\tctyp = append(ctyp, k)\n\t}\n\treturn ctyp\n}\n\n// newColumnSerie to create a serie from a known type\nfunc newColumnSerie(ctyp ColumnType, options ColumnOptions) (serie.Serie, error) {\n\tif s, ok := ctypes[ctyp]; ok {\n\t\treturn s(options), nil\n\t}\n\terr := errors.Errorf(\"unknown column type '%s'\", ctyp)\n\treturn nil, errors.Wrap(err, ErrUnknownColumnType.Error())\n}\n\n// Column describes a column in our datatable\ntype Column interface {\n\tName() string\n\tType() ColumnType\n\tUnderlyingType() reflect.Type\n\tIsVisible() bool\n\tIsComputed() bool\n\t//Clone(includeValues bool) Column\n}\n\ntype column struct {\n\tname     string\n\ttyp      ColumnType\n\thidden   bool\n\tformulae string\n\texpr     expr.Node\n\tserie    serie.Serie\n}\n\nfunc (c *column) Name() string {\n\treturn c.name\n}\n\nfunc (c *column) Type() ColumnType {\n\treturn c.typ\n}\n\nfunc (c *column) UnderlyingType() reflect.Type {\n\treturn c.serie.Type()\n}\n\nfunc (c *column) IsVisible() bool {\n\treturn !c.hidden\n}\n\nfunc (c *column) IsComputed() bool {\n\treturn len(c.formulae) > 0\n}\n\nfunc (c *column) emptyCopy() *column {\n\tcpy := &column{\n\t\tname:     c.name,\n\t\ttyp:      c.typ,\n\t\thidden:   c.hidden,\n\t\tformulae: c.formulae,\n\t\tserie:    c.serie.EmptyCopy(),\n\t}\n\tif len(cpy.formulae) > 0 {\n\t\tif parsed, err := expr.Parse(cpy.formulae); err == nil {\n\t\t\tcpy.expr = parsed\n\t\t}\n\t}\n\treturn cpy\n}\n\nfunc (c *column) copy() *column {\n\tcpy := &column{\n\t\tname:     c.name,\n\t\ttyp:      c.typ,\n\t\thidden:   c.hidden,\n\t\tformulae: c.formulae,\n\t\tserie:    c.serie.Copy(),\n\t}\n\tif len(cpy.formulae) > 0 {\n\t\tif parsed, err := expr.Parse(cpy.formulae); err == nil {\n\t\t\tcpy.expr = parsed\n\t\t}\n\t}\n\treturn cpy\n}\n"
  },
  {
    "path": "concat.go",
    "content": "package datatable\n\n// Concat datatables\nfunc (left *DataTable) Concat(table ...*DataTable) (*DataTable, error) {\n\tout := left.EmptyCopy()\n\tout.dirty = true\n\n\ttables := make([]*DataTable, 0, 1+len(table))\n\ttables = append(tables, left)\n\ttables = append(tables, table...)\n\n\tfor _, t := range tables {\n\t\tif t == nil {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, tc := range t.cols {\n\t\t\tpos := out.ColumnIndex(tc.name)\n\t\t\tif pos >= 0 {\n\t\t\t\toc := out.cols[pos]\n\t\t\t\tif oc.IsComputed() {\n\t\t\t\t\toc.serie.Grow(out.nrows - oc.serie.Len() + tc.serie.Len())\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif err := oc.serie.Concat(tc.serie); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tout.cols = append(out.cols, tc.emptyCopy())\n\t\t\t\toc := out.cols[len(out.cols)-1]\n\t\t\t\toc.serie.Grow(out.nrows - oc.serie.Len())\n\t\t\t\tif oc.IsComputed() {\n\t\t\t\t\toc.serie.Grow(tc.serie.Len())\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif err := oc.serie.Concat(tc.serie); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tout.nrows += t.nrows\n\t}\n\n\t// check\n\tfor _, oc := range out.cols {\n\t\tsize := out.nrows - oc.serie.Len()\n\t\tif size > 0 {\n\t\t\toc.serie.Grow(size)\n\t\t}\n\t}\n\n\treturn out, nil\n}\n\n// Concat datatables\nfunc Concat(tables []*DataTable) (*DataTable, error) {\n\tswitch len(tables) {\n\tcase 0:\n\t\treturn nil, ErrNoTables\n\tcase 1:\n\t\treturn tables[0].Concat()\n\tdefault:\n\t\treturn tables[0].Concat(tables[1:]...)\n\t}\n}\n"
  },
  {
    "path": "concat_test.go",
    "content": "package datatable_test\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/datasweet/datatable\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\n// Sample from https://sql.sh/cours/union\nfunc sampleForConcat(t *testing.T) (*datatable.DataTable, *datatable.DataTable, *datatable.DataTable) {\n\ta := datatable.New(\"magasin1\")\n\ta.AddColumn(\"prenom\", datatable.String)\n\ta.AddColumn(\"nom\", datatable.String)\n\ta.AddColumn(\"ville\", datatable.String)\n\ta.AddColumn(\"date_naissance\", datatable.Time)\n\ta.AddColumn(\"total_achat\", datatable.Int64)\n\n\ta.AppendRow(\"Léon\", \"Dupuis\", \"Paris\", \"1983-03-06\", 135)\n\ta.AppendRow(\"Marie\", \"Bernard\", \"Paris\", \"1993-07-03\", 75)\n\ta.AppendRow(\"Sophie\", \"Dupond\", \"Marseille\", \"1986-02-22\", 27)\n\ta.AppendRow(\"Marcel\", \"Martin\", \"Paris\", \"1976-11-24\", 39)\n\n\tb := datatable.New(\"magasin2\")\n\tb.AddColumn(\"prenom\", datatable.String)\n\tb.AddColumn(\"nom\", datatable.String)\n\tb.AddColumn(\"ville\", datatable.String)\n\tb.AddColumn(\"date_naissance\", datatable.Time)\n\tb.AddColumn(\"total_achat\", datatable.Int64)\n\n\tb.AppendRow(\"Marion\", \"Leroy\", \"Lyon\", \"1982-10-27\", 285)\n\tb.AppendRow(\"Paul\", \"Moreau\", \"Lyon\", \"1976-04-19\", 133)\n\tb.AppendRow(\"Marie\", \"Bernard\", \"Paris\", \"1993-07-03\", 75)\n\tb.AppendRow(\"Marcel\", \"Martin\", \"Paris\", \"1976-11-24\", 39)\n\n\tc := datatable.New(\"magasin3\")\n\tc.AddColumn(\"prenom\", datatable.String)\n\tc.AddColumn(\"nom\", datatable.String)\n\tc.AddColumn(\"ville\", datatable.String)\n\tc.AddColumn(\"date_naissance\", datatable.Time)\n\tc.AddColumn(\"marge\", datatable.Float64)\n\n\tc.AppendRow(\"Marion\", \"Leroy\", \"Lyon\", \"1982-10-27\", 5.2)\n\tc.AppendRow(\"Marie\", \"Bernard\", \"Paris\", \"1993-07-03\", 0.8)\n\n\treturn a, b, c\n}\n\nfunc TestSimpleConcat(t *testing.T) {\n\ta, b, _ := sampleForConcat(t)\n\tdt, err := a.Concat(b)\n\tassert.NoError(t, err)\n\tassert.Equal(t, \"magasin1\", dt.Name())\n\tassert.Equal(t, 8, dt.NumRows())\n\n\tcheckTable(t, dt,\n\t\t\"prenom\", \"nom\", \"ville\", \"date_naissance\", \"total_achat\",\n\t\t\"Léon\", \"Dupuis\", \"Paris\", time.Date(1983, time.March, 6, 0, 0, 0, 0, time.UTC), int64(135),\n\t\t\"Marie\", \"Bernard\", \"Paris\", time.Date(1993, time.July, 3, 0, 0, 0, 0, time.UTC), int64(75),\n\t\t\"Sophie\", \"Dupond\", \"Marseille\", time.Date(1986, time.February, 22, 0, 0, 0, 0, time.UTC), int64(27),\n\t\t\"Marcel\", \"Martin\", \"Paris\", time.Date(1976, time.November, 24, 0, 0, 0, 0, time.UTC), int64(39),\n\t\t\"Marion\", \"Leroy\", \"Lyon\", time.Date(1982, time.October, 27, 0, 0, 0, 0, time.UTC), int64(285),\n\t\t\"Paul\", \"Moreau\", \"Lyon\", time.Date(1976, time.April, 19, 0, 0, 0, 0, time.UTC), int64(133),\n\t\t\"Marie\", \"Bernard\", \"Paris\", time.Date(1993, time.July, 3, 0, 0, 0, 0, time.UTC), int64(75),\n\t\t\"Marcel\", \"Martin\", \"Paris\", time.Date(1976, time.November, 24, 0, 0, 0, 0, time.UTC), int64(39),\n\t)\n}\n\nfunc TestGrowColConcat(t *testing.T) {\n\ta, b, c := sampleForConcat(t)\n\tdt, err := a.Concat(b, c)\n\tassert.NoError(t, err)\n\tassert.Equal(t, \"magasin1\", dt.Name())\n\tassert.Equal(t, 10, dt.NumRows())\n\n\tcheckTable(t, dt,\n\t\t\"prenom\", \"nom\", \"ville\", \"date_naissance\", \"total_achat\", \"marge\",\n\t\t\"Léon\", \"Dupuis\", \"Paris\", time.Date(1983, time.March, 6, 0, 0, 0, 0, time.UTC), int64(135), nil,\n\t\t\"Marie\", \"Bernard\", \"Paris\", time.Date(1993, time.July, 3, 0, 0, 0, 0, time.UTC), int64(75), nil,\n\t\t\"Sophie\", \"Dupond\", \"Marseille\", time.Date(1986, time.February, 22, 0, 0, 0, 0, time.UTC), int64(27), nil,\n\t\t\"Marcel\", \"Martin\", \"Paris\", time.Date(1976, time.November, 24, 0, 0, 0, 0, time.UTC), int64(39), nil,\n\t\t\"Marion\", \"Leroy\", \"Lyon\", time.Date(1982, time.October, 27, 0, 0, 0, 0, time.UTC), int64(285), nil,\n\t\t\"Paul\", \"Moreau\", \"Lyon\", time.Date(1976, time.April, 19, 0, 0, 0, 0, time.UTC), int64(133), nil,\n\t\t\"Marie\", \"Bernard\", \"Paris\", time.Date(1993, time.July, 3, 0, 0, 0, 0, time.UTC), int64(75), nil,\n\t\t\"Marcel\", \"Martin\", \"Paris\", time.Date(1976, time.November, 24, 0, 0, 0, 0, time.UTC), int64(39), nil,\n\t\t\"Marion\", \"Leroy\", \"Lyon\", time.Date(1982, time.October, 27, 0, 0, 0, 0, time.UTC), nil, float64(5.2),\n\t\t\"Marie\", \"Bernard\", \"Paris\", time.Date(1993, time.July, 3, 0, 0, 0, 0, time.UTC), nil, float64(0.8),\n\t)\n}\n\nfunc TestConcatWithExpr(t *testing.T) {\n\ta, b, _ := sampleForConcat(t)\n\ta.AddColumn(\"upper_ville\", datatable.String, datatable.Expr(\"UPPER(ville)\"))\n\tb.AddColumn(\"upper_ville\", datatable.String, datatable.Expr(\"UPPER(ville)\"))\n\n\tdt, err := a.Concat(b)\n\tassert.NoError(t, err)\n\tassert.Equal(t, \"magasin1\", dt.Name())\n\tassert.Equal(t, 8, dt.NumRows())\n\n\tcheckTable(t, dt,\n\t\t\"prenom\", \"nom\", \"ville\", \"date_naissance\", \"total_achat\", \"upper_ville\",\n\t\t\"Léon\", \"Dupuis\", \"Paris\", time.Date(1983, time.March, 6, 0, 0, 0, 0, time.UTC), int64(135), \"PARIS\",\n\t\t\"Marie\", \"Bernard\", \"Paris\", time.Date(1993, time.July, 3, 0, 0, 0, 0, time.UTC), int64(75), \"PARIS\",\n\t\t\"Sophie\", \"Dupond\", \"Marseille\", time.Date(1986, time.February, 22, 0, 0, 0, 0, time.UTC), int64(27), \"MARSEILLE\",\n\t\t\"Marcel\", \"Martin\", \"Paris\", time.Date(1976, time.November, 24, 0, 0, 0, 0, time.UTC), int64(39), \"PARIS\",\n\t\t\"Marion\", \"Leroy\", \"Lyon\", time.Date(1982, time.October, 27, 0, 0, 0, 0, time.UTC), int64(285), \"LYON\",\n\t\t\"Paul\", \"Moreau\", \"Lyon\", time.Date(1976, time.April, 19, 0, 0, 0, 0, time.UTC), int64(133), \"LYON\",\n\t\t\"Marie\", \"Bernard\", \"Paris\", time.Date(1993, time.July, 3, 0, 0, 0, 0, time.UTC), int64(75), \"PARIS\",\n\t\t\"Marcel\", \"Martin\", \"Paris\", time.Date(1976, time.November, 24, 0, 0, 0, 0, time.UTC), int64(39), \"PARIS\",\n\t)\n}\n"
  },
  {
    "path": "copy.go",
    "content": "package datatable\n\n// EmptyCopy copies the structure of datatable (no values)\nfunc (t *DataTable) EmptyCopy() *DataTable {\n\tcpy := &DataTable{\n\t\tname:    t.name,\n\t\tdirty:   t.dirty,\n\t\thasExpr: t.hasExpr,\n\t\tnrows:   0,\n\t\tcols:    make([]*column, len(t.cols)),\n\t}\n\n\tfor i, col := range t.cols {\n\t\tcpy.cols[i] = col.emptyCopy()\n\t}\n\n\treturn cpy\n}\n\n// Copy the datatable\nfunc (t *DataTable) Copy() *DataTable {\n\tcpy := &DataTable{\n\t\tname:    t.name,\n\t\tdirty:   t.dirty,\n\t\thasExpr: t.hasExpr,\n\t\tnrows:   t.nrows,\n\t\tcols:    make([]*column, len(t.cols)),\n\t}\n\n\tfor i, col := range t.cols {\n\t\tcpy.cols[i] = col.copy()\n\t}\n\n\treturn cpy\n}\n"
  },
  {
    "path": "copy_test.go",
    "content": "package datatable_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestEmptyCopy(t *testing.T) {\n\ttb := New(t)\n\tcpy := tb.EmptyCopy()\n\n\tassert.NotNil(t, cpy)\n\tassert.NotSame(t, tb, cpy)\n\tassert.Equal(t, 0, cpy.NumRows())\n\tassert.Equal(t, tb.NumCols(), cpy.NumCols())\n}\n\nfunc TestCopy(t *testing.T) {\n\ttb := New(t)\n\tcpy := tb.Copy()\n\tassert.NotNil(t, cpy)\n\tassert.NotSame(t, tb, cpy)\n\tassert.Equal(t, tb.NumRows(), cpy.NumRows())\n\tassert.Equal(t, tb.NumCols(), cpy.NumCols())\n\n\tcheckTable(t, cpy,\n\t\t\"champ\", \"champion\", \"win\", \"loose\", \"winRate\", \"sum\", \"ok\",\n\t\t\"Malzahar\", \"MALZAHAR\", 10, 6, \"62.5 %\", 696.0, true,\n\t\t\"Xerath\", \"XERATH\", 20, 5, \"80 %\", 696.0, true,\n\t\t\"Teemo\", \"TEEMO\", 666, 666, \"50 %\", 696.0, true,\n\t)\n}\n"
  },
  {
    "path": "errors.go",
    "content": "package datatable\n\nimport (\n\t\"github.com/pkg/errors\"\n)\n\n// Errors in import/csv\nvar (\n\tErrOpenFile           = errors.New(\"open file\")\n\tErrCantReadHeaders    = errors.New(\"can't read headers\")\n\tErrReadingLine        = errors.New(\"could not read line\")\n\tErrNilDatas           = errors.New(\"nil datas\")\n\tErrWrongNumberOfTypes = errors.New(\"expected different number of types\")\n\tErrAddingColumn       = errors.New(\"could not add column with given type\")\n)\n\n// Errors in aggregate.go\nvar (\n\tErrNoGroupBy      = errors.New(\"no groupby\")\n\tErrNoGroups       = errors.New(\"no groups\")\n\tErrNilDatatable   = errors.New(\"nil datatable\")\n\tErrColumnNotFound = errors.New(\"column not found\")\n\tErrUnknownAgg     = errors.New(\"unknown agg\")\n\tErrCantAddColumn  = errors.New(\"can't add column\")\n)\n\n// Errors in column.go\nvar (\n\tErrEmptyName         = errors.New(\"empty name\")\n\tErrNilFactory        = errors.New(\"nil factory\")\n\tErrTypeAlreadyExists = errors.New(\"type already exists\")\n\tErrUnknownColumnType = errors.New(\"unknown column type\")\n)\n\n// Errors in concat.go\nvar (\n\tErrNoTables = errors.New(\"no tables\")\n)\n\n// Errors in eval_expr\nvar (\n\tErrEvaluateExprSizeMismatch = errors.New(\"size mismatch\")\n)\n\n// Errors in join.go\nvar (\n\tErrNilOutputDatatable  = errors.New(\"nil output datatable\")\n\tErrNoOutput            = errors.New(\"no output\")\n\tErrNilTable            = errors.New(\"table is nil\")\n\tErrNotEnoughDatatables = errors.New(\"not enough datatables\")\n\tErrNoOnClauses         = errors.New(\"no on clauses\")\n\tErrOnClauseIsNil       = errors.New(\"on clause is nil\")\n\tErrUnknownMode         = errors.New(\"unknown mode\")\n)\n\n// Errors in mutate_column.go\nvar (\n\tErrNilColumn           = errors.New(\"nil column\")\n\tErrNilColumnName       = errors.New(\"nil column name\")\n\tErrNilColumnType       = errors.New(\"nil column type\")\n\tErrColumnAlreadyExists = errors.New(\"column already exists\")\n\tErrFormulaeSyntax      = errors.New(\"formulae syntax\")\n\tErrNilSerie            = errors.New(\"nil serie\")\n\tErrCreateSerie         = errors.New(\"create serie\")\n)\n\n// Errors in mutate_rows.go\nvar (\n\tErrLengthMismatch = errors.New(\"length mismatch\")\n\tErrUpdateRow      = errors.New(\"update row\")\n)\n"
  },
  {
    "path": "eval_expr.go",
    "content": "package datatable\n\nimport \"github.com/pkg/errors\"\n\n// evaluateExpressions to evaluate all columns with a binded expression\nfunc (t *DataTable) evaluateExpressions() error {\n\tif !t.dirty || !t.hasExpr {\n\t\treturn nil\n\t}\n\n\tvar cols []int\n\tvar exprCols []int\n\tfor i, c := range t.cols {\n\t\tif c.IsComputed() {\n\t\t\texprCols = append(exprCols, i)\n\t\t} else {\n\t\t\tcols = append(cols, i)\n\t\t}\n\t}\n\n\tl := len(exprCols)\n\tif l == 0 {\n\t\tt.dirty = false\n\t\treturn nil\n\t}\n\n\t// Initialize params\n\tparams := make(map[string][]interface{}, len(t.cols))\n\tfor _, pos := range cols {\n\t\tcol := t.cols[pos]\n\t\tparams[col.name] = col.serie.All()\n\t}\n\n\t// Evaluate\n\tfor _, idx := range exprCols {\n\t\tcol := t.cols[idx]\n\t\tres, err := col.expr.Eval(params)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tname := col.Name()\n\n\t\tif arr, ok := res.([]interface{}); ok {\n\t\t\t// Is array\n\t\t\tls := col.serie.Len()\n\t\t\tla := len(arr)\n\n\t\t\tif t.nrows != ls || la != ls {\n\t\t\t\terr := errors.Errorf(\"evaluate expr : size mismatch %d vs %d\", la, ls)\n\t\t\t\treturn errors.Wrap(err, ErrEvaluateExprSizeMismatch.Error())\n\t\t\t}\n\n\t\t\tfor i := 0; i < t.nrows; i++ {\n\t\t\t\tcol.serie.Set(i, arr[i])\n\t\t\t}\n\n\t\t} else {\n\t\t\t// Is scalar\n\t\t\tfor i := 0; i < t.nrows; i++ {\n\t\t\t\tcol.serie.Set(i, res)\n\t\t\t}\n\t\t}\n\n\t\t// update dependency\n\t\tparams[name] = col.serie.All()\n\t}\n\n\tt.dirty = false\n\n\treturn nil\n}\n"
  },
  {
    "path": "export.go",
    "content": "package datatable\n\n// ExportOptions to add options for exporting (like showing hidden columns)\ntype ExportOptions struct {\n\tWithHiddenCols bool\n}\n\ntype ExportOption func(*ExportOptions)\n\n// ExportHidden to show a column when exporting (default false)\nfunc ExportHidden(v bool) ExportOption {\n\treturn func(opts *ExportOptions) {\n\t\topts.WithHiddenCols = v\n\t}\n}\n\n// newExportOptions to build the ExportOptions in order to acces the parameters\nfunc newExportOptions(opt ...ExportOption) ExportOptions {\n\tvar opts ExportOptions\n\tfor _, o := range opt {\n\t\to(&opts)\n\t}\n\treturn opts\n\n}\n\n// ToMap to export the datatable to a json-like struct\nfunc (t *DataTable) ToMap(opt ...ExportOption) []map[string]interface{} {\n\tif t == nil {\n\t\treturn nil\n\t}\n\n\topts := newExportOptions(opt...)\n\tif err := t.evaluateExpressions(); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// visible columns\n\tcols := make(map[string]int)\n\tfor i, col := range t.cols {\n\t\tif opts.WithHiddenCols || col.IsVisible() {\n\t\t\tcols[col.Name()] = i\n\t\t}\n\t}\n\n\trows := make([]map[string]interface{}, 0, t.nrows)\n\tfor i := 0; i < t.nrows; i++ {\n\t\tr := make(map[string]interface{}, len(cols))\n\t\tfor name, pos := range cols {\n\t\t\tr[name] = t.cols[pos].serie.Get(i)\n\t\t}\n\t\trows = append(rows, r)\n\t}\n\treturn rows\n}\n\n// ToTable to export the datatable to a csv-like struct\nfunc (t *DataTable) ToTable(opt ...ExportOption) [][]interface{} {\n\tif t == nil {\n\t\treturn nil\n\t}\n\n\topts := newExportOptions(opt...)\n\tif err := t.evaluateExpressions(); err != nil {\n\t\tpanic(err)\n\t}\n\n\trows := make([][]interface{}, 0, t.nrows+1)\n\n\t// visible columns\n\tvar headers []interface{}\n\tvar cols []int\n\tfor i, col := range t.cols {\n\t\tif opts.WithHiddenCols || col.IsVisible() {\n\t\t\tcols = append(cols, i)\n\t\t\theaders = append(headers, col.Name())\n\t\t}\n\t}\n\n\trows = append(rows, headers)\n\tfor i := 0; i < t.nrows; i++ {\n\t\tr := make([]interface{}, 0, len(cols))\n\t\tfor _, pos := range cols {\n\t\t\tr = append(r, t.cols[pos].serie.Get(i))\n\t\t}\n\t\trows = append(rows, r)\n\t}\n\treturn rows\n}\n\n// Schema describes a datatable\ntype Schema struct {\n\tName    string          `json:\"name\"`\n\tColumns []SchemaColumn  `json:\"cols\"`\n\tRows    [][]interface{} `json:\"rows\"`\n}\n\ntype SchemaColumn struct {\n\tName string `json:\"name\"`\n\tType string `json:\"type\"`\n}\n\n// ToSchema to export the datatable to a schema struct\nfunc (t *DataTable) ToSchema(opt ...ExportOption) *Schema {\n\tif t == nil {\n\t\treturn nil\n\t}\n\n\topts := newExportOptions(opt...)\n\tif err := t.evaluateExpressions(); err != nil {\n\t\tpanic(err)\n\t}\n\n\tschema := &Schema{\n\t\tName: t.name,\n\t\tRows: make([][]interface{}, 0, t.nrows),\n\t}\n\n\t// visible columns\n\tvar cols []int\n\tfor i, col := range t.cols {\n\t\tif opts.WithHiddenCols || col.IsVisible() {\n\t\t\tcols = append(cols, i)\n\t\t\tschema.Columns = append(schema.Columns, SchemaColumn{Type: col.UnderlyingType().Name(), Name: col.Name()})\n\t\t}\n\t}\n\n\tfor i := 0; i < t.nrows; i++ {\n\t\tr := make([]interface{}, 0, len(cols))\n\t\tfor _, pos := range cols {\n\t\t\tr = append(r, t.cols[pos].serie.Get(i))\n\t\t}\n\t\tschema.Rows = append(schema.Rows, r)\n\t}\n\n\treturn schema\n}\n"
  },
  {
    "path": "export_test.go",
    "content": "package datatable_test\n\nimport (\n\t\"encoding/json\"\n\t\"testing\"\n\n\t\"github.com/datasweet/datatable\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc sampleForExport(t *testing.T) *datatable.DataTable {\n\tcustomers := datatable.New(\"Customers\")\n\terr := customers.AddColumn(\"id\", datatable.Int)\n\tassert.NoError(t, err)\n\n\terr = customers.AddColumn(\"prenom\", datatable.String)\n\tassert.NoError(t, err)\n\n\terr = customers.AddColumn(\"nom\", datatable.String)\n\tassert.NoError(t, err)\n\t//dc.Hidden(true)\n\n\terr = customers.AddColumn(\"expr_nom\", datatable.String, datatable.Expr(\"`prenom` ~ ' ' ~ UPPER(`nom`)\"))\n\tassert.NoError(t, err)\n\t//dc.Label(\"nom\")\n\n\terr = customers.AddColumn(\"email\", datatable.String)\n\tassert.NoError(t, err)\n\n\terr = customers.AddColumn(\"ville\", datatable.String)\n\tassert.NoError(t, err)\n\n\tcustomers.AppendRow(1, \"Aimée\", \"Marechal\", nil, \"aime.marechal@example.com\", \"Paris\")\n\tcustomers.AppendRow(2, \"Esmée\", \"Lefort\", nil, \"esmee.lefort@example.com\", \"Lyon\")\n\tcustomers.AppendRow(3, \"Marine\", \"Prevost\", nil, \"m.prevost@example.com\", \"Lille\")\n\tcustomers.AppendRow(4, \"Luc\", \"Rolland\", nil, \"lucrolland@example.com\", \"Marseille\")\n\n\t// Change structs\n\tassert.NoError(t, customers.RenameColumn(\"id\", \"Client ID\"))\n\tcustomers.HideColumn(\"prenom\")\n\tcustomers.HideColumn(\"nom\")\n\tassert.Error(t, customers.RenameColumn(\"expr_nom\", \"nom\"))\n\tassert.NoError(t, customers.RenameColumn(\"expr_nom\", \"Nom\"))\n\n\tcheckTable(t, customers,\n\t\t\"Client ID\", \"Nom\", \"email\", \"ville\",\n\t\t1, \"Aimée MARECHAL\", \"aime.marechal@example.com\", \"Paris\",\n\t\t2, \"Esmée LEFORT\", \"esmee.lefort@example.com\", \"Lyon\",\n\t\t3, \"Marine PREVOST\", \"m.prevost@example.com\", \"Lille\",\n\t\t4, \"Luc ROLLAND\", \"lucrolland@example.com\", \"Marseille\",\n\t)\n\n\treturn customers\n}\n\nfunc TestToTable(t *testing.T) {\n\tdt := sampleForExport(t)\n\tout := dt.ToTable()\n\tassert.NotNil(t, out)\n\n\texpected := `[\n\t\t[\"Client ID\", \"Nom\", \"email\", \"ville\"],\n\t\t[1, \"Aimée MARECHAL\", \"aime.marechal@example.com\", \"Paris\"],\n\t\t[2, \"Esmée LEFORT\", \"esmee.lefort@example.com\", \"Lyon\"],\n\t\t[3, \"Marine PREVOST\", \"m.prevost@example.com\", \"Lille\"],\n\t\t[4, \"Luc ROLLAND\", \"lucrolland@example.com\", \"Marseille\"]\n\t]`\n\n\tbytes, err := json.Marshal(out)\n\tassert.NoError(t, err)\n\tassert.JSONEq(t, expected, string(bytes))\n\n\tout2 := dt.ToTable(datatable.ExportHidden(true))\n\tassert.NotNil(t, out2)\n\n\texpected2 := `[\n\t\t[\"Client ID\", \"prenom\", \"nom\", \"Nom\", \"email\", \"ville\"],\n\t\t[1, \"Aimée\", \"Marechal\", \"Aimée MARECHAL\", \"aime.marechal@example.com\", \"Paris\"],\n\t\t[2, \"Esmée\", \"Lefort\", \"Esmée LEFORT\", \"esmee.lefort@example.com\", \"Lyon\"],\n\t\t[3, \"Marine\", \"Prevost\", \"Marine PREVOST\", \"m.prevost@example.com\", \"Lille\"],\n\t\t[4, \"Luc\", \"Rolland\", \"Luc ROLLAND\", \"lucrolland@example.com\", \"Marseille\"]\n\t]`\n\tbytes, err = json.Marshal(out2)\n\tassert.NoError(t, err)\n\tassert.JSONEq(t, expected2, string(bytes))\n}\n\nfunc TestToMap(t *testing.T) {\n\tdt := sampleForExport(t)\n\tout := dt.ToMap()\n\tassert.NotNil(t, out)\n\n\texpected := `[\n\t{ \"Client ID\":1, \"Nom\":\"Aimée MARECHAL\", \"email\":\"aime.marechal@example.com\", \"ville\":\"Paris\" },\n\t{ \"Client ID\":2, \"Nom\":\"Esmée LEFORT\", \"email\":\"esmee.lefort@example.com\", \"ville\":\"Lyon\" },\n\t{ \"Client ID\":3, \"Nom\":\"Marine PREVOST\", \"email\":\"m.prevost@example.com\", \"ville\":\"Lille\" },\n\t{ \"Client ID\":4, \"Nom\":\"Luc ROLLAND\", \"email\":\"lucrolland@example.com\", \"ville\":\"Marseille\" }\n]`\n\n\tbytes, err := json.Marshal(out)\n\tassert.NoError(t, err)\n\tassert.JSONEq(t, expected, string(bytes))\n\n\tout2 := dt.ToMap(datatable.ExportHidden(true))\n\tassert.NotNil(t, out2)\n\n\texpected2 := `[\n\t{ \"Client ID\":1, \"prenom\": \"Aimée\", \"nom\": \"Marechal\", \"Nom\":\"Aimée MARECHAL\", \"email\":\"aime.marechal@example.com\", \"ville\":\"Paris\" },\n\t{ \"Client ID\":2, \"prenom\": \"Esmée\", \"nom\": \"Lefort\", \"Nom\":\"Esmée LEFORT\", \"email\":\"esmee.lefort@example.com\", \"ville\":\"Lyon\" },\n\t{ \"Client ID\":3, \"prenom\": \"Marine\", \"nom\": \"Prevost\", \"Nom\":\"Marine PREVOST\", \"email\":\"m.prevost@example.com\", \"ville\":\"Lille\" },\n\t{ \"Client ID\":4, \"prenom\": \"Luc\", \"nom\": \"Rolland\", \"Nom\":\"Luc ROLLAND\", \"email\":\"lucrolland@example.com\", \"ville\":\"Marseille\" }\n]`\n\n\tbytes, err = json.Marshal(out2)\n\tassert.NoError(t, err)\n\tassert.JSONEq(t, expected2, string(bytes))\n}\n\nfunc TestToSchema(t *testing.T) {\n\tdt := sampleForExport(t)\n\tschema := dt.ToSchema()\n\tassert.NotNil(t, schema)\n\tassert.Equal(t, \"Customers\", schema.Name)\n\tassert.Equal(t, []datatable.SchemaColumn{\n\t\tdatatable.SchemaColumn{\"Client ID\", \"NullInt\"},\n\t\tdatatable.SchemaColumn{\"Nom\", \"NullString\"},\n\t\tdatatable.SchemaColumn{\"email\", \"NullString\"},\n\t\tdatatable.SchemaColumn{\"ville\", \"NullString\"},\n\t}, schema.Columns)\n\tassert.Len(t, schema.Rows, 4)\n\tassert.Equal(t, []interface{}{1, \"Aimée MARECHAL\", \"aime.marechal@example.com\", \"Paris\"}, schema.Rows[0])\n\tassert.Equal(t, []interface{}{2, \"Esmée LEFORT\", \"esmee.lefort@example.com\", \"Lyon\"}, schema.Rows[1])\n\tassert.Equal(t, []interface{}{3, \"Marine PREVOST\", \"m.prevost@example.com\", \"Lille\"}, schema.Rows[2])\n\tassert.Equal(t, []interface{}{4, \"Luc ROLLAND\", \"lucrolland@example.com\", \"Marseille\"}, schema.Rows[3])\n\n\tschema2 := dt.ToSchema(datatable.ExportHidden(true))\n\tassert.NotNil(t, schema2)\n\tassert.Equal(t, \"Customers\", schema2.Name)\n\tassert.Equal(t, []datatable.SchemaColumn{\n\t\tdatatable.SchemaColumn{\"Client ID\", \"NullInt\"},\n\t\tdatatable.SchemaColumn{\"prenom\", \"NullString\"},\n\t\tdatatable.SchemaColumn{\"nom\", \"NullString\"},\n\t\tdatatable.SchemaColumn{\"Nom\", \"NullString\"},\n\t\tdatatable.SchemaColumn{\"email\", \"NullString\"},\n\t\tdatatable.SchemaColumn{\"ville\", \"NullString\"},\n\t}, schema2.Columns)\n\tassert.Len(t, schema2.Rows, 4)\n\tassert.Equal(t, []interface{}{1, \"Aimée\", \"Marechal\", \"Aimée MARECHAL\", \"aime.marechal@example.com\", \"Paris\"}, schema2.Rows[0])\n\tassert.Equal(t, []interface{}{2, \"Esmée\", \"Lefort\", \"Esmée LEFORT\", \"esmee.lefort@example.com\", \"Lyon\"}, schema2.Rows[1])\n\tassert.Equal(t, []interface{}{3, \"Marine\", \"Prevost\", \"Marine PREVOST\", \"m.prevost@example.com\", \"Lille\"}, schema2.Rows[2])\n\tassert.Equal(t, []interface{}{4, \"Luc\", \"Rolland\", \"Luc ROLLAND\", \"lucrolland@example.com\", \"Marseille\"}, schema2.Rows[3])\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/datasweet/datatable\n\ngo 1.12\n\nrequire (\n\tgithub.com/cespare/xxhash v1.1.0\n\tgithub.com/datasweet/cast v1.2.0\n\tgithub.com/datasweet/expr v1.3.0\n\tgithub.com/olekukonko/tablewriter v0.0.4\n\tgithub.com/pkg/errors v0.8.1\n\tgithub.com/stretchr/testify v1.5.1\n\tgonum.org/v1/gonum v0.7.0\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=\ngithub.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=\ngithub.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=\ngithub.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=\ngithub.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=\ngithub.com/datasweet/cast v1.2.0 h1:ApnOmjxZxR4r+LpBKP8lYrJULO/5hWPRCIvpH4TjLIY=\ngithub.com/datasweet/cast v1.2.0/go.mod h1:ByvG5xnUqdkDkxPp5AYlfvjuO87I/3R4xFy+Zi4SDdc=\ngithub.com/datasweet/expr v1.3.0 h1:4Anw5bjslDtXNOR2ZOScfCECCSoJqA491O9nSYpisLc=\ngithub.com/datasweet/expr v1.3.0/go.mod h1:CjkMBXNXUoZNrqglnyUKMf+j8FQxLYPFeWE/G40/7zk=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=\ngithub.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=\ngithub.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=\ngithub.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=\ngithub.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=\ngithub.com/montanaflynn/stats v0.5.0 h1:2EkzeTSqBB4V4bJwWrt5gIIrZmpJBcoIRGS2kWLgzmk=\ngithub.com/montanaflynn/stats v0.5.0/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=\ngithub.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=\ngithub.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=\ngithub.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=\ngithub.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=\ngithub.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=\ngithub.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=\ngolang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2 h1:y102fOLFqhV41b+4GPiJoa0k/x+pJcEi2/HB1Y5T6fU=\ngolang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=\ngolang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=\ngonum.org/v1/gonum v0.7.0 h1:Hdks0L0hgznZLG9nzXb8vZ0rRvqNvAcgAp84y7Mwkgw=\ngonum.org/v1/gonum v0.7.0/go.mod h1:L02bwd0sqlsvRv41G7wGWFCsVNZFv/k1xzGIxeANHGM=\ngonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc=\ngonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=\ngonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=\ngopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\nrsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=\n"
  },
  {
    "path": "hasher.go",
    "content": "package datatable\n\nimport (\n\t\"bytes\"\n\t\"encoding/gob\"\n\n\t\"github.com/cespare/xxhash\"\n)\n\nvar hasher = &hasherImpl{}\n\ntype hasherImpl struct{}\n\nfunc (h *hasherImpl) Row(row Row, cols []string) uint64 {\n\tif row == nil {\n\t\treturn 0\n\t}\n\tbuff := new(bytes.Buffer)\n\tenc := gob.NewEncoder(buff)\n\n\tfor _, name := range cols {\n\t\tenc.Encode(row[name])\n\t}\n\n\treturn xxhash.Sum64(buff.Bytes())\n}\n\nfunc (h *hasherImpl) Table(dt *DataTable, cols []string) map[uint64][]int {\n\tif dt == nil {\n\t\treturn nil\n\t}\n\tmh := make(map[uint64][]int, 0)\n\tfor i, row := range dt.Rows() {\n\t\thash := h.Row(row, cols)\n\t\tmh[hash] = append(mh[hash], i)\n\t}\n\treturn mh\n}\n"
  },
  {
    "path": "import/csv/import.go",
    "content": "package csv\n\nimport (\n\t\"encoding/csv\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/datasweet/cast\"\n\t\"github.com/datasweet/datatable\"\n\t\"github.com/pkg/errors\"\n)\n\n// Import a csv\nfunc Import(name, path string, opt ...Option) (*datatable.DataTable, error) {\n\toptions := Options{\n\t\tIgnoreIfReadLineError: true,\n\t\tComma:                 ',',\n\t\tTrimLeadingSpace:      true,\n\t}\n\tfor _, o := range opt {\n\t\to(&options)\n\t}\n\n\t// Open the file\n\tfile, err := os.Open(path)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, datatable.ErrOpenFile.Error())\n\t}\n\tdefer file.Close()\n\n\treader := csv.NewReader(file)\n\treader.Comma = options.Comma\n\treader.Comment = options.Comment\n\treader.LazyQuotes = options.LazyQuotes\n\treader.TrimLeadingSpace = options.TrimLeadingSpace\n\n\tdt := datatable.New(name)\n\n\tline := 1\n\n\t// Get columns names with headers\n\tif options.HasHeaders {\n\t\trec, err := reader.Read()\n\t\tif err != nil {\n\t\t\treturn nil, errors.Wrap(err, datatable.ErrCantReadHeaders.Error())\n\t\t}\n\t\tif len(options.ColumnNames) == 0 {\n\t\t\toptions.ColumnNames = append(options.ColumnNames, rec...)\n\t\t}\n\t\tline++\n\t}\n\n\tfor {\n\t\trec, err := reader.Read()\n\t\tif err != nil {\n\t\t\tif err == io.EOF {\n\t\t\t\tif line == 1 {\n\t\t\t\t\treturn nil, datatable.ErrNilDatas\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif options.IgnoreIfReadLineError {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\terr := errors.Wrapf(err, \"error line %d\", line)\n\t\t\treturn nil, errors.Wrap(err, datatable.ErrReadingLine.Error())\n\t\t}\n\n\t\t// Do we have columns names ?\n\t\tif len(options.ColumnNames) == 0 {\n\t\t\toptions.ColumnNames = make([]string, 0, len(rec))\n\t\t\tfor i := range rec {\n\t\t\t\toptions.ColumnNames = append(options.ColumnNames, fmt.Sprintf(\"col %d\", i+1))\n\t\t\t}\n\t\t}\n\n\t\t// Do we have columns in datatable\n\t\tif dt.NumCols() == 0 {\n\t\t\t// Detect type if needed\n\t\t\tif len(options.ColumnTypes) == 0 {\n\t\t\t\toptions.ColumnTypes = detectTypes(rec, options.DateFormats)\n\t\t\t}\n\t\t\tif len(options.ColumnNames) != len(options.ColumnTypes) {\n\t\t\t\terr := errors.Errorf(\"expected %d types, got %d\", len(options.ColumnNames), len(options.ColumnTypes))\n\t\t\t\treturn nil, errors.Wrap(err, datatable.ErrWrongNumberOfTypes.Error())\n\t\t\t}\n\t\t\tfor i := range options.ColumnNames {\n\t\t\t\tif err := dt.AddColumn(options.ColumnNames[i], options.ColumnTypes[i], datatable.TimeFormats(options.DateFormats...)); err != nil {\n\t\t\t\t\terr = errors.Wrapf(err, \"add column '%s' with type '%s'\", options.ColumnNames[i], options.ColumnTypes[i])\n\t\t\t\t\treturn nil, errors.Wrap(err, datatable.ErrAddingColumn.Error())\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// conv => []interface{}\n\t\tcells := make([]interface{}, 0, len(rec))\n\t\tfor _, r := range rec {\n\t\t\tcells = append(cells, r)\n\t\t}\n\t\tdt.AppendRow(cells...)\n\t\tline++\n\t}\n\n\treturn dt, nil\n}\n\nfunc detectTypes(rec, dateformat []string) []datatable.ColumnType {\n\tctypes := make([]datatable.ColumnType, 0, len(rec))\n\tfor _, r := range rec {\n\t\tif _, ok := cast.AsFloat64(r); ok {\n\t\t\tctypes = append(ctypes, datatable.Float64)\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := cast.AsBool(r); ok {\n\t\t\tctypes = append(ctypes, datatable.Bool)\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := cast.AsTime(r, dateformat...); ok {\n\t\t\tctypes = append(ctypes, datatable.Time)\n\t\t\tcontinue\n\t\t}\n\t\tctypes = append(ctypes, datatable.String)\n\t}\n\treturn ctypes\n}\n"
  },
  {
    "path": "import/csv/import_test.go",
    "content": "package csv_test\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/datasweet/datatable\"\n\n\t\"github.com/datasweet/datatable/import/csv\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestImport(t *testing.T) {\n\tdt, err := csv.Import(\"csv\", \"../../test/phone_data.csv\",\n\t\tcsv.HasHeader(true),\n\t\tcsv.AcceptDate(\"02/01/06 15:04\"),\n\t\tcsv.AcceptDate(\"2006-01\"),\n\t)\n\tassert.NoError(t, err)\n\tassert.NotNil(t, dt)\n\n\tdt.Print(os.Stdout, datatable.PrintMaxRows(24))\n\n\tdtc, err := dt.Aggregate(datatable.AggregateBy{Type: datatable.Count, Field: \"index\"})\n\tassert.NoError(t, err)\n\tfmt.Println(dtc)\n\n\tgroups, err := dt.GroupBy(datatable.GroupBy{\n\t\tName: \"year\",\n\t\tType: datatable.Int,\n\t\tKeyer: func(row datatable.Row) (interface{}, bool) {\n\t\t\tif d, ok := row[\"date\"]; ok {\n\t\t\t\tif tm, ok := d.(time.Time); ok {\n\t\t\t\t\treturn tm.Year(), true\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil, false\n\t\t},\n\t})\n\tassert.NoError(t, err)\n\tout, err := groups.Aggregate(\n\t\tdatatable.AggregateBy{Type: datatable.Sum, Field: \"duration\"},\n\t\tdatatable.AggregateBy{Type: datatable.CountDistinct, Field: \"network\"},\n\t)\n\tassert.NoError(t, err)\n\tfmt.Println(out)\n\n}\n"
  },
  {
    "path": "import/csv/options.go",
    "content": "package csv\n\nimport \"github.com/datasweet/datatable\"\n\n// Options are options to import a csv\ntype Options struct {\n\tHasHeaders            bool\n\tColumnNames           []string               // if len == 0 => take headers else \"col #i\"\n\tColumnTypes           []datatable.ColumnType // if len == 0 => detection\n\tIgnoreIfReadLineError bool\n\tComma                 rune\n\tComment               rune\n\tLazyQuotes            bool\n\tTrimLeadingSpace      bool\n\tDateFormats           []string\n}\n\n// Option is a setter\ntype Option func(*Options)\n\n// HasHeader to retrieve column names on line #1\nfunc HasHeader(v bool) Option {\n\treturn func(opts *Options) {\n\t\topts.HasHeaders = v\n\t}\n}\n\n// ColumnNames defines the column name\nfunc ColumnNames(v ...string) Option {\n\treturn func(opts *Options) {\n\t\topts.ColumnNames = v\n\t}\n}\n\n// ColumnTypes defines the column type\nfunc ColumnTypes(v ...datatable.ColumnType) Option {\n\treturn func(opts *Options) {\n\t\topts.ColumnTypes = v\n\t}\n}\n\n// IgnoreLineWithError to not stop the reading process if a line has an error\nfunc IgnoreLineWithError(v bool) Option {\n\treturn func(opts *Options) {\n\t\topts.IgnoreIfReadLineError = v\n\t}\n}\n\n// Comma is the field delimiter\n// Default to ','\nfunc Comma(v rune) Option {\n\treturn func(opts *Options) {\n\t\topts.Comma = v\n\t}\n}\n\n// Comment if not 0, is the comment character. Lines beginning with the\n// Comment character without preceding whitespace are ignored.\nfunc Comment(v rune) Option {\n\treturn func(opts *Options) {\n\t\topts.Comment = v\n\t}\n}\n\n// LazyQuotes is true, a quote may appear in an unquoted field and a\n// non-doubled quote may appear in a quoted field.\nfunc LazyQuotes(v bool) Option {\n\treturn func(opts *Options) {\n\t\topts.LazyQuotes = v\n\t}\n}\n\n// TrimLeadingSpace is true, leading white space in a field is ignored.\n// This is done even if the field delimiter, Comma, is white space.\nfunc TrimLeadingSpace(v bool) Option {\n\treturn func(opts *Options) {\n\t\topts.TrimLeadingSpace = v\n\t}\n}\n\n// AcceptDate to accept a specific date format\nfunc AcceptDate(v string) Option {\n\treturn func(opts *Options) {\n\t\topts.DateFormats = append(opts.DateFormats, v)\n\t}\n}\n"
  },
  {
    "path": "join.go",
    "content": "package datatable\n\nimport (\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n)\n\n// InnerJoin selects records that have matching values in both tables.\n// left datatable is used as reference datatable.\n// <!> InnerJoin transforms an expr column to a raw column\nfunc (left *DataTable) InnerJoin(right *DataTable, on []JoinOn) (*DataTable, error) {\n\treturn newJoinImpl(innerJoin, []*DataTable{left, right}, on).Compute()\n}\n\n// InnerJoin selects records that have matching values in both tables.\n// tables[0] is used as reference datatable.\nfunc InnerJoin(tables []*DataTable, on []JoinOn) (*DataTable, error) {\n\treturn newJoinImpl(innerJoin, tables, on).Compute()\n}\n\n// LeftJoin returns all records from the left table (table1), and the matched records from the right table (table2).\n// The result is NULL from the right side, if there is no match.\n// <!> LeftJoin transforms an expr column to a raw column\nfunc (left *DataTable) LeftJoin(right *DataTable, on []JoinOn) (*DataTable, error) {\n\treturn newJoinImpl(leftJoin, []*DataTable{left, right}, on).Compute()\n}\n\n// LeftJoin the tables.\n// tables[0] is used as reference datatable.\nfunc LeftJoin(tables []*DataTable, on []JoinOn) (*DataTable, error) {\n\treturn newJoinImpl(leftJoin, tables, on).Compute()\n}\n\n// RightJoin returns all records from the right table (table2), and the matched records from the left table (table1).\n// The result is NULL from the left side, when there is no match.\n// <!> RightJoin transforms an expr column to a raw column\nfunc (left *DataTable) RightJoin(right *DataTable, on []JoinOn) (*DataTable, error) {\n\treturn newJoinImpl(rightJoin, []*DataTable{left, right}, on).Compute()\n}\n\n// RightJoin the tables.\n// tables[0] is used as reference datatable.\nfunc RightJoin(tables []*DataTable, on []JoinOn) (*DataTable, error) {\n\treturn newJoinImpl(rightJoin, tables, on).Compute()\n}\n\n// OuterJoin returns all records when there is a match in either left or right table\n// <!> OuterJoin transforms an expr column to a raw column\nfunc (left *DataTable) OuterJoin(right *DataTable, on []JoinOn) (*DataTable, error) {\n\treturn newJoinImpl(outerJoin, []*DataTable{left, right}, on).Compute()\n}\n\n// OuterJoin the tables.\n// tables[0] is used as reference datatable.\nfunc OuterJoin(tables []*DataTable, on []JoinOn) (*DataTable, error) {\n\treturn newJoinImpl(outerJoin, tables, on).Compute()\n}\n\ntype JoinOn struct {\n\tTable string\n\tField string\n}\n\nvar rgOn = regexp.MustCompile(`^(?:\\[([^]]+)\\]\\.)?(?:\\[([^]]+)\\])$`)\n\n// On creates a \"join on\" expression\n// ie, as SQL, SELECT * FROM A INNER JOIN B ON B.id = A.user_id\n// Syntax: \"[table].[field]\", \"field\"\nfunc On(fields ...string) []JoinOn {\n\tvar jon []JoinOn\n\tfor _, f := range fields {\n\t\tmatches := rgOn.FindStringSubmatch(f)\n\n\t\tswitch len(matches) {\n\t\tcase 0:\n\t\t\tjon = append(jon, JoinOn{Table: \"*\", Field: f})\n\t\tcase 3:\n\t\t\tt := matches[1]\n\t\t\tif len(t) == 0 {\n\t\t\t\tt = \"*\"\n\t\t\t}\n\t\t\tjon = append(jon, JoinOn{Table: t, Field: matches[2]})\n\t\tdefault:\n\t\t\treturn nil\n\t\t}\n\t}\n\n\treturn jon\n}\n\n// Using creates a \"join using\" expression\n// ie, as SQL, SELECT * FROM A INNER JOIN B USING 'field'\nfunc Using(fields ...string) []JoinOn {\n\tvar jon []JoinOn\n\tfor _, f := range fields {\n\t\tjon = append(jon, JoinOn{Table: \"*\", Field: f})\n\t}\n\treturn jon\n}\n\ntype joinType uint8\n\nconst (\n\tinnerJoin joinType = iota\n\tleftJoin\n\trightJoin\n\touterJoin\n)\n\nfunc colname(dt *DataTable, col string) string {\n\tvar sb strings.Builder\n\tsb.WriteString(dt.Name())\n\tsb.WriteString(\".\")\n\tsb.WriteString(col)\n\treturn sb.String()\n}\n\ntype joinClause struct {\n\ttable         *DataTable\n\tmcols         map[string][]string\n\ton            []string\n\tincludeOnCols bool\n\tcmapper       [][2]string // [initial, output]\n\thashtable     map[uint64][]int\n\tconsumed      map[int]bool\n}\n\nfunc (jc *joinClause) copyColumnsTo(out *DataTable) error {\n\tif out == nil {\n\t\treturn ErrNilOutputDatatable\n\t}\n\n\tmon := make(map[string]bool, len(jc.on))\n\tfor _, o := range jc.on {\n\t\tmon[o] = true\n\t}\n\n\tfor _, col := range jc.table.cols {\n\t\tname := col.name\n\t\tcname := name\n\n\t\tif _, found := mon[name]; found {\n\t\t\tif !jc.includeOnCols {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t} else if v, ok := jc.mcols[name]; ok && len(v) > 1 {\n\t\t\t// commons col between table\n\t\t\tfor _, tn := range v {\n\t\t\t\tif tn == jc.table.name {\n\t\t\t\t\tcname = colname(jc.table, name)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tccpy := col.emptyCopy()\n\t\tccpy.name = cname\n\t\tif err := out.addColumn(ccpy); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tjc.cmapper = append(jc.cmapper, [2]string{name, cname})\n\t}\n\n\treturn nil\n}\n\nfunc (jc *joinClause) initHashTable() {\n\tjc.hashtable = hasher.Table(jc.table, jc.on)\n\tjc.consumed = make(map[int]bool, jc.table.NumRows())\n}\n\ntype joinImpl struct {\n\tmode    joinType\n\ttables  []*DataTable\n\ton      []JoinOn\n\tclauses []*joinClause\n\tmcols   map[string][]string\n}\n\nfunc newJoinImpl(mode joinType, tables []*DataTable, on []JoinOn) *joinImpl {\n\treturn &joinImpl{\n\t\tmode:   mode,\n\t\ttables: tables,\n\t\ton:     on,\n\t}\n}\n\nfunc (j *joinImpl) Compute() (*DataTable, error) {\n\tif err := j.checkInput(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tj.initColMapper()\n\n\tout := j.tables[0]\n\tfor i := 1; i < len(j.tables); i++ {\n\t\tjdt, err := j.join(out, j.tables[i])\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tout = jdt\n\t}\n\n\tif out == nil {\n\t\treturn nil, ErrNoOutput\n\t}\n\n\treturn out, nil\n}\n\nfunc (j *joinImpl) checkInput() error {\n\tif len(j.tables) < 2 {\n\t\treturn ErrNotEnoughDatatables\n\t}\n\tfor i, t := range j.tables {\n\t\tif t == nil || len(t.Name()) == 0 || t.NumCols() == 0 {\n\t\t\terr := errors.Errorf(\"table #%d is nil\", i)\n\t\t\treturn errors.Wrap(err, ErrNilTable.Error())\n\t\t}\n\t}\n\tif len(j.on) == 0 {\n\t\treturn ErrNoOnClauses\n\t}\n\tfor i, o := range j.on {\n\t\tif len(o.Field) == 0 {\n\t\t\terr := errors.Errorf(\"on #%d is nil\", i)\n\t\t\treturn errors.Wrap(err, ErrOnClauseIsNil.Error())\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (j *joinImpl) initColMapper() {\n\tmcols := make(map[string][]string)\n\tfor _, t := range j.tables {\n\t\tfor _, name := range t.cols {\n\t\t\tmcols[name.name] = append(mcols[name.name], t.Name())\n\t\t}\n\t}\n\tj.mcols = mcols\n}\n\nfunc (j *joinImpl) join(left, right *DataTable) (*DataTable, error) {\n\tif left == nil {\n\t\terr := errors.New(\"left is nil datatable\")\n\t\treturn nil, errors.Wrap(err, ErrNilDatatable.Error())\n\t}\n\tif right == nil {\n\t\terr := errors.New(\"right is nil datatable\")\n\t\treturn nil, errors.Wrap(err, ErrNilDatatable.Error())\n\t}\n\n\tclauses := [2]*joinClause{\n\t\t&joinClause{\n\t\t\ttable:         left,\n\t\t\tmcols:         j.mcols,\n\t\t\tincludeOnCols: true,\n\t\t},\n\t\t&joinClause{\n\t\t\ttable: right,\n\t\t\tmcols: j.mcols,\n\t\t},\n\t}\n\n\t// find on clauses\n\tfor _, o := range j.on {\n\t\tif o.Table == left.Name() {\n\t\t\tclauses[0].on = append(clauses[0].on, o.Field)\n\t\t\tcontinue\n\t\t}\n\n\t\tif o.Table == right.Name() {\n\t\t\tclauses[1].on = append(clauses[1].on, o.Field)\n\t\t\tcontinue\n\t\t}\n\n\t\tif o.Table == \"*\" || len(o.Table) == 0 {\n\t\t\tclauses[0].on = append(clauses[0].on, o.Field)\n\t\t\tclauses[1].on = append(clauses[1].on, o.Field)\n\t\t}\n\t}\n\n\t// create output\n\tout := New(left.Name())\n\tfor _, clause := range clauses {\n\t\tif err := clause.copyColumnsTo(out); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\t// mode\n\tvar ref, join *joinClause\n\tswitch j.mode {\n\tcase innerJoin, leftJoin, outerJoin:\n\t\tref, join = clauses[0], clauses[1]\n\tcase rightJoin:\n\t\tref, join = clauses[1], clauses[0]\n\tdefault:\n\t\terr := errors.Errorf(\"unknown mode '%v'\", j.mode)\n\t\treturn nil, errors.Wrap(err, ErrUnknownMode.Error())\n\t}\n\n\tjoin.initHashTable()\n\n\t// Copy rows\n\tfor _, refrow := range ref.table.Rows(ExportHidden(true)) {\n\t\t// Create hash\n\t\thash := hasher.Row(refrow, ref.on)\n\n\t\t// Have we same hash in jointable ?\n\t\tif indexes, ok := join.hashtable[hash]; ok {\n\t\t\tfor _, idx := range indexes {\n\t\t\t\tjoinrow := join.table.Row(idx, ExportHidden(true))\n\t\t\t\trow := out.NewRow()\n\t\t\t\tfor _, cm := range ref.cmapper {\n\t\t\t\t\trow[cm[1]] = refrow.Get(cm[0])\n\t\t\t\t}\n\t\t\t\tfor _, cm := range join.cmapper {\n\t\t\t\t\trow[cm[1]] = joinrow.Get(cm[0])\n\t\t\t\t}\n\t\t\t\tjoin.consumed[idx] = true\n\t\t\t\tout.Append(row)\n\t\t\t}\n\t\t} else if j.mode != innerJoin {\n\t\t\trow := make(Row, len(refrow))\n\t\t\tfor _, cm := range ref.cmapper {\n\t\t\t\trow[cm[1]] = refrow.Get(cm[0])\n\t\t\t}\n\t\t\tout.Append(row)\n\t\t}\n\t}\n\t// out.Print(os.Stdout, PrintColumnType(false))\n\n\t// Outer: we must copy rows not consummed in right (join) table\n\tif j.mode == outerJoin {\n\t\tfor i, joinrow := range join.table.Rows() {\n\t\t\tif b, ok := join.consumed[i]; ok && b {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\trow := make(Row, len(joinrow))\n\t\t\tfor _, cm := range join.cmapper {\n\t\t\t\trow[cm[1]] = joinrow.Get(cm[0])\n\t\t\t}\n\t\t\tout.Append(row)\n\t\t}\n\t}\n\n\treturn out, nil\n}\n"
  },
  {
    "path": "join_test.go",
    "content": "package datatable_test\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/datasweet/datatable\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\n// https://sql.sh/cours/jointures/inner-join\nfunc sampleForJoin() (*datatable.DataTable, *datatable.DataTable) {\n\tcustomers := datatable.New(\"Customers\")\n\tcustomers.AddColumn(\"id\", datatable.Int)\n\tcustomers.AddColumn(\"prenom\", datatable.String)\n\tcustomers.AddColumn(\"nom\", datatable.String)\n\tcustomers.AddColumn(\"email\", datatable.String)\n\tcustomers.AddColumn(\"ville\", datatable.String)\n\t// customers.AddColumn(\"concat\", datatable.String, datatable.Expr(\"CONCAT(`prenom`,`nom`)\"))\n\tcustomers.AppendRow(1, \"Aimée\", \"Marechal\", \"aime.marechal@example.com\", \"Paris\")\n\tcustomers.AppendRow(2, \"Esmée\", \"Lefort\", \"esmee.lefort@example.com\", \"Lyon\")\n\tcustomers.AppendRow(3, \"Marine\", \"Prevost\", \"m.prevost@example.com\", \"Lille\")\n\tcustomers.AppendRow(4, \"Luc\", \"Rolland\", \"lucrolland@example.com\", \"Marseille\")\n\n\torders := datatable.New(\"Orders\")\n\torders.AddColumn(\"user_id\", datatable.Int, datatable.Values(1, 1, 2, 3, 5))\n\torders.AddColumn(\"date_achat\", datatable.Time, datatable.Values(\"2013-01-23\", \"2013-02-14\", \"2013-02-17\", \"2013-02-21\", \"2013-03-02\"))\n\torders.AddColumn(\"num_facture\", datatable.String, datatable.Values(\"A00103\", \"A00104\", \"A00105\", \"A00106\", \"A00107\"))\n\torders.AddColumn(\"prix_total\", datatable.Float64, datatable.Values(203.14, 124.00, 149.45, 235.35, 47.58))\n\n\treturn customers, orders\n}\n\nfunc TestJoinOn(t *testing.T) {\n\ton := datatable.On(\"[customers].[id]\")\n\tassert.NotNil(t, on)\n\tassert.Len(t, on, 1)\n\tassert.Equal(t, \"customers\", on[0].Table)\n\tassert.Equal(t, \"id\", on[0].Field)\n\n\ton = datatable.On(\"[id]\")\n\tassert.NotNil(t, on)\n\tassert.Len(t, on, 1)\n\tassert.Equal(t, \"*\", on[0].Table)\n\tassert.Equal(t, \"id\", on[0].Field)\n\n\ton = datatable.On(\"id\")\n\tassert.NotNil(t, on)\n\tassert.Len(t, on, 1)\n\tassert.Equal(t, \"*\", on[0].Table)\n\tassert.Equal(t, \"id\", on[0].Field)\n\n\ton = datatable.On(\"customers.[id]\")\n\tassert.NotNil(t, on)\n\tassert.Len(t, on, 1)\n\tassert.Equal(t, \"*\", on[0].Table)\n\tassert.Equal(t, \"customers.[id]\", on[0].Field)\n}\n\nfunc TestInnerJoin(t *testing.T) {\n\tcustomers, orders := sampleForJoin()\n\tcustomers.AddColumn(\"concat\", datatable.String, datatable.Expr(\"concat(`prenom`, `nom`)\"))\n\tdt, err := customers.InnerJoin(orders, datatable.On(\"[Customers].[id]\", \"[Orders].[user_id]\"))\n\tassert.NoError(t, err)\n\tassert.NotNil(t, dt)\n\tcheckTable(t, dt,\n\t\t\"id\", \"prenom\", \"nom\", \"email\", \"ville\", \"concat\", \"date_achat\", \"num_facture\", \"prix_total\",\n\t\t1, \"Aimée\", \"Marechal\", \"aime.marechal@example.com\", \"Paris\", \"AiméeMarechal\", time.Date(2013, time.January, 23, 0, 0, 0, 0, time.UTC), \"A00103\", 203.14,\n\t\t1, \"Aimée\", \"Marechal\", \"aime.marechal@example.com\", \"Paris\", \"AiméeMarechal\", time.Date(2013, time.February, 14, 0, 0, 0, 0, time.UTC), \"A00104\", 124.00,\n\t\t2, \"Esmée\", \"Lefort\", \"esmee.lefort@example.com\", \"Lyon\", \"EsméeLefort\", time.Date(2013, time.February, 17, 0, 0, 0, 0, time.UTC), \"A00105\", 149.45,\n\t\t3, \"Marine\", \"Prevost\", \"m.prevost@example.com\", \"Lille\", \"MarinePrevost\", time.Date(2013, time.February, 21, 0, 0, 0, 0, time.UTC), \"A00106\", 235.35,\n\t)\n}\n\nfunc TestLeftJoin(t *testing.T) {\n\tcustomers, orders := sampleForJoin()\n\n\tdt, err := customers.LeftJoin(orders, datatable.On(\"[Customers].[id]\", \"[Orders].[user_id]\"))\n\tassert.NoError(t, err)\n\tassert.NotNil(t, dt)\n\n\tcheckTable(t, dt,\n\t\t\"id\", \"prenom\", \"nom\", \"email\", \"ville\", \"date_achat\", \"num_facture\", \"prix_total\",\n\t\t1, \"Aimée\", \"Marechal\", \"aime.marechal@example.com\", \"Paris\", time.Date(2013, time.January, 23, 0, 0, 0, 0, time.UTC), \"A00103\", 203.14,\n\t\t1, \"Aimée\", \"Marechal\", \"aime.marechal@example.com\", \"Paris\", time.Date(2013, time.February, 14, 0, 0, 0, 0, time.UTC), \"A00104\", 124.00,\n\t\t2, \"Esmée\", \"Lefort\", \"esmee.lefort@example.com\", \"Lyon\", time.Date(2013, time.February, 17, 0, 0, 0, 0, time.UTC), \"A00105\", 149.45,\n\t\t3, \"Marine\", \"Prevost\", \"m.prevost@example.com\", \"Lille\", time.Date(2013, time.February, 21, 0, 0, 0, 0, time.UTC), \"A00106\", 235.35,\n\t\t4, \"Luc\", \"Rolland\", \"lucrolland@example.com\", \"Marseille\", nil, nil, nil,\n\t)\n}\n\nfunc TestRightJoin(t *testing.T) {\n\tcustomers, orders := sampleForJoin()\n\n\tdt, err := customers.RightJoin(orders, datatable.On(\"[Customers].[id]\", \"[Orders].[user_id]\"))\n\tassert.NoError(t, err)\n\tassert.NotNil(t, dt)\n\n\tcheckTable(t, dt,\n\t\t\"id\", \"prenom\", \"nom\", \"email\", \"ville\", \"date_achat\", \"num_facture\", \"prix_total\",\n\t\t1, \"Aimée\", \"Marechal\", \"aime.marechal@example.com\", \"Paris\", time.Date(2013, time.January, 23, 0, 0, 0, 0, time.UTC), \"A00103\", 203.14,\n\t\t1, \"Aimée\", \"Marechal\", \"aime.marechal@example.com\", \"Paris\", time.Date(2013, time.February, 14, 0, 0, 0, 0, time.UTC), \"A00104\", 124.00,\n\t\t2, \"Esmée\", \"Lefort\", \"esmee.lefort@example.com\", \"Lyon\", time.Date(2013, time.February, 17, 0, 0, 0, 0, time.UTC), \"A00105\", 149.45,\n\t\t3, \"Marine\", \"Prevost\", \"m.prevost@example.com\", \"Lille\", time.Date(2013, time.February, 21, 0, 0, 0, 0, time.UTC), \"A00106\", 235.35,\n\t\tnil, nil, nil, nil, nil, time.Date(2013, time.March, 2, 0, 0, 0, 0, time.UTC), \"A00107\", 47.58,\n\t)\n}\n\nfunc TestOuterJoin(t *testing.T) {\n\tcustomers, orders := sampleForJoin()\n\n\tdt, err := customers.OuterJoin(orders, datatable.On(\"[Customers].[id]\", \"[Orders].[user_id]\"))\n\tassert.NoError(t, err)\n\tassert.NotNil(t, dt)\n\n\tcheckTable(t, dt,\n\t\t\"id\", \"prenom\", \"nom\", \"email\", \"ville\", \"date_achat\", \"num_facture\", \"prix_total\",\n\t\t1, \"Aimée\", \"Marechal\", \"aime.marechal@example.com\", \"Paris\", time.Date(2013, time.January, 23, 0, 0, 0, 0, time.UTC), \"A00103\", 203.14,\n\t\t1, \"Aimée\", \"Marechal\", \"aime.marechal@example.com\", \"Paris\", time.Date(2013, time.February, 14, 0, 0, 0, 0, time.UTC), \"A00104\", 124.00,\n\t\t2, \"Esmée\", \"Lefort\", \"esmee.lefort@example.com\", \"Lyon\", time.Date(2013, time.February, 17, 0, 0, 0, 0, time.UTC), \"A00105\", 149.45,\n\t\t3, \"Marine\", \"Prevost\", \"m.prevost@example.com\", \"Lille\", time.Date(2013, time.February, 21, 0, 0, 0, 0, time.UTC), \"A00106\", 235.35,\n\t\t4, \"Luc\", \"Rolland\", \"lucrolland@example.com\", \"Marseille\", nil, nil, nil,\n\t\tnil, nil, nil, nil, nil, time.Date(2013, time.March, 2, 0, 0, 0, 0, time.UTC), \"A00107\", 47.58,\n\t)\n}\n\nfunc TestInnerJoinWithExprOnHidden(t *testing.T) {\n\tcustomers, orders := sampleForJoin()\n\tcustomers.AddColumn(\"id2\", datatable.Int, datatable.Expr(\"`id`+100\"))\n\torders.AddColumn(\"user_id2\", datatable.Int, datatable.Expr(\"`user_id`+100\"))\n\tcustomers.HideColumn(\"id\")\n\tdt, err := customers.InnerJoin(orders, datatable.On(\"[Customers].[id2]\", \"[Orders].[user_id2]\"))\n\tassert.NoError(t, err)\n\tassert.NotNil(t, dt)\n\n\tfmt.Println(dt)\n\n\tcheckTable(t, dt,\n\t\t\"prenom\", \"nom\", \"email\", \"ville\", \"id2\", \"user_id\", \"date_achat\", \"num_facture\", \"prix_total\",\n\t\t\"Aimée\", \"Marechal\", \"aime.marechal@example.com\", \"Paris\", 101, 1, time.Date(2013, time.January, 23, 0, 0, 0, 0, time.UTC), \"A00103\", 203.14,\n\t\t\"Aimée\", \"Marechal\", \"aime.marechal@example.com\", \"Paris\", 101, 1, time.Date(2013, time.February, 14, 0, 0, 0, 0, time.UTC), \"A00104\", 124.00,\n\t\t\"Esmée\", \"Lefort\", \"esmee.lefort@example.com\", \"Lyon\", 102, 2, time.Date(2013, time.February, 17, 0, 0, 0, 0, time.UTC), \"A00105\", 149.45,\n\t\t\"Marine\", \"Prevost\", \"m.prevost@example.com\", \"Lille\", 103, 3, time.Date(2013, time.February, 21, 0, 0, 0, 0, time.UTC), \"A00106\", 235.35,\n\t)\n}\n\nfunc TestLeftJoinWithExpr(t *testing.T) {\n\tcustomers, orders := sampleForJoin()\n\tcustomers.AddColumn(\"id2\", datatable.Int, datatable.Expr(\"`id`+100\"))\n\torders.AddColumn(\"user_id2\", datatable.Int, datatable.Expr(\"`user_id`+100\"))\n\tdt, err := customers.LeftJoin(orders, datatable.On(\"[Customers].[id2]\", \"[Orders].[user_id2]\"))\n\tassert.NoError(t, err)\n\tassert.NotNil(t, dt)\n\n\tfmt.Println(dt)\n\n\tcheckTable(t, dt,\n\t\t\"id\", \"prenom\", \"nom\", \"email\", \"ville\", \"id2\", \"user_id\", \"date_achat\", \"num_facture\", \"prix_total\",\n\t\t1, \"Aimée\", \"Marechal\", \"aime.marechal@example.com\", \"Paris\", 101, 1, time.Date(2013, time.January, 23, 0, 0, 0, 0, time.UTC), \"A00103\", 203.14,\n\t\t1, \"Aimée\", \"Marechal\", \"aime.marechal@example.com\", \"Paris\", 101, 1, time.Date(2013, time.February, 14, 0, 0, 0, 0, time.UTC), \"A00104\", 124.00,\n\t\t2, \"Esmée\", \"Lefort\", \"esmee.lefort@example.com\", \"Lyon\", 102, 2, time.Date(2013, time.February, 17, 0, 0, 0, 0, time.UTC), \"A00105\", 149.45,\n\t\t3, \"Marine\", \"Prevost\", \"m.prevost@example.com\", \"Lille\", 103, 3, time.Date(2013, time.February, 21, 0, 0, 0, 0, time.UTC), \"A00106\", 235.35,\n\t\t4, \"Luc\", \"Rolland\", \"lucrolland@example.com\", \"Marseille\", 104, nil, nil, nil, nil,\n\t)\n}\n\nfunc TestRightJoinWithExpr(t *testing.T) {\n\tcustomers, orders := sampleForJoin()\n\tcustomers.AddColumn(\"id2\", datatable.Int, datatable.Expr(\"`id`+100\"))\n\torders.AddColumn(\"user_id2\", datatable.Int, datatable.Expr(\"`user_id`+100\"))\n\tdt, err := customers.RightJoin(orders, datatable.On(\"[Customers].[id2]\", \"[Orders].[user_id2]\"))\n\tassert.NoError(t, err)\n\tassert.NotNil(t, dt)\n\n\tfmt.Println(dt)\n\n\tcheckTable(t, dt,\n\t\t\"id\", \"prenom\", \"nom\", \"email\", \"ville\", \"id2\", \"user_id\", \"date_achat\", \"num_facture\", \"prix_total\",\n\t\t1, \"Aimée\", \"Marechal\", \"aime.marechal@example.com\", \"Paris\", 101, 1, time.Date(2013, time.January, 23, 0, 0, 0, 0, time.UTC), \"A00103\", 203.14,\n\t\t1, \"Aimée\", \"Marechal\", \"aime.marechal@example.com\", \"Paris\", 101, 1, time.Date(2013, time.February, 14, 0, 0, 0, 0, time.UTC), \"A00104\", 124.00,\n\t\t2, \"Esmée\", \"Lefort\", \"esmee.lefort@example.com\", \"Lyon\", 102, 2, time.Date(2013, time.February, 17, 0, 0, 0, 0, time.UTC), \"A00105\", 149.45,\n\t\t3, \"Marine\", \"Prevost\", \"m.prevost@example.com\", \"Lille\", 103, 3, time.Date(2013, time.February, 21, 0, 0, 0, 0, time.UTC), \"A00106\", 235.35,\n\t\tnil, nil, nil, nil, nil, nil, 5, time.Date(2013, time.March, 2, 0, 0, 0, 0, time.UTC), \"A00107\", 47.58,\n\t)\n}\n\nfunc TestJoinWithColumnName(t *testing.T) {\n\tcustomers, orders := sampleForJoin()\n\tassert.NoError(t, customers.RenameColumn(\"id\", \"ClientID\"))\n\n\tdt, err := customers.InnerJoin(orders, datatable.On(\"[Customers].[ClientID]\", \"[Orders].[user_id]\"))\n\tassert.NoError(t, err)\n\tassert.NotNil(t, dt)\n\n\tcheckTable(t, dt,\n\t\t\"ClientID\", \"prenom\", \"nom\", \"email\", \"ville\", \"date_achat\", \"num_facture\", \"prix_total\",\n\t\t1, \"Aimée\", \"Marechal\", \"aime.marechal@example.com\", \"Paris\", time.Date(2013, time.January, 23, 0, 0, 0, 0, time.UTC), \"A00103\", 203.14,\n\t\t1, \"Aimée\", \"Marechal\", \"aime.marechal@example.com\", \"Paris\", time.Date(2013, time.February, 14, 0, 0, 0, 0, time.UTC), \"A00104\", 124.00,\n\t\t2, \"Esmée\", \"Lefort\", \"esmee.lefort@example.com\", \"Lyon\", time.Date(2013, time.February, 17, 0, 0, 0, 0, time.UTC), \"A00105\", 149.45,\n\t\t3, \"Marine\", \"Prevost\", \"m.prevost@example.com\", \"Lille\", time.Date(2013, time.February, 21, 0, 0, 0, 0, time.UTC), \"A00106\", 235.35,\n\t)\n}\n"
  },
  {
    "path": "mutate_column.go",
    "content": "package datatable\n\nimport (\n\t\"strings\"\n\n\t\"github.com/datasweet/expr\"\n\t\"github.com/pkg/errors\"\n)\n\nfunc (t *DataTable) addColumn(col *column) error {\n\tif col == nil {\n\t\treturn ErrNilColumn\n\t}\n\n\t// Check name\n\tif len(col.name) == 0 {\n\t\treturn ErrNilColumnName\n\t}\n\tif c := t.Column(col.name); c != nil {\n\t\terr := errors.Errorf(\"column '%s' already exists\", col.name)\n\t\treturn errors.Wrap(err, ErrColumnAlreadyExists.Error())\n\t}\n\n\t// Check typ\n\tif len(col.typ) == 0 {\n\t\treturn ErrNilColumnType\n\t}\n\n\t// Check formula\n\tif len(col.formulae) > 0 {\n\t\tparsed, err := expr.Parse(col.formulae)\n\t\tif err != nil {\n\t\t\treturn errors.Wrapf(err, ErrFormulaeSyntax.Error())\n\t\t}\n\t\tcol.expr = parsed\n\t\tt.hasExpr = true\n\t}\n\n\t// Check serie\n\tif col.serie == nil {\n\t\treturn ErrNilSerie\n\t}\n\tln := col.serie.Len()\n\n\tif ln < t.nrows {\n\t\tcol.serie.Grow(t.nrows - ln)\n\t} else if ln > t.nrows {\n\t\tsize := ln - t.nrows\n\t\tfor _, col := range t.cols {\n\t\t\tcol.serie.Grow(size)\n\t\t}\n\t\tt.nrows = ln\n\t}\n\n\tt.cols = append(t.cols, col)\n\tt.dirty = true\n\treturn nil\n}\n\n// AddColumn to datatable with a serie of T\nfunc (t *DataTable) AddColumn(name string, ctyp ColumnType, opt ...ColumnOption) error {\n\tvar options ColumnOptions\n\tfor _, o := range opt {\n\t\to(&options)\n\t}\n\n\t// create serie based on ctyp\n\tsr, err := newColumnSerie(ctyp, options)\n\tif err != nil {\n\t\treturn errors.Wrap(err, ErrCreateSerie.Error())\n\t}\n\n\treturn t.addColumn(&column{\n\t\tname:     strings.TrimSpace(name),\n\t\ttyp:      ctyp,\n\t\tserie:    sr,\n\t\thidden:   options.Hidden,\n\t\tformulae: strings.TrimSpace(options.Expr),\n\t})\n}\n\n// RenameColumn to rename a column\nfunc (t *DataTable) RenameColumn(old, name string) error {\n\tname = strings.TrimSpace(name)\n\tif len(name) == 0 {\n\t\terr := errors.New(\"you must provided a column name\")\n\t\treturn errors.Wrap(err, ErrNilColumnName.Error())\n\t}\n\tif c := t.Column(name); c != nil {\n\t\terr := errors.Errorf(\"column '%s' already exists\", name)\n\t\treturn errors.Wrap(err, ErrColumnAlreadyExists.Error())\n\t}\n\tif col := t.Column(old); col != nil {\n\t\tcol.(*column).name = name\n\t\treturn nil\n\t}\n\terr := errors.Errorf(\"column '%s' does not exist\", name)\n\treturn errors.Wrap(err, ErrColumnNotFound.Error())\n}\n\n// HideAll to hides all column\n// a hidden column will not be exported\nfunc (t *DataTable) HideAll() {\n\tfor _, col := range t.cols {\n\t\tcol.hidden = true\n\t}\n}\n\n// HideColumn hides a column\n// a hidden column will not be exported\nfunc (t *DataTable) HideColumn(name string) {\n\tif c := t.Column(name); c != nil {\n\t\t(c.(*column)).hidden = true\n\t}\n}\n\n// ShowAll to show all column\n// a shown column will be exported\nfunc (t *DataTable) ShowAll() {\n\tfor _, col := range t.cols {\n\t\tcol.hidden = false\n\t}\n}\n\n// ShowColumn shows a column\n// a shown column will be exported\nfunc (t *DataTable) ShowColumn(name string) {\n\tif c := t.Column(name); c != nil {\n\t\t(c.(*column)).hidden = false\n\t}\n}\n\n// SwapColumn to swap 2 columns\nfunc (t *DataTable) SwapColumn(a, b string) error {\n\ti := t.ColumnIndex(a)\n\tif i < 0 {\n\t\terr := errors.Errorf(\"column '%s' not found\", a)\n\t\treturn errors.Wrap(err, ErrColumnNotFound.Error())\n\t}\n\tj := t.ColumnIndex(b)\n\tif j < 0 {\n\t\terr := errors.Errorf(\"column '%s' not found\", b)\n\t\treturn errors.Wrap(err, ErrColumnNotFound.Error())\n\t}\n\tt.cols[i], t.cols[j] = t.cols[j], t.cols[i]\n\treturn nil\n}\n"
  },
  {
    "path": "mutate_column_test.go",
    "content": "package datatable_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/datasweet/datatable\"\n)\n\nfunc TestSwapColumn(t *testing.T) {\n\ttb := datatable.New(\"test\")\n\ttb.AddColumn(\"champ\", datatable.String, datatable.Values(\"Malzahar\", \"Xerath\", \"Teemo\"))\n\ttb.AddColumn(\"champion\", datatable.String, datatable.Expr(\"upper(`champ`)\"))\n\ttb.AddColumn(\"win\", datatable.Int, datatable.Values(10, 20, 666))\n\ttb.AddColumn(\"loose\", datatable.Int, datatable.Values(6, 5, 666))\n\ttb.AddColumn(\"winRate\", datatable.Float64, datatable.Expr(\"(`win` * 100 / (`win` + `loose`))\"))\n\tcheckTable(t, tb,\n\t\t\"champ\", \"champion\", \"win\", \"loose\", \"winRate\",\n\t\t\"Malzahar\", \"MALZAHAR\", 10, 6, 62.5,\n\t\t\"Xerath\", \"XERATH\", 20, 5, 80.0,\n\t\t\"Teemo\", \"TEEMO\", 666, 666, 50.0,\n\t)\n\n\ttb.SwapColumn(\"champion\", \"winRate\")\n\n\tcheckTable(t, tb,\n\t\t\"champ\", \"winRate\", \"win\", \"loose\", \"champion\",\n\t\t\"Malzahar\", 62.5, 10, 6, \"MALZAHAR\",\n\t\t\"Xerath\", 80.0, 20, 5, \"XERATH\",\n\t\t\"Teemo\", 50.0, 666, 666, \"TEEMO\",\n\t)\n}\n"
  },
  {
    "path": "mutate_row.go",
    "content": "package datatable\n\nimport (\n\t\"github.com/pkg/errors\"\n)\n\n// NewRow create a new row\nfunc (t *DataTable) NewRow() Row {\n\tr := make(Row)\n\treturn r\n}\n\n// Append rows to the table\nfunc (t *DataTable) Append(row ...Row) {\n\tfor _, r := range row {\n\t\tif r == nil {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, col := range t.cols {\n\t\t\tif !col.IsComputed() {\n\t\t\t\tif cell, ok := r[col.Name()]; ok {\n\t\t\t\t\tcol.serie.Append(cell)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t\tcol.serie.Grow(1)\n\t\t}\n\t\tt.nrows++\n\t}\n\tt.dirty = true\n}\n\n// AppendRow creates a new row and append cells to this row\nfunc (t *DataTable) AppendRow(v ...interface{}) error {\n\tif len(v) != len(t.cols) {\n\t\terr := errors.Errorf(\"length mismatch: expected %d elements, values have %d elements\", len(t.cols), len(v))\n\t\treturn errors.Wrap(err, ErrLengthMismatch.Error())\n\t}\n\n\tfor i, col := range t.cols {\n\t\tif col.IsComputed() {\n\t\t\tcol.serie.Grow(1)\n\t\t} else {\n\t\t\tcol.serie.Append(v[i])\n\t\t}\n\t}\n\n\tt.nrows++\n\tt.dirty = true\n\n\treturn nil\n}\n\n// SwapRow in table\nfunc (t *DataTable) SwapRow(i, j int) {\n\tfor _, col := range t.cols {\n\t\tcol.serie.Swap(i, j)\n\t}\n}\n\n// Grow the table by size\nfunc (t *DataTable) Grow(size int) {\n\tfor _, col := range t.cols {\n\t\tcol.serie.Grow(size)\n\t}\n}\n\n// Update the row at index\nfunc (t *DataTable) Update(at int, row Row) error {\n\tif row == nil {\n\t\trow = make(Row, 0)\n\t}\n\n\tfor _, col := range t.cols {\n\t\tif col.IsComputed() {\n\t\t\tcontinue\n\t\t}\n\t\tcell, ok := row[col.name]\n\t\tif ok {\n\t\t\tif err := col.serie.Set(at, cell); err != nil {\n\t\t\t\terr := errors.Wrapf(err, \"col %s\", col.name)\n\t\t\t\treturn errors.Wrap(err, ErrUpdateRow.Error())\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tif err := col.serie.Set(at, nil); err != nil {\n\t\t\terr := errors.Wrapf(err, \"col %s\", col.name)\n\t\t\treturn errors.Wrap(err, ErrUpdateRow.Error())\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "mutate_row_test.go",
    "content": "package datatable_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/datasweet/datatable\"\n)\n\nfunc TestSwapRow(t *testing.T) {\n\ttb := datatable.New(\"test\")\n\ttb.AddColumn(\"champ\", datatable.String, datatable.Values(\"Malzahar\", \"Xerath\", \"Teemo\"))\n\ttb.AddColumn(\"champion\", datatable.String, datatable.Expr(\"upper(`champ`)\"))\n\ttb.AddColumn(\"win\", datatable.Int, datatable.Values(10, 20, 666))\n\ttb.AddColumn(\"loose\", datatable.Int, datatable.Values(6, 5, 666))\n\ttb.AddColumn(\"winRate\", datatable.Float64, datatable.Expr(\"(`win` * 100 / (`win` + `loose`))\"))\n\tcheckTable(t, tb,\n\t\t\"champ\", \"champion\", \"win\", \"loose\", \"winRate\",\n\t\t\"Malzahar\", \"MALZAHAR\", 10, 6, 62.5,\n\t\t\"Xerath\", \"XERATH\", 20, 5, 80.0,\n\t\t\"Teemo\", \"TEEMO\", 666, 666, 50.0,\n\t)\n\n\ttb.SwapRow(0, 2)\n\n\tcheckTable(t, tb,\n\t\t\"champ\", \"champion\", \"win\", \"loose\", \"winRate\",\n\t\t\"Teemo\", \"TEEMO\", 666, 666, 50.0,\n\t\t\"Xerath\", \"XERATH\", 20, 5, 80.0,\n\t\t\"Malzahar\", \"MALZAHAR\", 10, 6, 62.5,\n\t)\n}\n"
  },
  {
    "path": "row.go",
    "content": "package datatable\n\nimport (\n\t\"bytes\"\n\t\"encoding/gob\"\n\n\t\"github.com/cespare/xxhash\"\n)\n\n// Row contains a row relative to columns\ntype Row map[string]interface{}\n\n// Set cell\nfunc (r Row) Set(k string, v interface{}) Row {\n\tr[k] = v\n\treturn r\n}\n\n// Get cell\nfunc (r Row) Get(k string) interface{} {\n\t// Check colName exists\n\tif v, ok := r[k]; ok {\n\t\treturn v\n\t}\n\treturn nil\n}\n\n// Hash computes the hash code from this datarow\n// can be used to filter the table (distinct rows)\nfunc (r Row) Hash() uint64 {\n\tbuff := new(bytes.Buffer)\n\tenc := gob.NewEncoder(buff)\n\tfor _, v := range r {\n\t\tenc.Encode(v)\n\t}\n\treturn xxhash.Sum64(buff.Bytes())\n}\n"
  },
  {
    "path": "select.go",
    "content": "package datatable\n\n// Subset selects rows at index with size\nfunc (t *DataTable) Subset(at, size int) *DataTable {\n\tcpy := t.EmptyCopy()\n\n\tfor i, col := range t.cols {\n\t\tcpy.cols[i].serie = col.serie.Subset(at, size)\n\t}\n\n\tif len(cpy.cols) > 0 {\n\t\tcpy.nrows = cpy.cols[0].serie.Len()\n\n\t}\n\n\treturn cpy\n}\n\n// Head selects {size} first rows\nfunc (t *DataTable) Head(size int) *DataTable {\n\treturn t.Subset(0, size)\n}\n\n// Tail selects {size} last rows\nfunc (t *DataTable) Tail(size int) *DataTable {\n\treturn t.Subset(t.nrows-size, size)\n}\n"
  },
  {
    "path": "serie/converters.go",
    "content": "package serie\n\nimport (\n\t\"reflect\"\n\n\t\"github.com/datasweet/cast\"\n)\n\n// AsFloat64 to converts a serie to a serie of float64\n// Used for some statistics\nfunc AsFloat64(s Serie, missing *float64) Serie {\n\tif s == nil || s.Len() == 0 {\n\t\treturn Float64() // empty list of floats\n\t}\n\n\tswitch kind := s.Type().Kind(); kind {\n\tcase reflect.Float64:\n\t\treturn s\n\tdefault:\n\t\tln := s.Len()\n\t\tarr := make([]float64, 0, ln)\n\t\tfor i := 0; i < ln; i++ {\n\t\t\tif f, ok := cast.AsFloat64(s.Get(i)); ok {\n\t\t\t\tarr = append(arr, f)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif missing != nil {\n\t\t\t\tarr = append(arr, *missing)\n\t\t\t}\n\t\t}\n\n\t\tsf := Float64()\n\t\t(sf.(*serie)).slice = reflect.ValueOf(arr)\n\t\treturn sf\n\t}\n}\n"
  },
  {
    "path": "serie/copy.go",
    "content": "package serie\n\nimport (\n\t\"reflect\"\n)\n\nfunc (s *serie) makeEmptyCopy(capacity int) *serie {\n\treturn &serie{\n\t\ttyp:        s.typ,\n\t\tconverter:  s.converter,\n\t\tcomparer:   s.comparer,\n\t\tinterfacer: s.interfacer,\n\t\tslice:      reflect.MakeSlice(reflect.SliceOf(s.typ), 0, capacity),\n\t}\n}\n\nfunc (s *serie) EmptyCopy() Serie {\n\treturn s.makeEmptyCopy(0)\n}\n\nfunc (s *serie) Copy() Serie {\n\tcnt := s.Len()\n\tcpy := &serie{\n\t\ttyp:        s.typ,\n\t\tconverter:  s.converter,\n\t\tcomparer:   s.comparer,\n\t\tinterfacer: s.interfacer,\n\t\tslice:      reflect.MakeSlice(reflect.SliceOf(s.typ), cnt, cnt),\n\t}\n\treflect.Copy(cpy.slice, s.slice)\n\treturn cpy\n}\n"
  },
  {
    "path": "serie/copy_test.go",
    "content": "package serie_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/datasweet/datatable/serie\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestCopy(t *testing.T) {\n\toriginal := serie.Int(1, 2, 3, 4, 5, 6, 7, 8, 9)\n\tcpy := original.Copy()\n\tassert.NotSame(t, original, cpy)\n\tassert.Equal(t, original.Type(), cpy.Type())\n\tassert.Equal(t, original.Len(), cpy.Len())\n\tassertSerieEq(t, cpy, 1, 2, 3, 4, 5, 6, 7, 8, 9)\n\n\toriginal.Set(4, 50)\n\tassertSerieEq(t, original, 1, 2, 3, 4, 50, 6, 7, 8, 9)\n\tassertSerieEq(t, cpy, 1, 2, 3, 4, 5, 6, 7, 8, 9)\n}\n\nfunc TestEmptyCopy(t *testing.T) {\n\toriginal := serie.Int(1, 2, 3, 4, 5, 6, 7, 8, 9)\n\tcpy := original.EmptyCopy()\n\tassert.NotSame(t, original, cpy)\n\tassert.Equal(t, original.Type(), cpy.Type())\n\tassert.Equal(t, 9, original.Len())\n\tassert.Equal(t, 0, cpy.Len())\n}\n"
  },
  {
    "path": "serie/errors.go",
    "content": "package serie\n\nimport (\n\t\"github.com/pkg/errors\"\n)\n\n// Errors in mutate.go\nvar (\n\tErrOutOfRange                      = errors.New(\"out of range\")\n\tErrCantFlattenSliceWithSet         = errors.New(\"can't flatten slice with set\")\n\tErrGrowSizeMustBeStriclyPositive   = errors.New(\"grow: size must be > 0\")\n\tErrShrinkSizeMustBeStriclyPositive = errors.New(\"shrink: size must be > 0\")\n\tErrShrinkSizeMustBeLesserThanLen   = errors.New(\"shrink: size must be < len\")\n\tErrConcatTypeMismatch              = errors.New(\"concat: type mismatch\")\n)\n"
  },
  {
    "path": "serie/iterate.go",
    "content": "package serie\n\n// Iterator to creates a new iterator from the serie\nfunc (s *serie) Iterator() Iterator {\n\treturn &serieIterator{\n\t\tcurrent: -1,\n\t\tserie:   s,\n\t}\n}\n\n// Iterator defines an iterator\n// https://docs.microsoft.com/en-us/dotnet/api/system.collections.ienumerator.reset?view=netcore-3.1\ntype Iterator interface {\n\tNext() bool\n\tCurrent() interface{}\n\tReset()\n}\n\ntype serieIterator struct {\n\tcurrent int\n\tserie   *serie\n}\n\nfunc (it *serieIterator) Next() bool {\n\tit.current++\n\tif it.current >= it.serie.Len() {\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc (it *serieIterator) Current() interface{} {\n\treturn it.serie.Get(it.current)\n}\n\nfunc (it *serieIterator) Reset() {\n\tit.current = -1\n}\n"
  },
  {
    "path": "serie/iterate_test.go",
    "content": "package serie_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/datasweet/datatable/serie\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestIterate(t *testing.T) {\n\txs := []float64{\n\t\t32.32, 56.98, 21.52, 44.32,\n\t\t55.63, 13.75, 43.47, 43.34,\n\t\t12.34,\n\t}\n\n\ts := serie.Float64(xs)\n\n\tindex := 0\n\tfor it := s.Iterator(); it.Next(); {\n\t\tassert.Equal(t, xs[index], it.Current())\n\t\tindex++\n\t}\n}\n"
  },
  {
    "path": "serie/mutate.go",
    "content": "package serie\n\nimport (\n\t\"reflect\"\n\n\t\"github.com/pkg/errors\"\n)\n\nfunc (s *serie) asValue(i interface{}) []reflect.Value {\n\tin := i\n\n\tif cs, ok := in.(Serie); ok {\n\t\tin = cs.Slice()\n\t}\n\n\trv := reflect.ValueOf(in)\n\tkind := rv.Kind()\n\n\tswitch kind {\n\tcase reflect.Slice, reflect.Array:\n\t\tarr := make([]reflect.Value, 0, rv.Len())\n\t\tfor j := 0; j < rv.Len(); j++ {\n\t\t\tarr = append(arr, s.converter.Call([]reflect.Value{rv.Index(j)})...)\n\t\t}\n\t\treturn arr\n\tcase reflect.Invalid:\n\t\t// case \"nil\"\n\t\treturn s.converter.Call([]reflect.Value{reflect.Zero(s.typ)})\n\tdefault:\n\t\treturn s.converter.Call([]reflect.Value{rv})\n\t}\n\n}\n\n// Append values to the serie.\nfunc (s *serie) Append(v ...interface{}) {\n\tvalues := make([]reflect.Value, 0, len(v))\n\tfor _, val := range v {\n\t\tvalues = append(values, s.asValue(val)...)\n\t}\n\ts.slice = reflect.Append(s.slice, values...)\n}\n\n// Prepend values to the serie\nfunc (s *serie) Prepend(v ...interface{}) error {\n\treturn s.Insert(0, v...)\n}\n\n// Insert values to the serie at index\nfunc (s *serie) Insert(at int, v ...interface{}) (err error) {\n\tn := s.Len()\n\n\tif at < 0 || ((at > 0 || n > 0) && at >= n) {\n\t\terr := errors.Errorf(\"insert at [%d]: index out of range with length %d\", at, n)\n\t\treturn errors.Wrap(err, ErrOutOfRange.Error())\n\t}\n\n\tvalues := make([]reflect.Value, 0, len(v))\n\tfor _, val := range v {\n\t\tvalues = append(values, s.asValue(val)...)\n\t}\n\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\n\tfor i := 0; i < len(values); i++ {\n\t\ts.slice = reflect.Append(s.slice, reflect.Zero(s.typ))\n\t}\n\n\t// Refresh len\n\tn = s.Len()\n\n\treflect.Copy(s.slice.Slice(at+len(values), n), s.slice.Slice(at, n))\n\n\tfor i, rv := range values {\n\t\ts.slice.Index(i + at).Set(rv)\n\t}\n\n\treturn nil\n}\n\n// Set a value at index\nfunc (s *serie) Set(at int, v interface{}) error {\n\tif at < 0 || at >= s.Len() {\n\t\terr := errors.Errorf(\"set at [%d]: index out of range with length %d\", at, s.Len())\n\t\treturn errors.Wrap(err, ErrOutOfRange.Error())\n\t}\n\tvalues := s.asValue(v)\n\n\tif len(values) != 1 {\n\t\terr := errors.Errorf(\"set at [%d]: can't flatten slice with set\", at)\n\t\treturn errors.Wrap(err, ErrCantFlattenSliceWithSet.Error())\n\t}\n\n\ts.slice.Index(at).Set(values[0])\n\treturn nil\n}\n\n// Delete a value at index\nfunc (s *serie) Delete(at int) error {\n\tcnt := s.Len()\n\tif at < 0 || at >= cnt {\n\t\terr := errors.Errorf(\"delete at [%d]: index out of range with length %d\", at, cnt)\n\t\treturn errors.Wrap(err, ErrCantFlattenSliceWithSet.Error())\n\t}\n\tif at < cnt-1 {\n\t\treflect.Copy(s.slice.Slice(at, cnt), s.slice.Slice(at+1, cnt))\n\t}\n\ts.slice = s.slice.Slice(0, cnt-1)\n\treturn nil\n}\n\n// Grow the serie with size\n// Grow will create zero value\nfunc (s *serie) Grow(size int) error {\n\tif size < 0 {\n\t\terr := errors.Errorf(\"grow: size '%d' must be > 0\", size)\n\t\treturn errors.Wrap(err, ErrGrowSizeMustBeStriclyPositive.Error())\n\t}\n\tfor i := 0; i < size; i++ {\n\t\ts.slice = reflect.Append(s.slice, reflect.Zero(s.typ))\n\t}\n\treturn nil\n}\n\n// Shrink the serie with size\nfunc (s *serie) Shrink(size int) error {\n\tif size < 0 {\n\t\terr := errors.Errorf(\"shrink: size '%d' must be > 0\", size)\n\t\treturn errors.Wrap(err, ErrShrinkSizeMustBeStriclyPositive.Error())\n\t}\n\tcnt := s.Len()\n\tif size > cnt {\n\t\terr := errors.Errorf(\"shrink: size '%d' must be < length '%d'\", size, cnt)\n\t\treturn errors.Wrap(err, ErrShrinkSizeMustBeLesserThanLen.Error())\n\t}\n\ts.slice = s.slice.Slice(0, cnt-size)\n\treturn nil\n}\n\n// Concat the serie (mutate) with others series\n// series provided must be the same type as the source serie\nfunc (s *serie) Concat(serie ...Serie) error {\n\tif len(serie) == 0 {\n\t\treturn nil\n\t}\n\n\tfor i, other := range serie {\n\t\tif other.Type() != s.Type() {\n\t\t\terr := errors.Errorf(\"concat: serie #%d is not the same type as source\", i)\n\t\t\treturn errors.Wrap(err, ErrConcatTypeMismatch.Error())\n\t\t}\n\n\t\ts.Append(other.Slice())\n\t}\n\n\treturn nil\n}\n\nfunc (s *serie) Clear() {\n\ts.slice = reflect.MakeSlice(reflect.SliceOf(s.typ), 0, 0)\n}\n"
  },
  {
    "path": "serie/mutate_test.go",
    "content": "package serie_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/datasweet/datatable/serie\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestAppend(t *testing.T) {\n\ts := serie.Int()\n\tassertSerieEq(t, s)\n\n\ts.Append(1, 2, 3, 4, \"5\")\n\tassertSerieEq(t, s, 1, 2, 3, 4, 5)\n\n\ts.Append(nil, 6, 7, \"8\", 9, 10)\n\tassertSerieEq(t, s, 1, 2, 3, 4, 5, 0, 6, 7, 8, 9, 10)\n}\n\nfunc TestPrepend(t *testing.T) {\n\ts := serie.Int()\n\tassertSerieEq(t, s)\n\n\tassert.NoError(t, s.Prepend(1, 2, 3, 4, 5))\n\tassertSerieEq(t, s, 1, 2, 3, 4, 5)\n\n\tassert.NoError(t, s.Prepend(-4, -3, -2, -1, 0))\n\tassertSerieEq(t, s, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5)\n}\n\nfunc TestInsert(t *testing.T) {\n\ts := serie.Int(1, 2, 3, 4, 5)\n\tassertSerieEq(t, s, 1, 2, 3, 4, 5)\n\n\tassert.NoError(t, s.Insert(2, 7, 8, 9, 10))\n\tassertSerieEq(t, s, 1, 2, 7, 8, 9, 10, 3, 4, 5)\n\n\tassert.Error(t, s.Insert(-1, 7, 8, 9, 10))\n\tassert.Error(t, s.Insert(101, 7, 8, 9, 10))\n}\n\nfunc TestSet(t *testing.T) {\n\ts := serie.Int(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)\n\tassertSerieEq(t, s, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9)\n\n\tassert.Error(t, s.Set(-1, 100))\n\tassert.Error(t, s.Set(10, 100))\n\tassert.Error(t, s.Set(0, []int{0, 1, 2, 4}))\n\n\tassert.NoError(t, s.Set(5, 555))\n\tassertSerieEq(t, s, 0, 1, 2, 3, 4, 555, 6, 7, 8, 9)\n\n\tassert.NoError(t, s.Set(9, 999))\n\tassertSerieEq(t, s, 0, 1, 2, 3, 4, 555, 6, 7, 8, 999)\n\n\tassert.NoError(t, s.Set(0, -5))\n\tassertSerieEq(t, s, -5, 1, 2, 3, 4, 555, 6, 7, 8, 999)\n\n}\n\nfunc TestDelete(t *testing.T) {\n\ts := serie.Int(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)\n\tassertSerieEq(t, s, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9)\n\n\tassert.Error(t, s.Delete(-1))\n\tassert.Error(t, s.Delete(10))\n\n\tassert.NoError(t, s.Delete(5))\n\tassertSerieEq(t, s, 0, 1, 2, 3, 4, 6, 7, 8, 9)\n\n\tassert.NoError(t, s.Delete(8))\n\tassertSerieEq(t, s, 0, 1, 2, 3, 4, 6, 7, 8)\n\n\tassert.NoError(t, s.Delete(0))\n\tassertSerieEq(t, s, 1, 2, 3, 4, 6, 7, 8)\n}\n\nfunc TestGrow(t *testing.T) {\n\ts := serie.Int(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)\n\tassertSerieEq(t, s, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9)\n\n\tassert.Error(t, s.Grow(-5))\n\n\tassert.NoError(t, s.Grow(5))\n\tassertSerieEq(t, s, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0)\n\n\ts = serie.IntN(0, 1, 2, 3, 4, 5)\n\tassertSerieEq(t, s, 0, 1, 2, 3, 4, 5)\n\tassert.NoError(t, s.Grow(5))\n\tassertSerieEq(t, s, 0, 1, 2, 3, 4, 5, nil, nil, nil, nil, nil)\n}\n\nfunc TestShrink(t *testing.T) {\n\ts := serie.Int(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)\n\tassertSerieEq(t, s, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9)\n\n\tassert.Error(t, s.Shrink(-5))\n\tassert.Error(t, s.Shrink(11))\n\n\tassert.NoError(t, s.Shrink(5))\n\tassertSerieEq(t, s, 0, 1, 2, 3, 4)\n\n\tassert.NoError(t, s.Shrink(5))\n\tassertSerieEq(t, s)\n}\n\nfunc TestConcat(t *testing.T) {\n\ts := serie.Int(0, 1, 2, 3, 4)\n\tassert.Error(t, s.Concat(serie.IntN(-1, -2, nil)))\n\tassert.NoError(t, s.Concat(serie.Int(6, 7, 8, 9, 10)))\n\tassertSerieEq(t, s, 0, 1, 2, 3, 4, 6, 7, 8, 9, 10)\n\n\ts = serie.StringN(\"Léon\", \"Marie\", \"Sophie\", \"Marcel\")\n\tassertSerieEq(t, s, \"Léon\", \"Marie\", \"Sophie\", \"Marcel\")\n\tassert.NoError(t, s.Concat(serie.StringN(\"Marion\", \"Paul\", \"Marie\", \"Marcel\")))\n\tassertSerieEq(t, s, \"Léon\", \"Marie\", \"Sophie\", \"Marcel\", \"Marion\", \"Paul\", \"Marie\", \"Marcel\")\n}\n\nfunc TestClear(t *testing.T) {\n\ts := serie.Int(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)\n\tassert.Equal(t, 10, s.Len())\n\ts.Clear()\n\tassert.Equal(t, 0, s.Len())\n}\n"
  },
  {
    "path": "serie/select.go",
    "content": "package serie\n\nimport (\n\t\"reflect\"\n)\n\n// Head returns the first {size} rows of the serie\nfunc (s *serie) Head(size int) Serie {\n\treturn s.Subset(0, size)\n}\n\n// Head returns the last {size} rows of the serie\nfunc (s *serie) Tail(size int) Serie {\n\treturn s.Subset(s.Len()-size, size)\n}\n\n// Subset returns the a subset {at} index and with {size}\nfunc (s *serie) Subset(at, size int) Serie {\n\tcpy := s.makeEmptyCopy(0)\n\tln := s.Len()\n\tif at < 0 || at >= ln || size <= 0 {\n\t\treturn cpy\n\t}\n\tto := at + size\n\tif to > ln {\n\t\tto = ln\n\t}\n\tcpy.slice = s.slice.Slice(at, to)\n\treturn cpy\n}\n\n// Filter the series with a predicate\n// Predicate must be func(T) bool\nfunc (s *serie) Filter(predicate interface{}) Serie {\n\t// Check predicate\n\t// must be func(T) bool\n\n\tif predicate == nil {\n\t\tpanic(\"no predicate\")\n\t}\n\n\tpv := reflect.ValueOf(predicate)\n\tpt := pv.Type()\n\tif pt.Kind() != reflect.Func ||\n\t\tpt.NumIn() != 1 ||\n\t\tpt.NumOut() != 1 ||\n\t\tpt.In(0) != s.typ ||\n\t\tpt.Out(0).Kind() != reflect.Bool {\n\t\tpanic(\"wrong predicate signature, must be func(T) bool\")\n\t}\n\n\tcnt := s.Len()\n\tcpy := s.makeEmptyCopy(cnt)\n\n\tfor i := 0; i < cnt; i++ {\n\t\tv := s.slice.Index(i)\n\t\tok := pv.Call([]reflect.Value{v})[0].Interface().(bool)\n\t\tif ok {\n\t\t\tcpy.slice = reflect.Append(cpy.slice, v)\n\t\t}\n\t}\n\n\treturn cpy\n}\n\n// Distinct remove duplicate values\nfunc (s *serie) Distinct() Serie {\n\tcnt := s.Len()\n\tcpy := s.makeEmptyCopy(cnt)\n\n\tm := make(map[interface{}]bool)\n\n\tfor i := 0; i < cnt; i++ {\n\t\tv := s.slice.Index(i)\n\t\tif _, ok := m[v.Interface()]; !ok {\n\t\t\tcpy.slice = reflect.Append(cpy.slice, v)\n\t\t\tm[v.Interface()] = true\n\t\t}\n\t}\n\n\treturn cpy\n}\n\n// Pick picks some indexes {at} to create a new serie\n// If {at} is out of range, Pick will fill with a \"zero\" value\nfunc (s *serie) Pick(at ...int) Serie {\n\tcpy := s.makeEmptyCopy(len(at))\n\tcnt := s.Len()\n\n\tfor _, pos := range at {\n\t\tif pos >= 0 && pos < cnt {\n\t\t\tcpy.slice = reflect.Append(cpy.slice, s.slice.Index(pos))\n\t\t} else {\n\t\t\tcpy.slice = reflect.Append(cpy.slice, s.converter.Call([]reflect.Value{reflect.Zero(s.typ)})...)\n\t\t}\n\t}\n\treturn cpy\n}\n\n// Where to filter the serie on a predicate\nfunc (s *serie) Where(predicate func(interface{}) bool) Serie {\n\tcpy := s.makeEmptyCopy(s.Len())\n\n\tif predicate == nil {\n\t\treturn cpy\n\t}\n\n\tindex := 0\n\tfor it := s.Iterator(); it.Next(); {\n\t\tv := it.Current()\n\t\tif predicate(v) {\n\t\t\tcpy.slice = reflect.Append(cpy.slice, s.slice.Index(index))\n\t\t}\n\t\tindex++\n\t}\n\n\treturn cpy\n}\n\n// NonNils selects all non-nils values in serie\nfunc (s *serie) NonNils() Serie {\n\treturn s.Where(func(item interface{}) bool {\n\t\treturn item != nil\n\t})\n}\n"
  },
  {
    "path": "serie/select_test.go",
    "content": "package serie_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/datasweet/datatable/serie\"\n)\n\nfunc TestHead(t *testing.T) {\n\ts := serie.Int(1, 2, 3, 4, 5, 6, 7, 8, 9)\n\tassertSerieEq(t, s, 1, 2, 3, 4, 5, 6, 7, 8, 9)\n\tassertSerieEq(t, s.Head(5), 1, 2, 3, 4, 5)\n\tassertSerieEq(t, s.Head(1), 1)\n\tassertSerieEq(t, s.Head(9), 1, 2, 3, 4, 5, 6, 7, 8, 9)\n\tassertSerieEq(t, s.Head(10), 1, 2, 3, 4, 5, 6, 7, 8, 9)\n\tassertSerieEq(t, s.Head(0))\n\tassertSerieEq(t, s.Head(-1))\n\tassertSerieEq(t, s.Head(5).Head(1), 1)\n}\n\nfunc TestTail(t *testing.T) {\n\ts := serie.Int(1, 2, 3, 4, 5, 6, 7, 8, 9)\n\tassertSerieEq(t, s, 1, 2, 3, 4, 5, 6, 7, 8, 9)\n\tassertSerieEq(t, s.Tail(5), 5, 6, 7, 8, 9)\n\tassertSerieEq(t, s.Tail(1), 9)\n\tassertSerieEq(t, s.Tail(9), 1, 2, 3, 4, 5, 6, 7, 8, 9)\n\tassertSerieEq(t, s.Tail(10))\n\tassertSerieEq(t, s.Tail(0))\n\tassertSerieEq(t, s.Tail(-1))\n\tassertSerieEq(t, s.Tail(5).Tail(1), 9)\n}\n\nfunc TestSubset(t *testing.T) {\n\ts := serie.Int(1, 2, 3, 4, 5, 6, 7, 8, 9)\n\tassertSerieEq(t, s, 1, 2, 3, 4, 5, 6, 7, 8, 9)\n\tassertSerieEq(t, s.Subset(4, 3), 5, 6, 7)\n\tassertSerieEq(t, s.Subset(7, 2), 8, 9)\n\tassertSerieEq(t, s.Subset(7, 3), 8, 9)\n\tassertSerieEq(t, s.Subset(8, 1), 9)\n\tassertSerieEq(t, s.Subset(0, 5), 1, 2, 3, 4, 5)\n\tassertSerieEq(t, s.Subset(0, 1), 1)\n\tassertSerieEq(t, s.Subset(5, 0))\n\tassertSerieEq(t, s.Subset(5, -1))\n\tassertSerieEq(t, s.Subset(-1, 5))\n\tassertSerieEq(t, s.Subset(10, 5))\n}\n\n/*\nfunc TestFilter(t *testing.T) {\n\ts := serie.Int(1, 2, 3, 4, 5, 6, 7, 8, 9)\n\tassertSerieEq(t, s, 1, 2, 3, 4, 5, 6, 7, 8, 9)\n\n\tassert.Panics(t, func() { s.Filter(nil) })\n\tassert.Panics(t, func() {\n\t\ts.Filter(func(val float32) bool {\n\t\t\treturn val == 3.14\n\t\t})\n\t})\n\n\tres := s.Filter(func(val int) bool {\n\t\treturn val%2 == 1\n\t})\n\tassertSerieEq(t, res, 1, 3, 5, 7, 9)\n}\n*/\n\nfunc TestDistinct(t *testing.T) {\n\ts := serie.Int(\n\t\t31, 23, 98, 3, 59, 67, 5, 5, 87, 18,\n\t\t3, 88, 7, 63, 29, 62, 37, 66, 87, 26,\n\t\t24, 5, 62, 75, 69, 56, 15, 59, 40, 34,\n\t\t68, 32, 34, 29, 90, 21, 8, 8, 100, 64,\n\t\t30, 56, 73, 2, 65, 74, 3, 26, 92, 46,\n\t\t6, 100, 35, 17, 91, 55, 99, 87, 9, 25,\n\t\t55, 76, 39, 78, 43, 99, 35, 90, 36, 27,\n\t\t52, 65, 33, 49, 84, 87, 42, 92, 27, 65,\n\t\t48, 47, 74, 98, 76, 88, 18, 100, 69, 57,\n\t\t69, 90, 74, 25, 64, 37, 63, 61, 85, 12,\n\t)\n\tassertSerieEq(t, s.Distinct(),\n\t\t31, 23, 98, 3, 59, 67, 5, 87, 18, 88,\n\t\t7, 63, 29, 62, 37, 66, 26, 24, 75, 69,\n\t\t56, 15, 40, 34, 68, 32, 90, 21, 8, 100,\n\t\t64, 30, 73, 2, 65, 74, 92, 46, 6, 35,\n\t\t17, 91, 55, 99, 9, 25, 76, 39, 78, 43,\n\t\t36, 27, 52, 33, 49, 84, 42, 48, 47, 57,\n\t\t61, 85, 12,\n\t)\n}\n\nfunc TestPick(t *testing.T) {\n\ts := serie.Int(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)\n\tassertSerieEq(t, s, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9)\n\tassertSerieEq(t, s.Pick(4, 3), 4, 3)\n\tassertSerieEq(t, s.Pick(-1), 0)\n\tassertSerieEq(t, s.Pick(0, -1, 4, 3, 9, 10), 0, 0, 4, 3, 9, 0)\n\n\ts = serie.IntN(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)\n\tassertSerieEq(t, s, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9)\n\tassertSerieEq(t, s.Pick(4, 3), 4, 3)\n\tassertSerieEq(t, s.Pick(-1), nil)\n\tassertSerieEq(t, s.Pick(0, -1, 4, 3, 9, 10), 0, nil, 4, 3, 9, nil)\n\n}\n\nfunc TestWhere(t *testing.T) {\n\ts := serie.Int(1, 2, 3, 4, 5, 6, 7, 8, 9)\n\tassertSerieEq(t, s, 1, 2, 3, 4, 5, 6, 7, 8, 9)\n\n\tres := s.Where(nil)\n\tassert.NotNil(t, res)\n\tassert.Equal(t, 0, res.Len())\n\n\tassert.Panics(t, func() {\n\t\ts.Where(func(val interface{}) bool {\n\t\t\treturn val.(float32) == 3.14\n\t\t})\n\t})\n\n\tres = s.Where(func(val interface{}) bool {\n\t\treturn val.(int)%2 == 1\n\t})\n\tassertSerieEq(t, res, 1, 3, 5, 7, 9)\n}\n\nfunc TestNonNils(t *testing.T) {\n\ts := serie.IntN(\n\t\t31, 23, 98, 3, 59, 67, 5, 5, 87, 18,\n\t\t3, 88, 7, 63, 29, 62, 37, 66, 87, 26,\n\t\t24, 5, 62, 75, 69, 56, 15, 59, 40, 34,\n\t\t68, 32, 34, 29, 90, 21, 8, 8, 100, 64,\n\t\t30, 56, 73, 2, 65, \"74\", 3, 26, 92, 46,\n\t\t6, 100, 35, 17, 91, 55, 99, 87, 9, 25,\n\t\t55, \"teemo\", 39, 78, 43, 99, 35, 90, 36, 27,\n\t\t52, 65, 33, nil, 84, 87, 42, 92, 27, 65,\n\t\t48, 47, 74, 98, 76, 88, 18, 100, 69, 57,\n\t\t69, 90, 74, 25, 64, 37, 63, 61, 85, 12,\n\t)\n\tassertSerieEq(t, s,\n\t\t31, 23, 98, 3, 59, 67, 5, 5, 87, 18,\n\t\t3, 88, 7, 63, 29, 62, 37, 66, 87, 26,\n\t\t24, 5, 62, 75, 69, 56, 15, 59, 40, 34,\n\t\t68, 32, 34, 29, 90, 21, 8, 8, 100, 64,\n\t\t30, 56, 73, 2, 65, 74, 3, 26, 92, 46,\n\t\t6, 100, 35, 17, 91, 55, 99, 87, 9, 25,\n\t\t55, nil, 39, 78, 43, 99, 35, 90, 36, 27,\n\t\t52, 65, 33, nil, 84, 87, 42, 92, 27, 65,\n\t\t48, 47, 74, 98, 76, 88, 18, 100, 69, 57,\n\t\t69, 90, 74, 25, 64, 37, 63, 61, 85, 12)\n\tassertSerieEq(t, s.NonNils(),\n\t\t31, 23, 98, 3, 59, 67, 5, 5, 87, 18,\n\t\t3, 88, 7, 63, 29, 62, 37, 66, 87, 26,\n\t\t24, 5, 62, 75, 69, 56, 15, 59, 40, 34,\n\t\t68, 32, 34, 29, 90, 21, 8, 8, 100, 64,\n\t\t30, 56, 73, 2, 65, 74, 3, 26, 92, 46,\n\t\t6, 100, 35, 17, 91, 55, 99, 87, 9, 25,\n\t\t55, 39, 78, 43, 99, 35, 90, 36, 27,\n\t\t52, 65, 33, 84, 87, 42, 92, 27, 65,\n\t\t48, 47, 74, 98, 76, 88, 18, 100, 69, 57,\n\t\t69, 90, 74, 25, 64, 37, 63, 61, 85, 12,\n\t)\n}\n"
  },
  {
    "path": "serie/serie.go",
    "content": "package serie\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"sort\"\n)\n\ntype Serie interface {\n\tType() reflect.Type\n\tSlice() interface{}     // Underlying slice\n\tGet(at int) interface{} // T[i]. If T is an interfacer, returns Interfaced value\n\tAll() []interface{}\n\n\t// Iterate\n\tIterator() Iterator\n\n\t// Mutate\n\tAppend(v ...interface{})\n\tPrepend(v ...interface{}) error\n\tInsert(at int, v ...interface{}) error\n\tSet(at int, v interface{}) error\n\tDelete(at int) error\n\tGrow(size int) error\n\tShrink(size int) error\n\tConcat(serie ...Serie) error\n\tClear()\n\n\t// Select\n\tHead(size int) Serie\n\tTail(size int) Serie\n\tSubset(at, size int) Serie\n\tDistinct() Serie\n\tPick(at ...int) Serie\n\tWhere(predicate func(interface{}) bool) Serie\n\tNonNils() Serie\n\n\t// Copy\n\tEmptyCopy() Serie\n\tCopy() Serie\n\n\t// Sort\n\tsort.Interface\n\tCompare(i, j int) int\n\tSortAsc()\n\tSortDesc()\n\n\t// // Print\n\t// Print(opts ...PrintOption) string\n\t// fmt.Stringer\n\n\t// Statistics\n\tAvg(opt ...StatOption) float64\n\tCount(opt ...StatOption) int64\n\tCountDistinct(opt ...StatOption) int64\n\tCusum(opt ...StatOption) []float64\n\tMax(opt ...StatOption) float64\n\tMin(opt ...StatOption) float64\n\tMedian(opt ...StatOption) float64\n\tStddev(opt ...StatOption) float64\n\tSum(opt ...StatOption) float64\n\tVariance(opt ...StatOption) float64\n}\n\n// Interfacer to convert a value of serie to interface{}\n// Used with serie.Get(at) serie.All()\ntype Interfacer interface {\n\tInterface() interface{}\n}\n\nconst (\n\tLt = -1\n\tEq = 0\n\tGt = 1\n)\n\ntype serie struct {\n\ttyp        reflect.Type\n\tslice      reflect.Value\n\tconverter  reflect.Value\n\tcomparer   reflect.Value\n\tinterfacer bool\n}\n\nfunc New(typ interface{}, converter interface{}, comparer interface{}) Serie {\n\tif typ == nil {\n\t\tpanic(\"arg 'typ' is not a concrete type\")\n\t}\n\tif converter == nil {\n\t\tpanic(\"nil converter\")\n\t}\n\tif comparer == nil {\n\t\tpanic(\"nil comparer\")\n\t}\n\n\trv := reflect.ValueOf(typ)\n\tkind := rv.Kind()\n\n\tif kind == reflect.Invalid {\n\t\tpanic(fmt.Sprintf(\"type %T is invalid\", rv))\n\t}\n\n\tserie := &serie{}\n\n\tif kind == reflect.Slice {\n\t\tserie.slice = rv\n\t\tserie.typ = rv.Type().Elem()\n\t} else {\n\t\tserie.typ = rv.Type()\n\t\tserie.slice = reflect.MakeSlice(reflect.SliceOf(serie.typ), 0, 0)\n\t}\n\n\t// analyse converter\n\tconvValue := reflect.ValueOf(converter)\n\tconvType := convValue.Type()\n\tif convType.Kind() != reflect.Func ||\n\t\tconvType.NumIn() != 1 ||\n\t\tconvType.NumOut() != 1 ||\n\t\tconvType.In(0).Kind() != reflect.Interface ||\n\t\tconvType.Out(0) != serie.typ {\n\t\tpanic(fmt.Sprintf(\"wrong converter signature, must be func(i interface{}) %s\", serie.typ.Name()))\n\t}\n\tserie.converter = convValue\n\n\t// analyse comparer\n\tcmpValue := reflect.ValueOf(comparer)\n\tcmpType := cmpValue.Type()\n\tif cmpType.Kind() != reflect.Func ||\n\t\tcmpType.NumIn() != 2 ||\n\t\tcmpType.NumOut() != 1 ||\n\t\tcmpType.In(0) != serie.typ ||\n\t\tcmpType.In(1) != serie.typ ||\n\t\tcmpType.Out(0).Kind() != reflect.Int {\n\t\tpanic(\"wrong comparer signature, must be func(i, j T) int\")\n\t}\n\tserie.comparer = cmpValue\n\n\t// analyse interfacer\n\tif serie.typ.Implements(reflect.TypeOf((*Interfacer)(nil)).Elem()) {\n\t\tserie.interfacer = true\n\t}\n\n\treturn serie\n}\n\n// Len returns the len of the serie\nfunc (s *serie) Len() int {\n\treturn s.slice.Len()\n}\n\n// Type returns the underlying type of serie\nfunc (s *serie) Type() reflect.Type {\n\treturn s.typ\n}\n\n// Slice returns the underlying slice\nfunc (s *serie) Slice() interface{} {\n\treturn s.slice.Interface()\n}\n\n// Get returns the value at index\n// If the serie is an interfacer, ie, values have custom Interface() func,\n// the Interface() func will be called.\n// So you can have difference between serie.Slice()[at] and serie.Get(at)\nfunc (s *serie) Get(at int) interface{} {\n\tif s.interfacer {\n\t\treturn s.slice.Index(at).Interface().(Interfacer).Interface()\n\t}\n\treturn s.slice.Index(at).Interface()\n}\n\n// All to get all values\n// <!> Better to use serie.Iterator() if you want to work on values\nfunc (s *serie) All() []interface{} {\n\tall := make([]interface{}, 0, s.Len())\n\tfor it := s.Iterator(); it.Next(); {\n\t\tall = append(all, it.Current())\n\t}\n\treturn all\n}\n\nfunc (s *serie) String() string {\n\treturn fmt.Sprintf(\"%+v\", s.Slice())\n}\n"
  },
  {
    "path": "serie/serie_bool.go",
    "content": "package serie\n\nimport (\n\t\"github.com/datasweet/cast\"\n)\n\nfunc Bool(v ...interface{}) Serie {\n\ts := New(false, asBool, compareBool)\n\tif len(v) > 0 {\n\t\ts.Append(v...)\n\t}\n\treturn s\n}\n\nfunc BoolN(v ...interface{}) Serie {\n\ts := New(NullBool{}, asNullBool, compareNullBool)\n\tif len(v) > 0 {\n\t\ts.Append(v...)\n\t}\n\treturn s\n}\n\nfunc asBool(i interface{}) bool {\n\tb, _ := cast.AsBool(i)\n\treturn b\n}\n\nfunc compareBool(a, b bool) int {\n\tif a == b {\n\t\treturn Eq\n\t}\n\tif !a {\n\t\treturn Lt\n\t}\n\treturn Gt\n}\n\ntype NullBool struct {\n\tBool  bool\n\tValid bool\n}\n\nfunc (b NullBool) Interface() interface{} {\n\tif b.Valid {\n\t\treturn b.Bool\n\t}\n\treturn nil\n}\n\nfunc asNullBool(i interface{}) NullBool {\n\tvar ni NullBool\n\tif i == nil {\n\t\treturn ni\n\t}\n\n\tif v, ok := i.(NullBool); ok {\n\t\treturn v\n\t}\n\n\tif v, ok := cast.AsBool(i); ok {\n\t\tni.Bool = v\n\t\tni.Valid = true\n\t}\n\treturn ni\n}\n\nfunc compareNullBool(a, b NullBool) int {\n\tif !b.Valid {\n\t\tif !a.Valid {\n\t\t\treturn Eq\n\t\t}\n\t\treturn Gt\n\t}\n\tif !a.Valid {\n\t\treturn Lt\n\t}\n\treturn compareBool(a.Bool, b.Bool)\n}\n"
  },
  {
    "path": "serie/serie_bool_test.go",
    "content": "package serie_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/datasweet/datatable/serie\"\n)\n\nfunc TestSerieBool(t *testing.T) {\n\ts := serie.Bool()\n\tassert.NotNil(t, s)\n\n\ts.Append(1, 0, true, \"teemo\", nil)\n\n\tassertSerieEq(t, s, true, false, true, false, false)\n\n\ts.SortAsc()\n\tassertSerieEq(t, s, false, false, false, true, true)\n\n\ts.SortDesc()\n\tassertSerieEq(t, s, true, true, false, false, false)\n\n}\n\nfunc TestSerieBoolN(t *testing.T) {\n\ts := serie.BoolN()\n\tassert.NotNil(t, s)\n\n\ts.Append(1, 0, true, \"teemo\", nil)\n\tassertSerieEq(t, s, true, false, true, nil, nil)\n\n\ts.SortAsc()\n\tassertSerieEq(t, s, nil, nil, false, true, true)\n\n\ts.SortDesc()\n\tassertSerieEq(t, s, true, true, false, nil, nil)\n\n}\n"
  },
  {
    "path": "serie/serie_float32.go",
    "content": "package serie\n\nimport (\n\t\"github.com/datasweet/cast\"\n)\n\nfunc Float32(v ...interface{}) Serie {\n\ts := New(float32(0), asFloat32, compareFloat32)\n\tif len(v) > 0 {\n\t\ts.Append(v...)\n\t}\n\treturn s\n}\n\nfunc Float32N(v ...interface{}) Serie {\n\ts := New(NullFloat32{}, asNullFloat32, compareNullFloat32)\n\tif len(v) > 0 {\n\t\ts.Append(v...)\n\t}\n\treturn s\n}\n\nfunc asFloat32(i interface{}) float32 {\n\tf, _ := cast.AsFloat32(i)\n\treturn f\n}\n\nfunc compareFloat32(a, b float32) int {\n\tif a == b {\n\t\treturn Eq\n\t}\n\tif a < b {\n\t\treturn Lt\n\t}\n\treturn Gt\n}\n\ntype NullFloat32 struct {\n\tFloat32 float32\n\tValid   bool\n}\n\nfunc (f NullFloat32) Interface() interface{} {\n\tif f.Valid {\n\t\treturn f.Float32\n\t}\n\treturn nil\n}\n\nfunc asNullFloat32(i interface{}) NullFloat32 {\n\tvar ni NullFloat32\n\tif i == nil {\n\t\treturn ni\n\t}\n\n\tif v, ok := i.(NullFloat32); ok {\n\t\treturn v\n\t}\n\n\tif v, ok := cast.AsFloat32(i); ok {\n\t\tni.Float32 = v\n\t\tni.Valid = true\n\t}\n\treturn ni\n}\n\nfunc compareNullFloat32(a, b NullFloat32) int {\n\tif !b.Valid {\n\t\tif !a.Valid {\n\t\t\treturn Eq\n\t\t}\n\t\treturn Gt\n\t}\n\tif !a.Valid {\n\t\treturn Lt\n\t}\n\treturn compareFloat32(a.Float32, b.Float32)\n}\n"
  },
  {
    "path": "serie/serie_float32_test.go",
    "content": "package serie_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/datasweet/datatable/serie\"\n)\n\nfunc TestSerieFloat32(t *testing.T) {\n\ts := serie.Float32()\n\tassert.NotNil(t, s)\n\n\ts.Append(1, \"23\", 3.14, \"teemo\", true, nil)\n\n\tassertSerieEq(t, s, float32(1), float32(23), float32(3.14), float32(0), float32(1), float32(0))\n\n\ts.SortAsc()\n\tassertSerieEq(t, s, float32(0), float32(0), float32(1), float32(1), float32(3.14), float32(23))\n\n\ts.SortDesc()\n\tassertSerieEq(t, s, float32(23), float32(3.14), float32(1), float32(1), float32(0), float32(0))\n}\n\nfunc TestSerieFloat32N(t *testing.T) {\n\ts := serie.Float32N()\n\tassert.NotNil(t, s)\n\n\ts.Append(1, \"23\", 3.14, \"teemo\", true, nil)\n\tassertSerieEq(t, s, float32(1), float32(23), float32(3.14), nil, float32(1), nil)\n\n\ts.SortAsc()\n\tassertSerieEq(t, s, nil, nil, float32(1), float32(1), float32(3.14), float32(23))\n\n\ts.SortDesc()\n\tassertSerieEq(t, s, float32(23), float32(3.14), float32(1), float32(1), nil, nil)\n}\n"
  },
  {
    "path": "serie/serie_float64.go",
    "content": "package serie\n\nimport (\n\t\"github.com/datasweet/cast\"\n)\n\nfunc Float64(v ...interface{}) Serie {\n\ts := New(float64(0), asFloat64, compareFloat64)\n\tif len(v) > 0 {\n\t\ts.Append(v...)\n\t}\n\treturn s\n}\n\nfunc Float64N(v ...interface{}) Serie {\n\ts := New(NullFloat64{}, asNullFloat64, compareNullFloat64)\n\tif len(v) > 0 {\n\t\ts.Append(v...)\n\t}\n\treturn s\n}\n\nfunc asFloat64(i interface{}) float64 {\n\tf, _ := cast.AsFloat64(i)\n\treturn f\n}\n\nfunc compareFloat64(a, b float64) int {\n\tif a == b {\n\t\treturn Eq\n\t}\n\tif a < b {\n\t\treturn Lt\n\t}\n\treturn Gt\n}\n\ntype NullFloat64 struct {\n\tFloat64 float64\n\tValid   bool\n}\n\nfunc (f NullFloat64) Interface() interface{} {\n\tif f.Valid {\n\t\treturn f.Float64\n\t}\n\treturn nil\n}\n\nfunc asNullFloat64(i interface{}) NullFloat64 {\n\tvar ni NullFloat64\n\tif i == nil {\n\t\treturn ni\n\t}\n\n\tif v, ok := i.(NullFloat64); ok {\n\t\treturn v\n\t}\n\n\tif v, ok := cast.AsFloat64(i); ok {\n\t\tni.Float64 = v\n\t\tni.Valid = true\n\t}\n\treturn ni\n}\n\nfunc compareNullFloat64(a, b NullFloat64) int {\n\tif !b.Valid {\n\t\tif !a.Valid {\n\t\t\treturn Eq\n\t\t}\n\t\treturn Gt\n\t}\n\tif !a.Valid {\n\t\treturn Lt\n\t}\n\treturn compareFloat64(a.Float64, b.Float64)\n}\n"
  },
  {
    "path": "serie/serie_float64_test.go",
    "content": "package serie_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/datasweet/datatable/serie\"\n)\n\nfunc TestSerieFloat64(t *testing.T) {\n\ts := serie.Float64()\n\tassert.NotNil(t, s)\n\n\ts.Append(1, \"23\", 3.14, \"teemo\", true, nil)\n\n\tassertSerieEq(t, s, float64(1), float64(23), float64(3.14), float64(0), float64(1), float64(0))\n\n\ts.SortAsc()\n\tassertSerieEq(t, s, float64(0), float64(0), float64(1), float64(1), float64(3.14), float64(23))\n\n\ts.SortDesc()\n\tassertSerieEq(t, s, float64(23), float64(3.14), float64(1), float64(1), float64(0), float64(0))\n}\n\nfunc TestSerieFloat64N(t *testing.T) {\n\ts := serie.Float64N()\n\tassert.NotNil(t, s)\n\n\ts.Append(1, \"23\", 3.14, \"teemo\", true, nil)\n\tassertSerieEq(t, s, float64(1), float64(23), float64(3.14), nil, float64(1), nil)\n\n\ts.SortAsc()\n\tassertSerieEq(t, s, nil, nil, float64(1), float64(1), float64(3.14), float64(23))\n\n\ts.SortDesc()\n\tassertSerieEq(t, s, float64(23), float64(3.14), float64(1), float64(1), nil, nil)\n}\n"
  },
  {
    "path": "serie/serie_int.go",
    "content": "package serie\n\nimport (\n\t\"github.com/datasweet/cast\"\n)\n\nfunc Int(v ...interface{}) Serie {\n\ts := New(0, asInt, compareInt)\n\tif len(v) > 0 {\n\t\ts.Append(v...)\n\t}\n\treturn s\n}\n\nfunc IntN(v ...interface{}) Serie {\n\ts := New(NullInt{}, asNullInt, compareNullInt)\n\tif len(v) > 0 {\n\t\ts.Append(v...)\n\t}\n\treturn s\n}\n\nfunc asInt(i interface{}) int {\n\tn, _ := cast.AsInt(i)\n\treturn n\n}\n\nfunc compareInt(a, b int) int {\n\tif a == b {\n\t\treturn Eq\n\t}\n\tif a < b {\n\t\treturn Lt\n\t}\n\treturn Gt\n}\n\ntype NullInt struct {\n\tInt   int\n\tValid bool\n}\n\nfunc (i NullInt) Interface() interface{} {\n\tif i.Valid {\n\t\treturn i.Int\n\t}\n\treturn nil\n}\n\nfunc asNullInt(i interface{}) NullInt {\n\tvar ni NullInt\n\tif i == nil {\n\t\treturn ni\n\t}\n\n\tif v, ok := i.(NullInt); ok {\n\t\treturn v\n\t}\n\n\tif v, ok := cast.AsInt(i); ok {\n\t\tni.Int = v\n\t\tni.Valid = true\n\t}\n\treturn ni\n}\n\nfunc compareNullInt(a, b NullInt) int {\n\tif !b.Valid {\n\t\tif !a.Valid {\n\t\t\treturn Eq\n\t\t}\n\t\treturn Gt\n\t}\n\tif !a.Valid {\n\t\treturn Lt\n\t}\n\treturn compareInt(a.Int, b.Int)\n}\n"
  },
  {
    "path": "serie/serie_int32.go",
    "content": "package serie\n\nimport (\n\t\"github.com/datasweet/cast\"\n)\n\nfunc Int32(v ...interface{}) Serie {\n\ts := New(int32(0), asInt32, compareInt32)\n\tif len(v) > 0 {\n\t\ts.Append(v...)\n\t}\n\treturn s\n}\n\nfunc Int32N(v ...interface{}) Serie {\n\ts := New(NullInt32{}, asNullInt32, compareNullInt32)\n\tif len(v) > 0 {\n\t\ts.Append(v...)\n\t}\n\treturn s\n}\n\nfunc asInt32(i interface{}) int32 {\n\tn, _ := cast.AsInt32(i)\n\treturn n\n}\n\nfunc compareInt32(a, b int32) int {\n\tif a == b {\n\t\treturn Eq\n\t}\n\tif a < b {\n\t\treturn Lt\n\t}\n\treturn Gt\n}\n\ntype NullInt32 struct {\n\tInt32 int32\n\tValid bool\n}\n\nfunc (i NullInt32) Interface() interface{} {\n\tif i.Valid {\n\t\treturn i.Int32\n\t}\n\treturn nil\n}\n\nfunc asNullInt32(i interface{}) NullInt32 {\n\tvar ni NullInt32\n\tif i == nil {\n\t\treturn ni\n\t}\n\n\tif v, ok := i.(NullInt32); ok {\n\t\treturn v\n\t}\n\n\tif v, ok := cast.AsInt32(i); ok {\n\t\tni.Int32 = v\n\t\tni.Valid = true\n\t}\n\treturn ni\n}\n\nfunc compareNullInt32(a, b NullInt32) int {\n\tif !b.Valid {\n\t\tif !a.Valid {\n\t\t\treturn Eq\n\t\t}\n\t\treturn Gt\n\t}\n\tif !a.Valid {\n\t\treturn Lt\n\t}\n\treturn compareInt32(a.Int32, b.Int32)\n}\n"
  },
  {
    "path": "serie/serie_int32_test.go",
    "content": "package serie_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/datasweet/datatable/serie\"\n)\n\nfunc TestSerieInt32(t *testing.T) {\n\ts := serie.Int32()\n\tassert.NotNil(t, s)\n\n\ts.Append(31, \"23\", 98.5, \"teemo\", true, -67, nil)\n\tassertSerieEq(t, s,\n\t\tint32(31),\n\t\tint32(23),\n\t\tint32(98),\n\t\tint32(0),\n\t\tint32(1),\n\t\tint32(-67),\n\t\tint32(0),\n\t)\n\n\ts.SortAsc()\n\tassertSerieEq(t, s,\n\t\tint32(-67),\n\t\tint32(0),\n\t\tint32(0),\n\t\tint32(1),\n\t\tint32(23),\n\t\tint32(31),\n\t\tint32(98),\n\t)\n\n\ts.SortDesc()\n\tassertSerieEq(t, s,\n\t\tint32(98),\n\t\tint32(31),\n\t\tint32(23),\n\t\tint32(1),\n\t\tint32(0),\n\t\tint32(0),\n\t\tint32(-67),\n\t)\n}\n\nfunc TestSerieInt32N(t *testing.T) {\n\ts := serie.Int32N()\n\tassert.NotNil(t, s)\n\n\ts.Append(31, \"23\", 98.5, \"teemo\", true, -67, nil)\n\tassertSerieEq(t, s,\n\t\tint32(31),\n\t\tint32(23),\n\t\tint32(98),\n\t\tnil,\n\t\tint32(1),\n\t\tint32(-67),\n\t\tnil,\n\t)\n\n\ts.SortAsc()\n\tassertSerieEq(t, s,\n\t\tnil,\n\t\tnil,\n\t\tint32(-67),\n\t\tint32(1),\n\t\tint32(23),\n\t\tint32(31),\n\t\tint32(98),\n\t)\n\n\ts.SortDesc()\n\tassertSerieEq(t, s,\n\t\tint32(98),\n\t\tint32(31),\n\t\tint32(23),\n\t\tint32(1),\n\t\tint32(-67),\n\t\tnil,\n\t\tnil,\n\t)\n}\n"
  },
  {
    "path": "serie/serie_int64.go",
    "content": "package serie\n\nimport (\n\t\"github.com/datasweet/cast\"\n)\n\nfunc Int64(v ...interface{}) Serie {\n\ts := New(int64(0), asInt64, compareInt64)\n\tif len(v) > 0 {\n\t\ts.Append(v...)\n\t}\n\treturn s\n}\n\nfunc Int64N(v ...interface{}) Serie {\n\ts := New(NullInt64{}, asNullInt64, compareNullInt64)\n\tif len(v) > 0 {\n\t\ts.Append(v...)\n\t}\n\treturn s\n}\n\nfunc asInt64(i interface{}) int64 {\n\tn, _ := cast.AsInt64(i)\n\treturn n\n}\n\nfunc compareInt64(a, b int64) int {\n\tif a == b {\n\t\treturn Eq\n\t}\n\tif a < b {\n\t\treturn Lt\n\t}\n\treturn Gt\n}\n\ntype NullInt64 struct {\n\tInt64 int64\n\tValid bool\n}\n\nfunc (i NullInt64) Interface() interface{} {\n\tif i.Valid {\n\t\treturn i.Int64\n\t}\n\treturn nil\n}\n\nfunc asNullInt64(i interface{}) NullInt64 {\n\tvar ni NullInt64\n\tif i == nil {\n\t\treturn ni\n\t}\n\n\tif v, ok := i.(NullInt64); ok {\n\t\treturn v\n\t}\n\n\tif v, ok := cast.AsInt64(i); ok {\n\t\tni.Int64 = v\n\t\tni.Valid = true\n\t}\n\treturn ni\n}\n\nfunc compareNullInt64(a, b NullInt64) int {\n\tif !b.Valid {\n\t\tif !a.Valid {\n\t\t\treturn Eq\n\t\t}\n\t\treturn Gt\n\t}\n\tif !a.Valid {\n\t\treturn Lt\n\t}\n\treturn compareInt64(a.Int64, b.Int64)\n}\n"
  },
  {
    "path": "serie/serie_int64_test.go",
    "content": "package serie_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/datasweet/datatable/serie\"\n)\n\nfunc TestSerieInt64(t *testing.T) {\n\ts := serie.Int64()\n\tassert.NotNil(t, s)\n\n\ts.Append(31, \"23\", 98.5, \"teemo\", true, -67, nil)\n\tassertSerieEq(t, s,\n\t\tint64(31),\n\t\tint64(23),\n\t\tint64(98),\n\t\tint64(0),\n\t\tint64(1),\n\t\tint64(-67),\n\t\tint64(0),\n\t)\n\n\ts.SortAsc()\n\tassertSerieEq(t, s,\n\t\tint64(-67),\n\t\tint64(0),\n\t\tint64(0),\n\t\tint64(1),\n\t\tint64(23),\n\t\tint64(31),\n\t\tint64(98),\n\t)\n\n\ts.SortDesc()\n\tassertSerieEq(t, s,\n\t\tint64(98),\n\t\tint64(31),\n\t\tint64(23),\n\t\tint64(1),\n\t\tint64(0),\n\t\tint64(0),\n\t\tint64(-67),\n\t)\n}\n\nfunc TestSerieInt64N(t *testing.T) {\n\ts := serie.Int64N()\n\tassert.NotNil(t, s)\n\n\ts.Append(31, \"23\", 98.5, \"teemo\", true, -67, nil)\n\tassertSerieEq(t, s,\n\t\tint64(31),\n\t\tint64(23),\n\t\tint64(98),\n\t\tnil,\n\t\tint64(1),\n\t\tint64(-67),\n\t\tnil,\n\t)\n\n\ts.SortAsc()\n\tassertSerieEq(t, s,\n\t\tnil,\n\t\tnil,\n\t\tint64(-67),\n\t\tint64(1),\n\t\tint64(23),\n\t\tint64(31),\n\t\tint64(98),\n\t)\n\n\ts.SortDesc()\n\tassertSerieEq(t, s,\n\t\tint64(98),\n\t\tint64(31),\n\t\tint64(23),\n\t\tint64(1),\n\t\tint64(-67),\n\t\tnil,\n\t\tnil,\n\t)\n}\n"
  },
  {
    "path": "serie/serie_int_test.go",
    "content": "package serie_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/datasweet/datatable/serie\"\n)\n\nfunc TestSerieInt(t *testing.T) {\n\ts := serie.Int()\n\tassert.NotNil(t, s)\n\n\ts.Append(31, \"23\", 98.5, \"teemo\", true, -67, nil)\n\tassertSerieEq(t, s, 31, 23, 98, 0, 1, -67, 0)\n\n\ts.SortAsc()\n\tassertSerieEq(t, s, -67, 0, 0, 1, 23, 31, 98)\n\n\ts.SortDesc()\n\tassertSerieEq(t, s, 98, 31, 23, 1, 0, 0, -67)\n}\n\nfunc TestSerieIntN(t *testing.T) {\n\ts := serie.IntN()\n\tassert.NotNil(t, s)\n\n\ts.Append(31, \"23\", 98.5, \"teemo\", true, -67, nil)\n\tassertSerieEq(t, s, 31, 23, 98, nil, 1, -67, nil)\n\n\ts.SortAsc()\n\tassertSerieEq(t, s, nil, nil, -67, 1, 23, 31, 98)\n\n\ts.SortDesc()\n\tassertSerieEq(t, s, 98, 31, 23, 1, -67, nil, nil)\n}\n"
  },
  {
    "path": "serie/serie_raw.go",
    "content": "package serie\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\nfunc Raw(v ...interface{}) Serie {\n\ts := New(RawValue{}, asRawValue, compareRawValue)\n\tif len(v) > 0 {\n\t\ts.Append(v...)\n\t}\n\treturn s\n}\n\ntype RawValue struct {\n\tValue interface{}\n\tValid bool\n}\n\nfunc (r RawValue) Interface() interface{} {\n\tif r.Valid {\n\t\treturn r.Value\n\t}\n\treturn nil\n}\n\nfunc (r RawValue) String() string {\n\treturn fmt.Sprint(r.Value)\n}\n\nfunc asRawValue(i interface{}) RawValue {\n\tif rv, ok := i.(RawValue); ok {\n\t\treturn rv\n\t}\n\tvar r RawValue\n\tif i == nil {\n\t\treturn r\n\t}\n\tr.Valid = true\n\tr.Value = i\n\treturn r\n}\n\nfunc compareRawValue(a, b RawValue) int {\n\tif !b.Valid {\n\t\tif !a.Valid {\n\t\t\treturn Eq\n\t\t}\n\t\treturn Gt\n\t}\n\tif !a.Valid {\n\t\treturn Lt\n\t}\n\treturn strings.Compare(a.String(), b.String())\n}\n"
  },
  {
    "path": "serie/serie_raw_test.go",
    "content": "package serie_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/datasweet/datatable/serie\"\n)\n\nfunc TestSerieRaw(t *testing.T) {\n\ts := serie.Raw()\n\tassert.NotNil(t, s)\n\n\ts.Append(31, \"23\", 98.5, \"teemo\", true, nil, -67)\n\tassertSerieEq(t, s, 31, \"23\", 98.5, \"teemo\", true, nil, -67)\n\n\ts.SortAsc()\n\tassertSerieEq(t, s, nil, -67, \"23\", 31, 98.5, \"teemo\", true)\n\n\ts.SortDesc()\n\tassertSerieEq(t, s, true, \"teemo\", 98.5, 31, \"23\", -67, nil)\n}\n"
  },
  {
    "path": "serie/serie_string.go",
    "content": "package serie\n\nimport (\n\t\"strings\"\n\n\t\"github.com/datasweet/cast\"\n)\n\n// String to create a new string serie\nfunc String(v ...interface{}) Serie {\n\ts := New(\"\", asString, strings.Compare)\n\tif len(v) > 0 {\n\t\ts.Append(v...)\n\t}\n\treturn s\n}\n\n// StringN to create a new serie with null value handling\nfunc StringN(v ...interface{}) Serie {\n\ts := New(NullString{}, asNullString, compareNullString)\n\tif len(v) > 0 {\n\t\ts.Append(v...)\n\t}\n\treturn s\n}\n\nfunc asString(i interface{}) string {\n\ts, _ := cast.AsString(i)\n\treturn s\n}\n\ntype NullString struct {\n\tString string\n\tValid  bool\n}\n\nfunc (s NullString) Interface() interface{} {\n\tif s.Valid {\n\t\treturn s.String\n\t}\n\treturn nil\n}\n\nfunc asNullString(i interface{}) NullString {\n\tvar ns NullString\n\tif i == nil {\n\t\treturn ns\n\t}\n\n\tif v, ok := i.(NullString); ok {\n\t\treturn v\n\t}\n\n\tif v, ok := cast.AsString(i); ok {\n\t\tns.String = v\n\t\tns.Valid = true\n\t}\n\treturn ns\n}\n\nfunc compareNullString(a, b NullString) int {\n\tif !b.Valid {\n\t\tif !a.Valid {\n\t\t\treturn Eq\n\t\t}\n\t\treturn Gt\n\t}\n\tif !a.Valid {\n\t\treturn Lt\n\t}\n\treturn strings.Compare(a.String, b.String)\n}\n"
  },
  {
    "path": "serie/serie_string_test.go",
    "content": "package serie_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/datasweet/datatable/serie\"\n)\n\nfunc TestString(t *testing.T) {\n\ts := serie.String(\"A00103\", 1, 3.14, true, nil)\n\tassertSerieEq(t, s, \"A00103\", \"1\", \"3.14\", \"true\", \"\")\n}\n"
  },
  {
    "path": "serie/serie_test.go",
    "content": "package serie_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/datasweet/cast\"\n\t\"github.com/datasweet/datatable/serie\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestNewSerie(t *testing.T) {\n\tassert.Panics(t, func() { serie.New(nil, nil, nil) })\n\tassert.Panics(t, func() { serie.New(1, nil, nil) })\n\tassert.Panics(t, func() {\n\t\tserie.New(1,\n\t\t\tfunc(i interface{}) float32 {\n\t\t\t\tf, _ := cast.AsFloat32(i)\n\t\t\t\treturn f\n\t\t\t},\n\t\t\tfunc(i, j int) int {\n\t\t\t\treturn serie.Eq\n\t\t\t})\n\t})\n\tassert.Panics(t, func() {\n\t\tserie.New(1,\n\t\t\tfunc(i interface{}) int {\n\t\t\t\tf, _ := cast.AsInt(i)\n\t\t\t\treturn f\n\t\t\t},\n\t\t\tnil)\n\t})\n\n\t// OK\n\ts := serie.New(1,\n\t\tfunc(i interface{}) int {\n\t\t\tf, _ := cast.AsInt(i)\n\t\t\treturn f\n\t\t},\n\t\tfunc(i, j int) int {\n\t\t\treturn serie.Eq\n\t\t})\n\tassert.NotNil(t, s)\n\ts.Append(1, 2, 3, 4, 5)\n\tassertSerieEq(t, s, 1, 2, 3, 4, 5)\n}\n"
  },
  {
    "path": "serie/serie_time.go",
    "content": "package serie\n\nimport (\n\t\"time\"\n\n\t\"github.com/datasweet/cast\"\n)\n\n// Time to create a time serie\nfunc Time(format ...string) Serie {\n\treturn New(time.Time{}, asTime(format), compareTime)\n}\n\n// TimeN to create a time serie with nil value\nfunc TimeN(format ...string) Serie {\n\treturn New(NullTime{}, asNullTime(format), compareNullTime)\n}\n\nfunc compareTime(a, b time.Time) int {\n\tif a.Equal(b) {\n\t\treturn Eq\n\t}\n\tif a.Before(b) {\n\t\treturn Lt\n\t}\n\treturn Gt\n}\n\nfunc asTime(formats []string) func(interface{}) time.Time {\n\treturn func(i interface{}) time.Time {\n\t\tt, _ := cast.AsTime(i, formats...)\n\t\treturn t\n\t}\n}\n\ntype NullTime struct {\n\tTime  time.Time\n\tValid bool\n}\n\nfunc (t NullTime) Interface() interface{} {\n\tif t.Valid {\n\t\treturn t.Time\n\t}\n\treturn nil\n}\n\nfunc asNullTime(formats []string) func(interface{}) NullTime {\n\treturn func(i interface{}) NullTime {\n\t\tvar ni NullTime\n\t\tif i == nil {\n\t\t\treturn ni\n\t\t}\n\n\t\tif v, ok := i.(NullTime); ok {\n\t\t\treturn v\n\t\t}\n\n\t\tif v, ok := cast.AsTime(i, formats...); ok {\n\t\t\tni.Time = v\n\t\t\tni.Valid = true\n\t\t}\n\t\treturn ni\n\t}\n}\n\nfunc compareNullTime(a, b NullTime) int {\n\tif !b.Valid {\n\t\tif !a.Valid {\n\t\t\treturn Eq\n\t\t}\n\t\treturn Gt\n\t}\n\tif !a.Valid {\n\t\treturn Lt\n\t}\n\treturn compareTime(a.Time, b.Time)\n}\n"
  },
  {
    "path": "serie/serie_time_test.go",
    "content": "package serie_test\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/datasweet/datatable/serie\"\n)\n\nfunc TestSerieTime(t *testing.T) {\n\ts := serie.Time()\n\ts.Append(1551435220270, \"2019-03-01\", \"2019-03-01 10:13:40\", \"2019-03-01T10:13:40.27Z\", \"01/03/2019\", \"01/03/2019 10:13:40\", \"wrong\")\n\n\tdate := time.Date(2019, time.March, 1, 0, 0, 0, 0, time.UTC)           // only date\n\tdatetime := time.Date(2019, time.March, 1, 10, 13, 40, 0, time.UTC)    // date + time\n\ttimestamp := time.Unix(0, 1551435220270*int64(time.Millisecond)).UTC() // date + time + ns\n\n\tassertSerieEq(t, s, timestamp, date, datetime, timestamp, date, datetime, time.Time{})\n\n\ts = serie.Time(\"02/01/06\", \"02/01/06 15:04:05\")\n\ts.Append(\"01/03/19\", \"01/03/19 10:13:40\", \"wrong\")\n\tassertSerieEq(t, s, date, datetime, time.Time{})\n\n}\n\nfunc TestSerieTimeN(t *testing.T) {\n\ts := serie.TimeN()\n\ts.Append(1551435220270, \"2019-03-01\", \"2019-03-01 10:13:40\", \"2019-03-01T10:13:40.27Z\", \"01/03/2019\", \"01/03/2019 10:13:40\", \"wrong\")\n\n\tdate := time.Date(2019, time.March, 1, 0, 0, 0, 0, time.UTC)           // only date\n\tdatetime := time.Date(2019, time.March, 1, 10, 13, 40, 0, time.UTC)    // date + time\n\ttimestamp := time.Unix(0, 1551435220270*int64(time.Millisecond)).UTC() // date + time + ns\n\n\tassertSerieEq(t, s, timestamp, date, datetime, timestamp, date, datetime, nil)\n\n\ts = serie.TimeN(\"02/01/06\", \"02/01/06 15:04:05\")\n\ts.Append(\"01/03/19\", \"01/03/19 10:13:40\", \"wrong\")\n\tassertSerieEq(t, s, date, datetime, nil)\n\n}\n"
  },
  {
    "path": "serie/sort.go",
    "content": "package serie\n\nimport (\n\t\"reflect\"\n\t\"sort\"\n)\n\nfunc (s *serie) Swap(i, j int) {\n\ttmp := reflect.New(s.typ).Elem()\n\ta, b := s.slice.Index(i), s.slice.Index(j)\n\ttmp.Set(a)\n\ta.Set(b)\n\tb.Set(tmp)\n}\n\nfunc (s *serie) Less(i, j int) bool {\n\treturn s.Compare(i, j) == Lt\n}\n\n// Compare values at indexes i, j\n// panic if out of range\nfunc (s *serie) Compare(i, j int) int {\n\treturn s.comparer.Call([]reflect.Value{\n\t\ts.slice.Index(i),\n\t\ts.slice.Index(j),\n\t})[0].Interface().(int)\n}\n\nfunc (s *serie) SortAsc() {\n\tsort.Sort(s)\n}\n\nfunc (s *serie) SortDesc() {\n\tsort.Sort(sort.Reverse(s))\n}\n"
  },
  {
    "path": "serie/sort_test.go",
    "content": "package serie_test\n\nimport (\n\t\"sort\"\n\t\"testing\"\n\n\t\"github.com/datasweet/datatable/serie\"\n)\n\nfunc TestSortInt(t *testing.T) {\n\trandom := []interface{}{\n\t\t31, 23, 98, 3, 59, 67, 5, 5, 87, 18,\n\t\t3, 88, 7, 63, 29, 62, 37, 66, 87, 26,\n\t\t24, 5, 62, 75, 69, 56, 15, 59, 40, 34,\n\t\t68, 32, 34, 29, 90, 21, 8, 8, 100, 64,\n\t\t30, 56, 73, 2, 65, 74, 3, 26, 92, 46,\n\t\t6, 100, 35, 17, 91, 55, 99, 87, 9, 25,\n\t\t55, 76, 39, 78, 43, 99, 35, 90, 36, 27,\n\t\t52, 65, 33, 49, 84, 87, 42, 92, 27, 65,\n\t\t48, 47, 74, 98, 76, 88, 18, 100, 69, 57,\n\t\t69, 90, 74, 25, 64, 37, 63, 61, 85, 12,\n\t}\n\ts := serie.Int(random...)\n\n\tsort.Slice(random, func(i, j int) bool {\n\t\treturn random[i].(int) < random[j].(int)\n\t})\n\ts.SortAsc()\n\tassertSerieEq(t, s, random...)\n\n\tsort.Slice(random, func(i, j int) bool {\n\t\treturn random[i].(int) > random[j].(int)\n\t})\n\ts.SortDesc()\n\tassertSerieEq(t, s, random...)\n}\n\nfunc TestSortString(t *testing.T) {\n\ts := serie.String(\"A00103\", \"A00105\", \"A00104\", \"A00106\", \"A00104\", nil)\n\ts.SortAsc()\n\tassertSerieEq(t, s, \"\", \"A00103\", \"A00104\", \"A00104\", \"A00105\", \"A00106\")\n\n\ts.SortDesc()\n\tassertSerieEq(t, s, \"A00106\", \"A00105\", \"A00104\", \"A00104\", \"A00103\", \"\")\n}\n"
  },
  {
    "path": "serie/stat.go",
    "content": "package serie\n\nimport (\n\t\"math\"\n\t\"sort\"\n\n\t\"gonum.org/v1/gonum/floats\"\n\t\"gonum.org/v1/gonum/stat\"\n)\n\n// An aggregate function performs a calculation on a set of values, and returns a single value.\n// Aggregate functions ignore null values.\n\ntype StatOptions struct {\n\tMissing *float64 // replaces missing values with a value\n}\n\ntype StatOption func(opts *StatOptions)\n\n// Missing to treats all missing values (ie no-nils) as a value\nfunc Missing(f float64) StatOption {\n\treturn func(opts *StatOptions) {\n\t\topts.Missing = &f\n\t}\n}\n\nfunc (s *serie) asFloats(opt ...StatOption) []float64 {\n\tvar options StatOptions\n\tfor _, o := range opt {\n\t\to(&options)\n\t}\n\tconv := AsFloat64(s, options.Missing)\n\treturn conv.Slice().([]float64)\n}\n\n// Avg returns the average of non-nil values\n// returns NaN if no value\nfunc (s *serie) Avg(opt ...StatOption) float64 {\n\tsrc := s.asFloats(opt...)\n\tif len(src) == 0 {\n\t\treturn math.NaN()\n\t}\n\treturn stat.Mean(src, nil)\n}\n\n// Count returns the number of non-nil values\nfunc (s *serie) Count(opt ...StatOption) int64 {\n\tsrc := s.NonNils()\n\treturn int64(src.Len())\n}\n\n// CountDistinct returns the number of unique non-nil values\nfunc (s *serie) CountDistinct(opt ...StatOption) int64 {\n\tsrc := s.NonNils().Distinct()\n\treturn int64(src.Len())\n}\n\n// Cusum returns the cumulative sum of non-nil values\n// returns NaN if no value\nfunc (s *serie) Cusum(opt ...StatOption) []float64 {\n\topts := make([]StatOption, 0, len(opt)+1)\n\topts = append(opts, Missing(0))\n\topts = append(opts, opt...)\n\tsrc := s.asFloats(opts...)\n\n\tif len(src) == 0 {\n\t\treturn src\n\t}\n\n\tdst := make([]float64, 0, len(src))\n\tfloats.CumSum(dst, src)\n\treturn dst\n}\n\n// Max returns the maximum of non-nil values\n// returns NaN if no value\nfunc (s *serie) Max(opt ...StatOption) float64 {\n\tsrc := s.asFloats(opt...)\n\tif len(src) == 0 {\n\t\treturn math.NaN()\n\t}\n\treturn floats.Max(src)\n}\n\n// Min returns the minimum of non-nil values\n// returns NaN if no value\nfunc (s *serie) Min(opt ...StatOption) float64 {\n\tsrc := s.asFloats(opt...)\n\tif len(src) == 0 {\n\t\treturn math.NaN()\n\t}\n\treturn floats.Min(src)\n}\n\n// Median returns the median value of non-nil values\n// returns NaN if no value\nfunc (s *serie) Median(opt ...StatOption) float64 {\n\tsrc := s.asFloats(opt...)\n\tif len(src) == 0 {\n\t\treturn math.NaN()\n\t}\n\n\t// stat.Quantile needs the input slice to be sorted.\n\tsort.Float64s(src)\n\n\t// computes the median of the dataset.\n\treturn stat.Quantile(0.5, stat.Empirical, src, nil)\n}\n\n// Stddev returns the standard deviation of non-nils values\n// returns NaN if no value\nfunc (s *serie) Stddev(opt ...StatOption) float64 {\n\tsrc := s.asFloats(opt...)\n\tif len(src) == 0 {\n\t\treturn math.NaN()\n\t}\n\treturn stat.StdDev(src, nil)\n}\n\n// Sum returns the sum of non-nil values\nfunc (s *serie) Sum(opt ...StatOption) float64 {\n\tsrc := s.asFloats(opt...)\n\tif len(src) == 0 {\n\t\treturn 0\n\t}\n\treturn floats.Sum(src)\n}\n\n// Variance returns the variance of non-nil values\n// returns NaN if no value\nfunc (s *serie) Variance(opt ...StatOption) float64 {\n\tsrc := s.asFloats(opt...)\n\tif len(src) == 0 {\n\t\treturn math.NaN()\n\t}\n\treturn stat.Variance(src, nil)\n}\n"
  },
  {
    "path": "serie/stat_test.go",
    "content": "package serie_test\n\nimport (\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"testing\"\n\n\t\"github.com/datasweet/datatable/serie\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"gonum.org/v1/gonum/stat\"\n)\n\nfunc TestAvg(t *testing.T) {\n\txs := []float64{\n\t\t32.32, 56.98, 21.52, 44.32,\n\t\t55.63, 13.75, 43.47, 43.34,\n\t\t12.34,\n\t}\n\n\ts := serie.Float64(xs)\n\tassert.NotNil(t, s)\n\tassert.Equal(t, 9, s.Len())\n\tassert.Equal(t, stat.Mean(xs, nil), s.Avg())\n\n\ts = serie.Float64N(xs, \"teemo\", \"nil\")\n\tassert.NotNil(t, s)\n\tassert.Equal(t, 11, s.Len())\n\tassert.Equal(t, stat.Mean(xs, nil), s.Avg())\n\tassert.Greater(t, stat.Mean(xs, nil), s.Avg(serie.Missing(0)))\n}\n\nfunc TestCount(t *testing.T) {\n\txs := []float64{\n\t\t32.32, 56.98, 21.52, 44.32,\n\t\t55.63, 13.75, 43.47, 43.34,\n\t\t12.34,\n\t}\n\n\ts := serie.Float64(xs)\n\tassert.NotNil(t, s)\n\tassert.Equal(t, 9, s.Len())\n\tassert.Equal(t, int64(9), s.Count())\n\n\ts = serie.Float64N(xs, \"teemo\", \"nil\")\n\tassert.NotNil(t, s)\n\tassert.Equal(t, 11, s.Len())\n\tassert.Equal(t, int64(9), s.Count())\n}\n\nfunc TestSum(t *testing.T) {\n\ts := serie.Float64N(1, \"23\", 3.14, \"teemo\", true, nil)\n\tassert.NotNil(t, s)\n\tassertSerieEq(t, s, float64(1), float64(23), float64(3.14), nil, float64(1), nil)\n\tassert.Equal(t, 28.14, s.Sum())\n}\n\nfunc TestMedian(t *testing.T) {\n\txs := []float64{\n\t\t32.32, 56.98, 21.52, 44.32,\n\t\t55.63, 13.75, 43.47, 43.34,\n\t\t12.34,\n\t}\n\n\tfmt.Printf(\"data: %v\\n\", xs)\n\n\t// computes the weighted mean of the dataset.\n\t// we don't have any weights (ie: all weights are 1)\n\t// so we just pass a nil slice.\n\tmean := stat.Mean(xs, nil)\n\tvariance := stat.Variance(xs, nil)\n\tstddev := math.Sqrt(variance)\n\n\t// stat.Quantile needs the input slice to be sorted.\n\tsort.Float64s(xs)\n\tfmt.Printf(\"data: %v (sorted)\\n\", xs)\n\n\t// computes the median of the dataset.\n\t// here as well, we pass a nil slice as weights.\n\tmedian := stat.Quantile(0.5, stat.Empirical, xs, nil)\n\n\tfmt.Printf(\"mean=     %v\\n\", mean)\n\tfmt.Printf(\"median=   %v\\n\", median)\n\tfmt.Printf(\"variance= %v\\n\", variance)\n\tfmt.Printf(\"std-dev=  %v\\n\", stddev)\n\n}\n"
  },
  {
    "path": "serie/utils_test.go",
    "content": "package serie_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/datasweet/datatable/serie\"\n)\n\nfunc assertSerieEq(t *testing.T, s serie.Serie, v ...interface{}) {\n\tassert.NotNil(t, s)\n\tassert.Equal(t, len(v), s.Len())\n\tindex := 0\n\tfor it := s.Iterator(); it.Next(); {\n\t\tassert.Equalf(t, v[index], it.Current(), \"At index %d\", index)\n\t\tindex++\n\t}\n}\n"
  },
  {
    "path": "serie_test.go",
    "content": "package datatable\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestSerieFactory(t *testing.T) {\n\ttyps := ColumnTypes()\n\tfor _, typ := range typs {\n\t\tassert.NotPanics(t, func() { newColumnSerie(typ, ColumnOptions{}) }, typ)\n\t}\n}\n"
  },
  {
    "path": "sort.go",
    "content": "package datatable\n\nimport (\n\t\"sort\"\n\n\t\"github.com/datasweet/datatable/serie\"\n)\n\n// SortBy defines a sort to be applied\ntype SortBy struct {\n\tColumn string\n\tDesc   bool\n\tindex  int\n}\n\n// credits : https://stackoverflow.com/questions/36122668/how-to-sort-struct-with-multiple-sort-parameters\ntype sorter struct {\n\tt  *DataTable\n\tby []SortBy\n}\n\nfunc (s *sorter) Len() int {\n\treturn s.t.nrows\n}\n\nfunc (s *sorter) Swap(i, j int) {\n\ts.t.SwapRow(i, j)\n}\n\nfunc (s *sorter) Less(i, j int) bool {\n\tfor _, by := range s.by {\n\t\tsr := s.t.cols[by.index].serie\n\t\tswitch cmp := sr.Compare(i, j); cmp {\n\t\tcase serie.Eq:\n\t\t\tcontinue\n\t\tcase serie.Gt:\n\t\t\treturn by.Desc\n\t\tcase serie.Lt:\n\t\t\treturn !by.Desc\n\t\t}\n\t}\n\n\treturn false\n}\n\n// Sort the table\nfunc (t *DataTable) Sort(by ...SortBy) *DataTable {\n\tcpy := t.Copy()\n\n\tif len(by) == 0 {\n\t\treturn cpy\n\t}\n\n\tfor i := range by {\n\t\tb := &by[i]\n\t\t// Check if column exists\n\t\tb.index = t.ColumnIndex(b.Column)\n\t\tif b.index < 0 {\n\t\t\treturn cpy\n\t\t}\n\t}\n\n\tsrt := &sorter{\n\t\tt:  cpy,\n\t\tby: by,\n\t}\n\n\tsort.Sort(srt)\n\treturn cpy\n}\n"
  },
  {
    "path": "sort_test.go",
    "content": "package datatable_test\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/datasweet/datatable\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestSort(t *testing.T) {\n\t// from join test\n\tcustomers, orders := sampleForJoin()\n\n\tdt, err := customers.LeftJoin(orders, datatable.On(\"[Customers].[id]\", \"[Orders].[user_id]\"))\n\tassert.NoError(t, err)\n\tassert.NotNil(t, dt)\n\tcheckTable(t, dt,\n\t\t\"id\", \"prenom\", \"nom\", \"email\", \"ville\", \"date_achat\", \"num_facture\", \"prix_total\",\n\t\t1, \"Aimée\", \"Marechal\", \"aime.marechal@example.com\", \"Paris\", time.Date(2013, time.January, 23, 0, 0, 0, 0, time.UTC), \"A00103\", 203.14,\n\t\t1, \"Aimée\", \"Marechal\", \"aime.marechal@example.com\", \"Paris\", time.Date(2013, time.February, 14, 0, 0, 0, 0, time.UTC), \"A00104\", 124.00,\n\t\t2, \"Esmée\", \"Lefort\", \"esmee.lefort@example.com\", \"Lyon\", time.Date(2013, time.February, 17, 0, 0, 0, 0, time.UTC), \"A00105\", 149.45,\n\t\t3, \"Marine\", \"Prevost\", \"m.prevost@example.com\", \"Lille\", time.Date(2013, time.February, 21, 0, 0, 0, 0, time.UTC), \"A00106\", 235.35,\n\t\t4, \"Luc\", \"Rolland\", \"lucrolland@example.com\", \"Marseille\", nil, nil, nil,\n\t)\n\n\tsorted := dt.Sort(datatable.SortBy{Column: \"num_facture\", Desc: true})\n\n\tcheckTable(t, sorted,\n\t\t\"id\", \"prenom\", \"nom\", \"email\", \"ville\", \"date_achat\", \"num_facture\", \"prix_total\",\n\t\t3, \"Marine\", \"Prevost\", \"m.prevost@example.com\", \"Lille\", time.Date(2013, time.February, 21, 0, 0, 0, 0, time.UTC), \"A00106\", 235.35,\n\t\t2, \"Esmée\", \"Lefort\", \"esmee.lefort@example.com\", \"Lyon\", time.Date(2013, time.February, 17, 0, 0, 0, 0, time.UTC), \"A00105\", 149.45,\n\t\t1, \"Aimée\", \"Marechal\", \"aime.marechal@example.com\", \"Paris\", time.Date(2013, time.February, 14, 0, 0, 0, 0, time.UTC), \"A00104\", 124.00,\n\t\t1, \"Aimée\", \"Marechal\", \"aime.marechal@example.com\", \"Paris\", time.Date(2013, time.January, 23, 0, 0, 0, 0, time.UTC), \"A00103\", 203.14,\n\t\t4, \"Luc\", \"Rolland\", \"lucrolland@example.com\", \"Marseille\", nil, nil, nil,\n\t)\n\n\t// dt must not be modified\n\tsorted = dt.Sort(datatable.SortBy{Column: \"ville\"}, datatable.SortBy{Column: \"id\", Desc: true})\n\tcheckTable(t, sorted,\n\t\t\"id\", \"prenom\", \"nom\", \"email\", \"ville\", \"date_achat\", \"num_facture\", \"prix_total\",\n\t\t3, \"Marine\", \"Prevost\", \"m.prevost@example.com\", \"Lille\", time.Date(2013, time.February, 21, 0, 0, 0, 0, time.UTC), \"A00106\", 235.35,\n\t\t2, \"Esmée\", \"Lefort\", \"esmee.lefort@example.com\", \"Lyon\", time.Date(2013, time.February, 17, 0, 0, 0, 0, time.UTC), \"A00105\", 149.45,\n\t\t4, \"Luc\", \"Rolland\", \"lucrolland@example.com\", \"Marseille\", nil, nil, nil,\n\t\t1, \"Aimée\", \"Marechal\", \"aime.marechal@example.com\", \"Paris\", time.Date(2013, time.January, 23, 0, 0, 0, 0, time.UTC), \"A00103\", 203.14,\n\t\t1, \"Aimée\", \"Marechal\", \"aime.marechal@example.com\", \"Paris\", time.Date(2013, time.February, 14, 0, 0, 0, 0, time.UTC), \"A00104\", 124.00,\n\t)\n\n\tsorted = dt.Sort(datatable.SortBy{Column: \"ville\"}, datatable.SortBy{Column: \"prix_total\", Desc: true})\n\tcheckTable(t, sorted,\n\t\t\"id\", \"prenom\", \"nom\", \"email\", \"ville\", \"date_achat\", \"num_facture\", \"prix_total\",\n\t\t3, \"Marine\", \"Prevost\", \"m.prevost@example.com\", \"Lille\", time.Date(2013, time.February, 21, 0, 0, 0, 0, time.UTC), \"A00106\", 235.35,\n\t\t2, \"Esmée\", \"Lefort\", \"esmee.lefort@example.com\", \"Lyon\", time.Date(2013, time.February, 17, 0, 0, 0, 0, time.UTC), \"A00105\", 149.45,\n\t\t4, \"Luc\", \"Rolland\", \"lucrolland@example.com\", \"Marseille\", nil, nil, nil,\n\t\t1, \"Aimée\", \"Marechal\", \"aime.marechal@example.com\", \"Paris\", time.Date(2013, time.January, 23, 0, 0, 0, 0, time.UTC), \"A00103\", 203.14,\n\t\t1, \"Aimée\", \"Marechal\", \"aime.marechal@example.com\", \"Paris\", time.Date(2013, time.February, 14, 0, 0, 0, 0, time.UTC), \"A00104\", 124.00,\n\t)\n}\n"
  },
  {
    "path": "spec.md",
    "content": "## Que doit faire notre table\n\n- input directement depuis json => traiter les NaN, les json dates, etc.\n- input directement depuis un csv\n- faire des calculs (expr)\n- formatter la table avec les options: précision, numeral, etc.\n- types de colonnes : \n  - int\n  - uint\n  - bool\n  - float\n  - decimal\n  - string\n  - time\n  - serie....\n\n  [ N°commande, Produit, Prix ]\n  A, toto, 10\n  A, tata, 15\n  A, titi , 347\n  B, lionel, 3568\n\n\n[N° commande, Produits]\nA, [{nom, prix}, {nom, prix}]\n\n=> flatten(table)\n\n\nA, toto, 10\nA, tata, 15\nA, titi , 347\nB, lionel, 3568\n"
  },
  {
    "path": "table.go",
    "content": "package datatable\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\n// New creates a new datatable\nfunc New(name string) *DataTable {\n\treturn &DataTable{name: name}\n}\n\n// DataTable is our main struct\ntype DataTable struct {\n\tname    string\n\tcols    []*column\n\tnrows   int\n\tdirty   bool\n\thasExpr bool\n}\n\n// Name returns the datatable's name\nfunc (t *DataTable) Name() string {\n\treturn t.name\n}\n\n// Rename the datatable\nfunc (t *DataTable) Rename(name string) {\n\tt.name = name\n}\n\n// NumRows returns the number of rows in datatable\nfunc (t *DataTable) NumRows() int {\n\treturn t.nrows\n}\n\n// NumCols returns the number of visible columns in datatable\nfunc (t *DataTable) NumCols() int {\n\treturn len(t.Columns())\n}\n\n// Columns returns the visible column names in datatable\nfunc (t *DataTable) Columns() []string {\n\tvar cols []string\n\tfor _, col := range t.cols {\n\t\tif col.IsVisible() {\n\t\t\tcols = append(cols, col.Name())\n\t\t}\n\t}\n\treturn cols\n}\n\n// HiddenColumns returns the hidden column names in datatable\nfunc (t *DataTable) HiddenColumns() []string {\n\tvar cols []string\n\tfor _, col := range t.cols {\n\t\tif !col.IsVisible() {\n\t\t\tcols = append(cols, col.Name())\n\t\t}\n\t}\n\treturn cols\n}\n\n// Column gets the column with name\n// returns nil if not found\nfunc (t *DataTable) Column(name string) Column {\n\tfor _, col := range t.cols {\n\t\tif col.Name() == name {\n\t\t\treturn col\n\t\t}\n\t}\n\treturn nil\n}\n\n// ColumnIndex gets the index of the column with name\n// returns -1 if not found\nfunc (t *DataTable) ColumnIndex(name string) int {\n\tfor i, col := range t.cols {\n\t\tif col.Name() == name {\n\t\t\treturn i\n\t\t}\n\t}\n\treturn -1\n}\n\n// Records returns the rows in datatable as string\n// Computes all expressions.\nfunc (t *DataTable) Records() [][]string {\n\tif t == nil {\n\t\treturn nil\n\t}\n\n\tif err := t.evaluateExpressions(); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// visible columns\n\tvar cols []int\n\tfor i, col := range t.cols {\n\t\tif col.IsVisible() {\n\t\t\tcols = append(cols, i)\n\t\t}\n\t}\n\n\trows := make([][]string, 0, t.nrows)\n\tfor i := 0; i < t.nrows; i++ {\n\t\tr := make([]string, 0, len(cols))\n\t\tfor _, pos := range cols {\n\t\t\tr = append(r, fmt.Sprintf(\"%v\", t.cols[pos].serie.Get(i)))\n\t\t}\n\t\trows = append(rows, r)\n\t}\n\treturn rows\n}\n\n// Rows returns the rows in datatable\n// Computes all expressions.\nfunc (t *DataTable) Rows(opt ...ExportOption) []Row {\n\tif t == nil {\n\t\treturn nil\n\t}\n\n\topts := newExportOptions(opt...)\n\tif err := t.evaluateExpressions(); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// visible columns\n\tcols := make(map[string]int)\n\tfor i, col := range t.cols {\n\t\tif opts.WithHiddenCols || col.IsVisible() {\n\t\t\tcols[col.Name()] = i\n\t\t}\n\t}\n\n\trows := make([]Row, 0, t.nrows)\n\tfor i := 0; i < t.nrows; i++ {\n\t\tr := make(Row, len(cols))\n\t\tfor name, pos := range cols {\n\t\t\tr[name] = t.cols[pos].serie.Get(i)\n\t\t}\n\t\trows = append(rows, r)\n\t}\n\treturn rows\n}\n\nfunc (t *DataTable) String() string {\n\tvar sb strings.Builder\n\tt.Print(&sb)\n\treturn sb.String()\n}\n\n// Row gets the row at index\nfunc (t *DataTable) Row(at int, opt ...ExportOption) Row {\n\topts := newExportOptions(opt...)\n\tt.evaluateExpressions()\n\tr := make(Row, len(t.cols))\n\tfor _, col := range t.cols {\n\t\tif opts.WithHiddenCols || col.IsVisible() {\n\t\t\tr[col.name] = col.serie.Get(at)\n\t\t}\n\t}\n\treturn r\n}\n"
  },
  {
    "path": "table_print.go",
    "content": "package datatable\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/olekukonko/tablewriter\"\n)\n\n// PrintOptions to control the printer\ntype PrintOptions struct {\n\tColumnName bool\n\tColumnType bool\n\tRowNumber  bool\n\tMaxRows    int\n}\n\ntype PrintOption func(opts *PrintOptions)\n\nfunc PrintColumnName(v bool) PrintOption {\n\treturn func(opts *PrintOptions) {\n\t\topts.ColumnName = v\n\t}\n}\n\nfunc PrintColumnType(v bool) PrintOption {\n\treturn func(opts *PrintOptions) {\n\t\topts.ColumnType = v\n\t}\n}\n\nfunc PrintRowNumber(v bool) PrintOption {\n\treturn func(opts *PrintOptions) {\n\t\topts.RowNumber = v\n\t}\n}\n\nfunc PrintMaxRows(v int) PrintOption {\n\treturn func(opts *PrintOptions) {\n\t\topts.MaxRows = v\n\t}\n}\n\n// Print the tables with options\nfunc (t *DataTable) Print(writer io.Writer, opt ...PrintOption) {\n\toptions := PrintOptions{\n\t\tColumnName: true,\n\t\tColumnType: true,\n\t\tRowNumber:  true,\n\t\tMaxRows:    100,\n\t}\n\n\tfor _, o := range opt {\n\t\to(&options)\n\t}\n\n\tif writer == nil {\n\t\twriter = os.Stdout\n\t}\n\n\ttw := tablewriter.NewWriter(writer)\n\ttw.SetAutoWrapText(false)\n\ttw.SetHeaderAlignment(tablewriter.ALIGN_LEFT)\n\ttw.SetAlignment(tablewriter.ALIGN_LEFT)\n\ttw.SetCenterSeparator(\"\")\n\ttw.SetColumnSeparator(\"\")\n\ttw.SetRowSeparator(\"\")\n\ttw.SetHeaderLine(false)\n\ttw.SetBorder(false)\n\ttw.SetTablePadding(\"\\t\")\n\ttw.SetNoWhiteSpace(true)\n\n\tif options.ColumnName || options.ColumnType {\n\t\theaders := make([]string, 0, len(t.cols))\n\n\t\tfor _, col := range t.cols {\n\t\t\tif !col.IsVisible() {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tvar h []string\n\t\t\tif options.ColumnName {\n\t\t\t\th = append(h, col.Name())\n\t\t\t}\n\t\t\tif options.ColumnType {\n\t\t\t\th = append(h, fmt.Sprintf(\"<%s>\", col.serie.Type().Name()))\n\t\t\t}\n\t\t\theaders = append(headers, strings.Join(h, \" \"))\n\t\t}\n\t\ttw.SetHeader(headers)\n\t}\n\n\tif options.MaxRows > 1 && options.MaxRows <= t.NumRows() {\n\t\tmr := options.MaxRows / 2\n\t\ttw.AppendBulk(t.Head(mr).Records())\n\t\tseps := make([]string, 0, len(t.cols))\n\t\tfor _, col := range t.cols {\n\t\t\tif !col.IsVisible() {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tseps = append(seps, \"...\")\n\t\t}\n\t\ttw.Append(seps)\n\t\ttw.AppendBulk(t.Tail(mr).Records())\n\t} else {\n\t\ttw.AppendBulk(t.Records())\n\t}\n\n\ttw.Render()\n}\n"
  },
  {
    "path": "table_print_test.go",
    "content": "package datatable_test\n\nimport (\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/datasweet/datatable\"\n)\n\nfunc TestPrint(t *testing.T) {\n\ttb := New(t)\n\n\tcheckTable(t, tb,\n\t\t\"champ\", \"champion\", \"win\", \"loose\", \"winRate\", \"sum\", \"ok\",\n\t\t\"Malzahar\", \"MALZAHAR\", 10, 6, \"62.5 %\", 696.0, true,\n\t\t\"Xerath\", \"XERATH\", 20, 5, \"80 %\", 696.0, true,\n\t\t\"Teemo\", \"TEEMO\", 666, 666, \"50 %\", 696.0, true,\n\t)\n\n\ttb.Print(os.Stdout, datatable.PrintColumnType(false), datatable.PrintMaxRows(3))\n}\n"
  },
  {
    "path": "table_test.go",
    "content": "package datatable_test\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/datasweet/datatable\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestNewTable(t *testing.T) {\n\ttb := datatable.New(\"test\")\n\tassert.Equal(t, 0, tb.NumCols())\n\n\tassert.NoError(t, tb.AddColumn(\"sessions\", datatable.Int, datatable.Values(120)))\n\tassert.NoError(t, tb.AddColumn(\"bounces\", datatable.Int))\n\tassert.NoError(t, tb.AddColumn(\"bounceRate\", datatable.Float64))\n\tassert.Error(t, tb.AddColumn(\"bounces\", datatable.Int, datatable.Values(11)))\n\tassert.Error(t, tb.AddColumn(\"    \", datatable.Int, datatable.Values(11)))\n\tassert.Error(t, tb.AddColumn(\"nil\", datatable.ColumnType(\"unknown\")))\n\tassert.NoError(t, tb.AddColumn(\"hidden\", datatable.Int, datatable.Values(34), datatable.ColumnHidden(true)))\n\tassert.Equal(t, []string{\"sessions\", \"bounces\", \"bounceRate\"}, tb.Columns())\n\tassert.Equal(t, 1, tb.NumRows())\n\n\tassert.NoError(t, tb.AddColumn(\"pageViews\", datatable.Int, datatable.Values(1, 2, 3, 4, 5)))\n\tassert.Equal(t, 4, tb.NumCols())\n\tassert.Equal(t, 5, tb.NumRows())\n\n\tfmt.Println(tb)\n\n\tcheckTable(t, tb,\n\t\t\"sessions\", \"bounces\", \"bounceRate\", \"pageViews\",\n\t\t120, nil, nil, 1,\n\t\tnil, nil, nil, 2,\n\t\tnil, nil, nil, 3,\n\t\tnil, nil, nil, 4,\n\t\tnil, nil, nil, 5,\n\t)\n}\n\nfunc TestNewRow(t *testing.T) {\n\ttb := datatable.New(\"test\")\n\tassert.NoError(t, tb.AddColumn(\"champ\", datatable.String))\n\tassert.Equal(t, 1, tb.NumCols())\n\tassert.Equal(t, 0, tb.NumRows())\n\n\tr := make(datatable.Row)\n\tr[\"champ\"] = \"Malzahar\"\n\ttb.Append(r)\n\tassert.Equal(t, 1, tb.NumRows())\n\n\ttb.Append(nil)\n\tassert.Equal(t, 1, tb.NumRows())\n\n\ttb.Append()\n\tassert.Equal(t, 1, tb.NumRows())\n\n\ttb.Append(\n\t\ttb.NewRow().Set(\"champ\", \"Xerath\"),\n\t\ttb.NewRow().Set(\"satan\", \"Teemo\"), // wrong column => not set\n\t\ttb.NewRow().Set(\"champ\", \"Ahri\"),\n\t)\n\n\tcheckTable(t, tb,\n\t\t\"champ\",\n\t\t\"Malzahar\",\n\t\t\"Xerath\",\n\t\tnil,\n\t\t\"Ahri\",\n\t)\n\n\ttb.AddColumn(\"win\", datatable.Int)\n\tcheckTable(t, tb,\n\t\t\"champ\", \"win\",\n\t\t\"Malzahar\", nil,\n\t\t\"Xerath\", nil,\n\t\tnil, nil,\n\t\t\"Ahri\", nil,\n\t)\n\n\ttb.AddColumn(\"loose\", datatable.Int, datatable.Values(3, 4, nil))\n\tcheckTable(t, tb,\n\t\t\"champ\", \"win\", \"loose\",\n\t\t\"Malzahar\", nil, 3,\n\t\t\"Xerath\", nil, 4,\n\t\tnil, nil, nil,\n\t\t\"Ahri\", nil, nil,\n\t)\n}\n\nfunc TestExprColumn(t *testing.T) {\n\ttb := datatable.New(\"test\")\n\ttb.AddColumn(\"champ\", datatable.String, datatable.Values(\"Malzahar\", \"Xerath\", \"Teemo\"))\n\ttb.AddColumn(\"champion\", datatable.String, datatable.Expr(\"upper(`champ`)\"))\n\ttb.AddColumn(\"win\", datatable.Int, datatable.Values(10, 20, 666))\n\ttb.AddColumn(\"loose\", datatable.Int, datatable.Values(6, 5, 666))\n\ttb.AddColumn(\"winRate\", datatable.String, datatable.Expr(\"(`win` * 100 / (`win` + `loose`)) ~ \\\" %\\\"\"))\n\ttb.AddColumn(\"sum\", datatable.Int, datatable.Expr(\"sum(`win`)\"))\n\ttb.AddColumn(\"ok\", datatable.Bool, datatable.Expr(\"true\"))\n\n\tcheckTable(t, tb,\n\t\t\"champ\", \"champion\", \"win\", \"loose\", \"winRate\", \"sum\", \"ok\",\n\t\t\"Malzahar\", \"MALZAHAR\", 10, 6, \"62.5 %\", 696, true,\n\t\t\"Xerath\", \"XERATH\", 20, 5, \"80 %\", 696, true,\n\t\t\"Teemo\", \"TEEMO\", 666, 666, \"50 %\", 696, true,\n\t)\n}\n\nfunc TestAppendRow(t *testing.T) {\n\ttb := datatable.New(\"test\")\n\tassert.NoError(t, tb.AddColumn(\"champ\", datatable.String))\n\tassert.NoError(t, tb.AddColumn(\"win\", datatable.Int))\n\tassert.NoError(t, tb.AddColumn(\"loose\", datatable.Int))\n\tassert.NoError(t, tb.AddColumn(\"winRate\", datatable.Float64, datatable.Expr(\"(`win` * 100 / (`win` + `loose`))\")))\n\tassert.Error(t, tb.AddColumn(\"winRate\", datatable.String, datatable.Expr(\"test\")))\n\n\tassert.NoError(t, tb.AppendRow(\"Xerath\", 25, 15, \"expr\"))\n\tassert.NoError(t, tb.AppendRow(\"Malzahar\", 16, 16, nil))\n\tassert.NoError(t, tb.AppendRow(\"Vel'Koz\", 7, 5, 3))\n\n\tcheckTable(t, tb,\n\t\t\"champ\", \"win\", \"loose\", \"winRate\",\n\t\t\"Xerath\", 25, 15, 62.5,\n\t\t\"Malzahar\", 16, 16, 50.0,\n\t\t\"Vel'Koz\", 7, 5, 58.333333333333336,\n\t)\n}\n\nfunc TestRows(t *testing.T) {\n\ttb := datatable.New(\"test\")\n\tassert.NoError(t, tb.AddColumn(\"champ\", datatable.String))\n\tassert.NoError(t, tb.AddColumn(\"win\", datatable.Int))\n\tassert.NoError(t, tb.AddColumn(\"loose\", datatable.Int, datatable.ColumnHidden(true)))\n\tassert.NoError(t, tb.AddColumn(\"winRate\", datatable.Float64, datatable.Expr(\"(`win` * 100 / (`win` + `loose`))\")))\n\tassert.Error(t, tb.AddColumn(\"winRate\", datatable.String, datatable.Expr(\"test\")))\n\n\tassert.NoError(t, tb.AppendRow(\"Xerath\", 25, 15, \"expr\"))\n\tassert.NoError(t, tb.AppendRow(\"Malzahar\", 16, 16, nil))\n\tassert.NoError(t, tb.AppendRow(\"Vel'Koz\", 7, 5, 3))\n\n\tcheckTable(t, tb,\n\t\t\"champ\", \"win\", \"winRate\",\n\t\t\"Xerath\", 25, 62.5,\n\t\t\"Malzahar\", 16, 50.0,\n\t\t\"Vel'Koz\", 7, 58.333333333333336,\n\t)\n\n\tfor _, r := range tb.Rows() {\n\t\tassert.Len(t, r, 3)\n\t}\n\n\tfor _, r := range tb.Rows(datatable.ExportHidden(true)) {\n\t\tassert.Len(t, r, 4)\n\t}\n}\n\nfunc TestRow(t *testing.T) {\n\ttb := datatable.New(\"test\")\n\tassert.NoError(t, tb.AddColumn(\"champ\", datatable.String))\n\tassert.NoError(t, tb.AddColumn(\"win\", datatable.Int))\n\tassert.NoError(t, tb.AddColumn(\"loose\", datatable.Int, datatable.ColumnHidden(true)))\n\tassert.NoError(t, tb.AddColumn(\"winRate\", datatable.Float64, datatable.Expr(\"(`win` * 100 / (`win` + `loose`))\")))\n\tassert.Error(t, tb.AddColumn(\"winRate\", datatable.String, datatable.Expr(\"test\")))\n\n\tassert.NoError(t, tb.AppendRow(\"Xerath\", 25, 15, \"expr\"))\n\tassert.NoError(t, tb.AppendRow(\"Malzahar\", 16, 16, nil))\n\tassert.NoError(t, tb.AppendRow(\"Vel'Koz\", 7, 5, 3))\n\n\tcheckTable(t, tb,\n\t\t\"champ\", \"win\", \"winRate\",\n\t\t\"Xerath\", 25, 62.5,\n\t\t\"Malzahar\", 16, 50.0,\n\t\t\"Vel'Koz\", 7, 58.333333333333336,\n\t)\n\n\tr := tb.Row(0)\n\tassert.Len(t, r, 3)\n\tassert.Equal(t, r.Get(\"champ\"), \"Xerath\")\n\tassert.Equal(t, r.Get(\"win\"), 25)\n\tassert.Equal(t, r.Get(\"winRate\"), 62.5)\n\tassert.Nil(t, r.Get(\"loose\"))\n\tr = tb.Row(0, datatable.ExportHidden(true))\n\tassert.Len(t, r, 4)\n\tassert.Equal(t, r.Get(\"champ\"), \"Xerath\")\n\tassert.Equal(t, r.Get(\"win\"), 25)\n\tassert.Equal(t, r.Get(\"winRate\"), 62.5)\n\tassert.Equal(t, r.Get(\"loose\"), 15)\n}\n"
  },
  {
    "path": "test/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/datasweet/datatable\"\n\t\"github.com/datasweet/datatable/import/csv\"\n)\n\nfunc main() {\n\tdt, err := csv.Import(\"csv\", \"phone_data.csv\",\n\t\tcsv.HasHeader(true),\n\t\tcsv.AcceptDate(\"02/01/06 15:04\"),\n\t\tcsv.AcceptDate(\"2006-01\"),\n\t)\n\tif err != nil {\n\t\tlog.Fatalf(\"reading csv: %v\", err)\n\t}\n\n\tdt.Print(os.Stdout, datatable.PrintMaxRows(24))\n\n\tdt2, err := dt.Aggregate(datatable.AggregateBy{Type: datatable.Count, Field: \"index\"})\n\tif err != nil {\n\t\tlog.Fatalf(\"aggregate COUNT('index'): %v\", err)\n\t}\n\tfmt.Println(dt2)\n\n\tgroups, err := dt.GroupBy(datatable.GroupBy{\n\t\tName: \"year\",\n\t\tType: datatable.Int,\n\t\tKeyer: func(row datatable.Row) (interface{}, bool) {\n\t\t\tif d, ok := row[\"date\"]; ok {\n\t\t\t\tif tm, ok := d.(time.Time); ok {\n\t\t\t\t\treturn tm.Year(), true\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil, false\n\t\t},\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"GROUP BY 'year': %v\", err)\n\t}\n\tdt3, err := groups.Aggregate(\n\t\tdatatable.AggregateBy{Type: datatable.Sum, Field: \"duration\"},\n\t\tdatatable.AggregateBy{Type: datatable.CountDistinct, Field: \"network\"},\n\t)\n\tif err != nil {\n\t\tlog.Fatalf(\"Aggregate SUM('duration'), COUNT_DISTINCT('network') GROUP BY 'year': %v\", err)\n\t}\n\tfmt.Println(dt3)\n\n}\n"
  },
  {
    "path": "test/phone_data.csv",
    "content": "index,date,duration,item,month,network,network_type\r\n0,15/10/14 06:58,34.429,data,2014-11,data,data\r\n1,15/10/14 06:58,13,call,2014-11,Vodafone,mobile\r\n2,15/10/14 14:46,23,call,2014-11,Meteor,mobile\r\n3,15/10/14 14:48,4,call,2014-11,Tesco,mobile\r\n4,15/10/14 17:27,4,call,2014-11,Tesco,mobile\r\n5,15/10/14 18:55,4,call,2014-11,Tesco,mobile\r\n6,16/10/14 06:58,34.429,data,2014-11,data,data\r\n7,16/10/14 15:01,602,call,2014-11,Three,mobile\r\n8,16/10/14 15:12,1050,call,2014-11,Three,mobile\r\n9,16/10/14 15:30,19,call,2014-11,voicemail,voicemail\r\n10,16/10/14 16:21,1183,call,2014-11,Three,mobile\r\n11,16/10/14 22:18,1,sms,2014-11,Meteor,mobile\r\n12,16/10/14 22:21,1,sms,2014-11,Meteor,mobile\r\n13,17/10/14 06:58,34.429,data,2014-11,data,data\r\n14,17/10/14 10:53,1,sms,2014-11,Tesco,mobile\r\n15,17/10/14 11:19,1,sms,2014-11,Tesco,mobile\r\n16,17/10/14 11:20,1,sms,2014-11,Meteor,mobile\r\n17,17/10/14 17:22,1,sms,2014-11,Vodafone,mobile\r\n18,17/10/14 17:23,1,sms,2014-11,Vodafone,mobile\r\n19,17/10/14 17:26,92,call,2014-11,Three,mobile\r\n20,17/10/14 17:29,4,call,2014-11,Vodafone,mobile\r\n21,17/10/14 17:30,375,call,2014-11,Tesco,mobile\r\n22,17/10/14 17:42,1,sms,2014-11,Vodafone,mobile\r\n23,17/10/14 17:44,1,sms,2014-11,Vodafone,mobile\r\n24,17/10/14 17:44,1,sms,2014-11,Vodafone,mobile\r\n25,17/10/14 17:44,1,sms,2014-11,Vodafone,mobile\r\n26,18/10/14 06:58,34.429,data,2014-11,data,data\r\n27,18/10/14 11:51,783,call,2014-11,Tesco,mobile\r\n28,18/10/14 12:06,4,call,2014-11,Vodafone,mobile\r\n29,18/10/14 12:06,3,call,2014-11,Vodafone,mobile\r\n30,18/10/14 13:08,101,call,2014-11,Vodafone,mobile\r\n31,18/10/14 13:10,1714,call,2014-11,Three,mobile\r\n32,18/10/14 14:01,96,call,2014-11,voicemail,voicemail\r\n33,18/10/14 18:52,1,sms,2014-11,Vodafone,mobile\r\n34,18/10/14 20:44,384,call,2014-11,Three,mobile\r\n35,18/10/14 21:04,4,call,2014-11,Three,mobile\r\n36,18/10/14 21:06,1,sms,2014-11,Three,mobile\r\n37,18/10/14 21:23,1,sms,2014-11,Vodafone,mobile\r\n38,18/10/14 22:37,1,sms,2014-11,Three,mobile\r\n39,19/10/14 06:58,34.429,data,2014-11,data,data\r\n40,19/10/14 14:47,53,call,2014-11,Three,mobile\r\n41,19/10/14 15:46,86,call,2014-11,Tesco,mobile\r\n42,19/10/14 16:21,23,call,2014-11,Three,mobile\r\n43,19/10/14 16:30,38,call,2014-11,Three,mobile\r\n44,19/10/14 20:25,428,call,2014-11,Three,mobile\r\n45,20/10/14 06:58,34.429,data,2014-11,data,data\r\n46,20/10/14 09:43,69,call,2014-11,voicemail,voicemail\r\n47,20/10/14 09:43,18,call,2014-11,voicemail,voicemail\r\n48,20/10/14 13:55,3,call,2014-11,Vodafone,mobile\r\n49,20/10/14 13:56,6,call,2014-11,landline,landline\r\n50,20/10/14 18:14,5,call,2014-11,Tesco,mobile\r\n51,20/10/14 18:24,131,call,2014-11,Vodafone,mobile\r\n52,20/10/14 19:59,1,sms,2014-11,Vodafone,mobile\r\n53,20/10/14 20:16,1,sms,2014-11,Vodafone,mobile\r\n54,21/10/14 06:58,34.429,data,2014-11,data,data\r\n55,21/10/14 16:17,550,call,2014-11,Three,mobile\r\n56,22/10/14 06:58,34.429,data,2014-11,data,data\r\n57,22/10/14 12:04,7,call,2014-11,Three,mobile\r\n58,23/10/14 06:58,34.429,data,2014-11,data,data\r\n59,23/10/14 08:34,1940,call,2014-11,landline,landline\r\n60,23/10/14 09:45,281,call,2014-11,Meteor,mobile\r\n61,23/10/14 10:46,1,sms,2014-11,Tesco,mobile\r\n62,23/10/14 10:54,1,sms,2014-11,Vodafone,mobile\r\n63,23/10/14 11:17,1,sms,2014-11,Vodafone,mobile\r\n64,23/10/14 11:25,24,call,2014-11,Vodafone,mobile\r\n65,23/10/14 17:48,263,call,2014-11,Meteor,mobile\r\n66,24/10/14 06:58,34.429,data,2014-11,data,data\r\n67,24/10/14 13:35,1,sms,2014-11,Vodafone,mobile\r\n68,24/10/14 13:39,1,sms,2014-11,Vodafone,mobile\r\n69,24/10/14 13:39,1,sms,2014-11,Vodafone,mobile\r\n70,24/10/14 13:47,1,sms,2014-11,Vodafone,mobile\r\n71,24/10/14 13:48,1,sms,2014-11,Vodafone,mobile\r\n72,24/10/14 13:50,1,sms,2014-11,Vodafone,mobile\r\n73,24/10/14 13:57,1,sms,2014-11,Vodafone,mobile\r\n74,24/10/14 13:57,1,sms,2014-11,Vodafone,mobile\r\n75,24/10/14 14:20,1,sms,2014-11,Three,mobile\r\n76,24/10/14 14:27,1,sms,2014-11,Vodafone,mobile\r\n77,24/10/14 18:29,1,sms,2014-11,Vodafone,mobile\r\n78,24/10/14 18:33,387,call,2014-11,Tesco,mobile\r\n79,24/10/14 18:40,1,sms,2014-11,Vodafone,mobile\r\n80,25/10/14 06:58,34.429,data,2014-11,data,data\r\n81,26/10/14 06:58,34.429,data,2014-11,data,data\r\n82,26/10/14 14:51,4,call,2014-11,Three,mobile\r\n83,26/10/14 21:10,637,call,2014-11,Tesco,mobile\r\n84,26/10/14 21:22,28,call,2014-11,Meteor,mobile\r\n85,26/10/14 21:38,62,call,2014-11,Three,mobile\r\n86,27/10/14 01:45,3,call,2014-11,Tesco,mobile\r\n87,27/10/14 06:58,34.429,data,2014-11,data,data\r\n88,27/10/14 11:03,146,call,2014-11,Three,mobile\r\n89,27/10/14 16:30,48,call,2014-11,Three,mobile\r\n90,27/10/14 19:20,862,call,2014-11,Three,mobile\r\n91,27/10/14 19:55,25,call,2014-11,Three,mobile\r\n92,28/10/14 06:58,34.429,data,2014-11,data,data\r\n93,28/10/14 16:39,833,call,2014-11,landline,landline\r\n94,28/10/14 20:44,206,call,2014-11,Tesco,mobile\r\n95,29/10/14 06:58,34.429,data,2014-11,data,data\r\n96,29/10/14 12:56,442,call,2014-11,Vodafone,mobile\r\n97,30/10/14 06:58,34.429,data,2014-11,data,data\r\n98,30/10/14 14:31,463,call,2014-11,Vodafone,mobile\r\n99,30/10/14 19:48,4,call,2014-11,Vodafone,mobile\r\n100,30/10/14 20:02,4,call,2014-11,Meteor,mobile\r\n101,31/10/14 06:58,34.429,data,2014-11,data,data\r\n102,31/10/14 07:46,1,sms,2014-11,Vodafone,mobile\r\n103,31/10/14 08:00,1,sms,2014-11,Vodafone,mobile\r\n104,31/10/14 13:26,5,call,2014-11,Three,mobile\r\n105,31/10/14 13:27,1234,call,2014-11,Tesco,mobile\r\n106,31/10/14 14:10,43,call,2014-11,Three,mobile\r\n107,31/10/14 18:29,1,sms,2014-11,Three,mobile\r\n108,31/10/14 18:29,1,sms,2014-11,Three,mobile\r\n109,31/10/14 18:30,483,call,2014-11,Meteor,mobile\r\n110,31/10/14 18:39,3,call,2014-11,Tesco,mobile\r\n111,01/11/14 06:58,34.429,data,2014-11,data,data\r\n112,01/11/14 15:13,955,call,2014-11,Vodafone,mobile\r\n113,01/11/14 17:54,4,call,2014-11,Tesco,mobile\r\n114,02/11/14 06:58,34.429,data,2014-11,data,data\r\n115,02/11/14 14:34,459,call,2014-11,Three,mobile\r\n116,02/11/14 15:44,1023,call,2014-11,Three,mobile\r\n117,02/11/14 19:16,1025,call,2014-11,Three,mobile\r\n118,02/11/14 21:42,169,call,2014-11,Meteor,mobile\r\n119,02/11/14 22:55,8,call,2014-11,Meteor,mobile\r\n120,03/11/14 06:58,34.429,data,2014-11,data,data\r\n121,03/11/14 08:40,1,sms,2014-11,special,special\r\n122,03/11/14 10:30,135,call,2014-11,Three,mobile\r\n123,03/11/14 10:37,7,call,2014-11,Vodafone,mobile\r\n124,03/11/14 10:47,37,call,2014-11,Vodafone,mobile\r\n125,03/11/14 14:04,1,sms,2014-11,Vodafone,mobile\r\n126,03/11/14 15:27,5,call,2014-11,Three,mobile\r\n127,03/11/14 16:07,123,call,2014-11,landline,landline\r\n128,03/11/14 16:10,4,call,2014-11,Tesco,mobile\r\n129,03/11/14 17:03,90,call,2014-11,Vodafone,mobile\r\n130,03/11/14 22:36,3,call,2014-11,Tesco,mobile\r\n131,04/11/14 06:58,34.429,data,2014-11,data,data\r\n132,04/11/14 11:58,1,sms,2014-11,Vodafone,mobile\r\n133,04/11/14 11:58,1,sms,2014-11,Vodafone,mobile\r\n134,04/11/14 13:12,1,sms,2014-11,Three,mobile\r\n135,04/11/14 14:05,1,sms,2014-11,Three,mobile\r\n136,04/11/14 14:26,1,call,2014-11,voicemail,voicemail\r\n137,04/11/14 14:26,98,call,2014-11,voicemail,voicemail\r\n138,04/11/14 16:13,1,sms,2014-11,Three,mobile\r\n139,04/11/14 16:19,8,call,2014-11,Meteor,mobile\r\n140,04/11/14 16:22,9,call,2014-11,Meteor,mobile\r\n141,04/11/14 16:24,4,call,2014-11,Meteor,mobile\r\n142,04/11/14 16:34,1,sms,2014-11,Three,mobile\r\n143,04/11/14 16:39,1,sms,2014-11,Meteor,mobile\r\n144,04/11/14 16:41,1,sms,2014-11,Meteor,mobile\r\n145,04/11/14 16:45,1,sms,2014-11,Three,mobile\r\n146,04/11/14 18:26,4,call,2014-11,Tesco,mobile\r\n147,04/11/14 20:15,11,call,2014-11,Three,mobile\r\n148,04/11/14 20:15,1,sms,2014-11,Meteor,mobile\r\n149,04/11/14 20:15,1,sms,2014-11,Meteor,mobile\r\n150,04/11/14 20:16,166,call,2014-11,Meteor,mobile\r\n151,05/11/14 06:58,34.429,data,2014-11,data,data\r\n152,05/11/14 10:59,55,call,2014-11,Three,mobile\r\n153,05/11/14 11:30,1,sms,2014-11,Vodafone,mobile\r\n154,05/11/14 11:43,1,sms,2014-11,Vodafone,mobile\r\n155,05/11/14 12:43,1,sms,2014-11,Vodafone,mobile\r\n156,05/11/14 19:35,29,call,2014-11,Vodafone,mobile\r\n157,06/11/14 01:02,1,sms,2014-11,Vodafone,mobile\r\n158,06/11/14 01:02,1,sms,2014-11,Vodafone,mobile\r\n159,06/11/14 06:58,34.429,data,2014-11,data,data\r\n160,06/11/14 09:04,1,sms,2014-11,Vodafone,mobile\r\n161,06/11/14 09:05,1,sms,2014-11,Vodafone,mobile\r\n162,06/11/14 09:47,150,call,2014-11,Vodafone,mobile\r\n163,06/11/14 09:50,89,call,2014-11,Three,mobile\r\n164,06/11/14 09:52,75,call,2014-11,Meteor,mobile\r\n165,06/11/14 09:54,5,call,2014-11,Three,mobile\r\n166,06/11/14 11:47,8,call,2014-11,Vodafone,mobile\r\n167,06/11/14 14:52,1,sms,2014-11,Vodafone,mobile\r\n168,06/11/14 18:02,279,call,2014-11,Tesco,mobile\r\n169,06/11/14 18:07,452,call,2014-11,Vodafone,mobile\r\n170,07/11/14 06:58,34.429,data,2014-11,data,data\r\n171,07/11/14 09:33,1205,call,2014-11,Vodafone,mobile\r\n172,07/11/14 17:51,218,call,2014-11,Three,mobile\r\n173,07/11/14 21:04,1,sms,2014-11,Vodafone,mobile\r\n174,07/11/14 21:10,1,sms,2014-11,Vodafone,mobile\r\n175,07/11/14 21:12,1,sms,2014-11,Vodafone,mobile\r\n176,07/11/14 21:12,1,sms,2014-11,Vodafone,mobile\r\n177,07/11/14 21:19,1,sms,2014-11,Vodafone,mobile\r\n178,07/11/14 21:25,1,sms,2014-11,Vodafone,mobile\r\n179,07/11/14 21:31,1,sms,2014-11,Vodafone,mobile\r\n180,07/11/14 21:31,1,sms,2014-11,Vodafone,mobile\r\n181,07/11/14 22:04,1,sms,2014-11,Vodafone,mobile\r\n182,08/11/14 06:58,34.429,data,2014-11,data,data\r\n183,08/11/14 16:33,41,call,2014-11,Three,mobile\r\n184,08/11/14 18:18,6,call,2014-11,Tesco,mobile\r\n185,09/11/14 01:41,1,sms,2014-11,Three,mobile\r\n186,09/11/14 01:49,1,sms,2014-11,Three,mobile\r\n187,09/11/14 01:50,1,sms,2014-11,Three,mobile\r\n188,09/11/14 02:04,1,sms,2014-11,Three,mobile\r\n189,09/11/14 06:58,34.429,data,2014-11,data,data\r\n190,09/11/14 19:21,14,call,2014-11,Tesco,mobile\r\n191,09/11/14 22:09,3,call,2014-11,Three,mobile\r\n192,10/11/14 06:58,34.429,data,2014-11,data,data\r\n193,10/11/14 09:29,178,call,2014-11,Vodafone,mobile\r\n194,10/11/14 11:36,412,call,2014-11,Three,mobile\r\n195,10/11/14 14:59,13,call,2014-11,Three,mobile\r\n196,10/11/14 15:15,459,call,2014-11,Three,mobile\r\n197,10/11/14 18:19,1,sms,2014-11,Three,mobile\r\n198,10/11/14 18:34,1,sms,2014-11,Three,mobile\r\n199,11/11/14 06:58,34.429,data,2014-11,data,data\r\n200,11/11/14 09:28,3,call,2014-11,Vodafone,mobile\r\n201,11/11/14 11:32,3,call,2014-11,Vodafone,mobile\r\n202,11/11/14 11:37,1,sms,2014-11,Three,mobile\r\n203,11/11/14 12:39,36,call,2014-11,Three,mobile\r\n204,11/11/14 14:13,1,sms,2014-11,Meteor,mobile\r\n205,11/11/14 14:20,1,sms,2014-11,Meteor,mobile\r\n206,11/11/14 14:41,1,sms,2014-11,Meteor,mobile\r\n207,11/11/14 19:56,1,sms,2014-11,Three,mobile\r\n208,12/11/14 06:58,34.429,data,2014-11,data,data\r\n209,12/11/14 10:48,1,sms,2014-11,Vodafone,mobile\r\n210,12/11/14 10:48,1,sms,2014-11,Three,mobile\r\n211,12/11/14 10:48,1,sms,2014-11,Vodafone,mobile\r\n212,12/11/14 10:49,1,sms,2014-11,Three,mobile\r\n213,12/11/14 12:04,1,sms,2014-11,Three,mobile\r\n214,12/11/14 13:49,1,sms,2014-11,Vodafone,mobile\r\n215,12/11/14 16:16,1,sms,2014-11,Vodafone,mobile\r\n216,12/11/14 16:19,4,call,2014-11,landline,landline\r\n217,12/11/14 16:42,1,sms,2014-11,Vodafone,mobile\r\n218,12/11/14 16:42,1,sms,2014-11,Vodafone,mobile\r\n219,12/11/14 17:14,1,sms,2014-11,Vodafone,mobile\r\n220,12/11/14 17:14,1,sms,2014-11,Vodafone,mobile\r\n221,12/11/14 17:49,1,sms,2014-11,Three,mobile\r\n222,12/11/14 17:56,145,call,2014-11,Three,mobile\r\n223,12/11/14 17:59,1001,call,2014-11,Three,mobile\r\n224,12/11/14 19:01,7,call,2014-11,Vodafone,mobile\r\n225,12/11/14 19:18,1,sms,2014-11,Three,mobile\r\n226,12/11/14 19:18,1,sms,2014-11,Three,mobile\r\n227,12/11/14 19:20,1,sms,2014-11,Vodafone,mobile\r\n228,13/11/14 06:58,34.429,data,2014-12,data,data\r\n229,13/11/14 22:30,1,sms,2014-11,Three,mobile\r\n230,13/11/14 22:31,1,sms,2014-11,Vodafone,mobile\r\n231,14/11/14 06:58,34.429,data,2014-12,data,data\r\n232,14/11/14 17:24,124,call,2014-12,voicemail,voicemail\r\n233,14/11/14 17:28,1,sms,2014-12,Vodafone,mobile\r\n234,15/11/14 06:58,34.429,data,2014-12,data,data\r\n235,16/11/14 06:58,34.429,data,2014-12,data,data\r\n236,16/11/14 14:05,4,call,2014-12,Vodafone,mobile\r\n237,17/11/14 06:58,34.429,data,2014-12,data,data\r\n238,18/11/14 06:58,34.429,data,2014-12,data,data\r\n239,18/11/14 08:22,1,sms,2014-12,Vodafone,mobile\r\n240,18/11/14 08:29,1,sms,2014-12,Vodafone,mobile\r\n241,18/11/14 08:29,1,sms,2014-12,Vodafone,mobile\r\n242,18/11/14 08:33,1,sms,2014-12,Vodafone,mobile\r\n243,18/11/14 08:34,1,sms,2014-12,Vodafone,mobile\r\n244,18/11/14 08:34,1,sms,2014-12,Vodafone,mobile\r\n245,18/11/14 08:39,1,sms,2014-12,Vodafone,mobile\r\n246,18/11/14 08:42,1,sms,2014-12,Vodafone,mobile\r\n247,18/11/14 08:45,1,sms,2014-12,Vodafone,mobile\r\n248,18/11/14 09:35,1,sms,2014-12,Vodafone,mobile\r\n249,19/11/14 06:58,34.429,data,2014-12,data,data\r\n250,19/11/14 14:05,128,call,2014-12,Tesco,mobile\r\n251,19/11/14 14:11,249,call,2014-12,Meteor,mobile\r\n252,19/11/14 18:56,2120,call,2014-12,Three,mobile\r\n253,19/11/14 22:48,1,sms,2014-12,Vodafone,mobile\r\n254,20/11/14 06:58,34.429,data,2014-12,data,data\r\n255,20/11/14 14:57,71,call,2014-12,voicemail,voicemail\r\n256,20/11/14 14:59,56,call,2014-12,Three,mobile\r\n257,20/11/14 16:22,1,sms,2014-12,Meteor,mobile\r\n258,20/11/14 19:08,9,call,2014-12,Three,mobile\r\n259,20/11/14 21:03,3,call,2014-12,Three,mobile\r\n260,20/11/14 21:03,3,call,2014-12,Three,mobile\r\n261,21/11/14 00:17,17,call,2014-12,Tesco,mobile\r\n262,21/11/14 01:13,1,sms,2014-12,Vodafone,mobile\r\n263,21/11/14 06:58,34.429,data,2014-12,data,data\r\n264,21/11/14 10:29,1,sms,2014-12,Meteor,mobile\r\n265,21/11/14 10:29,1,sms,2014-12,Meteor,mobile\r\n266,21/11/14 10:30,1,sms,2014-12,Meteor,mobile\r\n267,21/11/14 11:29,8,call,2014-12,landline,landline\r\n268,21/11/14 11:31,982,call,2014-12,Vodafone,mobile\r\n269,21/11/14 11:49,11,call,2014-12,landline,landline\r\n270,21/11/14 11:50,34,call,2014-12,Three,mobile\r\n271,21/11/14 11:50,8,call,2014-12,Three,mobile\r\n272,21/11/14 13:31,600,call,2014-12,Tesco,mobile\r\n273,21/11/14 18:07,244,call,2014-12,Meteor,mobile\r\n274,22/11/14 02:10,186,call,2014-12,Tesco,mobile\r\n275,22/11/14 06:58,34.429,data,2014-12,data,data\r\n276,22/11/14 12:02,75,call,2014-12,Meteor,mobile\r\n277,22/11/14 12:10,90,call,2014-12,Vodafone,mobile\r\n278,22/11/14 14:30,13,call,2014-12,Vodafone,mobile\r\n279,22/11/14 14:33,20,call,2014-12,Vodafone,mobile\r\n280,22/11/14 14:34,2,call,2014-12,Vodafone,mobile\r\n281,23/11/14 06:58,34.429,data,2014-12,data,data\r\n282,23/11/14 13:24,208,call,2014-12,Three,mobile\r\n283,23/11/14 16:10,107,call,2014-12,Three,mobile\r\n284,23/11/14 17:36,55,call,2014-12,Three,mobile\r\n285,23/11/14 17:53,5,call,2014-12,Three,mobile\r\n286,23/11/14 17:53,20,call,2014-12,Three,mobile\r\n287,23/11/14 17:54,2,call,2014-12,Three,mobile\r\n288,24/11/14 06:58,34.429,data,2014-12,data,data\r\n289,24/11/14 09:40,1,sms,2014-12,Three,mobile\r\n290,24/11/14 12:24,4,call,2014-12,Meteor,mobile\r\n291,25/11/14 06:58,34.429,data,2014-12,data,data\r\n292,25/11/14 11:25,21,call,2014-12,Vodafone,mobile\r\n293,25/11/14 16:09,1,sms,2014-12,Meteor,mobile\r\n294,25/11/14 16:19,1,sms,2014-12,Meteor,mobile\r\n295,25/11/14 17:10,114,call,2014-12,Meteor,mobile\r\n296,25/11/14 18:06,1,sms,2014-12,Meteor,mobile\r\n297,25/11/14 18:18,81,call,2014-12,Meteor,mobile\r\n298,25/11/14 18:47,174,call,2014-12,voicemail,voicemail\r\n299,25/11/14 19:10,71,call,2014-12,Tesco,mobile\r\n300,25/11/14 19:20,40,call,2014-12,Tesco,mobile\r\n301,25/11/14 19:21,29,call,2014-12,voicemail,voicemail\r\n302,25/11/14 19:25,37,call,2014-12,Tesco,mobile\r\n303,25/11/14 19:26,63,call,2014-12,voicemail,voicemail\r\n304,25/11/14 20:39,1,sms,2014-12,Three,mobile\r\n305,26/11/14 06:58,34.429,data,2014-12,data,data\r\n306,26/11/14 07:03,14,call,2014-12,Meteor,mobile\r\n307,26/11/14 07:15,1,sms,2014-12,Vodafone,mobile\r\n308,26/11/14 07:57,1,sms,2014-12,Vodafone,mobile\r\n309,26/11/14 07:59,4,call,2014-12,Three,mobile\r\n310,26/11/14 08:00,1,sms,2014-12,Vodafone,mobile\r\n311,26/11/14 08:13,10,call,2014-12,Three,mobile\r\n312,26/11/14 08:16,3,call,2014-12,Three,mobile\r\n313,26/11/14 08:26,3,call,2014-12,Three,mobile\r\n314,26/11/14 08:27,3,call,2014-12,Three,mobile\r\n315,26/11/14 09:01,1,sms,2014-12,Meteor,mobile\r\n316,26/11/14 11:53,1,sms,2014-12,Meteor,mobile\r\n317,26/11/14 11:54,1,sms,2014-12,Meteor,mobile\r\n318,26/11/14 11:54,1,sms,2014-12,Meteor,mobile\r\n319,26/11/14 11:56,1,sms,2014-12,Meteor,mobile\r\n320,26/11/14 17:48,3,call,2014-12,Three,mobile\r\n321,27/11/14 06:58,34.429,data,2014-12,data,data\r\n322,27/11/14 16:53,1116,call,2014-12,Three,mobile\r\n323,27/11/14 18:38,1,sms,2014-12,Three,mobile\r\n324,28/11/14 06:58,34.429,data,2014-12,data,data\r\n325,28/11/14 13:05,1,sms,2014-12,Three,mobile\r\n326,28/11/14 13:12,1,sms,2014-12,Three,mobile\r\n327,28/11/14 19:03,143,call,2014-12,Tesco,mobile\r\n328,29/11/14 06:58,34.429,data,2014-12,data,data\r\n329,29/11/14 14:44,151,call,2014-12,Three,mobile\r\n330,30/11/14 06:58,34.429,data,2014-12,data,data\r\n331,30/11/14 11:45,1,sms,2014-12,Three,mobile\r\n332,30/11/14 11:48,1,sms,2014-12,Three,mobile\r\n333,30/11/14 11:48,1,sms,2014-12,Three,mobile\r\n334,30/11/14 12:06,1,sms,2014-12,Three,mobile\r\n335,30/11/14 14:24,1,sms,2014-12,Three,mobile\r\n336,30/11/14 14:44,1,sms,2014-12,Three,mobile\r\n337,30/11/14 14:51,4,call,2014-12,Three,mobile\r\n338,01/12/14 06:58,34.429,data,2014-12,data,data\r\n339,01/12/14 12:51,1,sms,2014-12,Three,mobile\r\n340,01/12/14 12:59,1,sms,2014-12,Three,mobile\r\n341,02/12/14 06:58,34.429,data,2014-12,data,data\r\n342,02/12/14 11:40,526,call,2014-12,Meteor,mobile\r\n343,03/12/14 06:58,34.429,data,2014-12,data,data\r\n344,03/12/14 15:01,844,call,2014-12,landline,landline\r\n345,03/12/14 18:10,383,call,2014-12,Tesco,mobile\r\n346,04/12/14 06:58,34.429,data,2014-12,data,data\r\n347,04/12/14 13:52,6,call,2014-12,Vodafone,mobile\r\n348,04/12/14 15:34,37,call,2014-12,Three,mobile\r\n349,04/12/14 16:02,15,call,2014-12,Meteor,mobile\r\n350,04/12/14 23:41,71,call,2014-12,voicemail,voicemail\r\n351,05/12/14 06:58,34.429,data,2014-12,data,data\r\n352,05/12/14 16:49,465,call,2014-12,landline,landline\r\n353,05/12/14 18:17,153,call,2014-12,Tesco,mobile\r\n354,05/12/14 18:25,826,call,2014-12,Three,mobile\r\n355,06/12/14 06:58,34.429,data,2014-12,data,data\r\n356,06/12/14 11:33,442,call,2014-12,Meteor,mobile\r\n357,06/12/14 18:25,1,sms,2014-12,Vodafone,mobile\r\n358,06/12/14 18:26,1,sms,2014-12,Tesco,mobile\r\n359,06/12/14 18:26,1,sms,2014-12,Vodafone,mobile\r\n360,06/12/14 18:27,1,sms,2014-12,world,world\r\n361,06/12/14 18:28,1,sms,2014-12,world,world\r\n362,06/12/14 19:40,191,call,2014-12,Meteor,mobile\r\n363,07/12/14 06:58,34.429,data,2014-12,data,data\r\n364,07/12/14 13:03,99,call,2014-12,voicemail,voicemail\r\n365,07/12/14 13:45,428,call,2014-12,Three,mobile\r\n366,07/12/14 14:39,3,call,2014-12,Three,mobile\r\n367,07/12/14 20:23,727,call,2014-12,Three,mobile\r\n368,07/12/14 20:36,33,call,2014-12,Tesco,mobile\r\n369,07/12/14 20:37,120,call,2014-12,Three,mobile\r\n370,07/12/14 23:22,1,sms,2014-12,world,world\r\n371,07/12/14 23:22,1,sms,2014-12,world,world\r\n372,08/12/14 06:58,34.429,data,2014-12,data,data\r\n373,08/12/14 17:38,55,call,2014-12,Meteor,mobile\r\n374,09/12/14 06:58,34.429,data,2014-12,data,data\r\n375,09/12/14 18:32,28,call,2014-12,Tesco,mobile\r\n376,10/12/14 06:58,34.429,data,2014-12,data,data\r\n377,11/12/14 06:58,34.429,data,2014-12,data,data\r\n378,12/12/14 06:58,34.429,data,2014-12,data,data\r\n379,12/12/14 11:00,112,call,2014-12,Vodafone,mobile\r\n380,12/12/14 18:14,52,call,2014-12,Vodafone,mobile\r\n381,13/12/14 06:58,34.429,data,2015-01,data,data\r\n382,13/12/14 14:56,223,call,2014-12,Three,mobile\r\n383,14/12/14 02:05,14,call,2014-12,landline,landline\r\n384,14/12/14 02:07,8,call,2014-12,landline,landline\r\n385,14/12/14 02:09,74,call,2014-12,landline,landline\r\n386,14/12/14 06:58,34.429,data,2015-01,data,data\r\n387,14/12/14 15:28,59,call,2014-12,voicemail,voicemail\r\n388,14/12/14 19:54,25,call,2014-12,Three,mobile\r\n389,15/12/14 06:58,34.429,data,2015-01,data,data\r\n390,15/12/14 19:56,1,sms,2015-01,Three,mobile\r\n391,15/12/14 19:58,1,sms,2015-01,Three,mobile\r\n392,15/12/14 20:03,4,call,2015-01,Three,mobile\r\n393,15/12/14 20:10,1,sms,2015-01,Vodafone,mobile\r\n394,15/12/14 20:10,1,sms,2015-01,Three,mobile\r\n395,15/12/14 23:12,1,sms,2015-01,Three,mobile\r\n396,16/12/14 06:58,34.429,data,2015-01,data,data\r\n397,17/12/14 06:58,34.429,data,2015-01,data,data\r\n398,17/12/14 18:08,1859,call,2015-01,Vodafone,mobile\r\n399,17/12/14 23:26,1,sms,2015-01,Vodafone,mobile\r\n400,18/12/14 06:58,34.429,data,2015-01,data,data\r\n401,18/12/14 12:36,61,call,2015-01,Three,mobile\r\n402,18/12/14 15:46,268,call,2015-01,Meteor,mobile\r\n403,18/12/14 15:57,192,call,2015-01,Meteor,mobile\r\n404,18/12/14 16:10,14,call,2015-01,Meteor,mobile\r\n405,18/12/14 17:54,17,call,2015-01,Three,mobile\r\n406,18/12/14 19:05,46,call,2015-01,Tesco,mobile\r\n407,18/12/14 21:58,4,call,2015-01,Meteor,mobile\r\n408,18/12/14 21:59,4,call,2015-01,Meteor,mobile\r\n409,19/12/14 06:58,34.429,data,2015-01,data,data\r\n410,19/12/14 08:57,1,sms,2015-01,Vodafone,mobile\r\n411,19/12/14 10:14,41,call,2015-01,Three,mobile\r\n412,19/12/14 12:40,3,call,2015-01,Three,mobile\r\n413,19/12/14 12:41,217,call,2015-01,Tesco,mobile\r\n414,19/12/14 12:41,18,call,2015-01,Three,mobile\r\n415,19/12/14 14:48,58,call,2015-01,Meteor,mobile\r\n416,19/12/14 16:49,48,call,2015-01,Tesco,mobile\r\n417,19/12/14 16:51,543,call,2015-01,Tesco,mobile\r\n418,19/12/14 17:00,131,call,2015-01,Three,mobile\r\n419,19/12/14 18:44,1,sms,2015-01,Vodafone,mobile\r\n420,20/12/14 06:58,34.429,data,2015-01,data,data\r\n421,20/12/14 14:39,1,sms,2015-01,Tesco,mobile\r\n422,20/12/14 15:20,1,sms,2015-01,Tesco,mobile\r\n423,20/12/14 15:53,553,call,2015-01,Meteor,mobile\r\n424,20/12/14 16:09,1,sms,2015-01,Tesco,mobile\r\n425,21/12/14 00:05,54,call,2015-01,Meteor,mobile\r\n426,21/12/14 06:58,34.429,data,2015-01,data,data\r\n427,22/12/14 06:58,34.429,data,2015-01,data,data\r\n428,22/12/14 10:42,489,call,2015-01,Tesco,mobile\r\n429,22/12/14 11:22,1,sms,2015-01,Vodafone,mobile\r\n430,22/12/14 11:22,1,sms,2015-01,Meteor,mobile\r\n431,22/12/14 13:33,46,call,2015-01,Vodafone,mobile\r\n432,22/12/14 14:09,1,sms,2015-01,Vodafone,mobile\r\n433,22/12/14 14:15,47,call,2015-01,Vodafone,mobile\r\n434,22/12/14 18:03,1,sms,2015-01,Vodafone,mobile\r\n435,22/12/14 18:03,1,sms,2015-01,Vodafone,mobile\r\n436,22/12/14 18:03,1,sms,2015-01,Vodafone,mobile\r\n437,22/12/14 19:10,1,sms,2015-01,Vodafone,mobile\r\n438,22/12/14 19:12,566,call,2015-01,Tesco,mobile\r\n439,22/12/14 19:35,1,sms,2015-01,Meteor,mobile\r\n440,22/12/14 19:36,1,sms,2015-01,Meteor,mobile\r\n441,22/12/14 23:12,956,call,2015-01,Three,mobile\r\n442,23/12/14 00:57,55,call,2015-01,Tesco,mobile\r\n443,23/12/14 06:58,34.429,data,2015-01,data,data\r\n444,23/12/14 09:17,145,call,2015-01,voicemail,voicemail\r\n445,23/12/14 11:03,40,call,2015-01,landline,landline\r\n446,23/12/14 12:49,449,call,2015-01,Three,mobile\r\n447,23/12/14 15:39,1,sms,2015-01,Vodafone,mobile\r\n448,23/12/14 15:40,37,call,2015-01,landline,landline\r\n449,23/12/14 15:43,1,sms,2015-01,Vodafone,mobile\r\n450,23/12/14 19:45,39,call,2015-01,voicemail,voicemail\r\n451,23/12/14 20:02,28,call,2015-01,landline,landline\r\n452,23/12/14 21:06,1,sms,2015-01,Vodafone,mobile\r\n453,24/12/14 06:58,34.429,data,2015-01,data,data\r\n454,24/12/14 13:22,4,call,2015-01,Three,mobile\r\n455,24/12/14 13:29,234,call,2015-01,Tesco,mobile\r\n456,24/12/14 13:56,165,call,2015-01,Three,mobile\r\n457,24/12/14 13:56,3,call,2015-01,Three,mobile\r\n458,24/12/14 17:06,1,sms,2015-01,Vodafone,mobile\r\n459,24/12/14 17:07,37,call,2015-01,Vodafone,mobile\r\n460,24/12/14 18:44,5,call,2015-01,Meteor,mobile\r\n461,24/12/14 20:44,4,call,2015-01,Meteor,mobile\r\n462,24/12/14 23:34,1,sms,2015-01,Three,mobile\r\n463,25/12/14 06:58,34.429,data,2015-01,data,data\r\n464,25/12/14 12:27,1,sms,2015-01,Vodafone,mobile\r\n465,26/12/14 06:58,34.429,data,2015-01,data,data\r\n466,26/12/14 11:09,101,call,2015-01,voicemail,voicemail\r\n467,26/12/14 11:48,1,sms,2015-01,Vodafone,mobile\r\n468,27/12/14 06:58,34.429,data,2015-01,data,data\r\n469,27/12/14 22:30,1,sms,2015-01,Meteor,mobile\r\n470,27/12/14 22:30,1,sms,2015-01,Meteor,mobile\r\n471,27/12/14 22:30,1,sms,2015-01,Meteor,mobile\r\n472,27/12/14 22:30,1,sms,2015-01,Meteor,mobile\r\n473,28/12/14 06:58,34.429,data,2015-01,data,data\r\n474,29/12/14 06:58,34.429,data,2015-01,data,data\r\n475,29/12/14 12:09,368,call,2015-01,Tesco,mobile\r\n476,30/12/14 06:58,34.429,data,2015-01,data,data\r\n477,30/12/14 11:57,1,sms,2015-01,Three,mobile\r\n478,30/12/14 11:57,1,sms,2015-01,Three,mobile\r\n479,30/12/14 12:01,1,sms,2015-01,Three,mobile\r\n480,30/12/14 12:01,1,sms,2015-01,Three,mobile\r\n481,30/12/14 12:02,1,sms,2015-01,Three,mobile\r\n482,30/12/14 12:03,1,sms,2015-01,Three,mobile\r\n483,30/12/14 12:04,1,sms,2015-01,Three,mobile\r\n484,30/12/14 12:04,1,sms,2015-01,Three,mobile\r\n485,30/12/14 12:05,1,sms,2015-01,Three,mobile\r\n486,30/12/14 12:05,1,sms,2015-01,Three,mobile\r\n487,30/12/14 12:05,1,sms,2015-01,Three,mobile\r\n488,30/12/14 12:05,1,sms,2015-01,Three,mobile\r\n489,30/12/14 12:06,1,sms,2015-01,Three,mobile\r\n490,30/12/14 12:10,1,sms,2015-01,Three,mobile\r\n491,30/12/14 12:10,1,sms,2015-01,Three,mobile\r\n492,30/12/14 12:10,1,sms,2015-01,Three,mobile\r\n493,30/12/14 12:13,1,sms,2015-01,Three,mobile\r\n494,30/12/14 12:14,1,sms,2015-01,Three,mobile\r\n495,30/12/14 12:14,1,sms,2015-01,Three,mobile\r\n496,31/12/14 06:58,34.429,data,2015-01,data,data\r\n497,31/12/14 13:00,5,call,2015-01,Meteor,mobile\r\n498,31/12/14 13:03,358,call,2015-01,Meteor,mobile\r\n499,31/12/14 13:49,526,call,2015-01,landline,landline\r\n500,31/12/14 23:05,1,sms,2015-01,Vodafone,mobile\r\n501,31/12/14 23:37,1,sms,2015-01,Vodafone,mobile\r\n502,31/12/14 23:37,1,sms,2015-01,Vodafone,mobile\r\n503,31/12/14 23:37,1,sms,2015-01,Vodafone,mobile\r\n504,01/01/15 06:58,34.429,data,2015-01,data,data\r\n505,02/01/15 06:58,34.429,data,2015-01,data,data\r\n506,02/01/15 11:27,640,call,2015-01,Vodafone,mobile\r\n507,02/01/15 23:26,1,sms,2015-01,Meteor,mobile\r\n508,02/01/15 23:28,1,sms,2015-01,Meteor,mobile\r\n509,03/01/15 06:58,34.429,data,2015-01,data,data\r\n510,03/01/15 12:01,158,call,2015-01,Vodafone,mobile\r\n511,04/01/15 00:57,104,call,2015-01,Three,mobile\r\n512,04/01/15 06:58,34.429,data,2015-01,data,data\r\n513,04/01/15 14:20,3,call,2015-01,Vodafone,mobile\r\n514,04/01/15 14:31,6,call,2015-01,Vodafone,mobile\r\n515,04/01/15 14:32,41,call,2015-01,Vodafone,mobile\r\n516,05/01/15 06:58,34.429,data,2015-01,data,data\r\n517,05/01/15 09:49,56,call,2015-01,landline,landline\r\n518,05/01/15 09:51,128,call,2015-01,landline,landline\r\n519,05/01/15 10:10,77,call,2015-01,Three,mobile\r\n520,05/01/15 10:25,5,call,2015-01,Three,mobile\r\n521,05/01/15 10:25,1,sms,2015-01,Three,mobile\r\n522,05/01/15 10:52,1,sms,2015-01,Three,mobile\r\n523,05/01/15 10:56,144,call,2015-01,landline,landline\r\n524,05/01/15 11:58,99,call,2015-01,Meteor,mobile\r\n525,05/01/15 14:29,60,call,2015-01,landline,landline\r\n526,05/01/15 16:41,682,call,2015-01,Three,mobile\r\n527,05/01/15 17:22,36,call,2015-01,Tesco,mobile\r\n528,05/01/15 20:23,734,call,2015-01,Three,mobile\r\n529,06/01/15 06:58,34.429,data,2015-01,data,data\r\n530,06/01/15 09:04,1,sms,2015-01,Three,mobile\r\n531,06/01/15 09:04,1,sms,2015-01,Three,mobile\r\n532,06/01/15 13:28,16,call,2015-01,Meteor,mobile\r\n533,06/01/15 13:29,295,call,2015-01,Vodafone,mobile\r\n534,06/01/15 13:55,1,sms,2015-01,Vodafone,mobile\r\n535,06/01/15 19:17,106,call,2015-01,Meteor,mobile\r\n536,06/01/15 20:40,29,call,2015-01,Vodafone,mobile\r\n537,07/01/15 06:58,34.429,data,2015-01,data,data\r\n538,07/01/15 09:28,1,sms,2015-01,Vodafone,mobile\r\n539,07/01/15 21:20,1,sms,2015-01,Vodafone,mobile\r\n540,07/01/15 21:20,1,sms,2015-01,Vodafone,mobile\r\n541,08/01/15 06:58,34.429,data,2015-01,data,data\r\n542,08/01/15 15:02,4,call,2015-01,Meteor,mobile\r\n543,08/01/15 15:10,3,call,2015-01,Meteor,mobile\r\n544,08/01/15 15:10,23,call,2015-01,Meteor,mobile\r\n545,08/01/15 20:15,290,call,2015-01,Tesco,mobile\r\n546,08/01/15 20:26,1,sms,2015-01,Vodafone,mobile\r\n547,08/01/15 20:30,12,call,2015-01,Tesco,mobile\r\n548,08/01/15 20:31,1247,call,2015-01,Three,mobile\r\n549,08/01/15 22:41,1,sms,2015-01,Vodafone,mobile\r\n550,08/01/15 22:41,1,sms,2015-01,Vodafone,mobile\r\n551,08/01/15 22:52,1,sms,2015-01,Vodafone,mobile\r\n552,08/01/15 22:52,1,sms,2015-01,Vodafone,mobile\r\n553,08/01/15 23:06,1,sms,2015-01,Vodafone,mobile\r\n554,08/01/15 23:06,1,sms,2015-01,Vodafone,mobile\r\n555,09/01/15 06:58,34.429,data,2015-01,data,data\r\n556,09/01/15 09:25,1,sms,2015-01,Meteor,mobile\r\n557,09/01/15 09:43,33,call,2015-01,Three,mobile\r\n558,09/01/15 10:07,4,call,2015-01,Vodafone,mobile\r\n559,09/01/15 17:32,57,call,2015-01,Vodafone,mobile\r\n560,10/01/15 06:58,34.429,data,2015-01,data,data\r\n561,10/01/15 14:10,2,call,2015-01,Three,mobile\r\n562,10/01/15 14:36,6,call,2015-01,Vodafone,mobile\r\n563,10/01/15 14:44,398,call,2015-01,Vodafone,mobile\r\n564,10/01/15 15:58,412,call,2015-01,Meteor,mobile\r\n565,10/01/15 16:57,568,call,2015-01,Three,mobile\r\n566,10/01/15 21:16,1,sms,2015-01,Vodafone,mobile\r\n567,10/01/15 21:16,1,sms,2015-01,Vodafone,mobile\r\n568,11/01/15 06:58,34.429,data,2015-01,data,data\r\n569,11/01/15 13:29,1,sms,2015-01,Vodafone,mobile\r\n570,11/01/15 13:54,201,call,2015-01,Three,mobile\r\n571,12/01/15 06:58,34.429,data,2015-01,data,data\r\n572,12/01/15 12:01,18,call,2015-01,Meteor,mobile\r\n573,12/01/15 12:01,7,call,2015-01,Meteor,mobile\r\n574,12/01/15 18:23,4,call,2015-01,Three,mobile\r\n575,12/01/15 18:26,1,sms,2015-01,Vodafone,mobile\r\n576,12/01/15 18:26,1,sms,2015-01,Vodafone,mobile\r\n577,13/01/15 06:58,34.429,data,2015-02,data,data\r\n578,13/01/15 15:04,503,call,2015-01,Three,mobile\r\n579,13/01/15 19:09,1,sms,2015-01,Three,mobile\r\n580,13/01/15 19:44,1,sms,2015-01,Three,mobile\r\n581,13/01/15 19:44,1,sms,2015-01,Three,mobile\r\n582,13/01/15 19:57,1,sms,2015-01,Vodafone,mobile\r\n583,13/01/15 19:57,1,sms,2015-01,Vodafone,mobile\r\n584,13/01/15 19:58,105,call,2015-01,landline,landline\r\n585,13/01/15 20:00,466,call,2015-01,landline,landline\r\n586,14/01/15 06:58,34.429,data,2015-02,data,data\r\n587,14/01/15 17:15,13,call,2015-01,landline,landline\r\n588,14/01/15 19:16,397,call,2015-01,Three,mobile\r\n589,14/01/15 20:47,36,call,2015-01,Three,mobile\r\n590,14/01/15 23:34,1,sms,2015-01,Vodafone,mobile\r\n591,14/01/15 23:34,1,sms,2015-01,Vodafone,mobile\r\n592,14/01/15 23:35,1,sms,2015-01,Three,mobile\r\n593,14/01/15 23:36,1,sms,2015-01,Three,mobile\r\n594,15/01/15 06:58,34.429,data,2015-02,data,data\r\n595,15/01/15 10:36,28,call,2015-02,Three,mobile\r\n596,15/01/15 12:23,1,sms,2015-02,special,special\r\n597,15/01/15 17:22,168,call,2015-02,Tesco,mobile\r\n598,16/01/15 06:58,34.429,data,2015-02,data,data\r\n599,16/01/15 09:45,1,call,2015-02,Meteor,mobile\r\n600,16/01/15 09:56,61,call,2015-02,Meteor,mobile\r\n601,16/01/15 10:17,14,call,2015-02,Three,mobile\r\n602,16/01/15 10:25,20,call,2015-02,Three,mobile\r\n603,16/01/15 17:46,411,call,2015-02,Tesco,mobile\r\n604,16/01/15 18:07,1,sms,2015-02,Vodafone,mobile\r\n605,16/01/15 18:07,1,sms,2015-02,Vodafone,mobile\r\n606,16/01/15 18:07,1,sms,2015-02,Vodafone,mobile\r\n607,16/01/15 18:07,1,sms,2015-02,Vodafone,mobile\r\n608,16/01/15 18:07,1,sms,2015-02,Three,mobile\r\n609,16/01/15 18:07,1,sms,2015-02,Three,mobile\r\n610,17/01/15 06:58,34.429,data,2015-02,data,data\r\n611,17/01/15 18:50,78,call,2015-02,Three,mobile\r\n612,17/01/15 21:59,82,call,2015-02,Tesco,mobile\r\n613,18/01/15 06:58,34.429,data,2015-02,data,data\r\n614,18/01/15 16:27,478,call,2015-02,Three,mobile\r\n615,18/01/15 17:04,700,call,2015-02,Tesco,mobile\r\n616,19/01/15 06:58,34.429,data,2015-02,data,data\r\n617,19/01/15 12:44,1,sms,2015-02,Vodafone,mobile\r\n618,19/01/15 19:57,103,call,2015-02,Tesco,mobile\r\n619,19/01/15 20:08,53,call,2015-02,Tesco,mobile\r\n620,19/01/15 20:14,38,call,2015-02,Tesco,mobile\r\n621,20/01/15 06:58,34.429,data,2015-02,data,data\r\n622,20/01/15 15:08,1,sms,2015-02,Vodafone,mobile\r\n623,20/01/15 19:49,1,sms,2015-02,Vodafone,mobile\r\n624,20/01/15 20:23,1,sms,2015-02,Vodafone,mobile\r\n625,21/01/15 06:58,34.429,data,2015-02,data,data\r\n626,21/01/15 10:13,48,call,2015-02,Three,mobile\r\n627,21/01/15 14:36,93,call,2015-02,voicemail,voicemail\r\n628,21/01/15 14:44,1,sms,2015-02,Vodafone,mobile\r\n629,21/01/15 15:56,27,call,2015-02,voicemail,voicemail\r\n630,21/01/15 15:57,265,call,2015-02,Three,mobile\r\n631,21/01/15 18:04,777,call,2015-02,Tesco,mobile\r\n632,21/01/15 19:38,1090,call,2015-02,Meteor,mobile\r\n633,21/01/15 19:59,491,call,2015-02,Vodafone,mobile\r\n634,22/01/15 06:58,34.429,data,2015-02,data,data\r\n635,22/01/15 18:59,41,call,2015-02,voicemail,voicemail\r\n636,22/01/15 19:00,1107,call,2015-02,Vodafone,mobile\r\n637,23/01/15 06:58,34.429,data,2015-02,data,data\r\n638,23/01/15 12:56,29,call,2015-02,landline,landline\r\n639,23/01/15 14:32,200,call,2015-02,Tesco,mobile\r\n640,23/01/15 15:09,129,call,2015-02,Three,mobile\r\n641,23/01/15 15:22,1,sms,2015-02,Vodafone,mobile\r\n642,23/01/15 15:24,1,sms,2015-02,Vodafone,mobile\r\n643,23/01/15 15:37,1,sms,2015-02,Vodafone,mobile\r\n644,23/01/15 21:22,206,call,2015-02,Tesco,mobile\r\n645,24/01/15 06:58,34.429,data,2015-02,data,data\r\n646,25/01/15 06:58,34.429,data,2015-02,data,data\r\n647,25/01/15 09:16,4,call,2015-02,Vodafone,mobile\r\n648,25/01/15 16:55,1863,call,2015-02,Three,mobile\r\n649,26/01/15 06:58,34.429,data,2015-02,data,data\r\n650,26/01/15 16:54,104,call,2015-02,Three,mobile\r\n651,26/01/15 17:15,501,call,2015-02,Three,mobile\r\n652,27/01/15 06:58,34.429,data,2015-02,data,data\r\n653,27/01/15 09:36,37,call,2015-02,Three,mobile\r\n654,27/01/15 10:55,36,call,2015-02,Meteor,mobile\r\n655,28/01/15 06:58,34.429,data,2015-02,data,data\r\n656,28/01/15 09:44,8,call,2015-02,Vodafone,mobile\r\n657,28/01/15 10:02,7,call,2015-02,landline,landline\r\n658,28/01/15 15:53,272,call,2015-02,landline,landline\r\n659,29/01/15 06:58,34.429,data,2015-02,data,data\r\n660,29/01/15 11:35,1,sms,2015-02,Vodafone,mobile\r\n661,29/01/15 11:50,1,sms,2015-02,Vodafone,mobile\r\n662,29/01/15 17:11,255,call,2015-02,Tesco,mobile\r\n663,29/01/15 17:19,1,sms,2015-02,Vodafone,mobile\r\n664,29/01/15 17:58,362,call,2015-02,Three,mobile\r\n665,29/01/15 18:05,100,call,2015-02,Tesco,mobile\r\n666,29/01/15 19:27,74,call,2015-02,Tesco,mobile\r\n667,30/01/15 06:58,34.429,data,2015-02,data,data\r\n668,30/01/15 19:43,33,call,2015-02,Tesco,mobile\r\n669,30/01/15 19:56,45,call,2015-02,Tesco,mobile\r\n670,31/01/15 06:58,34.429,data,2015-02,data,data\r\n671,31/01/15 12:48,31,call,2015-02,Tesco,mobile\r\n672,31/01/15 13:14,7,call,2015-02,Vodafone,mobile\r\n673,01/02/15 06:58,34.429,data,2015-02,data,data\r\n674,01/02/15 13:33,103,call,2015-02,landline,landline\r\n675,02/02/15 06:58,34.429,data,2015-02,data,data\r\n676,02/02/15 17:11,280,call,2015-02,Tesco,mobile\r\n677,02/02/15 17:16,183,call,2015-02,Tesco,mobile\r\n678,02/02/15 17:35,1,sms,2015-02,Tesco,mobile\r\n679,02/02/15 17:35,1,sms,2015-02,Tesco,mobile\r\n680,02/02/15 17:35,1,sms,2015-02,Three,mobile\r\n681,02/02/15 17:35,1,sms,2015-02,Three,mobile\r\n682,02/02/15 18:17,7,call,2015-02,landline,landline\r\n683,03/02/15 06:58,34.429,data,2015-02,data,data\r\n684,03/02/15 14:45,6,call,2015-02,Three,mobile\r\n685,04/02/15 06:58,34.429,data,2015-02,data,data\r\n686,04/02/15 12:36,21,call,2015-02,voicemail,voicemail\r\n687,04/02/15 14:52,227,call,2015-02,Tesco,mobile\r\n688,04/02/15 17:04,1,sms,2015-02,special,special\r\n689,05/02/15 06:58,34.429,data,2015-02,data,data\r\n690,05/02/15 13:37,62,call,2015-02,voicemail,voicemail\r\n691,05/02/15 13:38,91,call,2015-02,landline,landline\r\n692,06/02/15 06:58,34.429,data,2015-02,data,data\r\n693,06/02/15 10:36,24,call,2015-02,voicemail,voicemail\r\n694,06/02/15 10:37,128,call,2015-02,Vodafone,mobile\r\n695,06/02/15 18:39,23,call,2015-02,Three,mobile\r\n696,06/02/15 18:41,51,call,2015-02,Three,mobile\r\n697,07/02/15 06:58,34.429,data,2015-02,data,data\r\n698,07/02/15 09:48,1,sms,2015-02,Vodafone,mobile\r\n699,07/02/15 09:48,1,sms,2015-02,Vodafone,mobile\r\n700,07/02/15 10:03,119,call,2015-02,Vodafone,mobile\r\n701,07/02/15 11:13,1,sms,2015-02,Vodafone,mobile\r\n702,07/02/15 11:37,4,call,2015-02,Three,mobile\r\n703,07/02/15 15:04,1,sms,2015-02,Vodafone,mobile\r\n704,07/02/15 16:06,1,sms,2015-02,Three,mobile\r\n705,07/02/15 16:11,1,sms,2015-02,Three,mobile\r\n706,07/02/15 16:11,1,sms,2015-02,Three,mobile\r\n707,07/02/15 16:16,1,sms,2015-02,Three,mobile\r\n708,07/02/15 16:27,141,call,2015-02,landline,landline\r\n709,07/02/15 17:33,795,call,2015-02,Three,mobile\r\n710,07/02/15 17:56,121,call,2015-02,Tesco,mobile\r\n711,07/02/15 18:23,2,call,2015-02,Three,mobile\r\n712,07/02/15 22:18,1,sms,2015-02,Three,mobile\r\n713,07/02/15 22:30,1,sms,2015-02,Three,mobile\r\n714,07/02/15 22:30,1,sms,2015-02,Three,mobile\r\n715,08/02/15 06:58,34.429,data,2015-02,data,data\r\n716,08/02/15 20:54,80,call,2015-02,landline,landline\r\n717,09/02/15 06:58,34.429,data,2015-02,data,data\r\n718,09/02/15 09:08,653,call,2015-02,Three,mobile\r\n719,09/02/15 17:41,729,call,2015-02,Three,mobile\r\n720,09/02/15 17:54,89,call,2015-02,Three,mobile\r\n721,09/02/15 18:32,1,sms,2015-02,Vodafone,mobile\r\n722,09/02/15 22:54,1,sms,2015-02,Meteor,mobile\r\n723,10/02/15 00:24,1,sms,2015-02,Vodafone,mobile\r\n724,10/02/15 00:24,1,sms,2015-02,Vodafone,mobile\r\n725,10/02/15 06:58,34.429,data,2015-02,data,data\r\n726,10/02/15 21:40,1,sms,2015-02,Vodafone,mobile\r\n727,11/02/15 06:58,34.429,data,2015-02,data,data\r\n728,12/02/15 06:58,34.429,data,2015-02,data,data\r\n729,12/02/15 20:15,69,call,2015-03,landline,landline\r\n730,12/02/15 20:51,86,call,2015-03,Tesco,mobile\r\n731,13/02/15 06:58,34.429,data,2015-03,data,data\r\n732,13/02/15 10:58,451,call,2015-03,Vodafone,mobile\r\n733,13/02/15 21:13,8,call,2015-03,Vodafone,mobile\r\n734,14/02/15 06:58,34.429,data,2015-03,data,data\r\n735,14/02/15 15:40,106,call,2015-03,Three,mobile\r\n736,14/02/15 16:06,148,call,2015-03,Tesco,mobile\r\n737,15/02/15 06:58,34.429,data,2015-03,data,data\r\n738,15/02/15 18:44,21,call,2015-03,landline,landline\r\n739,16/02/15 06:58,34.429,data,2015-03,data,data\r\n740,17/02/15 06:58,34.429,data,2015-03,data,data\r\n741,17/02/15 15:59,24,call,2015-03,Meteor,mobile\r\n742,17/02/15 19:09,2328,call,2015-03,Three,mobile\r\n743,18/02/15 06:58,34.429,data,2015-03,data,data\r\n744,18/02/15 18:49,165,call,2015-03,Tesco,mobile\r\n745,18/02/15 19:56,94,call,2015-03,Three,mobile\r\n746,19/02/15 06:58,34.429,data,2015-03,data,data\r\n747,19/02/15 18:46,1,sms,2015-03,Vodafone,mobile\r\n748,19/02/15 22:00,1,sms,2015-03,Vodafone,mobile\r\n749,19/02/15 22:00,1,sms,2015-03,Vodafone,mobile\r\n750,19/02/15 22:00,1,sms,2015-03,Vodafone,mobile\r\n751,20/02/15 06:58,34.429,data,2015-03,data,data\r\n752,20/02/15 13:45,68,call,2015-03,Vodafone,mobile\r\n753,21/02/15 06:58,34.429,data,2015-03,data,data\r\n754,22/02/15 06:58,34.429,data,2015-03,data,data\r\n755,23/02/15 06:58,34.429,data,2015-03,data,data\r\n756,23/02/15 20:49,182,call,2015-03,landline,landline\r\n757,24/02/15 06:58,34.429,data,2015-03,data,data\r\n758,24/02/15 10:05,107,call,2015-03,voicemail,voicemail\r\n759,24/02/15 13:32,1,sms,2015-03,Three,mobile\r\n760,24/02/15 13:32,1,sms,2015-03,Three,mobile\r\n761,25/02/15 06:58,34.429,data,2015-03,data,data\r\n762,25/02/15 12:56,117,call,2015-03,Tesco,mobile\r\n763,25/02/15 12:58,129,call,2015-03,Vodafone,mobile\r\n764,25/02/15 13:15,356,call,2015-03,Three,mobile\r\n765,25/02/15 13:21,1,sms,2015-03,Tesco,mobile\r\n766,25/02/15 13:22,1,sms,2015-03,Tesco,mobile\r\n767,25/02/15 13:22,1,sms,2015-03,Tesco,mobile\r\n768,25/02/15 13:22,1,sms,2015-03,Tesco,mobile\r\n769,25/02/15 13:26,194,call,2015-03,Tesco,mobile\r\n770,25/02/15 13:46,229,call,2015-03,Tesco,mobile\r\n771,25/02/15 15:45,32,call,2015-03,Vodafone,mobile\r\n772,26/02/15 06:58,34.429,data,2015-03,data,data\r\n773,26/02/15 16:38,570,call,2015-03,Vodafone,mobile\r\n774,26/02/15 22:34,29,call,2015-03,voicemail,voicemail\r\n775,27/02/15 06:58,34.429,data,2015-03,data,data\r\n776,27/02/15 13:50,27,call,2015-03,Three,mobile\r\n777,27/02/15 14:33,107,call,2015-03,Three,mobile\r\n778,27/02/15 14:36,128,call,2015-03,Vodafone,mobile\r\n779,28/02/15 06:58,34.429,data,2015-03,data,data\r\n780,28/02/15 14:09,335,call,2015-03,landline,landline\r\n781,28/02/15 14:57,49,call,2015-03,Meteor,mobile\r\n782,28/02/15 15:09,5,call,2015-03,Three,mobile\r\n783,28/02/15 15:49,305,call,2015-03,landline,landline\r\n784,28/02/15 16:00,207,call,2015-03,landline,landline\r\n785,28/02/15 17:13,11,call,2015-03,landline,landline\r\n786,28/02/15 17:14,3,call,2015-03,landline,landline\r\n787,28/02/15 17:17,33,call,2015-03,landline,landline\r\n788,28/02/15 21:25,357,call,2015-03,Three,mobile\r\n789,28/02/15 21:55,1,sms,2015-03,Vodafone,mobile\r\n790,28/02/15 22:39,1,sms,2015-03,Three,mobile\r\n791,01/03/15 06:58,34.429,data,2015-03,data,data\r\n792,01/03/15 12:19,9,call,2015-03,Meteor,mobile\r\n793,02/03/15 06:58,34.429,data,2015-03,data,data\r\n794,02/03/15 09:19,1,sms,2015-03,Vodafone,mobile\r\n795,02/03/15 09:19,1,sms,2015-03,Vodafone,mobile\r\n796,02/03/15 09:23,1,sms,2015-03,Vodafone,mobile\r\n797,02/03/15 09:30,1,sms,2015-03,Vodafone,mobile\r\n798,02/03/15 09:30,1,sms,2015-03,Vodafone,mobile\r\n799,02/03/15 13:07,463,call,2015-03,Three,mobile\r\n800,02/03/15 14:53,2,call,2015-03,voicemail,voicemail\r\n801,02/03/15 14:54,93,call,2015-03,voicemail,voicemail\r\n802,02/03/15 17:35,192,call,2015-03,Meteor,mobile\r\n803,02/03/15 20:48,34,call,2015-03,Tesco,mobile\r\n804,03/03/15 06:58,34.429,data,2015-03,data,data\r\n805,03/03/15 09:57,76,call,2015-03,landline,landline\r\n806,03/03/15 09:59,355,call,2015-03,Three,mobile\r\n807,03/03/15 10:12,745,call,2015-03,Vodafone,mobile\r\n808,03/03/15 10:27,57,call,2015-03,Vodafone,mobile\r\n809,03/03/15 14:34,1325,call,2015-03,Vodafone,mobile\r\n810,03/03/15 18:36,768,call,2015-03,Three,mobile\r\n811,04/03/15 06:58,34.429,data,2015-03,data,data\r\n812,04/03/15 07:02,1,sms,2015-03,Vodafone,mobile\r\n813,04/03/15 07:16,1,sms,2015-03,Vodafone,mobile\r\n814,04/03/15 10:30,1,sms,2015-03,Three,mobile\r\n815,04/03/15 10:30,1,sms,2015-03,Three,mobile\r\n816,04/03/15 12:29,10528,call,2015-03,landline,landline\r\n817,05/03/15 06:58,34.429,data,2015-03,data,data\r\n818,06/03/15 06:58,34.429,data,2015-03,data,data\r\n819,07/03/15 06:58,34.429,data,2015-03,data,data\r\n820,08/03/15 06:58,34.429,data,2015-03,data,data\r\n821,09/03/15 06:58,34.429,data,2015-03,data,data\r\n822,10/03/15 06:58,34.429,data,2015-03,data,data\r\n823,11/03/15 06:58,34.429,data,2015-03,data,data\r\n824,12/03/15 06:58,34.429,data,2015-03,data,data\r\n825,13/03/15 00:38,1,sms,2015-03,world,world\r\n826,13/03/15 00:39,1,sms,2015-03,Vodafone,mobile\r\n827,13/03/15 06:58,34.429,data,2015-03,data,data\r\n828,14/03/15 00:13,1,sms,2015-03,world,world\r\n829,14/03/15 00:16,1,sms,2015-03,world,world\r\n"
  },
  {
    "path": "utils_test.go",
    "content": "package datatable_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/datasweet/datatable\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\n// checkTable to check if a table contains cells\nfunc checkTable(t *testing.T, tb *datatable.DataTable, cells ...interface{}) {\n\tncols := tb.NumCols()\n\tnrows := tb.NumRows()\n\tassert.Len(t, cells, ncols*(nrows+1)) // + headers\n\n\tcols := tb.Columns()\n\trows := tb.Rows()\n\n\tfor i, v := range cells {\n\t\tr := i/ncols - 1\n\t\tc := i % ncols\n\n\t\tif r == -1 {\n\t\t\tassert.Equal(t, v, cols[c], \"HEADER COL #%d\", r, c)\n\t\t\tcontinue\n\t\t}\n\n\t\tassert.Equal(t, v, rows[r][cols[c]], \"ROW #%d, COL #%d\", r, c)\n\t}\n}\n\nfunc New(t *testing.T) *datatable.DataTable {\n\ttb := datatable.New(\"test\")\n\ttb.AddColumn(\"champ\", datatable.String, datatable.Values(\"Malzahar\", \"Xerath\", \"Teemo\"))\n\ttb.AddColumn(\"champion\", datatable.String, datatable.Expr(\"upper(`champ`)\"))\n\ttb.AddColumn(\"win\", datatable.Int, datatable.Values(10, 20, 666))\n\ttb.AddColumn(\"loose\", datatable.Int, datatable.Values(6, 5, 666))\n\ttb.AddColumn(\"winRate\", datatable.String, datatable.Expr(\"(`win` * 100 / (`win` + `loose`)) ~ \\\" %\\\"\"))\n\ttb.AddColumn(\"sum\", datatable.Float64, datatable.Expr(\"sum(`win`)\"))\n\ttb.AddColumn(\"ok\", datatable.Bool, datatable.Expr(\"true\"))\n\ttb.AddColumn(\"hidden\", datatable.Bool, datatable.Expr(\"false\"))\n\ttb.HideColumn(\"hidden\")\n\n\tcheckTable(t, tb,\n\t\t\"champ\", \"champion\", \"win\", \"loose\", \"winRate\", \"sum\", \"ok\",\n\t\t\"Malzahar\", \"MALZAHAR\", 10, 6, \"62.5 %\", 696.0, true,\n\t\t\"Xerath\", \"XERATH\", 20, 5, \"80 %\", 696.0, true,\n\t\t\"Teemo\", \"TEEMO\", 666, 666, \"50 %\", 696.0, true,\n\t)\n\n\treturn tb\n}\n"
  },
  {
    "path": "where.go",
    "content": "package datatable\n\n// Where filters the datatable based on a predicate\nfunc (t *DataTable) Where(predicate func(row Row) bool) *DataTable {\n\tif predicate == nil {\n\t\treturn t.EmptyCopy()\n\t}\n\n\tif err := t.evaluateExpressions(); err != nil {\n\t\tpanic(err)\n\t}\n\n\tsubset := make([]int, 0, t.nrows) // max\n\n\tfor i := 0; i < t.nrows; i++ {\n\t\tr := make(Row, len(t.cols))\n\t\tfor _, col := range t.cols {\n\t\t\tr[col.name] = col.serie.Get(i)\n\t\t}\n\t\tif predicate(r) {\n\t\t\tsubset = append(subset, i)\n\t\t}\n\t}\n\n\tcpy := t.EmptyCopy()\n\n\tif len(subset) == 0 {\n\t\treturn cpy\n\t}\n\n\tcpy.nrows = len(subset)\n\tfor i, col := range t.cols {\n\t\tcpy.cols[i].serie = col.serie.Pick(subset...)\n\t}\n\n\treturn cpy\n}\n"
  },
  {
    "path": "where_test.go",
    "content": "package datatable_test\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/datasweet/cast\"\n\t\"github.com/datasweet/datatable\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestWhere(t *testing.T) {\n\t// from join test\n\tcustomers, orders := sampleForJoin()\n\tdt, err := customers.LeftJoin(orders, datatable.On(\"[Customers].[id]\", \"[Orders].[user_id]\"))\n\tassert.NoError(t, err)\n\tassert.NotNil(t, dt)\n\tcheckTable(t, dt,\n\t\t\"id\", \"prenom\", \"nom\", \"email\", \"ville\", \"date_achat\", \"num_facture\", \"prix_total\",\n\t\t1, \"Aimée\", \"Marechal\", \"aime.marechal@example.com\", \"Paris\", time.Date(2013, time.January, 23, 0, 0, 0, 0, time.UTC), \"A00103\", 203.14,\n\t\t1, \"Aimée\", \"Marechal\", \"aime.marechal@example.com\", \"Paris\", time.Date(2013, time.February, 14, 0, 0, 0, 0, time.UTC), \"A00104\", 124.00,\n\t\t2, \"Esmée\", \"Lefort\", \"esmee.lefort@example.com\", \"Lyon\", time.Date(2013, time.February, 17, 0, 0, 0, 0, time.UTC), \"A00105\", 149.45,\n\t\t3, \"Marine\", \"Prevost\", \"m.prevost@example.com\", \"Lille\", time.Date(2013, time.February, 21, 0, 0, 0, 0, time.UTC), \"A00106\", 235.35,\n\t\t4, \"Luc\", \"Rolland\", \"lucrolland@example.com\", \"Marseille\", nil, nil, nil,\n\t)\n\n\tdt = dt.Where(func(row datatable.Row) bool {\n\t\tprenom, okp := cast.AsString(row[\"prenom\"])\n\t\tnum_facture, okf := cast.AsString(row[\"num_facture\"])\n\t\treturn okp && okf && (strings.ToLower(prenom) == \"aimée\" || num_facture == \"A00106\")\n\t})\n\n\tcheckTable(t, dt,\n\t\t\"id\", \"prenom\", \"nom\", \"email\", \"ville\", \"date_achat\", \"num_facture\", \"prix_total\",\n\t\t1, \"Aimée\", \"Marechal\", \"aime.marechal@example.com\", \"Paris\", time.Date(2013, time.January, 23, 0, 0, 0, 0, time.UTC), \"A00103\", 203.14,\n\t\t1, \"Aimée\", \"Marechal\", \"aime.marechal@example.com\", \"Paris\", time.Date(2013, time.February, 14, 0, 0, 0, 0, time.UTC), \"A00104\", 124.00,\n\t\t3, \"Marine\", \"Prevost\", \"m.prevost@example.com\", \"Lille\", time.Date(2013, time.February, 21, 0, 0, 0, 0, time.UTC), \"A00106\", 235.35,\n\t)\n}\n"
  }
]