Repository: jtablesaw/tablesaw
Pull request #748 (2020-01-30 22:22:01)

Add ignoreZeroDecimal to ReadOptions
- [x] Tick to sign-off your agreement to the [Developer Certificate of Origin (DCO) 1.1](https://developercertificate.org)

## Description

Introduces new option `ignoreZeroDecimal` to `ReadOptions`. This option controls whether a numeric value ending with ".0" may be considered an integer (or short, long). Default value is `true` and retains the current behavior. If set to false, values ending with ".0" will be not be considered an integer and instead be considered a floating point.

Since different software/systems have different opinions on this matter, an option to control this behavior could be justified. Will be useful when using TableSaw together with other systems.

The logic could be centralized in `AbstractColumnParser` if that is preferred. Open for other variable naming suggestions.

Relates to issue #747.

## Testing

Tests added to `CsvReaderTest` and `StringUtilsTest`.


larshelge: Fully appreciating that people are busy, I was just wondering whether you may consider this patch, or if I should start working on an external work-around?
larshelge: Would it be possible to kindly get a timeline for when a review of this PR could take place? I am happy to change the approach to what the reviewer find most appropriate.
lwhite1: I guess I don't see why you can't read the file as a double or float column and then convert it to an int column. The asIntColumn() method uses a cast 

```
  public IntColumn asIntColumn() {
    IntColumn result = IntColumn.create(name());
    for (double d : data) {
      if (DoubleColumnType.valueIsMissing(d)) {
        result.appendMissing();
      } else {
        result.append((int) d);
      }
    }
    return result;
  }

```
DoubleColumn also implements asShortColumn and asLongColumn.

One advantage to doing this work here as opposed to in the reader is that most numeric operators return a double column, which you may want to convert to an integral value.
benmccann: Yeah, I'd debated that as well. You could generally convert after the fact, but it's probably enough extra work that I didn't think this approach was too bad.

Also, there might be circumstances where the current approach makes you lose information. Right now we always strip ".0" and treat as an int. But what if you had a data source where some columns had trailing zeros and others didn't and you want to retain that information? Then I think you'd need to turn off the automatic stripping of ".0". I think that's sort of what was being suggested in https://github.com/jtablesaw/tablesaw/issues/747
larshelge: Many thanks for the comments, really appreciate it.

Just wanted to mention that the original use-case for #747 is _data type detection_, where for various reasons one would like to consider numbers ending in zero-decimals only as type double.

Converting the column to int after the fact is not really an option as we do not know the data type of the data files/columns up front, as they are uploaded by users. We are using TableSaw to
inform us what data type each column has.

Note that this PR will also coincidentally solve the problem of #732.

larshelge: I have added tests now, see `CsvReaderTest.java`.
larshelge: I have added tests. @lwhite1 would it be possible to kindly ask for a review?
larshelge: I have added tests. @lwhite1 would it be possible to kindly ask for a review?

Patch:
diff --git a/core/src/main/java/tech/tablesaw/columns/AbstractColumn.java b/core/src/main/java/tech/tablesaw/columns/AbstractColumn.java
index e9215855..9ea1b762 100644
--- a/core/src/main/java/tech/tablesaw/columns/AbstractColumn.java
+++ b/core/src/main/java/tech/tablesaw/columns/AbstractColumn.java
@@ -17,6 +17,7 @@ package tech.tablesaw.columns;
 import java.util.Comparator;
 import java.util.function.Function;
 import java.util.function.Predicate;
+import java.util.stream.IntStream;
 import tech.tablesaw.api.ColumnType;
 import tech.tablesaw.api.StringColumn;
 import tech.tablesaw.selection.Selection;
@@ -146,4 +147,9 @@ public abstract class AbstractColumn<C extends Column<T>, T> implements Column<T
     }
     return sc;
   }
+
+  @Override
+  public int indexOf(final Object o) {
+    return IntStream.range(0, size()).filter(i -> get(i).equals(o)).findFirst().orElse(-1);
+  }
 }
