Repository: jtablesaw/tablesaw
Pull request #302 (2018-07-10 15:46:05)

Add convenience methods to convert to double arrays for easier usage with Smile
- [X] Tick to sign-off your agreement to the [Developer Certificate of Origin (DCO) 1.1](https://developercertificate.org)

## Description

This makes using Tablesaw with Smile a bit smoother as described in https://github.com/jtablesaw/tablesaw/issues/280#issuecomment-403702698.

Converting to double arrays and matrices is super common. E.g.:
```
moneyball.numberColumn("RD").asDoubleArray()
moneyball.copy().retainColumns("W").asMatrix()
```

This PR provides a much simpler API for this common operation:
```
moneyball.asDoubleArray("RD")
moneyball.asMatrix("W")
```


lwhite1: I think asDoubleArray() belongs on the column since you're only converting the column. You're not doing anything with the table other than retrieving the column, which is a common operation any tablesaw user would know. I would rather not add it to the relation/table API. 

AsMatrix is definitely better in the sense that it is a multi-column operation, if not an actual table operation like sort. The benefit is that it's easier to find than the DoubleArrays class, but it's not necessarily _easy_ to find. There are over 150 methods on Relation/Table.  

You don't have to copy and retain, you can use any of these: 

- DoubleArrays.to2DArray(moneyball.columns());
- DoubleArrays.to2DArray(moneyball.columns("W", "L"));
- DoubleArrays.to2DArray(moneyball.numberColumns());
etc.

These are properly multi-column operations. 

I'm sure the uses for this functionality expands beyond the (vanishingly small intersection of Smile/Tablesaw users) so it's worth spending some time on, but I suspect there are Smile functions that take int[] or int[][], or other similar things. I've seen some visualization libraries (JavaFX for example), that like float[] instead of double[].  

All things considered, I'm not convinced that there isn't a better way to organize this functionality. Maybe by renaming the DoubleArrays class (and methods) and getting it out of utils (api or a new converter/conversions package might not be so bad). 

tech.tablesaw.api.TableConverter? 

What do you think?
lwhite1: An example of an additional, possibly useful, conversion is to ND4J (N-Dimensional Arrays for Java) format. https://nd4j.org/. 

They're billing it as like numpy for java. It seems likely that future ML libraries will support it.
lwhite1: > Today I do have to copy and retain, because the columns(String...) method doesn't exist. 

The PR moves it from table to relation, which is a clear improvement and should be done. 

> Regarding ND4J, you create a row/column vector from a double[] or a 2d NDArray from a double[][], so these would help there as well.

or an int[] or float[], but sure. 

> I have another idea. What if we make the API look like:
```
moneyball.as().matrix("W")

```
I like it. Should the matrix method be typed, rather than always producing double[][]?

benmccann: Ok, I've updated the PR to have `as().floatMatrix()`, `as().doubleMatrix()`, `as().intMatrix()`

Patch:
diff --git a/core/src/main/java/tech/tablesaw/api/Table.java b/core/src/main/java/tech/tablesaw/api/Table.java
index 71979c28..96d16988 100644
--- a/core/src/main/java/tech/tablesaw/api/Table.java
+++ b/core/src/main/java/tech/tablesaw/api/Table.java
@@ -256,17 +256,6 @@ public class Table extends Relation implements Iterable<Row> {
         return columnList.toArray(new Column[columnCount()]);
     }
 
-    /**
-     * Returns only the columns whose names are given in the input array
-     */
-    public List<Column> columns(String... columnNames) {
-        List<Column> columns = new ArrayList<>();
-        for (String columnName : columnNames) {
-            columns.add(column(columnName));
-        }
-        return columns;
-    }
-
     /**
      * Returns only the columns whose names are given in the input array
      */
diff --git a/core/src/main/java/tech/tablesaw/columns/Column.java b/core/src/main/java/tech/tablesaw/columns/Column.java
index e519dec9..671f397a 100644
--- a/core/src/main/java/tech/tablesaw/columns/Column.java
+++ b/core/src/main/java/tech/tablesaw/columns/Column.java
@@ -195,9 +195,7 @@ public interface Column {
         return "Column: " + name() + '\n';
     }
 
-    default double[] asDoubleArray() {
-        throw new UnsupportedOperationException("Method asDoubleArray() is not supported on non-numeric columns");
-    }
+    double[] asDoubleArray();
 
     /**
      * Returns the width of the column in characters, for printing
diff --git a/core/src/main/java/tech/tablesaw/columns/dates/DateFilters.java b/core/src/main/java/tech/tablesaw/columns/dates/DateFilters.java
index eef5e35f..cf238ef1 100644
--- a/core/src/main/java/tech/tablesaw/columns/dates/DateFilters.java
+++ b/core/src/main/java/tech/tablesaw/columns/dates/DateFilters.java
@@ -4,7 +4,6 @@ import it.unimi.dsi.fastutil.ints.IntArrayList;
 import it.unimi.dsi.fastutil.ints.IntIterator;
 import tech.tablesaw.api.DateColumn;
 import tech.tablesaw.columns.Column;
-import tech.tablesaw.columns.DateAndTimePredicates;
 import tech.tablesaw.filtering.predicates.IntBiPredicate;
 import tech.tablesaw.selection.BitmapBackedSelection;
 import tech.tablesaw.selection.Selection;
diff --git a/core/src/main/java/tech/tablesaw/conversion/TableConverter.java b/core/src/main/java/tech/tablesaw/conversion/TableConverter.java
new file mode 100644
index 00000000..8ae09e59
--- /dev/null
+++ b/core/src/main/java/tech/tablesaw/conversion/TableConverter.java
@@ -0,0 +1,93 @@
+package tech.tablesaw.conversion;
+
+import java.util.List;
+
+import com.google.common.base.Preconditions;
+
+import tech.tablesaw.columns.Column;
+import tech.tablesaw.table.Relation;
+
+public class TableConverter {
+
+  private final Relation table;
+  
+  public TableConverter(Relation table) {
+    this.table = table;
+  }
+
+  public double[][] doubleMatrix() {
+    return doubleMatrix(table.columns());
+  }
+
+  public double[][] doubleMatrix(int... columnIndicies) {
+    return doubleMatrix(table.columns(columnIndicies));
+  }
+
+  public double[][] doubleMatrix(String... columnNames) {
+    return doubleMatrix(table.columns(columnNames));
+  }
+
+  public float[][] floatMatrix() {
+    return floatMatrix(table.columns());
+  }
+
+  public float[][] floatMatrix(int... columnIndicies) {
+    return floatMatrix(table.columns(columnIndicies));
+  }
+
+  public float[][] floatMatrix(String... columnNames) {
+    return floatMatrix(table.columns(columnNames));
+  }
+  
+  public int[][] intMatrix() {
+    return intMatrix(table.columns());
+  }
+
+  public int[][] intMatrix(int... columnIndicies) {
+    return intMatrix(table.columns(columnIndicies));
+  }
+
+  public int[][] intMatrix(String... columnNames) {
+    return intMatrix(table.columns(columnNames));
+  }
+
+  private static double[][] doubleMatrix(List<Column> columns) {
+    Preconditions.checkArgument(columns.size() >= 1);
+    int obs = columns.get(0).size();
+    double[][] allVals = new double[obs][columns.size()];
+
+    for (int r = 0; r < obs; r++) {
+      for (int c = 0; c < columns.size(); c++) {
+        allVals[r][c] = columns.get(c).getDouble(r);
+      }
+    }
+    return allVals;
+  }
+
+  private static float[][] floatMatrix(List<Column> columns) {
+    Preconditions.checkArgument(columns.size() >= 1);
+    int obs = columns.get(0).size();
+    float[][] allVals = new float[obs][columns.size()];
+
+    for (int r = 0; r < obs; r++) {
+      for (int c = 0; c < columns.size(); c++) {
+        allVals[r][c] = (float) columns.get(c).getDouble(r);
+      }
+    }
+    return allVals;
+  }
+
+  private static int[][] intMatrix(List<Column> columns) {
+    Preconditions.checkArgument(columns.size() >= 1);
+    int obs = columns.get(0).size();
+    int[][] allVals = new int[obs][columns.size()];
+
+    for (int r = 0; r < obs; r++) {
+      for (int c = 0; c < columns.size(); c++) {
+        allVals[r][c] = (int) columns.get(c).getDouble(r);
+      }
+    }
+    return allVals;
+  }
+
+}
diff --git a/core/src/main/java/tech/tablesaw/io/csv/CsvReader.java b/core/src/main/java/tech/tablesaw/io/csv/CsvReader.java
index 744496df..d55fce46 100644
--- a/core/src/main/java/tech/tablesaw/io/csv/CsvReader.java
+++ b/core/src/main/java/tech/tablesaw/io/csv/CsvReader.java
@@ -256,6 +256,9 @@ public class CsvReader {
                 return;
             case LOCAL_TIME :
                 ((TimeColumn) newColumn).setFormatter(options.timeFormatter());
+                return;
+            default:
+                return;
         }
     }
 
@@ -446,7 +449,6 @@ public class CsvReader {
         boolean header = options.header();
         char delimiter = options.separator();
         boolean useSampling = options.sample();
-        Locale locale = options.locale();
 
         int linesToSkip = header ? 1 : 0;
 
diff --git a/core/src/main/java/tech/tablesaw/table/Relation.java b/core/src/main/java/tech/tablesaw/table/Relation.java
index cb1bd26c..7b727cb8 100644
--- a/core/src/main/java/tech/tablesaw/table/Relation.java
+++ b/core/src/main/java/tech/tablesaw/table/Relation.java
@@ -26,11 +26,12 @@ import tech.tablesaw.api.StringColumn;
 import tech.tablesaw.api.Table;
 import tech.tablesaw.api.TimeColumn;
 import tech.tablesaw.columns.Column;
+import tech.tablesaw.conversion.TableConverter;
 import tech.tablesaw.io.string.DataFramePrinter;
 import tech.tablesaw.sorting.comparators.DescendingIntComparator;
-import tech.tablesaw.util.DoubleArrays;
 
 import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.stream.Collectors;
 
@@ -51,7 +52,7 @@ public abstract class Relation {
         return rowCount() + " rows X " + columnCount() + " cols";
     }
 
-    public Relation removeColumns(int ... columnIndexes) {
+    public Relation removeColumns(int... columnIndexes) {
         IntArrays.quickSort(columnIndexes, DescendingIntComparator.instance());
         for (int i : columnIndexes) {
             removeColumns(column(i));
@@ -130,6 +131,28 @@ public abstract class Relation {
      */
     public abstract List<Column> columns();
 
+    /**
+     * Returns the columns whose names are given in the input array
+     */
+    public List<Column> columns(String... columnName) {
+        List<Column> cols = new ArrayList<>(columnName.length);
+        for (int i = 0; i < columnName.length; i++) {
+            cols.add(column(columnName[i]));
+        }
+        return cols;
+    }
+
+    /**
+     * Returns the columns whose indices are given in the input array
+     */
+    public List<Column> columns(int... columnIndices) {
+        List<Column> cols = new ArrayList<>(columnIndices.length);
+        for (int i : columnIndices) {
+            cols.add(column(i));
+        }
+        return cols;
+    }
+
     /**
      * Returns the index of the given column
      */
@@ -354,8 +377,8 @@ public abstract class Relation {
         return (DateTimeColumn) column(columnName);
     }
 
-    public double[][] asMatrix() {
-        return DoubleArrays.to2dArray(columns());
+    public TableConverter as() {
+        return new TableConverter(this);
     }
 
     public String getUnformatted(int r1, int c) {
diff --git a/core/src/main/java/tech/tablesaw/table/TableSlice.java b/core/src/main/java/tech/tablesaw/table/TableSlice.java
index 204bca19..b8a52ee7 100644
--- a/core/src/main/java/tech/tablesaw/table/TableSlice.java
+++ b/core/src/main/java/tech/tablesaw/table/TableSlice.java
@@ -137,7 +137,7 @@ public class TableSlice extends Relation implements IntIterable {
     public Table asTable() {
         Table table = Table.create(this.name());
         for (Column column : columns()) {
-            table.addColumns(column.subset(selection));
+            table.addColumns(column.where(selection));
         }
         return table;
     }
diff --git a/core/src/main/java/tech/tablesaw/util/DoubleArrays.java b/core/src/main/java/tech/tablesaw/util/DoubleArrays.java
index b4e3a1ab..d307aed6 100644
--- a/core/src/main/java/tech/tablesaw/util/DoubleArrays.java
+++ b/core/src/main/java/tech/tablesaw/util/DoubleArrays.java
@@ -14,14 +14,6 @@
 
 package tech.tablesaw.util;
 
-import com.google.common.base.Preconditions;
-import tech.tablesaw.api.NumberColumn;
-import tech.tablesaw.columns.Column;
-import tech.tablesaw.table.TableSlice;
-import tech.tablesaw.table.TableSliceGroup;
-
-import java.util.List;
-
 /**
  * Utility functions for creating 2D double arrays from columns and other arrays
  */
@@ -38,54 +30,4 @@ public class DoubleArrays {
         return result;
     }
 
-    public static double[][] to2dArray(Column... columns) {
-        Preconditions.checkArgument(columns.length >= 1);
-        int obs = columns[0].size();
-        double[][] allVals = new double[obs][columns.length];
-
-        for (int r = 0; r < obs; r++) {
-            for (int c = 0; c < columns.length; c++) {
-                allVals[r][c] = columns[c].getDouble(r);
-            }
-        }
-        return allVals;
-    }
-
-    public static double[][] to2dArray(List<Column> columnList) {
-        return to2dArray(columnList.toArray(new Column[columnList.size()]));
-    }
-
-    public static double[][] to2dArray(TableSliceGroup views, int columnNumber) {
-
-        int viewCount = views.size();
-
-        double[][] allVals = new double[viewCount][];
-        for (int viewNumber = 0; viewNumber < viewCount; viewNumber++) {
-            TableSlice view = views.get(viewNumber);
-            allVals[viewNumber] = new double[view.rowCount()];
-            NumberColumn numberColumn = view.numberColumn(columnNumber);
-            for (int r = 0; r < view.rowCount(); r++) {
-                allVals[viewNumber][r] = numberColumn.get(r);
-            }
-        }
-        return allVals;
-    }
-
-    public static double[][] to2dArray(double[] x, double[] y) {
-        double[][] allVals = new double[x.length][2];
-        for (int i = 0; i < x.length; i++) {
-            allVals[i][0] = x[i];
-            allVals[i][1] = y[i];
-        }
-        return allVals;
-    }
-
-    public static double[][] to2dArray(Column x, Column y) {
-        double[][] allVals = new double[x.size()][2];
-        for (int i = 0; i < x.size(); i++) {
-            allVals[i][0] = x.getDouble(i);
-            allVals[i][1] = y.getDouble(i);
-        }
-        return allVals;
-    }
 }
diff --git a/docs/gettingstarted.md b/docs/gettingstarted.md
index df984981..9a26d7cd 100644
--- a/docs/gettingstarted.md
+++ b/docs/gettingstarted.md
@@ -1,6 +1,8 @@
 # Getting started with Tablesaw
 
-Java is a great language, but it wasn't designed for data analysis. Tablesaw makes it easy to do data analysis in Java, but to accomplish that, we did things a little differently than you might expect. After you're up and running, we'll tell you what you need to know to use Tablesaw happily and productively.
+Java is a great language, but it wasn't designed for data analysis. Tablesaw makes it easy to do data analysis in Java. 
+
+Once you're up and running, this tutorial will tell you what you need to know to use Tablesaw.
 
 ## Setup
 
@@ -24,11 +26,11 @@ As you would expect, Tablesaw is all about tables, and tables are made of column
 
 ### Columns
 
-A column is a named, one-dimensional collection of data. All data in a column must be of the same type. 
+A column is a named, one-dimensional collection of data. It may or may not be part of a table. All data in a column must be of the same type. 
 
-Currently, Tablesaw supports columns for Strings, doubles, booleans, LocalDates, LocalTimes, and LocalDateTimes. The date and time types are based on the java.time classes introduced in Java 8.
+Tablesaw supports columns for Strings, doubles, booleans, LocalDates, LocalTimes, and LocalDateTimes. The date and time columns are comparable with the java.time classes introduced in Java 8.
 
-To create a column of numbers you can use one of its *create()* methods:
+To create a column you can use one of its static *create()* methods:
 
 ```java
 double[] numbers = {1, 2, 3, 4};
@@ -51,7 +53,7 @@ which returns 3.0.
 
 #### Array Operations
 
-Tablesaw makes columns easy to work with. Many operations that work on numbers in standard Java, for example, work on *columns* of numbers in Tablesaw. To multiply every value in a column by 4, we use the *multiply()* method, which returns a new column of the same size as the original.
+Tablesaw makes columns easy to work with. Operations that work on numbers in standard Java, for example, often work on *columns* of numbers in Tablesaw. To multiply every value in a column by 4, we use the *multiply()* method, which returns a new column like the original.
 
 ```java
 NumberColumn nc2 = nc.multiply(4);
@@ -65,21 +67,19 @@ Column: Test * 4.0
 12.0
 16.0
 ```
-There are so many columnar operations in Tablesaw that, as a general rule, if you find yourself writing a for loop to process a column or table, you may be missing something. 
+As you can see, the values are 4x the values in the original. The new column's name is made by combining the original "Test" and the operation (* 4). You can change it if you like using *setName(aString)*.
 
-Generally, Tablesaw will provide a name for columns created this way. If you don't like the name, you can change it by calling *setName("new name")* on the column.
+There are so many columnar operations in Tablesaw that, as a general rule, if you find yourself writing a for loop to process a column or table, you may be missing something. 
 
 #### Objects and Primitives
 
-Many Java programs and programmers work exclusively with Objects, rather than primitives. In Tablesaw, we use primitives whenever possible because they use *so much* less memory than their boxed alternatives.  A Byte object, for example, uses as much memory as a primitive double, even though bytes have a range of only 256 values. 
+Many Java programs and programmers work exclusively with Objects, rather than primitives. In Tablesaw, we use primitives whenever possible because they use  *much* less memory than their boxed alternatives.  A Byte object, for example, uses as much memory as a primitive double, even though bytes have a range of only 256 values. 
 
-There is a price for this frugality. When you work with primitives, you forgo some common java capabilities, like the use of standard Java 8 predicates. While Java thoughtfully provides some specialized predicate interfaces (e.g. *IntPredicate*), they don't provide any primitive *BiPredicate* implementations. Without an IntBiPredicate, we can't implement operations like a < b. So we were left to roll our own. You can find them in the package *tech.tablesaw.filtering.predicates*. 
-
-For that reason, the syntax for Tablesaw may sometimes seem a bit odd relative to typical Java, but it doesn't take long to learn. 
+There is a price for this frugality. When you work with primitives, you forgo some common java capabilities, like the use of standard Java 8 predicates. While Java thoughtfully provides some specialized predicate interfaces (e.g. *IntPredicate*), they don't provide any primitive *BiPredicate* implementations, nor do their primitive interfaces cover all primitive types. Without an IntBiPredicate, we can't implement operations like a < b. So we were left to roll our own. You can find them in the package *tech.tablesaw.filtering.predicates*. They work like the standard objects. 
 
 ### Selections
 
-Before going on to tables, we should talk about selections. *Selections* are used to filter both tables and columns. Often they work behind the scenes, but sometimes you use them directly.  For example, consider our NumberColumn containing the values {1, 2, 3, 4}. You can filter that column by sending it a message. For example: 
+Before going on to tables, we should talk about *selections*. *Selections* are used to filter both tables and columns. Often they work behind the scenes, but you can use them directly. For example, consider our NumberColumn containing the values {1, 2, 3, 4}. You can filter that column by sending it a message. For example: 
 
 ```java
 nc.isLessThan(3);
@@ -87,13 +87,16 @@ nc.isLessThan(3);
 
 This operation returns a *Selection*. Logically, it's a bitmap of the same size as the original column. The method above, effectively, returns 1, 1, 0, 0, since the first two values in the column are less than three, and the last two are not. 
 
-What you probably wanted was not a Selection object, but a new NumberColumn that contains only the values that passed the filter. To get this, you use the *where(aSelection)* method:
+What you probably wanted, though, was not a Selection object, but a new NumberColumn that contains only the values that passed the filter. To get this, you use the *where(aSelection)* method:
 
 ```java
 NumberColumn filtered = nc.where(nc.isLessThan(3));
+> Column: Test < 3.0
+1.0
+2.0
 ```
 
-This extra step is a necessary evil, as it lets us combine filters. For example: 
+Doing this in two steps provides many benefits. For one, it lets us combine filters. For example: 
 
 ```java
 NumberColumn filtered = nc.where(nc.isLessThan(3).and(nc.isOdd());
@@ -112,27 +115,28 @@ nc.where(Selection.withRange(1, 3));		// returns rows 1-3 inclusive
 nc.where(Selection.withoutRange(1, 3));		// returns row 0
 ```
 
-Obviously, if you have several columns of the same size (i.e. length) as you would in a table of data, you can make a selection with one column and use it to filter another:
+If you have several columns of the same length as you would in a table of data, you can make a selection with one column and use it to filter another:
 
 ```java
 NumberColumn result = firstColumn.where(someOtherColumn.startsWith("foo"));
 ```
 
-> **Key point:** Note the StringColumn method *startsWith(aString)*. There are many such column-type specific methods that can be used in building queries. For StringColumn, these methods are defined in the tech.tablesaw.columns.strings.StringFilters interface. It also includes endsWith(), isEmpty(), isAlpha(), containsString()[^2]. Each column has a similar set of filter operations. They can all be found in their filter interfaces located in sub-folders of tech.tablesaw.columns (e.g. tech.tablesaw.columns.dates.DateFilters). 
+> **Key point:** Note the methods *startsWith(aString)*, *isLessThan(aNumber)*, and *isOdd()*. These were predefined for your use. There are many such methods that can be used in building queries. For StringColumn, they're defined in the [tech.tablesaw.columns.strings.StringFilters interface](https://jtablesaw.github.io/tablesaw/apidocs/tech/tablesaw/columns/strings/StringFilters.html). It also includes *endsWith()*, *isEmpty()*, *isAlpha()*, *containsString()*[^2], etc. Each column has a similar set of filter operations. They can all be found in the filter interfaces located in sub-folders of tech.tablesaw.columns (e.g. tech.tablesaw.columns.dates.DateFilters). 
 
 #### Map functions
 
-There is nothing special about map operations; they're simply methods on columns that return new Columns as their result. You've already seen one: The column *multiply(aNumber)* method above is a map function. To multiple two columns, use:
+Map functions are methods defined on columns that return other new Columns as their result. You've already seen one: The column *multiply(aNumber)* method above is a map function with a scalar argument. To multiple the values in two columns, use *multiply(aNumberColumn)*:
 
 ```java
-nc1.multiply(nc2);
+NumberColumn newColumn = nc1.multiply(nc2);
 ```
 
-In this case, each value in column nc1 is multiplied by the corresponding value in nc2, rather than by a scalar constant as in the example above.
+Each value in column nc1 is multiplied by the corresponding value in nc2, rather than by a scalar value in the earlier example.
 
 There are many map functions built-in for the various column types. Here are some examples for StringColumn:
 
 ```java
+StringColumn s;
 s = aStringColumn.upperCase();
 s = s.replaceFirst("foo", "bar")
 s = s.substring(3, 10);
@@ -144,31 +148,32 @@ y = s.commonPrefix(anotherStringColumn);
 // this returns a measure of the similarity (levenshtein distance) between two columns
 nc = s.distance(anotherStringColumn);
 ```
+As you can see, for many String methods that return a new String. StringColumn provides an equivilent map method that returns a new StringColumn. It also includes other helpful methods found in Guava's String library and in the Apache Commons String library.
 
-> **Key point:** Every column type has a set of map operations like *multiply(aNumber)*. For StringColumn, these methods are defined in the *tech.tablesaw.columns.strings.StringMapFunctions* interface. It includes many methods in addition to those shown above. Each column has a similar set of map operations. They can all be found in their filter interfaces located in the sub-folders of tech.tablesaw.columns (e.g. *tech.tablesaw.columns.dates.DateMapFunctions*).  
+> **Key point:** Every column type has a set of map operations like *multiply(aNumber)*. For StringColumn, these methods are defined in the [*tech.tablesaw.columns.strings.StringMapFunctions*](https://jtablesaw.github.io/tablesaw/apidocs/tech/tablesaw/columns/strings/StringFilters.html) interface. It includes many methods beyond those shown above. Methods for all column types can all be found in their filter interfaces located in the sub-folders of tech.tablesaw.columns (e.g. [*tech.tablesaw.columns.dates.DateMapFunctions*](https://jtablesaw.github.io/tablesaw/apidocs/tech/tablesaw/columns/strings/StringFilters.html), which provides date methods like *plusDays(anInt)*, *year()*, and *month()*).  
 >
 
 #### Reduce (aggregate) functions: Summarizing a column 
 
-Sometimes you want to derive a value that summarizes in some sense the data in a column. Aggregate functions do just that. Each such function scan all the values in a column and returns a single scalar value as a result.  All columns support some aggregate functions: *min*() and *max*(), for example, plus *count()*, *countUnique()*, and *countMissing()*. Some column types support type-specific functions. BooleanColumn, for example, supports *all()*, which returns *true* if all of the values in the column are *true*. The functions *any()*, and *none()*,  return true if any or none the values in the column are true, respectively. The functions *countTrue()*, and *countFalse()* are also available.
+Sometimes you want to derive a singly value that summarizes in some sense the data in a column. Aggregate functions do just that. Each such function scan all the values in a column and returns a single scalar value as a result.  All columns support some aggregate functions: *min*() and *max*(), for example, plus *count()*, *countUnique()*, and *countMissing()*. Some also support type-specific functions. BooleanColumn, for example, supports *all()*, which returns *true* if all of the values in the column are *true*. The functions *any()*, and *none()*,  return true if any or none the values in the column are true, respectively. The functions *countTrue()*, and *countFalse()* are also available.
 
-For example, to calculate the standard deviation of the values in a column, you would call:
+NumberColumn has many more aggregate functions. For example, to calculate the standard deviation of the values in a column, you would call:
 
 ```java
-nc.standardDeviation();			// returns the standard deviation of all values
+double stdDev = nc.standardDeviation();	
 ```
 
-> **Key point:** NumberColumn supports many aggregation functions, including many of the most useful. Among those available are *sum*, *count*, *mean*, *median*, *percentile(n)*, *range*, *variance*, *sumOfLogs*, and many others. These are defined in the NumberColumn class. 
+> **Key point:** NumberColumn supports many aggregation functions, including many of the most useful. Among those available are *sum*, *count*, *mean*, *median*, *percentile(n)*, *range*, *variance*, *sumOfLogs*, and so on. These are defined in the [NumberColumn](https://jtablesaw.github.io/tablesaw/apidocs/tech/tablesaw/api/NumberColumn.html) class. 
 >
 
-When we discuss tables below, we'll show how to summarize a column to create sub-totals by the values in one or more grouping columns.
+When we discuss tables below, we'll show how to calculate sub-totals in one or more numeric columns by the values in one or more grouping columns.
 
 ### Tables
-As described above, a table is a named collection of columns. All columns in the table must be the same size, although missing values are allowed. A table can contain any combination of column types.
+A table is a named collection of columns. All columns in the table must have the same number of elements, although missing values are allowed. A table can contain any combination of column types.
 
 #### Creating Tables
 
-You can create a table in code. For example:
+You can create a table in code. Here we create a table and add two new columns to it:
 
 ```java
 String[] animals = {"bear", "cat", "giraffe"};
@@ -188,15 +193,15 @@ More frequently, you will load a table from a CSV or other delimited text file.
 Table x = Table.read().csv("testing.csv");
 ```
 
-Tablesaw does a pretty good job at guessing the column types for many data sets, but you can specify them    if it guesses wrong, or to improve performance. Numerous other options are available, such as specifying whether or not there's a header, using a non-standard delimiter, and so on. 
+Tablesaw does a pretty good job at guessing the column types for many data sets, but you can specify them    if it guesses wrong, or to improve performance. Numerous other options are available, such as specifying whether or not there's a header, using a non-standard delimiter, supplying a custom missing value indicator, and so on. 
 
 ***Note:*** Getting data loaded is sometimes the hardest part of data analysis. Advanced options for loading data are described in the section on [Importing Data](https://jtablesaw.github.io/tablesaw/userguide/importing_data).
 
-The IO section also shows how you can read data from a database or a stream. The latter is useful for reading data from a Web site or an S3 bucket.  
+The IO section also shows how you can read data from a database, a stream, or an HTML table. The stream interfaces lets you read data from a Web site or an S3 bucket.  
 
 #### Exploring Tables
 
-Because Tablesaw excels at manipulating tables, we use them whenever we can.  When you ask tablesaw for the structure of a table, the answer comes back in the form of another table where one column contains the column names, etc.  The structure() method is one of several that are great for getting to know your table. Here are some examples.
+Because Tablesaw excels at manipulating tables, we use them whenever we can.  When you ask tablesaw for the structure of a table, the answer comes back in the form of another table where one column contains the column names, etc.  The structure() method is one of several that help you to know a new data set. Here are some examples.
 
 ```java
 Table structure = bushTable.structure();
@@ -223,20 +228,20 @@ Table tail = myTable.last(3);
 > etc.
 ```
 
-Table's toString() method returns a String representation like those shown above. It returns a limited number of rows by default, but you can also use *table.printAll()*, or *table.print(n)* to get the output you want.
+Table's *toString()* method returns a String representation like those shown above. It returns a limited number of rows by default, but you can also use *table.printAll()*, or *table.print(n)* to get the output you want.
 
 Of course, this is just the beginning of exploratory data analysis. You can also use numeric and visual tools to explore your data. These facilities are described in the documentation on statistics and plotting, respectively.
 
 #### Working with a table's columns
 
-Often you'll want to work with specific columns in a table. Here are some useful methods:
+Often you'll work with specific columns in a table. Here are some useful methods:
 
 ```java
 table.columnNames();  			// returns all column names
 List<Column> = table.columns(); // returns all the columns in the table
 
 // removing columns
-table.removeColumns("Foo");			// keep everything but foo
+table.removeColumns("Foo");			// keep everything but "foo"
 table.retainColumns("Foo", "Bar");  // only keep foo and bar
 table.removeColumnsWithMissingValues();
 
@@ -244,6 +249,16 @@ table.removeColumnsWithMissingValues();
 table.addColumns(column1, column2, column3);
 ```
 
+In tablesaw, column names are case-insensitive. You get the same column if you ask for any of these:
+
+```java
+t.column("FOO");
+t.column("foo");
+t.column("foO");
+```
+
+remembering column names is enough of a burden without having to remember exactly which characters are capitalized. 
+
 ##### Getting specific column types from a table
 
 Columns can be retrieved from tables by name or position. The simplest method *column()* returns a object of type Column. This may be good enough, but often you want to get a column of a specific type. For example, you would need to cast the value returned to a NumberColumn to use its values in a scatter plot. 
diff --git a/jsplot/pom.xml b/jsplot/pom.xml
index d6ed19ea..aa9f9540 100644
--- a/jsplot/pom.xml
+++ b/jsplot/pom.xml
@@ -59,7 +59,7 @@
         <dependency>
             <groupId>tech.tablesaw</groupId>
             <artifactId>tablesaw-core</artifactId>
-            <version>0.21.0</version>
+            <version>0.22.0-SNAPSHOT</version>
         </dependency>
         <dependency>
             <groupId>junit</groupId>
diff --git a/jsplot/src/examples/java/tech/tablesaw/plotly/HistogramOverlayExample.java b/jsplot/src/examples/java/tech/tablesaw/plotly/HistogramOverlayExample.java
new file mode 100644
index 00000000..03d2c548
--- /dev/null
+++ b/jsplot/src/examples/java/tech/tablesaw/plotly/HistogramOverlayExample.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package tech.tablesaw.plotly;
+
+import tech.tablesaw.api.Table;
+import tech.tablesaw.plotly.components.Figure;
+import tech.tablesaw.plotly.components.Layout;
+import tech.tablesaw.plotly.traces.HistogramTrace;
+import tech.tablesaw.table.TableSliceGroup;
+
+/**
+ *
+ */
+public class HistogramOverlayExample {
+
+    public static void main(String[] args) throws Exception {
+        Table baseball = Table.read().csv("../data/baseball.csv");
+
+        Layout layout = Layout.builder()
+                .title("Distribution of team batting averages")
+                .barMode(Layout.BarMode.OVERLAY)
+                .build();
+        TableSliceGroup groups = baseball.splitOn("league");
+        Table t1 = groups.get(0).asTable();
+        HistogramTrace trace1 = HistogramTrace
+                .builder(t1.nCol("BA"))
+                .opacity(.75)
+                .nBinsX(24)
+                .build();
+        Table t2 = groups.get(1).asTable();
+
+        HistogramTrace trace2 = HistogramTrace
+                .builder(t2.nCol("BA"))
+                .opacity(.75)
+                .nBinsX(24)
+                .build();
+
+        Plot.show(new Figure(layout, trace1, trace2));
+    }
+}
\ No newline at end of file
diff --git a/jsplot/src/main/java/tech/tablesaw/plotly/traces/HistogramTrace.java b/jsplot/src/main/java/tech/tablesaw/plotly/traces/HistogramTrace.java
index 6104ad17..f6dc789e 100644
--- a/jsplot/src/main/java/tech/tablesaw/plotly/traces/HistogramTrace.java
+++ b/jsplot/src/main/java/tech/tablesaw/plotly/traces/HistogramTrace.java
@@ -14,6 +14,10 @@ public class HistogramTrace extends AbstractTrace {
 
     private double[] x;
     private double opacity;
+    private int nBinsX;
+    private int nBinsY;
+    private boolean autoBinX;
+    private boolean autoBinY;
 
     public static HistogramBuilder builder(double[] values) {
         return new HistogramBuilder(values);
@@ -26,6 +30,10 @@ public class HistogramTrace extends AbstractTrace {
     private HistogramTrace(HistogramBuilder builder) {
         super(builder);
         this.x = builder.x;
+        this.nBinsX = builder.nBinsX;
+        this.nBinsY = builder.nBinsY;
+        this.autoBinX = builder.autoBinX;
+        this.autoBinX = builder.autoBinY;
         this.opacity = builder.opacity;
     }
 
@@ -49,13 +57,20 @@ public class HistogramTrace extends AbstractTrace {
         context.put("variableName", "trace" + i);
         context.put("x", Utils.dataAsString(x));
         context.put("opacity", opacity);
+        context.put("nBinsX", nBinsX);
+        context.put("nBinsY", nBinsY);
+        context.put("autoBinX", autoBinX);
+        context.put("autoBinY", autoBinY);
         return context;
     }
 
     public static class HistogramBuilder extends TraceBuilder {
 
         String type = "histogram";
-        int bins;
+        int nBinsX;
+        int nBinsY;
+        boolean autoBinX;
+        boolean autoBinY;
         String barMode;
         String histFunction;
         String histNorm;
@@ -65,8 +80,17 @@ public class HistogramTrace extends AbstractTrace {
             this.x = values;
         }
 
-        public HistogramBuilder setBins(int bins) {
-            this.bins = bins;
+        /**
+         * Specifies the maximum number of desired bins. This value will be used in an algorithm that will decide
+         * the optimal bin size such that the histogram best visualizes the distribution of the data.
+         */
+        public HistogramBuilder nBinsX(int bins) {
+            this.nBinsX = bins;
+            return this;
+        }
+
+        public HistogramBuilder nBinsY(int bins) {
+            this.nBinsY = bins;
             return this;
         }
 
@@ -75,6 +99,23 @@ public class HistogramTrace extends AbstractTrace {
             return this;
         }
 
+        /**
+         * Determines whether or not the x axis bin attributes are picked by an algorithm.
+         * Note that this should be set to False if you want to manually set the number of bins using the attributes
+         * in xbins.
+         *
+         * Note also that this should be true (default) to use nbinsx to suggest a bin count
+         */
+        public HistogramBuilder autoBinX(boolean autoBinX) {
+            this.autoBinX = autoBinX;
+            return this;
+        }
+
+        public HistogramBuilder autoBinY(boolean autoBinY) {
+            this.autoBinY = autoBinY;
+            return this;
+        }
+
         public HistogramBuilder histFunction(String histFunction) {
             this.histFunction = histFunction;
             return this;
diff --git a/jsplot/src/main/resources/trace_template.html b/jsplot/src/main/resources/trace_template.html
index a293bf83..c26be796 100644
--- a/jsplot/src/main/resources/trace_template.html
+++ b/jsplot/src/main/resources/trace_template.html
@@ -30,6 +30,20 @@ orientation: '{{orientation}}',
 opacity: '{{opacity}}',
 {% endif %}
 
+{% if nBinsX is not null %}
+nbinsx: {{nBinsX}},
+{% endif %}
+{% if autoBinX is not null %}
+autobinx: {{autoBinX}},
+{% endif %}
+
+{% if nBinsY is not null %}
+nbinsy: {{nBinsY}},
+{% endif %}
+{% if autoBinY is not null %}
+autobiny: {{autoBinY}},
+{% endif %}
+
 type: '{{type}}',
 name: '{{name}}',
 };
\ No newline at end of file


Test patch:
diff --git a/core/src/test/java/tech/tablesaw/api/TableTest.java b/core/src/test/java/tech/tablesaw/api/TableTest.java
index 25f22e24..7830fce0 100644
--- a/core/src/test/java/tech/tablesaw/api/TableTest.java
+++ b/core/src/test/java/tech/tablesaw/api/TableTest.java
@@ -432,7 +432,7 @@ public class TableTest {
         NumberColumn third =  DoubleColumn.create("c3", new double[]{10.0, 20.0, 30.0, 40.0, 50.0});
 
         Table t = Table.create("table", first, second, third);
-        double[][] matrix = t.asMatrix();
+        double[][] matrix = t.as().doubleMatrix();
         assertEquals(5, matrix.length);
         assertArrayEquals(new double[]{1.0, 6.0, 10.0}, matrix[0], 0.0000001);
         assertArrayEquals(new double[]{2.0, 7.0, 20.0}, matrix[1], 0.0000001);
diff --git a/core/src/test/java/tech/tablesaw/conversion/TableConverterTest.java b/core/src/test/java/tech/tablesaw/conversion/TableConverterTest.java
new file mode 100644
index 00000000..0a2e22c7
--- /dev/null
+++ b/core/src/test/java/tech/tablesaw/conversion/TableConverterTest.java
@@ -0,0 +1,117 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package tech.tablesaw.conversion;
+
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+
+import tech.tablesaw.api.DoubleColumn;
+import tech.tablesaw.api.Table;
+
+public class TableConverterTest {
+
+    @Test
+    public void asDoubleMatrix() {
+        double[] array1 = {0, 1, 2};
+        double[] array2 = {0, 1, 2};
+
+        DoubleColumn c1 = DoubleColumn.create("1", array1);
+        DoubleColumn c2 = DoubleColumn.create("2", array2);
+        Table table = Table.create("test", c1, c2);
+
+        double[][] expected = {{0.0, 0.0}, {1.0, 1.0}, {2.0, 2.0}};
+        double[][] results = table.as().doubleMatrix();
+        assertTrue(Arrays.deepEquals(expected, results));
+    }
+
+    @Test
+    public void asDoubleMatrixColArgs() {
+        double[] array1 = {0, 1, 1};
+        double[] array2 = {0, 1, 2};
+        double[] array3 = {0, 1, 3};
+
+        DoubleColumn c1 = DoubleColumn.create("1", array1);
+        DoubleColumn c2 = DoubleColumn.create("2", array2);
+        DoubleColumn c3 = DoubleColumn.create("3", array3);
+        Table table = Table.create("test", c1, c2, c3);
+
+        double[][] expected = {{0.0, 0.0}, {1.0, 1.0}, {1.0, 3.0}};
+        double[][] results = table.as().doubleMatrix("1", "3");
+        assertTrue(Arrays.deepEquals(expected, results));
+    }    
+
+    @Test
+    public void asIntMatrix() {
+        double[] array1 = {0, 1, 2};
+        double[] array2 = {0, 1, 2};
+
+        DoubleColumn c1 = DoubleColumn.create("1", array1);
+        DoubleColumn c2 = DoubleColumn.create("2", array2);
+        Table table = Table.create("test", c1, c2);
+
+        int[][] expected = {{0, 0}, {1, 1}, {2, 2}};
+        int[][] results = table.as().intMatrix();
+        assertTrue(Arrays.deepEquals(expected, results));
+    }
+
+    @Test
+    public void asIntMatrixColArgs() {
+        double[] array1 = {0, 1, 1};
+        double[] array2 = {0, 1, 2};
+        double[] array3 = {0, 1, 3};
+
+        DoubleColumn c1 = DoubleColumn.create("1", array1);
+        DoubleColumn c2 = DoubleColumn.create("2", array2);
+        DoubleColumn c3 = DoubleColumn.create("3", array3);
+        Table table = Table.create("test", c1, c2, c3);
+
+        int[][] expected = {{0, 0}, {1, 1}, {1, 3}};
+        int[][] results = table.as().intMatrix("1", "3");
+        assertTrue(Arrays.deepEquals(expected, results));
+    }
+
+    @Test
+    public void asFloatMatrix() {
+        double[] array1 = {0, 1, 2};
+        double[] array2 = {0, 1, 2};
+
+        DoubleColumn c1 = DoubleColumn.create("1", array1);
+        DoubleColumn c2 = DoubleColumn.create("2", array2);
+        Table table = Table.create("test", c1, c2);
+
+        float[][] expected = {{0.0f, 0.0f}, {1.0f, 1.0f}, {2.0f, 2.0f}};
+        float[][] results = table.as().floatMatrix();
+        assertTrue(Arrays.deepEquals(expected, results));
+    }
+
+    @Test
+    public void asFloatMatrixColArgs() {
+        double[] array1 = {0, 1, 1};
+        double[] array2 = {0, 1, 2};
+        double[] array3 = {0, 1, 3};
+
+        DoubleColumn c1 = DoubleColumn.create("1", array1);
+        DoubleColumn c2 = DoubleColumn.create("2", array2);
+        DoubleColumn c3 = DoubleColumn.create("3", array3);
+        Table table = Table.create("test", c1, c2, c3);
+
+        float[][] expected = {{0.0f, 0.0f}, {1.0f, 1.0f}, {1.0f, 3.0f}};
+        float[][] results = table.as().floatMatrix("1", "3");
+        assertTrue(Arrays.deepEquals(expected, results));
+    }
+}
diff --git a/core/src/test/java/tech/tablesaw/filters/SearchPerformanceTest.java b/core/src/test/java/tech/tablesaw/filters/SearchPerformanceTest.java
index 39427871..d4111b74 100644
--- a/core/src/test/java/tech/tablesaw/filters/SearchPerformanceTest.java
+++ b/core/src/test/java/tech/tablesaw/filters/SearchPerformanceTest.java
@@ -111,19 +111,6 @@ public class SearchPerformanceTest {
         return rowNumber;
     }
 
-    private static int getRowNumberSimple(Table t, LocalDateTime testDate, double testLow, double testHigh) {
-        int rowNumber = -1;
-        long testPackedDateTime = PackedLocalDateTime.pack(testDate);  // packing saves time
-        for (Row row : t) {
-            if (row.getPackedDateTime("date").isOnOrAfter(testPackedDateTime)
-                    && (row.getDouble("lowValue") > testLow
-                    || row.getDouble("highValue") > testHigh)) {
-                return row.getRowNumber();
-            }
-        }
-        return rowNumber;
-    }
-
     private static Table defineSchema() {
         Table t;
         t = Table.create("Observations");
diff --git a/core/src/test/java/tech/tablesaw/util/DoubleArraysTest.java b/core/src/test/java/tech/tablesaw/util/DoubleArraysTest.java
index 76c5dbc1..39506d2c 100644
--- a/core/src/test/java/tech/tablesaw/util/DoubleArraysTest.java
+++ b/core/src/test/java/tech/tablesaw/util/DoubleArraysTest.java
@@ -14,71 +14,18 @@
 
 package tech.tablesaw.util;
 
-import com.google.common.collect.Lists;
-import org.junit.Test;
-import tech.tablesaw.api.DoubleColumn;
-import tech.tablesaw.api.Table;
-import tech.tablesaw.columns.Column;
-import tech.tablesaw.table.TableSliceGroup;
+import static org.junit.Assert.assertTrue;
 
-import java.util.ArrayList;
 import java.util.Arrays;
 
-import static org.junit.Assert.assertTrue;
+import org.junit.Test;
 
-/**
- *
- */
 public class DoubleArraysTest {
 
-    @Test
-    public void testTo2dArray() throws Exception {
-        Table table = Table.read().csv("../data/tornadoes_1950-2014.csv");
-        TableSliceGroup tableSliceGroup = table.splitOn(table.numberColumn("Scale"));
-        int columnNuumber = table.columnIndex("Injuries");
-        DoubleArrays.to2dArray(tableSliceGroup, columnNuumber);
-    }
-
     @Test
     public void testToN() {
         double[] array = {0, 1, 2};
         assertTrue(Arrays.equals(array, DoubleArrays.toN(3)));
     }
 
-    @Test
-    public void testTo2DArray2() {
-        double[] array1 = {0, 1, 2};
-        double[] array2 = {0, 1, 2};
-
-        double[][] expected = {{0.0, 0.0}, {1.0, 1.0}, {2.0, 2.0}};
-        double[][] results = DoubleArrays.to2dArray(array1, array2);
-        assertTrue(Arrays.deepEquals(expected, results));
-    }
-
-    @Test
-    public void testTo2DArray3() {
-        double[] array1 = {0, 1, 2};
-        double[] array2 = {0, 1, 2};
-
-        DoubleColumn c1 = DoubleColumn.create("1", array1);
-        DoubleColumn c2 = DoubleColumn.create("2", array2);
-
-        double[][] expected = {{0.0, 0.0}, {1.0, 1.0}, {2.0, 2.0}};
-        double[][] results = DoubleArrays.to2dArray(c1, c2);
-        assertTrue(Arrays.deepEquals(expected, results));
-    }
-
-    @Test
-    public void testTo2DArray4() {
-        double[] array1 = {0, 1, 2};
-        double[] array2 = {0, 1, 2};
-
-        DoubleColumn c1 = DoubleColumn.create("1", array1);
-        DoubleColumn c2 = DoubleColumn.create("2", array2);
-        ArrayList<Column> columnArrayList = Lists.newArrayList(c1, c2);
-
-        double[][] expected = {{0.0, 0.0}, {1.0, 1.0}, {2.0, 2.0}};
-        double[][] results = DoubleArrays.to2dArray(columnArrayList);
-        assertTrue(Arrays.deepEquals(expected, results));
-    }
-}
\ No newline at end of file
+}