Repository: jtablesaw/tablesaw
Pull request #984 (2021-08-28 16:44:12)

#982 Fix for undesirable use of TextColumns as String Columns when slices are converted back to tables.
Thanks for contributing.

- [x] Tick to sign-off your agreement to the [Developer Certificate of Origin (DCO) 1.1](https://developercertificate.org)

## Description

StandardTableSliceGroups create tableSlices that can be reconverted to Tables with structures matching the original 

## Testing

Did you add a unit test? Yes



Patch:
diff --git a/core/src/main/java/tech/tablesaw/table/SelectionTableSliceGroup.java b/core/src/main/java/tech/tablesaw/table/SelectionTableSliceGroup.java
index e8151eab..b1476d51 100644
--- a/core/src/main/java/tech/tablesaw/table/SelectionTableSliceGroup.java
+++ b/core/src/main/java/tech/tablesaw/table/SelectionTableSliceGroup.java
@@ -41,7 +41,7 @@ public class SelectionTableSliceGroup extends TableSliceGroup {
 
   private void splitOnSelection(String nameTemplate, List<Selection> selections) {
     for (int i = 0; i < selections.size(); i++) {
-      TableSlice view = new TableSlice(getSourceTable(), selections.get(i));
+      TableSlice view = new TableSlice(getSourceTable(), selections.get(i), textColumns);
       String name = nameTemplate + ": " + i + 1;
       view.setName(name);
       getSlices().add(view);
diff --git a/core/src/main/java/tech/tablesaw/table/StandardTableSliceGroup.java b/core/src/main/java/tech/tablesaw/table/StandardTableSliceGroup.java
index be289321..3a590608 100644
--- a/core/src/main/java/tech/tablesaw/table/StandardTableSliceGroup.java
+++ b/core/src/main/java/tech/tablesaw/table/StandardTableSliceGroup.java
@@ -93,7 +93,7 @@ public class StandardTableSliceGroup extends TableSliceGroup {
 
     // Add all slices
     for (Entry<ByteArray, Selection> entry : selectionMap.entrySet()) {
-      TableSlice slice = new TableSlice(getSourceTable(), entry.getValue());
+      TableSlice slice = new TableSlice(getSourceTable(), entry.getValue(), textColumns);
       slice.setName(sliceNameMap.get(entry.getKey()));
       addSlice(slice);
     }
diff --git a/core/src/main/java/tech/tablesaw/table/TableSlice.java b/core/src/main/java/tech/tablesaw/table/TableSlice.java
index ba274e16..e83d28ea 100644
--- a/core/src/main/java/tech/tablesaw/table/TableSlice.java
+++ b/core/src/main/java/tech/tablesaw/table/TableSlice.java
@@ -27,6 +27,7 @@ import javax.annotation.Nullable;
 import tech.tablesaw.aggregate.NumericAggregateFunction;
 import tech.tablesaw.api.NumericColumn;
 import tech.tablesaw.api.Row;
+import tech.tablesaw.api.StringColumn;
 import tech.tablesaw.api.Table;
 import tech.tablesaw.columns.Column;
 import tech.tablesaw.selection.Selection;
@@ -43,6 +44,10 @@ import tech.tablesaw.sorting.comparators.IntComparatorChain;
 public class TableSlice extends Relation {
 
   private final Table table;
+
+  // the names of any columns that are TextColumns in the original table
+  private List<String> textColumns = new ArrayList<>();
+
   private String name;
   @Nullable private Selection selection;
   @Nullable private int[] sortOrder = null;
@@ -57,6 +62,17 @@ public class TableSlice extends Relation {
     this.table = table;
   }
 
+  /**
+   * Returns a new View constructed from the given table, containing only the rows represented by
+   * the bitmap
+   */
+  public TableSlice(Table table, Selection rowSelection, List<String> textColumns) {
+    this.name = table.name();
+    this.textColumns = textColumns;
+    this.selection = rowSelection;
+    this.table = table;
+  }
+
   /**
    * Returns a new view constructed from the given table. The view can be sorted independently of
    * the table.
@@ -67,6 +83,17 @@ public class TableSlice extends Relation {
     this.table = table;
   }
 
+  /**
+   * Returns a new view constructed from the given table. The view can be sorted independently of
+   * the table.
+   */
+  public TableSlice(Table table, List<String> textColumns) {
+    this.name = table.name();
+    this.textColumns = textColumns;
+    this.selection = null;
+    this.table = table;
+  }
+
   @Override
   public Column<?> column(int columnIndex) {
     Column<?> col = table.column(columnIndex);
@@ -180,12 +207,20 @@ public class TableSlice extends Relation {
     return this;
   }
 
+  /**
+   * Returns a Table with the data contained in this slice. If the slice contains string columns
+   * that were TextColumns in the original table, they are converted back to TextColumns here
+   */
   public Table asTable() {
-    Table table = Table.create(this.name());
+    Table t = Table.create(this.name());
     for (Column<?> column : this.columns()) {
-      table.addColumns(column);
+      if (textColumns.contains(column.name())) {
+        t.addColumns(((StringColumn) column).asTextColumn());
+      } else {
+        t.addColumns(column);
+      }
     }
-    return table;
+    return t;
   }
 
   /**
diff --git a/core/src/main/java/tech/tablesaw/table/TableSliceGroup.java b/core/src/main/java/tech/tablesaw/table/TableSliceGroup.java
index fe35654f..92dd1c91 100644
--- a/core/src/main/java/tech/tablesaw/table/TableSliceGroup.java
+++ b/core/src/main/java/tech/tablesaw/table/TableSliceGroup.java
@@ -29,7 +29,12 @@ import tech.tablesaw.api.StringColumn;
 import tech.tablesaw.api.Table;
 import tech.tablesaw.columns.Column;
 
-/** A group of tables formed by performing splitting operations on an original table */
+/**
+ * A group of table slices formed by performing splitting operations on an original table
+ *
+ * <p>NOTE: This can use a tremendous amount of memory on a large table containing many TextColumns.
+ * If that is your use case, consider handling this manually instead.
+ */
 public class TableSliceGroup implements Iterable<TableSlice> {
 
   // A string that is used internally as a delimiter in creating a column name from all the grouping
@@ -40,6 +45,8 @@ public class TableSliceGroup implements Iterable<TableSlice> {
   // grouping columns
   private static final Splitter SPLITTER = Splitter.on(SPLIT_STRING);
 
+  protected List<String> textColumns = new ArrayList<>();
+
   // The list of slices or views over the source table that I contain
   private final List<TableSlice> subTables = new ArrayList<>();
 
@@ -84,6 +91,7 @@ public class TableSliceGroup implements Iterable<TableSlice> {
     for (int i = 0; i < sourceTable.columnCount(); i++) {
       if (sourceTable.column(i).type().equals(ColumnType.TEXT)) {
         String originalName = sourceTable.column(i).name();
+        textColumns.add(originalName);
         sourceTable.replaceColumn(i, sourceTable.textColumn(i).asStringColumn());
         sourceTable.column(i).setName(originalName);
       }


Test patch:
diff --git a/core/src/test/java/tech/tablesaw/table/TableSliceTest.java b/core/src/test/java/tech/tablesaw/table/TableSliceTest.java
index 81ddef6b..b212f676 100644
--- a/core/src/test/java/tech/tablesaw/table/TableSliceTest.java
+++ b/core/src/test/java/tech/tablesaw/table/TableSliceTest.java
@@ -8,15 +8,13 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static tech.tablesaw.aggregate.AggregateFunctions.sum;
+import static tech.tablesaw.api.ColumnType.TEXT;
 
 import com.google.common.collect.Streams;
 import java.util.stream.IntStream;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
-import tech.tablesaw.api.IntColumn;
-import tech.tablesaw.api.Row;
-import tech.tablesaw.api.StringColumn;
-import tech.tablesaw.api.Table;
+import tech.tablesaw.api.*;
 import tech.tablesaw.columns.Column;
 import tech.tablesaw.selection.Selection;
 import tech.tablesaw.sorting.Sort;
@@ -316,4 +314,15 @@ public class TableSliceTest {
       assertNotNull(slice.structure());
     }
   }
+
+  /** Test that we can append both text and string columns to a string column */
+  @Test
+  void asTableWithTextColumn() {
+    Table sourceCopy = source.copy();
+    sourceCopy.replaceColumn("who", sourceCopy.stringColumn("who").asTextColumn());
+    TableSliceGroup group = sourceCopy.splitOn("who");
+    TableSlice slice = group.get(0);
+    Table t = slice.asTable();
+    assertEquals(t.column("who").type(), TEXT);
+  }
 }