diff --git a/core/src/main/java/tech/tablesaw/columns/Column.java b/core/src/main/java/tech/tablesaw/columns/Column.java
index 509d7fd0..24f6e1ec 100644
--- a/core/src/main/java/tech/tablesaw/columns/Column.java
+++ b/core/src/main/java/tech/tablesaw/columns/Column.java
@@ -656,4 +656,10 @@ public interface Column<T> extends Iterable<T>, Comparator<T> {
    * @return a {@link StringColumn} built using the column {@link #getUnformattedString} method
    */
   StringColumn asStringColumn();
+
+  /**
+   * Returns the index of the first occurrence of {@code o} in the column or -1 if the element is
+   * not in the column.
+   */
+  int indexOf(Object o);
 }
diff --git a/core/src/main/java/tech/tablesaw/columns/numbers/IntParser.java b/core/src/main/java/tech/tablesaw/columns/numbers/IntParser.java
index 8f792d09..3ea4e043 100644
--- a/core/src/main/java/tech/tablesaw/columns/numbers/IntParser.java
+++ b/core/src/main/java/tech/tablesaw/columns/numbers/IntParser.java
@@ -4,11 +4,15 @@ import com.google.common.collect.Lists;
 import tech.tablesaw.api.ColumnType;
 import tech.tablesaw.columns.AbstractColumnParser;
 import tech.tablesaw.io.ReadOptions;
+import tech.tablesaw.util.StringUtils;
 
 public class IntParser extends AbstractColumnParser<Integer> {
 
+  private final boolean ignoreZeroDecimal;
+
   public IntParser(ColumnType columnType) {
     super(columnType);
+    ignoreZeroDecimal = ReadOptions.DEFAULT_IGNORE_ZERO_DECIMAL;
   }
 
   public IntParser(IntColumnType columnType, ReadOptions readOptions) {
@@ -16,6 +20,7 @@ public class IntParser extends AbstractColumnParser<Integer> {
     if (readOptions.missingValueIndicator() != null) {
       missingValueStrings = Lists.newArrayList(readOptions.missingValueIndicator());
     }
+    ignoreZeroDecimal = readOptions.ignoreZeroDecimal();
   }
 
   @Override
@@ -25,8 +30,8 @@ public class IntParser extends AbstractColumnParser<Integer> {
     }
     String s = str;
     try {
-      if (s.endsWith(".0")) {
-        s = s.substring(0, s.length() - 2);
+      if (ignoreZeroDecimal) {
+        s = StringUtils.removeZeroDecimal(s);
       }
       Integer.parseInt(AbstractColumnParser.remove(s, ','));
       return true;
@@ -52,8 +57,8 @@ public class IntParser extends AbstractColumnParser<Integer> {
       return IntColumnType.missingValueIndicator();
     }
     String s = str;
-    if (s.endsWith(".0")) {
-      s = s.substring(0, s.length() - 2);
+    if (ignoreZeroDecimal) {
+      s = StringUtils.removeZeroDecimal(s);
     }
     return Integer.parseInt(AbstractColumnParser.remove(s, ','));
   }
diff --git a/core/src/main/java/tech/tablesaw/columns/numbers/LongParser.java b/core/src/main/java/tech/tablesaw/columns/numbers/LongParser.java
index 700d7825..17e2aa34 100644
--- a/core/src/main/java/tech/tablesaw/columns/numbers/LongParser.java
+++ b/core/src/main/java/tech/tablesaw/columns/numbers/LongParser.java
@@ -4,11 +4,15 @@ import com.google.common.collect.Lists;
 import tech.tablesaw.api.ColumnType;
 import tech.tablesaw.columns.AbstractColumnParser;
 import tech.tablesaw.io.ReadOptions;
+import tech.tablesaw.util.StringUtils;
 
 public class LongParser extends AbstractColumnParser<Long> {
 
+  private final boolean ignoreZeroDecimal;
+
   public LongParser(ColumnType columnType) {
     super(columnType);
+    ignoreZeroDecimal = ReadOptions.DEFAULT_IGNORE_ZERO_DECIMAL;
   }
 
   public LongParser(LongColumnType columnType, ReadOptions readOptions) {
@@ -16,6 +20,7 @@ public class LongParser extends AbstractColumnParser<Long> {
     if (readOptions.missingValueIndicator() != null) {
       missingValueStrings = Lists.newArrayList(readOptions.missingValueIndicator());
     }
+    ignoreZeroDecimal = readOptions.ignoreZeroDecimal();
   }
 
   @Override
@@ -25,8 +30,8 @@ public class LongParser extends AbstractColumnParser<Long> {
     }
     String s = str;
     try {
-      if (s.endsWith(".0")) {
-        s = s.substring(0, s.length() - 2);
+      if (ignoreZeroDecimal) {
+        s = StringUtils.removeZeroDecimal(s);
       }
       Long.parseLong(AbstractColumnParser.remove(s, ','));
       return true;
@@ -52,8 +57,8 @@ public class LongParser extends AbstractColumnParser<Long> {
       return LongColumnType.missingValueIndicator();
     }
     String s = str;
-    if (s.endsWith(".0")) {
-      s = s.substring(0, s.length() - 2);
+    if (ignoreZeroDecimal) {
+      s = StringUtils.removeZeroDecimal(s);
     }
     return Long.parseLong(AbstractColumnParser.remove(s, ','));
   }
diff --git a/core/src/main/java/tech/tablesaw/columns/numbers/ShortParser.java b/core/src/main/java/tech/tablesaw/columns/numbers/ShortParser.java
index 64219d78..4e6b232a 100644
--- a/core/src/main/java/tech/tablesaw/columns/numbers/ShortParser.java
+++ b/core/src/main/java/tech/tablesaw/columns/numbers/ShortParser.java
@@ -3,11 +3,15 @@ package tech.tablesaw.columns.numbers;
 import com.google.common.collect.Lists;
 import tech.tablesaw.columns.AbstractColumnParser;
 import tech.tablesaw.io.ReadOptions;
+import tech.tablesaw.util.StringUtils;
 
 public class ShortParser extends AbstractColumnParser<Short> {
 
+  private final boolean ignoreZeroDecimal;
+
   public ShortParser(ShortColumnType columnType) {
     super(columnType);
+    ignoreZeroDecimal = ReadOptions.DEFAULT_IGNORE_ZERO_DECIMAL;
   }
 
   public ShortParser(ShortColumnType columnType, ReadOptions readOptions) {
@@ -15,6 +19,7 @@ public class ShortParser extends AbstractColumnParser<Short> {
     if (readOptions.missingValueIndicator() != null) {
       missingValueStrings = Lists.newArrayList(readOptions.missingValueIndicator());
     }
+    ignoreZeroDecimal = readOptions.ignoreZeroDecimal();
   }
 
   @Override
@@ -24,8 +29,8 @@ public class ShortParser extends AbstractColumnParser<Short> {
     }
     String s = str;
     try {
-      if (s.endsWith(".0")) {
-        s = s.substring(0, s.length() - 2);
+      if (ignoreZeroDecimal) {
+        s = StringUtils.removeZeroDecimal(s);
       }
       Short.parseShort(AbstractColumnParser.remove(s, ','));
       return true;
@@ -51,8 +56,8 @@ public class ShortParser extends AbstractColumnParser<Short> {
       return ShortColumnType.missingValueIndicator();
     }
     String s = str;
-    if (s.endsWith(".0")) {
-      s = s.substring(0, s.length() - 2);
+    if (ignoreZeroDecimal) {
+      s = StringUtils.removeZeroDecimal(s);
     }
     return Short.parseShort(AbstractColumnParser.remove(s, ','));
   }
diff --git a/core/src/main/java/tech/tablesaw/columns/strings/StringMapFunctions.java b/core/src/main/java/tech/tablesaw/columns/strings/StringMapFunctions.java
index 943b1ea4..6429c833 100644
--- a/core/src/main/java/tech/tablesaw/columns/strings/StringMapFunctions.java
+++ b/core/src/main/java/tech/tablesaw/columns/strings/StringMapFunctions.java
@@ -312,7 +312,7 @@ public interface StringMapFunctions extends Column<String> {
    * @param columns the column to append
    * @return the new column
    */
-  default StringColumn join(String separator, Column... columns) {
+  default StringColumn join(String separator, Column<?>... columns) {
     StringColumn newColumn = StringColumn.create(name() + "[column appended]", this.size());
     for (int r = 0; r < size(); r++) {
       StringBuilder result = new StringBuilder(getString(r));
@@ -347,11 +347,11 @@ public interface StringMapFunctions extends Column<String> {
    * @param stringColumns the string columns to append
    * @return the new column
    */
-  default StringColumn concatenate(Column... stringColumns) {
+  default StringColumn concatenate(Column<?>... stringColumns) {
     StringColumn newColumn = StringColumn.create(name() + "[append]", this.size());
     for (int r = 0; r < size(); r++) {
       StringBuilder s = new StringBuilder(getString(r));
-      for (Column stringColumn : stringColumns) s.append(stringColumn.getString(r));
+      for (Column<?> stringColumn : stringColumns) s.append(stringColumn.getString(r));
       newColumn.set(r, s.toString());
     }
     return newColumn;
diff --git a/core/src/main/java/tech/tablesaw/io/ReadOptions.java b/core/src/main/java/tech/tablesaw/io/ReadOptions.java
index 3b2ff0cb..28332412 100644
--- a/core/src/main/java/tech/tablesaw/io/ReadOptions.java
+++ b/core/src/main/java/tech/tablesaw/io/ReadOptions.java
@@ -40,6 +40,8 @@ import tech.tablesaw.api.ColumnType;
 
 public class ReadOptions {
 
+  public static final boolean DEFAULT_IGNORE_ZERO_DECIMAL = true;
+
   private static final List<ColumnType> DEFAULT_TYPES =
       Lists.newArrayList(
           LOCAL_DATE_TIME, LOCAL_TIME, LOCAL_DATE, BOOLEAN, INTEGER, LONG, DOUBLE, STRING);
@@ -75,6 +77,7 @@ public class ReadOptions {
   protected final String missingValueIndicator;
   protected final boolean minimizeColumnSizes;
   protected final int maxCharsPerColumn;
+  protected final boolean ignoreZeroDecimal;
 
   protected final DateTimeFormatter dateFormatter;
   protected final DateTimeFormatter dateTimeFormatter;
@@ -94,6 +97,7 @@ public class ReadOptions {
     minimizeColumnSizes = builder.minimizeColumnSizes;
     header = builder.header;
     maxCharsPerColumn = builder.maxCharsPerColumn;
+    ignoreZeroDecimal = builder.ignoreZeroDecimal;
 
     dateFormatter = builder.dateFormatter;
     timeFormatter = builder.timeFormatter;
@@ -138,6 +142,10 @@ public class ReadOptions {
     return header;
   }
 
+  public boolean ignoreZeroDecimal() {
+    return ignoreZeroDecimal;
+  }
+
   public DateTimeFormatter dateTimeFormatter() {
     if (dateTimeFormatter != null) {
       return dateTimeFormatter;
@@ -186,6 +194,7 @@ public class ReadOptions {
     protected boolean minimizeColumnSizes = false;
     protected boolean header = true;
     protected int maxCharsPerColumn = 4096;
+    protected boolean ignoreZeroDecimal = DEFAULT_IGNORE_ZERO_DECIMAL;
 
     protected Builder() {
       source = null;
@@ -269,6 +278,12 @@ public class ReadOptions {
       return this;
     }
 
+    /** Ignore zero value decimals in data values. Defaults to {@code true}. */
+    public Builder ignoreZeroDecimal(boolean ignoreZeroDecimal) {
+      this.ignoreZeroDecimal = ignoreZeroDecimal;
+      return this;
+    }
+
     public Builder sample(boolean sample) {
       this.sample = sample;
       return this;
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 25e281a9..1b67420b 100644
--- a/core/src/main/java/tech/tablesaw/io/csv/CsvReadOptions.java
+++ b/core/src/main/java/tech/tablesaw/io/csv/CsvReadOptions.java
@@ -314,5 +314,11 @@ public class CsvReadOptions extends ReadOptions {
       super.minimizeColumnSizes();
       return this;
     }
+
+    @Override
+    public Builder ignoreZeroDecimal(boolean ignoreZeroDecimal) {
+      super.ignoreZeroDecimal(ignoreZeroDecimal);
+      return this;
+    }
   }
 }
diff --git a/core/src/main/java/tech/tablesaw/io/fixed/FixedWidthReadOptions.java b/core/src/main/java/tech/tablesaw/io/fixed/FixedWidthReadOptions.java
index 1ee50bda..c05b575a 100644
--- a/core/src/main/java/tech/tablesaw/io/fixed/FixedWidthReadOptions.java
+++ b/core/src/main/java/tech/tablesaw/io/fixed/FixedWidthReadOptions.java
@@ -294,5 +294,11 @@ public class FixedWidthReadOptions extends ReadOptions {
       super.minimizeColumnSizes();
       return this;
     }
+
+    @Override
+    public Builder ignoreZeroDecimal(boolean ignoreZeroDecimal) {
+      super.ignoreZeroDecimal(ignoreZeroDecimal);
+      return this;
+    }
   }
 }
diff --git a/core/src/main/java/tech/tablesaw/util/StringUtils.java b/core/src/main/java/tech/tablesaw/util/StringUtils.java
index d9045ece..4b9f310e 100644
--- a/core/src/main/java/tech/tablesaw/util/StringUtils.java
+++ b/core/src/main/java/tech/tablesaw/util/StringUtils.java
@@ -19,6 +19,7 @@ package tech.tablesaw.util;
 import com.google.common.base.Strings;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.regex.Pattern;
 
 /**
  * Operations on {@link java.lang.String} that are {@code null} safe.
@@ -66,6 +67,8 @@ public class StringUtils {
   /** The maximum size to which the padding constant(s) can expand. */
   private static final int PAD_LIMIT = 8192;
 
+  private static final Pattern ZERO_DECIMAL_PATTERN = Pattern.compile("\\.0+$");
+
   private StringUtils() {}
 
   // Empty checks
@@ -520,6 +523,22 @@ public class StringUtils {
     return true;
   }
 
+  /**
+   * Removes all trailing zero decimals from the given String, assuming all decimals are zero and
+   * any zero decimals actually exist.
+   *
+   * <p>A {@code null} input String returns {@code null}.
+   *
+   * @param str the String to handle, may be null
+   * @return string without trailing zero decimals
+   */
+  public static String removeZeroDecimal(final String str) {
+    if (Strings.isNullOrEmpty(str)) {
+      return str;
+    }
+    return ZERO_DECIMAL_PATTERN.matcher(str).replaceFirst(EMPTY);
+  }
+
   // Abbreviating
   // -----------------------------------------------------------------------
   /**
diff --git a/data/immunization.csv b/data/immunization.csv
new file mode 100644
index 00000000..23d460f1
--- /dev/null
+++ b/data/immunization.csv
@@ -0,0 +1,71 @@
+OrgUnitName,OrgUnitCode,Period,BCGCoverage,DropoutRatePenta13,DropoutPenta1,FIC,MeaslesCoverage
+Bombali,OU_193190,March 2019,104.0,27.6,24,75.00,86.0
+Bombali,OU_193190,April 2019,100,28.5,30,74.01,79.0
+Bombali,OU_193190,May 2019,110.00,31.3,29,76.04,79.0
+Bombali,OU_193190,June 2019,104.0,26.8,25,76.0,80.0
+Bombali,OU_193190,July 2019,91.0,25.9,30,75.00,77.0
+Bombali,OU_193190,August 2019,102.000,23.2,25,76.4,81.0
+Bombali,OU_193190,September 2019,100.0,18.1,19,83.00,89.0
+Bombali,OU_193190,October 2019,84.00,23.7,20,69.0,75.0
+Bombali,OU_193190,December 2019,78.0,24.9,21,119.0,68.0
+Bombali,OU_193190,January 2020,104.0,29.9,21,75.2,82.0
+Bombali,OU_193190,February 2020,95.00,22.4,22,77.5,84.0
+Bonthe,OU_197385,March 2019,94,27.2,24,69.02,71.0
+Bonthe,OU_197385,April 2019,99.0,26.6,34,68.0,64.0
+Bonthe,OU_197385,May 2019,102.000,30.3,13,70.8,76.0
+Bonthe,OU_197385,June 2019,142.0,32.9,41,72.6,81.0
+Bonthe,OU_197385,July 2019,115.0,21.4,24,88.0,91.0
+Bonthe,OU_197385,August 2019,140,15.2,22,98.00,100.0
+Bonthe,OU_197385,September 2019,112.0,17,22,112.07,101.0
+Bonthe,OU_197385,November 2019,119.0,15.6,21,96.0,100.0
+Bonthe,OU_197385,January 2020,117.0,36.9,48,58.4,51.0
+Bonthe,OU_197385,February 2020,90.0,29.2,33,69,70.00
+Kailahun,OU_204856,March 2019,96.0,10.6,7,80.2,86.0
+Kailahun,OU_204856,April 2019,91,9.7,2,81.3,93.0
+Kailahun,OU_204856,May 2019,90.00,12.5,8,82.2,88.0
+Kailahun,OU_204856,June 2019,104.0,15.3,26,78.5,81.00
+Kailahun,OU_204856,July 2019,90.00,8.6,19,66,74.0
+Kailahun,OU_204856,August 2019,95.0,11,17,80.0,86.0
+Kailahun,OU_204856,September 2019,85.00,2,6,83.2,83.0
+Kailahun,OU_204856,October 2019,70.000,6.4,19,60.1,60.0
+Kailahun,OU_204856,November 2019,78.0,3.7,13,78.8,81.0
+Kailahun,OU_204856,December 2019,46.0,13.5,12,52.0,55.0
+Kailahun,OU_204856,January 2020,89.00,15.6,17,72.5,76.0
+Kailahun,OU_204856,February 2020,92.00,9.9,11,82.00,87.0
+Kambia,OU_211212,March 2019,107.0,36.1,25,61,81.0
+Kambia,OU_211212,April 2019,112.0,31,25,61,79.0
+Kambia,OU_211212,May 2019,106,32.9,30,53.5,70.0
+Kambia,OU_211212,June 2019,121.0,32.9,26,64.6,84.0
+Kambia,OU_211212,July 2019,91.00,32.2,27,57.5,76.0
+Kambia,OU_211212,August 2019,100.0,33.7,25,59.0,78.000
+Kambia,OU_211212,September 2019,111.00,25.6,20,73.4,89.0
+Kambia,OU_211212,October 2019,97.00,30.6,9,63.9,88.0
+Kambia,OU_211212,November 2019,116.0,26,21,72.2,91.0
+Kambia,OU_211212,December 2019,89,40.6,30,51.5,65
+Kambia,OU_211212,January 2020,106.0,44,23,59.5,81.00
+Kambia,OU_211212,February 2020,118,40,19,67.2,87.0
+Kenema,OU_222616,March 2019,114.00,15.4,2,89.0,94.0
+Kenema,OU_222616,April 2019,108.0,17.7,5,93,92.0
+Kenema,OU_222616,May 2019,127.0,14.1,3,97.8,102.0
+Kenema,OU_222616,June 2019,125.0,20.5,9,90.0,95.0
+Kenema,OU_222616,July 2019,117.0,18.3,18,75.6,80.0
+Kenema,OU_222616,August 2019,100.00,11.3,13,79.6,88.0
+Kenema,OU_222616,September 2019,110.0,4.8,2,92.0,99.0
+Kenema,OU_222616,October 2019,99.0,2.3,4,71.9,78.0
+Kenema,OU_222616,November 2019,100.0,-5.6,2,88.2,91.0
+Kenema,OU_222616,December 2019,87.00,8.3,11,63.0,67.000
+Kenema,OU_222616,January 2020,95.00,25.5,12,76.2,76.0
+Kenema,OU_222616,February 2020,108.00,23.6,8,82.7,82.00
+Koinadugu,OU_226213,March 2019,86.0,12.6,8,72.0,69.0
+Koinadugu,OU_226213,April 2019,71.00,16.9,13,66.1,74.0
+Koinadugu,OU_226213,May 2019,125.0,26.3,11,85.0,92.0
+Koinadugu,OU_226213,June 2019,76,26.2,13,49.7,63.0
+Koinadugu,OU_226213,July 2019,62.0,23.6,16,57.00,63.0
+Koinadugu,OU_226213,August 2019,89.0,14.1,13,76.5,82.0
+Koinadugu,OU_226213,September 2019,108.00,18.3,13,92.5,102.0
+Koinadugu,OU_226213,November 2019,118.0,15.3,23,86.7,94.000
+Koinadugu,OU_226213,January 2020,69.0,29.3,31,54.5,53.0
+Koinadugu,OU_226213,February 2020,73.0,25,15,71.00,76.0
+Kono,OU_233310,March 2019,70.0,23,0,33.7,49.0
+Kono,OU_233310,April 2019,87.0,33.1,11,68.1,69.0
+Kono,OU_233310,May 2019,71.00,15.5,12,35.7,51.0
diff --git a/pom.xml b/pom.xml
index 961c7a52..5cba0a56 100644
--- a/pom.xml
+++ b/pom.xml
@@ -250,8 +250,8 @@
             </plugin>
             <plugin>
                 <groupId>com.cosium.code</groupId>
-                <artifactId>maven-git-code-format</artifactId>
-                <version>1.38</version>
+                <artifactId>git-code-format-maven-plugin</artifactId>
+                <version>2.4</version>
                 <executions>
                     <!-- On commit, format the modified java files -->
                     <execution>


Test patch:
diff --git a/core/src/test/java/tech/tablesaw/columns/ColumnTest.java b/core/src/test/java/tech/tablesaw/columns/ColumnTest.java
index d00a0756..195fe731 100644
--- a/core/src/test/java/tech/tablesaw/columns/ColumnTest.java
+++ b/core/src/test/java/tech/tablesaw/columns/ColumnTest.java
@@ -331,4 +331,13 @@ public class ColumnTest {
     assertContentEquals(
         DoubleColumn.create("t1", new double[] {1, -1, 0}).sorted(Double::compare), -1.0, 0.0, 1.0);
   }
+
+  @Test
+  public void indexOfTest() {
+    DoubleColumn testDoubleCol = DoubleColumn.create("t1", 0, 1, 1);
+    assertEquals(-1, testDoubleCol.indexOf(2));
+    assertEquals(-1, testDoubleCol.indexOf(0));
+    assertEquals(0, testDoubleCol.indexOf(0.0));
+    assertEquals(1, testDoubleCol.indexOf(1.0));
+  }
 }
diff --git a/core/src/test/java/tech/tablesaw/io/csv/CsvReaderTest.java b/core/src/test/java/tech/tablesaw/io/csv/CsvReaderTest.java
index a5343c9c..537eca1c 100644
--- a/core/src/test/java/tech/tablesaw/io/csv/CsvReaderTest.java
+++ b/core/src/test/java/tech/tablesaw/io/csv/CsvReaderTest.java
@@ -177,6 +177,32 @@ public class CsvReaderTest {
     assertArrayEquals(bus_types, columnTypes);
   }
 
+  @Test
+  public void testNumberTypeDetectionIgnoreZeroDecimal() throws IOException {
+    Reader reader = new FileReader("../data/immunization.csv");
+    CsvReadOptions options =
+        CsvReadOptions.builder(reader).header(true).sample(false).ignoreZeroDecimal(true).build();
+
+    // Column index 3 and 7 contain values with none to 3 zero values as suffix
+    // Should map to type INTEGER when ignoreZeroDecimal = true
+    ColumnType[] columnTypes = new CsvReader().detectColumnTypes(reader, options);
+    assertEquals(INTEGER, columnTypes[3]);
+    assertEquals(INTEGER, columnTypes[7]);
+  }
+
+  @Test
+  public void testNumberTypeDetectionRetainZeroDecimal() throws IOException {
+    Reader reader = new FileReader("../data/immunization.csv");
+    CsvReadOptions options =
+        CsvReadOptions.builder(reader).header(true).sample(false).ignoreZeroDecimal(false).build();
+
+    // Column index 3 and 7 contain values with none to 3 zero values as suffix
+    // Should map to type DOUBLE when ignoreZeroDecimal = false
+    ColumnType[] columnTypes = new CsvReader().detectColumnTypes(reader, options);
+    assertEquals(DOUBLE, columnTypes[3]);
+    assertEquals(DOUBLE, columnTypes[7]);
+  }
+
   @Test
   public void testMillis() {
     long[] times = {1530486314124L, 1530488214124L};
diff --git a/core/src/test/java/tech/tablesaw/io/fixed/FixedWidthReaderTest.java b/core/src/test/java/tech/tablesaw/io/fixed/FixedWidthReaderTest.java
index df42bafc..ba208b35 100644
--- a/core/src/test/java/tech/tablesaw/io/fixed/FixedWidthReaderTest.java
+++ b/core/src/test/java/tech/tablesaw/io/fixed/FixedWidthReaderTest.java
@@ -36,7 +36,7 @@ import tech.tablesaw.api.Table;
 public class FixedWidthReaderTest {
 
   private final FixedWidthFields car_fields_specs = new FixedWidthFields(4, 5, 40, 40, 8);
-  private final ColumnType[] car_types = {SHORT, STRING, STRING, STRING, FLOAT};
+  private final ColumnType[] car_types = {SHORT, STRING, STRING, STRING, SHORT};
   private final ColumnType[] car_types_with_SKIP = {SHORT, STRING, STRING, SKIP, FLOAT};
 
   @Test
diff --git a/core/src/test/java/tech/tablesaw/util/StringUtilsTest.java b/core/src/test/java/tech/tablesaw/util/StringUtilsTest.java
new file mode 100644
index 00000000..dee25038
--- /dev/null
+++ b/core/src/test/java/tech/tablesaw/util/StringUtilsTest.java
@@ -0,0 +1,43 @@
+/*
+ * 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.util;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import org.junit.jupiter.api.Test;
+
+public class StringUtilsTest {
+
+  @Test
+  public void testRemoveZeroDecimal() {
+    // Assert zero decimals being removed
+    assertEquals("246", StringUtils.removeZeroDecimal("246.0"));
+    assertEquals("146", StringUtils.removeZeroDecimal("146.00"));
+    assertEquals("357", StringUtils.removeZeroDecimal("357.000"));
+    assertEquals("347", StringUtils.removeZeroDecimal("347.0000"));
+
+    // Assert no change to input value
+    assertEquals("468", StringUtils.removeZeroDecimal("468"));
+    assertEquals("24", StringUtils.removeZeroDecimal("24"));
+    assertEquals("468.02", StringUtils.removeZeroDecimal("468.02"));
+    assertEquals("246.004", StringUtils.removeZeroDecimal("246.004"));
+    assertEquals("246.4000", StringUtils.removeZeroDecimal("246.4000"));
+
+    // Assert empty string and null handling
+    assertEquals("", StringUtils.removeZeroDecimal(""));
+    assertNull(StringUtils.removeZeroDecimal(null));
+  }
+}