Repository: jtablesaw/tablesaw
Pull request #528 (2019-05-19 09:51:23)

Fixed Smile Nominal Attribute creation and added stratified sampling
…column

Thanks for contributing.

- [x] Tick to sign-off your agreement to the [Developer Certificate of Origin (DCO) 1.1](https://developercertificate.org)

## Description
There was a mistake when creating nominal attribute for smile, it uses the length of the original column and not the unique column. The issue is noticed when you have a reasonable dataset of say 10,000 records and you  are trying to use smile.feature.OneHotEncoder, it creates close to 16,000 columns after encoding from  an original 12 columns and sometimes leading to OutOfMemoryException. 

I added stratified sampling as well

## Testing

For the nominal attribute creation, It already had a unit test  it
For the stratified sampling, I added a test **testStratifiesSampleSplit** to check the  that the proportion of foxes in the split table is equal to the proportion specified for the whole table. 

benmccann: Thanks for this @kiamesdavies. It looks good to me. Can you add a regression test for the change to `SmileConverter`? There are some existing tests in `SmileConverterTest`
kiamesdavies: @benmccann when will you merge this to master, planning to add a Weka Converter soon, not satisfied with Smile ML . 
benmccann: I was going to give @lwhite1 a chance to review
kiamesdavies: Roger



Patch:
diff --git a/aggregate/pom.xml b/aggregate/pom.xml
index a66a0363..1cd5cd37 100644
--- a/aggregate/pom.xml
+++ b/aggregate/pom.xml
@@ -5,7 +5,7 @@
   <parent>
     <groupId>tech.tablesaw</groupId>
     <artifactId>tablesaw-parent</artifactId>
-    <version>0.34.1-SNAPSHOT</version>
+    <version>0.34.2-SNAPSHOT</version>
   </parent>
 
   <!--
@@ -50,32 +50,32 @@
     <dependency>
       <groupId>tech.tablesaw</groupId>
       <artifactId>tablesaw-core</artifactId>
-      <version>0.34.1-SNAPSHOT</version>
+      <version>0.34.2-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>tech.tablesaw</groupId>
       <artifactId>tablesaw-beakerx</artifactId>
-      <version>0.34.1-SNAPSHOT</version>
+      <version>0.34.2-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>tech.tablesaw</groupId>
       <artifactId>tablesaw-excel</artifactId>
-      <version>0.34.1-SNAPSHOT</version>
+      <version>0.34.2-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>tech.tablesaw</groupId>
       <artifactId>tablesaw-html</artifactId>
-      <version>0.34.1-SNAPSHOT</version>
+      <version>0.34.2-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>tech.tablesaw</groupId>
       <artifactId>tablesaw-json</artifactId>
-      <version>0.34.1-SNAPSHOT</version>
+      <version>0.34.2-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>tech.tablesaw</groupId>
       <artifactId>tablesaw-jsplot</artifactId>
-      <version>0.34.1-SNAPSHOT</version>
+      <version>0.34.2-SNAPSHOT</version>
     </dependency>
   </dependencies>
 </project>
diff --git a/beakerx/pom.xml b/beakerx/pom.xml
index ce6da627..661b57c5 100644
--- a/beakerx/pom.xml
+++ b/beakerx/pom.xml
@@ -5,7 +5,7 @@
   <parent>
     <groupId>tech.tablesaw</groupId>
     <artifactId>tablesaw-parent</artifactId>
-    <version>0.34.1-SNAPSHOT</version>
+    <version>0.34.2-SNAPSHOT</version>
   </parent>
 
   <artifactId>tablesaw-beakerx</artifactId>
@@ -63,7 +63,7 @@
     <dependency>
       <groupId>tech.tablesaw</groupId>
       <artifactId>tablesaw-core</artifactId>
-      <version>0.34.1-SNAPSHOT</version>
+      <version>0.34.2-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>com.github.twosigma</groupId>
diff --git a/core/pom.xml b/core/pom.xml
index ecd59071..427ad5de 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <groupId>tech.tablesaw</groupId>
         <artifactId>tablesaw-parent</artifactId>
-        <version>0.34.1-SNAPSHOT</version>
+        <version>0.34.2-SNAPSHOT</version>
     </parent>
 
     <artifactId>tablesaw-core</artifactId>
diff --git a/core/src/main/java/tech/tablesaw/api/DoubleColumn.java b/core/src/main/java/tech/tablesaw/api/DoubleColumn.java
index 95481cf3..67f0b062 100644
--- a/core/src/main/java/tech/tablesaw/api/DoubleColumn.java
+++ b/core/src/main/java/tech/tablesaw/api/DoubleColumn.java
@@ -30,7 +30,6 @@ import java.util.function.DoublePredicate;
 import java.util.function.DoubleSupplier;
 import java.util.function.Function;
 import java.util.function.Predicate;
-import java.util.function.ToDoubleFunction;
 
 public class DoubleColumn extends NumberColumn<Double> implements NumberFillers<DoubleColumn> {
 
@@ -340,24 +339,6 @@ public class DoubleColumn extends NumberColumn<Double> implements NumberFillers<
         return set(row, doubleColumn.getDouble(sourceRow));
     }
 
-    /**
-     * Maps the function across all rows, appending the results to a new NumberColumn
-     *
-     * @param fun function to map
-     * @return the NumberColumn with the results
-     */
-    public DoubleColumn map(ToDoubleFunction<Double> fun) {
-        DoubleColumn result = DoubleColumn.create(name());
-        for (double t : this) {
-            try {
-                result.append(fun.applyAsDouble(t));
-            } catch (Exception e) {
-                result.appendMissing();
-            }
-        }
-        return result;
-    }
-
     /**
      * Returns a new NumberColumn with only those rows satisfying the predicate
      *
diff --git a/core/src/main/java/tech/tablesaw/api/NumericColumn.java b/core/src/main/java/tech/tablesaw/api/NumericColumn.java
index 04546882..7977cf57 100644
--- a/core/src/main/java/tech/tablesaw/api/NumericColumn.java
+++ b/core/src/main/java/tech/tablesaw/api/NumericColumn.java
@@ -42,8 +42,6 @@ import tech.tablesaw.columns.numbers.NumberFilters;
 import tech.tablesaw.columns.numbers.NumberMapFunctions;
 import tech.tablesaw.columns.numbers.NumberRollingColumn;
 import tech.tablesaw.columns.numbers.Stats;
-import tech.tablesaw.filtering.predicates.DoubleBiPredicate;
-import tech.tablesaw.filtering.predicates.DoubleRangePredicate;
 import tech.tablesaw.selection.BitmapBackedSelection;
 import tech.tablesaw.selection.Selection;
 
@@ -75,30 +73,6 @@ public interface NumericColumn<T> extends Column<T>, NumberMapFunctions, NumberF
         return bitmap;
     }
 
-    @Override
-    default Selection eval(final DoubleBiPredicate predicate, final DoubleColumn otherColumn) {
-        final Selection selection = new BitmapBackedSelection();
-        for (int idx = 0; idx < size(); idx++) {
-            if (predicate.test(getDouble(idx), otherColumn.getDouble(idx))) {
-                selection.add(idx);
-            }
-        }
-        return selection;
-    }
-
-    @Override
-    default Selection eval(final DoubleBiPredicate predicate, final Number number) {
-        final double value = number.doubleValue();
-        final Selection bitmap = new BitmapBackedSelection();
-        for (int idx = 0; idx < size(); idx++) {
-            final double next = getDouble(idx);
-            if (predicate.test(next, value)) {
-                bitmap.add(idx);
-            }
-        }
-        return bitmap;
-    }
-
     @Override
     default Selection eval(final BiPredicate<Number, Number> predicate, final Number number) {
         final double value = number.doubleValue();
@@ -112,20 +86,6 @@ public interface NumericColumn<T> extends Column<T>, NumberMapFunctions, NumberF
         return bitmap;
     }
 
-    @Override
-    default Selection eval(final DoubleRangePredicate predicate, final Number rangeStart, final Number rangeEnd) {
-        final double start = rangeStart.doubleValue();
-        final double end = rangeEnd.doubleValue();
-        final Selection bitmap = new BitmapBackedSelection();
-        for (int idx = 0; idx < size(); idx++) {
-            final double next = getDouble(idx);
-            if (predicate.test(next, start, end)) {
-                bitmap.add(idx);
-            }
-        }
-        return bitmap;
-    }
-
     @Override
     default Selection isIn(final Number... numbers) {
         return isIn(Arrays.stream(numbers).mapToDouble(Number::doubleValue).toArray());
@@ -284,7 +244,7 @@ public interface NumericColumn<T> extends Column<T>, NumberMapFunctions, NumberF
      * @param into Column to which results are appended
      * @return the provided Column, to which results are appended
      */
-    default <R> Column<R> mapInto(DoubleFunction<? extends R> fun, Column<R> into) {
+    default <R extends Column<RT>, RT> R mapInto(DoubleFunction<? extends RT> fun, R into) {
         for (int i = 0; i < size(); i++) {
             try {
                 into.append(fun.apply(getDouble(i)));
diff --git a/core/src/main/java/tech/tablesaw/api/Table.java b/core/src/main/java/tech/tablesaw/api/Table.java
index afd3d94c..67ffc791 100644
--- a/core/src/main/java/tech/tablesaw/api/Table.java
+++ b/core/src/main/java/tech/tablesaw/api/Table.java
@@ -25,8 +25,10 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
+import java.util.stream.Stream;
 
 import com.google.common.base.Preconditions;
+import com.google.common.collect.Streams;
 import com.google.common.primitives.Ints;
 
 import io.github.classgraph.ClassGraph;
@@ -454,6 +456,28 @@ public class Table extends Relation implements Iterable<Row> {
         tables[1] = where(table2Selection);
         return tables;
     }
+    
+    /**
+     * Splits the table into two stratified samples, this uses the specified column to divide the table into groups, randomly assigning records to each according to the proportion given in trainingProportion. 
+     * @param column the column to be used for the stratified sampling
+     * @param table1Proportion The proportion to go in the first table
+     * @return An array two tables, with the first table having the proportion specified in the method parameter,
+     * and the second table having the balance of the rows
+     */
+    public Table[] stratifiedSampleSplit(CategoricalColumn column, double table1Proportion) {
+        Preconditions.checkArgument(containsColumn(column),
+                "The categorical column must be part of the table, you can create a string column and add it to this table before sampling.");
+        final Table first = emptyCopy();
+        final Table second = emptyCopy();
+        
+        splitOn(column).asTableList().forEach(tab-> {
+            Table[] splits = tab.sampleSplit(table1Proportion); 
+            first.append(splits[0]);
+            second.append(splits[1]);
+        });
+        
+        return new Table[]{ first, second };
+    }
 
     /**
      * Returns a table consisting of randomly selected records from this table. The sample size is based on the
@@ -669,11 +693,33 @@ public class Table extends Relation implements Iterable<Row> {
         return where(selection);
     }
 
+    /**
+     * Retains the first rowCount rows if rowCount positive.
+     * Retains the last rowCount rows if rowCount negative.
+     */
+    public Table inRange(int rowCount) {
+        Preconditions.checkArgument(rowCount <= rowCount());
+        int rowStart = rowCount >= 0 ? 0 : rowCount() + rowCount;
+        int rowEnd = rowCount >= 0 ? rowCount : rowCount();
+        return where(Selection.withRange(rowStart, rowEnd));
+    }
+
     public Table inRange(int rowStart, int rowEnd) {
         Preconditions.checkArgument(rowEnd <= rowCount());
         return where(Selection.withRange(rowStart, rowEnd));
     }
 
+    /**
+     * Drops the first rowCount rows if rowCount positive.
+     * Drops the last rowCount rows if rowCount negative.
+     */
+    public Table dropRange(int rowCount) {
+        Preconditions.checkArgument(rowCount <= rowCount());
+        int rowStart = rowCount >= 0 ? rowCount : 0;
+        int rowEnd = rowCount >= 0 ? rowCount() : rowCount() + rowCount;
+        return where(Selection.withRange(rowStart, rowEnd));
+    }
+
     public Table dropRange(int rowStart, int rowEnd) {
         Preconditions.checkArgument(rowEnd <= rowCount());
         return where(Selection.withoutRange(0, rowCount(), rowStart, rowEnd));
@@ -1002,27 +1048,24 @@ public class Table extends Relation implements Iterable<Row> {
         };
     }
 
+    public Stream<Row> stream() {
+        return Streams.stream(iterator());
+    }
+
     /**
      * Applies the operation in {@code doable} to every row in the table
+     * @deprecated use {@code stream().forEach}
      */
     public void doWithRows(Consumer<Row> doable) {
-        Row row = new Row(this);
-        while (row.hasNext()) {
-            doable.accept(row.next());
-        }
+        stream().forEach(doable);
     }
 
     /**
      * Applies the predicate to each row, and return true if any row returns true
+     * @deprecated use {@code stream().anyMatch}
      */
     public boolean detect(Predicate<Row> predicate) {
-        Row row = new Row(this);
-        while (row.hasNext()) {
-            if (predicate.test(row.next())) {
-                return true;
-            }
-        }
-        return false;
+        return stream().anyMatch(predicate);
     }
 
     /**
@@ -1136,4 +1179,4 @@ public class Table extends Relation implements Iterable<Row> {
             throw new UnsupportedOperationException("This Pairs function returns no results");
         }
     }
-}
\ No newline at end of file
+}
diff --git a/core/src/main/java/tech/tablesaw/columns/numbers/NumberFilters.java b/core/src/main/java/tech/tablesaw/columns/numbers/NumberFilters.java
index 1f72f74c..e603b1dc 100644
--- a/core/src/main/java/tech/tablesaw/columns/numbers/NumberFilters.java
+++ b/core/src/main/java/tech/tablesaw/columns/numbers/NumberFilters.java
@@ -14,26 +14,15 @@
 
 package tech.tablesaw.columns.numbers;
 
-import static tech.tablesaw.columns.numbers.NumberPredicates.isBetweenExclusive;
-import static tech.tablesaw.columns.numbers.NumberPredicates.isBetweenInclusive;
-import static tech.tablesaw.columns.numbers.NumberPredicates.isEqualTo;
-import static tech.tablesaw.columns.numbers.NumberPredicates.isGreaterThan;
-import static tech.tablesaw.columns.numbers.NumberPredicates.isGreaterThanOrEqualTo;
-import static tech.tablesaw.columns.numbers.NumberPredicates.isLessThan;
-import static tech.tablesaw.columns.numbers.NumberPredicates.isLessThanOrEqualTo;
 import static tech.tablesaw.columns.numbers.NumberPredicates.isNegative;
 import static tech.tablesaw.columns.numbers.NumberPredicates.isNonNegative;
-import static tech.tablesaw.columns.numbers.NumberPredicates.isNotEqualTo;
 import static tech.tablesaw.columns.numbers.NumberPredicates.isPositive;
 import static tech.tablesaw.columns.numbers.NumberPredicates.isZero;
 
 import java.util.function.BiPredicate;
 import java.util.function.DoublePredicate;
 
-import tech.tablesaw.api.DoubleColumn;
 import tech.tablesaw.api.NumericColumn;
-import tech.tablesaw.filtering.predicates.DoubleBiPredicate;
-import tech.tablesaw.filtering.predicates.DoubleRangePredicate;
 import tech.tablesaw.selection.BitmapBackedSelection;
 import tech.tablesaw.selection.Selection;
 
@@ -41,45 +30,38 @@ public interface NumberFilters {
 
     Selection eval(DoublePredicate predicate);
 
-    Selection eval(DoubleRangePredicate predicate, Number rangeStart, Number rangeEnd);
-
-    Selection eval(DoubleBiPredicate predicate, DoubleColumn otherColumn);
-
-    Selection eval(DoubleBiPredicate predicate, Number value);
-
     Selection eval(BiPredicate<Number, Number> predicate, Number value);
 
-
     default Selection isEqualTo(double d) {
-        return eval(isEqualTo, d);
+        return eval(NumberPredicates.isEqualTo(d));
     }
 
     default Selection isNotEqualTo(double d) {
-        return eval(isNotEqualTo, d);
+        return eval(NumberPredicates.isNotEqualTo(d));
     }
 
-    default Selection isBetweenExclusive(double start, Number end) {
-        return eval(isBetweenExclusive, start, end);
+    default Selection isBetweenExclusive(double start, double end) {
+        return eval(NumberPredicates.isBetweenExclusive(start, end));
     }
 
-    default Selection isBetweenInclusive(double start, Number end) {
-        return eval(isBetweenInclusive, start, end);
+    default Selection isBetweenInclusive(double start, double end) {
+        return eval(NumberPredicates.isBetweenInclusive(start, end));
     }
 
     default Selection isGreaterThan(double f) {
-        return eval(isGreaterThan, f);
+        return eval(NumberPredicates.isGreaterThan(f));
     }
 
     default Selection isGreaterThanOrEqualTo(double f) {
-        return eval(isGreaterThanOrEqualTo, f);
+        return eval(NumberPredicates.isGreaterThanOrEqualTo(f));
     }
 
     default Selection isLessThan(double f) {
-        return eval(isLessThan, f);
+        return eval(NumberPredicates.isLessThan(f));
     }
 
     default Selection isLessThanOrEqualTo(double f) {
-        return eval(isLessThanOrEqualTo, f);
+        return eval(NumberPredicates.isLessThanOrEqualTo(f));
     }
 
     Selection isIn(Number... numbers);
diff --git a/core/src/main/java/tech/tablesaw/columns/numbers/NumberPredicates.java b/core/src/main/java/tech/tablesaw/columns/numbers/NumberPredicates.java
index 49a273f3..bfad2f1f 100644
--- a/core/src/main/java/tech/tablesaw/columns/numbers/NumberPredicates.java
+++ b/core/src/main/java/tech/tablesaw/columns/numbers/NumberPredicates.java
@@ -14,10 +14,6 @@
 
 package tech.tablesaw.columns.numbers;
 
-import tech.tablesaw.columns.Column;
-import tech.tablesaw.filtering.predicates.DoubleBiPredicate;
-import tech.tablesaw.filtering.predicates.DoubleRangePredicate;
-
 import java.util.function.DoublePredicate;
 
 /**
@@ -25,7 +21,7 @@ import java.util.function.DoublePredicate;
  * <p>
  * TODO(lwhite): Ensure each returns false when handling missing values
  */
-public interface NumberPredicates extends Column<Double> {
+public interface NumberPredicates {
 
     DoublePredicate isZero = i -> i == 0.0;
 
@@ -35,33 +31,37 @@ public interface NumberPredicates extends Column<Double> {
 
     DoublePredicate isNonNegative = i -> i >= 0;
 
-    DoubleBiPredicate isGreaterThan = (valueToTest, valueToCompareAgainst) -> valueToTest > valueToCompareAgainst;
+    static DoublePredicate isGreaterThan(double valueToCompareAgainst) {
+      return valueToTest -> valueToTest > valueToCompareAgainst;
+    }
 
-    DoubleRangePredicate isBetweenExclusive = new DoubleRangePredicate() {
-        @Override
-        public boolean test(double valueToTest, double rangeStart, double rangeEnd) {
-            return valueToTest > rangeStart && valueToTest < rangeEnd;
-        }
+    static DoublePredicate isBetweenExclusive(double rangeStart, double rangeEnd) {
+      return valueToTest -> valueToTest > rangeStart && valueToTest < rangeEnd;
     };
 
-    DoubleRangePredicate isBetweenInclusive = new DoubleRangePredicate() {
-        @Override
-        public boolean test(double valueToTest, double rangeStart, double rangeEnd) {
-            return valueToTest >= rangeStart && valueToTest <= rangeEnd;
-        }
+    static DoublePredicate isBetweenInclusive(double rangeStart, double rangeEnd) {
+      return valueToTest -> valueToTest >= rangeStart && valueToTest <= rangeEnd;
     };
 
-    DoubleBiPredicate isGreaterThanOrEqualTo = (valueToTest, valueToCompareAgainst) -> valueToTest >=
-            valueToCompareAgainst;
+    static DoublePredicate isGreaterThanOrEqualTo(double valueToCompareAgainst) {
+      return valueToTest -> valueToTest >= valueToCompareAgainst;  
+    }
 
-    DoubleBiPredicate isLessThan = (valueToTest, valueToCompareAgainst) -> valueToTest < valueToCompareAgainst;
+    static DoublePredicate isLessThan(double valueToCompareAgainst) {
+      return valueToTest -> valueToTest < valueToCompareAgainst;
+    }
 
-    DoubleBiPredicate isLessThanOrEqualTo = (valueToTest, valueToCompareAgainst) -> valueToTest <=
-            valueToCompareAgainst;
+    static DoublePredicate isLessThanOrEqualTo(double valueToCompareAgainst) {
+       return valueToTest -> valueToTest <= valueToCompareAgainst;
+    }
 
-    DoubleBiPredicate isEqualTo = (valueToTest, valueToCompareAgainst) -> valueToTest == valueToCompareAgainst;
+    static DoublePredicate isEqualTo(double valueToCompareAgainst) {
+      return valueToTest -> valueToTest == valueToCompareAgainst;
+    }
 
-    DoubleBiPredicate isNotEqualTo = (valueToTest, valueToCompareAgainst) -> valueToTest != valueToCompareAgainst;
+    static DoublePredicate isNotEqualTo(double valueToCompareAgainst) {
+      return valueToTest -> valueToTest != valueToCompareAgainst;
+    }
 
     DoublePredicate isMissing = i -> i != i;
 
diff --git a/core/src/main/java/tech/tablesaw/conversion/smile/SmileConverter.java b/core/src/main/java/tech/tablesaw/conversion/smile/SmileConverter.java
index 6132101f..8246ed38 100644
--- a/core/src/main/java/tech/tablesaw/conversion/smile/SmileConverter.java
+++ b/core/src/main/java/tech/tablesaw/conversion/smile/SmileConverter.java
@@ -110,8 +110,9 @@ public class SmileConverter {
     }
 
     private NominalAttribute colAsNominalAttribute(Column<?> col) {
+        Column<?> unique = col.unique();
         return new NominalAttribute(col.name(),
-            col.unique().mapInto(o -> o.toString(), StringColumn.create(col.name(), col.size())).asObjectArray());
+            unique.mapInto(o -> o.toString(), StringColumn.create(col.name(), unique.size())).asObjectArray());
     }
 
     private static enum AttributeType {
diff --git a/core/src/main/java/tech/tablesaw/filtering/predicates/DoubleBiPredicate.java b/core/src/main/java/tech/tablesaw/filtering/predicates/DoubleBiPredicate.java
deleted file mode 100644
index 8f4aa2ea..00000000
--- a/core/src/main/java/tech/tablesaw/filtering/predicates/DoubleBiPredicate.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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.filtering.predicates;
-
-public interface DoubleBiPredicate {
-
-    /**
-     * Returns true if valueToTest meets the criteria of this predicate when valueToCompareAgainst is considered
-     * <p>
-     * Example (to compare all the values v in a column such that v is greater than 4.0, v is the value to test and 4.0
-     * is the value to compare against
-     *
-     * @param valueToTest           the value you're checking. Often this is the value of a cell in a double column
-     * @param valueToCompareAgainst the value to compare against. Often this is a single value for all comparisons
-     */
-    boolean test(double valueToTest, double valueToCompareAgainst);
-}
diff --git a/core/src/main/java/tech/tablesaw/filtering/predicates/DoubleRangePredicate.java b/core/src/main/java/tech/tablesaw/filtering/predicates/DoubleRangePredicate.java
deleted file mode 100644
index 091c4e30..00000000
--- a/core/src/main/java/tech/tablesaw/filtering/predicates/DoubleRangePredicate.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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.filtering.predicates;
-
-public interface DoubleRangePredicate {
-
-    /**
-     * Returns true if valueToTest meets the criteria of this predicate when rangeStart is considered
-     * <p>
-     * Example (to compare all the values v in a column such that v is greater than 4.0 and less than 6.0.
-     *
-     * @param valueToTest the value you're checking. Often this is the value of a cell in a double column
-     * @param rangeStart  the start of the range to compare against. Often this is a single value for all comparisons
-     * @param rangeEnd    the end of the range to compare against. Often this is a single value for all comparisons
-     */
-    boolean test(double valueToTest, double rangeStart, double rangeEnd);
-}
diff --git a/core/src/main/java/tech/tablesaw/io/csv/CsvReadOptions.java b/core/src/main/java/tech/tablesaw/io/csv/CsvReadOptions.java
index 9f25dca0..3348c6a3 100644
--- a/core/src/main/java/tech/tablesaw/io/csv/CsvReadOptions.java
+++ b/core/src/main/java/tech/tablesaw/io/csv/CsvReadOptions.java
@@ -31,6 +31,7 @@ public class CsvReadOptions extends ReadOptions {
 
     private final ColumnType[] columnTypes;
     private final Character separator;
+    private final Character quoteChar;
     private final String lineEnding;
     private final Integer maxNumberOfColumns;
     private final Character commentPrefix;
@@ -41,6 +42,7 @@ public class CsvReadOptions extends ReadOptions {
 
         columnTypes = builder.columnTypes;
         separator = builder.separator;
+        quoteChar = builder.quoteChar;
         lineEnding = builder.lineEnding;
         maxNumberOfColumns = builder.maxNumberOfColumns;
         commentPrefix = builder.commentPrefix;
@@ -108,6 +110,10 @@ public class CsvReadOptions extends ReadOptions {
         return separator;
     }
 
+    public Character quoteChar() {
+        return quoteChar;
+    }
+
     public String lineEnding() {
         return lineEnding;
     }
@@ -131,6 +137,7 @@ public class CsvReadOptions extends ReadOptions {
     public static class Builder extends ReadOptions.Builder {
 
         private Character separator = ',';
+        private Character quoteChar;
         private String lineEnding;
         private ColumnType[] columnTypes;
         private Integer maxNumberOfColumns = 10_000;
@@ -167,6 +174,11 @@ public class CsvReadOptions extends ReadOptions {
             return this;
         }
 
+        public Builder quoteChar(Character quoteChar) {
+            this.quoteChar = quoteChar;
+            return this;
+        }
+
         public Builder lineEnding(String lineEnding) {
             this.lineEnding = lineEnding;
             this.lineSeparatorDetectionEnabled = false;
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 0c080b11..0b06fafe 100644
--- a/core/src/main/java/tech/tablesaw/io/csv/CsvReader.java
+++ b/core/src/main/java/tech/tablesaw/io/csv/CsvReader.java
@@ -159,6 +159,9 @@ public class CsvReader extends FileReader implements DataReader<CsvReadOptions>
 
     private CsvFormat csvFormat(CsvReadOptions options) {
         CsvFormat format = new CsvFormat();
+        if (options.quoteChar() != null) {
+            format.setQuote(options.quoteChar());
+        }
         if (options.separator() != null) {
             format.setDelimiter(options.separator());
         }
diff --git a/core/src/main/java/tech/tablesaw/io/csv/CsvWriteOptions.java b/core/src/main/java/tech/tablesaw/io/csv/CsvWriteOptions.java
index e088052d..b3188829 100644
--- a/core/src/main/java/tech/tablesaw/io/csv/CsvWriteOptions.java
+++ b/core/src/main/java/tech/tablesaw/io/csv/CsvWriteOptions.java
@@ -13,16 +13,16 @@ public class CsvWriteOptions extends WriteOptions {
 
     private final boolean header;
     private final char separator;
-    private final char quotechar;
-    private final char escapechar;
+    private final char quoteChar;
+    private final char escapeChar;
     private final String lineEnd;
 
     private CsvWriteOptions(Builder builder) {
         super(builder);
         this.header = builder.header;
         this.separator = builder.separator;
-        this.quotechar = builder.quoteChar;
-        this.escapechar = builder.escapeChar;
+        this.quoteChar = builder.quoteChar;
+        this.escapeChar = builder.escapeChar;
         this.lineEnd = builder.lineEnd;
     }
 
@@ -35,11 +35,11 @@ public class CsvWriteOptions extends WriteOptions {
     }
 
     public char escapeChar() {
-        return escapechar;
+        return escapeChar;
     }
 
     public char quoteChar() {
-        return quotechar;
+        return quoteChar;
     }
 
     public String lineEnd() {
diff --git a/excel/pom.xml b/excel/pom.xml
index 536279dc..07a12868 100644
--- a/excel/pom.xml
+++ b/excel/pom.xml
@@ -5,7 +5,7 @@
   <parent>
     <groupId>tech.tablesaw</groupId>
     <artifactId>tablesaw-parent</artifactId>
-    <version>0.34.1-SNAPSHOT</version>
+    <version>0.34.2-SNAPSHOT</version>
   </parent>
 
   <artifactId>tablesaw-excel</artifactId>
@@ -75,7 +75,7 @@
     <dependency>
       <groupId>tech.tablesaw</groupId>
       <artifactId>tablesaw-core</artifactId>
-      <version>0.34.1-SNAPSHOT</version>
+      <version>0.34.2-SNAPSHOT</version>
       <scope>provided</scope>
     </dependency>
     <dependency>
diff --git a/html/pom.xml b/html/pom.xml
index 5f22d96f..703e3bcf 100644
--- a/html/pom.xml
+++ b/html/pom.xml
@@ -5,7 +5,7 @@
   <parent>
     <groupId>tech.tablesaw</groupId>
     <artifactId>tablesaw-parent</artifactId>
-    <version>0.34.1-SNAPSHOT</version>
+    <version>0.34.2-SNAPSHOT</version>
   </parent>
 
   <artifactId>tablesaw-html</artifactId>
@@ -75,7 +75,7 @@
     <dependency>
       <groupId>tech.tablesaw</groupId>
       <artifactId>tablesaw-core</artifactId>
-      <version>0.34.1-SNAPSHOT</version>
+      <version>0.34.2-SNAPSHOT</version>
       <scope>provided</scope>
     </dependency>
     <dependency>
diff --git a/json/pom.xml b/json/pom.xml
index 73b3dcbc..e02253a7 100644
--- a/json/pom.xml
+++ b/json/pom.xml
@@ -5,7 +5,7 @@
   <parent>
     <groupId>tech.tablesaw</groupId>
     <artifactId>tablesaw-parent</artifactId>
-    <version>0.34.1-SNAPSHOT</version>
+    <version>0.34.2-SNAPSHOT</version>
   </parent>
 
   <artifactId>tablesaw-json</artifactId>
@@ -75,7 +75,7 @@
     <dependency>
       <groupId>tech.tablesaw</groupId>
       <artifactId>tablesaw-core</artifactId>
-      <version>0.34.1-SNAPSHOT</version>
+      <version>0.34.2-SNAPSHOT</version>
       <scope>provided</scope>
     </dependency>
     <dependency>
diff --git a/jsplot/pom.xml b/jsplot/pom.xml
index cfa934d9..2e68d7af 100644
--- a/jsplot/pom.xml
+++ b/jsplot/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <groupId>tech.tablesaw</groupId>
         <artifactId>tablesaw-parent</artifactId>
-        <version>0.34.1-SNAPSHOT</version>
+        <version>0.34.2-SNAPSHOT</version>
     </parent>
 
     <artifactId>tablesaw-jsplot</artifactId>
@@ -85,7 +85,7 @@
         <dependency>
             <groupId>tech.tablesaw</groupId>
             <artifactId>tablesaw-core</artifactId>
-            <version>0.34.1-SNAPSHOT</version>
+            <version>0.34.2-SNAPSHOT</version>
         </dependency>
         <dependency>
             <groupId>org.junit.jupiter</groupId>
diff --git a/pom.xml b/pom.xml
index bfe372ad..e51222d7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
 
     <groupId>tech.tablesaw</groupId>
     <artifactId>tablesaw-parent</artifactId>
-    <version>0.34.1-SNAPSHOT</version>
+    <version>0.34.2-SNAPSHOT</version>
     <name>Tablesaw</name>
     <description>High-performance Java Dataframe</description>
     <url>https://github.com/jtablesaw/tablesaw</url>


Test patch:
diff --git a/core/src/test/java/tech/tablesaw/api/NumberColumnTest.java b/core/src/test/java/tech/tablesaw/api/NumberColumnTest.java
index b0bce44a..2d641924 100644
--- a/core/src/test/java/tech/tablesaw/api/NumberColumnTest.java
+++ b/core/src/test/java/tech/tablesaw/api/NumberColumnTest.java
@@ -664,22 +664,29 @@ public class NumberColumnTest {
         assertFalse(DoubleColumn.create("t1", new double[] {1, 0, -1}).noneMatch(isNegativeD));
     }
 
-    private <T> void check(Column<T> column, @SuppressWarnings("unchecked") T... ts) {
-        assertEquals(ts.length, column.size());
-        for (int i = 0; i < ts.length; i++) {
-            assertEquals(ts[i], column.get(i));
+    private <T> void assertColumnContentsEquals(T[] expected, Column<T> column) {
+        assertEquals(expected.length, column.size());
+        for (int i = 0; i < expected.length; i++) {
+            assertEquals(expected[i], column.get(i));
         }
     }
 
     @Test
     public void testFilter() {
         Column<Double> filtered = DoubleColumn.create("t1", new double[] {-1, 0, 1}).filter(isPositiveOrZeroD);
-        check(filtered, 0.0, 1.0);
+        assertColumnContentsEquals(new Double[] { 0.0, 1.0 }, filtered);
+    }
+
+    @Test
+    public void testMap() {
+        DoubleColumn mapped = DoubleColumn.create("t1", new double[] {-1, 0, 1}).map(x -> x * 2.0 + 1.0);
+        assertColumnContentsEquals(new Double[] { -1.0, 1.0, 3.0 }, mapped);
     }
 
     @Test
     public void testMapInto() {
-        check(DoubleColumn.create("t1", new double[] {-1, 0, 1}).mapInto(toStringD, StringColumn.create("result")), "-1.0", "0.0", "1.0");
+        StringColumn mapped = DoubleColumn.create("t1", new double[] {-1, 0, 1}).mapInto(toStringD, StringColumn.create("result"));
+        assertColumnContentsEquals(new String[] { "-1.0", "0.0", "1.0" }, mapped);
     }
 
     @Test
diff --git a/core/src/test/java/tech/tablesaw/api/TableTest.java b/core/src/test/java/tech/tablesaw/api/TableTest.java
index 09e7ce84..72dfe844 100644
--- a/core/src/test/java/tech/tablesaw/api/TableTest.java
+++ b/core/src/test/java/tech/tablesaw/api/TableTest.java
@@ -214,6 +214,17 @@ public class TableTest {
         Table[] results = t.sampleSplit(.75);
         assertEquals(t.rowCount(), results[0].rowCount() + results[1].rowCount());
     }
+    
+    @Test
+    public void testStratifiedSampleSplit() throws Exception {
+        Table t = Table.read().csv("../data/bush.csv");
+        Table[] results = t.stratifiedSampleSplit(t.stringColumn("who"), .75);
+        assertEquals(t.rowCount(), results[0].rowCount() + results[1].rowCount());
+        int totalFoxCount = t.where(t.stringColumn("who").equalsIgnoreCase("fox")).rowCount();
+        int stratifiedFoxCount = results[0].where(results[0].stringColumn("who").equalsIgnoreCase("fox")).rowCount();
+        
+        assertEquals(.75, (double) stratifiedFoxCount/totalFoxCount, 0.0);
+    }
 
     @Test
     public void testDoWithEachRow() throws Exception {
@@ -535,6 +546,54 @@ public class TableTest {
         }
     }
 
+    @Test
+    public void dropRangeStarting() throws IOException {
+        Table table = Table.read().csv(CsvReadOptions.builder("../data/bush.csv"));
+        Table result = table.dropRange(20);
+        assertEquals(table.rowCount() - 20, result.rowCount());
+        for (Column<?> c: result.columns()) {
+            for (int r = 0; r < result.rowCount(); r++) {
+                assertEquals(result.getString(r, c.name()), table.getString(r + 20, c.name()));
+            }
+        }
+    }
+
+    @Test
+    public void dropRangeEnding() throws IOException {
+        Table table = Table.read().csv(CsvReadOptions.builder("../data/bush.csv"));
+        Table result = table.dropRange(-20);
+        assertEquals(table.rowCount() - 20, result.rowCount());
+        for (Column<?> c: result.columns()) {
+            for (int r = 0; r < result.rowCount(); r++) {
+                assertEquals(result.getString(r, c.name()), table.getString(r, c.name()));
+            }
+        }
+    }    
+
+    @Test
+    public void inRangeStarting() throws IOException {
+        Table table = Table.read().csv(CsvReadOptions.builder("../data/bush.csv"));
+        Table result = table.inRange(20);
+        assertEquals(20, result.rowCount());
+        for (Column<?> c: result.columns()) {
+            for (int r = 0; r < result.rowCount(); r++) {
+                assertEquals(result.getString(r, c.name()), table.getString(r, c.name()));
+            }
+        }
+    }
+
+    @Test
+    public void inRangeEnding() throws IOException {
+        Table table = Table.read().csv(CsvReadOptions.builder("../data/bush.csv"));
+        Table result = table.inRange(-20);
+        assertEquals(20, result.rowCount());
+        for (Column<?> c: result.columns()) {
+            for (int r = 0; r < result.rowCount(); r++) {
+                assertEquals(result.getString(r, c.name()), table.getString(table.rowCount() - 20 + r, c.name()));
+            }
+        }
+    }  
+
     private DoubleColumn sum(DoubleColumn... columns) {
         int size = columns[0].size();
         DoubleColumn result = DoubleColumn.create("sum", size);
diff --git a/core/src/test/java/tech/tablesaw/conversion/smile/SmileConverterTest.java b/core/src/test/java/tech/tablesaw/conversion/smile/SmileConverterTest.java
index 890b05f0..3707fbf4 100644
--- a/core/src/test/java/tech/tablesaw/conversion/smile/SmileConverterTest.java
+++ b/core/src/test/java/tech/tablesaw/conversion/smile/SmileConverterTest.java
@@ -37,6 +37,16 @@ public class SmileConverterTest {
         OLS winsModel = new OLS(moneyball.select("W", "RD").smile().numericDataset("RD"));
         assertNotNull(winsModel.toString());
     }
+    
+    @Test
+    public void regressionWithStratifiedSampleTest() throws IOException {
+        Table moneyball = Table.read().csv("../data/baseball.csv");
+        Table[] splits = moneyball.stratifiedSampleSplit(moneyball.stringColumn("Team"), 0.6);
+        Table stratifiedMoneyBall = splits[0];
+        stratifiedMoneyBall.addColumns(stratifiedMoneyBall.numberColumn("RS").subtract(stratifiedMoneyBall.numberColumn("RA")).setName("RD"));
+        OLS winsModel = new OLS(stratifiedMoneyBall.select("W", "RD").smile().numericDataset("RD"));
+        assertNotNull(winsModel.toString());
+    }
 
     @Test
     public void classification() throws IOException {
diff --git a/core/src/test/java/tech/tablesaw/index/DoubleIndexTest.java b/core/src/test/java/tech/tablesaw/index/DoubleIndexTest.java
index b0cb54e9..9682d5e0 100644
--- a/core/src/test/java/tech/tablesaw/index/DoubleIndexTest.java
+++ b/core/src/test/java/tech/tablesaw/index/DoubleIndexTest.java
@@ -50,35 +50,35 @@ public class DoubleIndexTest {
 
     @Test
     public void testGet() {
-        Selection fromCol = table.numberColumn("stop_lat").eval(NumberPredicates.isEqualTo, 30.330425);
+        Selection fromCol = table.numberColumn("stop_lat").eval(NumberPredicates.isEqualTo(30.330425));
         Selection fromIdx = index.get(30.330425);
         assertEquals(fromCol, fromIdx);
     }
 
     @Test
     public void testGTE() {
-        Selection fromCol = table.numberColumn("stop_lat").eval(NumberPredicates.isGreaterThanOrEqualTo, 30.330425);
+        Selection fromCol = table.numberColumn("stop_lat").eval(NumberPredicates.isGreaterThanOrEqualTo(30.330425));
         Selection fromIdx = index.atLeast(30.330425);
         assertEquals(fromCol, fromIdx);
     }
 
     @Test
     public void testLTE() {
-        Selection fromCol = table.numberColumn("stop_lat").eval(NumberPredicates.isLessThanOrEqualTo, 30.330425);
+        Selection fromCol = table.numberColumn("stop_lat").eval(NumberPredicates.isLessThanOrEqualTo(30.330425));
         Selection fromIdx = index.atMost(30.330425);
         assertEquals(fromCol, fromIdx);
     }
 
     @Test
     public void testLT() {
-        Selection fromCol = table.numberColumn("stop_lat").eval(NumberPredicates.isLessThan, 30.330425);
+        Selection fromCol = table.numberColumn("stop_lat").eval(NumberPredicates.isLessThan(30.330425));
         Selection fromIdx = index.lessThan(30.330425);
         assertEquals(fromCol, fromIdx);
     }
 
     @Test
     public void testGT() {
-        Selection fromCol = table.numberColumn("stop_lat").eval(NumberPredicates.isGreaterThan, 30.330425);
+        Selection fromCol = table.numberColumn("stop_lat").eval(NumberPredicates.isGreaterThan(30.330425));
         Selection fromIdx = index.greaterThan(30.330425);
         assertEquals(fromCol, fromIdx);
     }
diff --git a/core/src/test/java/tech/tablesaw/index/IntIndexTest.java b/core/src/test/java/tech/tablesaw/index/IntIndexTest.java
index 84b76c3e..b99267b6 100644
--- a/core/src/test/java/tech/tablesaw/index/IntIndexTest.java
+++ b/core/src/test/java/tech/tablesaw/index/IntIndexTest.java
@@ -51,7 +51,7 @@ public class IntIndexTest {
 
     @Test
     public void testGet() {
-        Selection fromCol = table.numberColumn("approval").eval(NumberPredicates.isEqualTo, 71);
+        Selection fromCol = table.numberColumn("approval").eval(NumberPredicates.isEqualTo(71));
         Selection fromIdx = index.get(71);
         assertEquals(fromCol, fromIdx);
     }
@@ -69,7 +69,7 @@ public class IntIndexTest {
 
     @Test
     public void testGTE() {
-        Selection fromCol = table.numberColumn("approval").eval(NumberPredicates.isGreaterThanOrEqualTo, 71);
+        Selection fromCol = table.numberColumn("approval").eval(NumberPredicates.isGreaterThanOrEqualTo(71));
         Selection fromIdx = index.atLeast(71);
         assertEquals(fromCol, fromIdx);
     }
@@ -85,21 +85,21 @@ public class IntIndexTest {
 
     @Test
     public void testLTE() {
-        Selection fromCol = table.numberColumn("approval").eval(NumberPredicates.isLessThanOrEqualTo, 71);
+        Selection fromCol = table.numberColumn("approval").eval(NumberPredicates.isLessThanOrEqualTo(71));
         Selection fromIdx = index.atMost(71);
         assertEquals(fromCol, fromIdx);
     }
 
     @Test
     public void testLT() {
-        Selection fromCol = table.numberColumn("approval").eval(NumberPredicates.isLessThan, 71);
+        Selection fromCol = table.numberColumn("approval").eval(NumberPredicates.isLessThan(71));
         Selection fromIdx = index.lessThan(71);
         assertEquals(fromCol, fromIdx);
     }
 
     @Test
     public void testGT() {
-        Selection fromCol = table.numberColumn("approval").eval(NumberPredicates.isGreaterThan, 71);
+        Selection fromCol = table.numberColumn("approval").eval(NumberPredicates.isGreaterThan(71));
         Selection fromIdx = index.greaterThan(71);
         assertEquals(fromCol, fromIdx);
     }