Repository: jtablesaw/tablesaw
Pull request #1034 (2021-11-03 18:55:46)

Simplify adding new column to table
Thanks for contributing.

- [x] Tick to sign-off your agreement to the [Developer Certificate of Origin (DCO) 1.1](https://developercertificate.org)

## Description

Automatically fill empty columns to match the size of the table they are added to. Columns that are not empty must continue to match the size of the table when added as adding a 24 row column to a 23 row table is most likely a bug

## Testing

Did you add a unit test? Yes. 



Patch:
diff --git a/core/src/main/java/tech/tablesaw/analytic/AnalyticQueryEngine.java b/core/src/main/java/tech/tablesaw/analytic/AnalyticQueryEngine.java
index 23b36a8b..6a43688c 100644
--- a/core/src/main/java/tech/tablesaw/analytic/AnalyticQueryEngine.java
+++ b/core/src/main/java/tech/tablesaw/analytic/AnalyticQueryEngine.java
@@ -29,6 +29,7 @@ final class AnalyticQueryEngine {
     this.rowComparator = sort.isPresent() ? SortUtils.getChain(query.getTable(), sort.get()) : null;
   }
 
+  /** Returns a new AnalyticQueryEngine to execute the given query */
   public static AnalyticQueryEngine create(AnalyticQuery query) {
     return new AnalyticQueryEngine(query);
   }
diff --git a/core/src/main/java/tech/tablesaw/analytic/FunctionMetaData.java b/core/src/main/java/tech/tablesaw/analytic/FunctionMetaData.java
index 9b5d6fcb..76e4928a 100644
--- a/core/src/main/java/tech/tablesaw/analytic/FunctionMetaData.java
+++ b/core/src/main/java/tech/tablesaw/analytic/FunctionMetaData.java
@@ -4,9 +4,13 @@ import tech.tablesaw.api.ColumnType;
 
 /** Base class for an Analytic Function. */
 interface FunctionMetaData {
+
+  /** Returns the name of the function */
   String functionName();
 
+  /** Returns the type of column that will hold the return values of the function */
   ColumnType returnType();
 
+  /** Returns true if the function can be applied to data of the given {@link ColumnType} */
   boolean isCompatibleColumn(ColumnType type);
 }
diff --git a/core/src/main/java/tech/tablesaw/api/Table.java b/core/src/main/java/tech/tablesaw/api/Table.java
index 1d71a228..a84c7aae 100644
--- a/core/src/main/java/tech/tablesaw/api/Table.java
+++ b/core/src/main/java/tech/tablesaw/api/Table.java
@@ -236,7 +236,10 @@ public class Table extends Relation implements Iterable<Row> {
     return new DataFrameWriter(defaultWriterRegistry, this);
   }
 
-  /** Adds the given column to this table */
+  /**
+   * Adds the given column to this table. Column must either be empty or have size() == the
+   * rowCount() of the table they're being added to. Column names in the table must remain unique.
+   */
   @Override
   public Table addColumns(final Column<?>... cols) {
     for (final Column<?> c : cols) {
@@ -257,7 +260,9 @@ public class Table extends Relation implements Iterable<Row> {
 
   /**
    * Throws an IllegalArgumentException if a column with the given name is already in the table, or
-   * if the number of rows in the column does not match the number of rows in the table
+   * if the number of rows in the column does not match the number of rows in the table. Regarding
+   * the latter rule, however, if the column is completely empty (size == 0), then it is filled with
+   * missing values to match the rowSize() as a convenience.
    */
   private void validateColumn(final Column<?> newColumn) {
     Preconditions.checkNotNull(
@@ -278,10 +283,18 @@ public class Table extends Relation implements Iterable<Row> {
 
   /**
    * Throws an IllegalArgumentException if the column size doesn't match the rowCount() for the
-   * table
+   * table. Columns that are completely empty, however, are initialized to match rowcount by filling
+   * with missing values
    */
   private void checkColumnSize(Column<?> newColumn) {
     if (columnCount() != 0) {
+      if (!isEmpty()) {
+        if (newColumn.isEmpty()) {
+          while (newColumn.size() < rowCount()) {
+            newColumn.appendMissing();
+          }
+        }
+      }
       Preconditions.checkArgument(
           newColumn.size() == rowCount(),
           "Column "
@@ -291,7 +304,9 @@ public class Table extends Relation implements Iterable<Row> {
   }
 
   /**
-   * Adds the given column to this table at the given position in the column list
+   * Adds the given column to this table at the given position in the column list. Columns must
+   * either be empty or have size() == the rowCount() of the table they're being added to. Column
+   * names in the table must remain unique.
    *
    * @param index Zero-based index into the column list
    * @param column Column to be added


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 0313e011..f483bf61 100644
--- a/core/src/test/java/tech/tablesaw/api/TableTest.java
+++ b/core/src/test/java/tech/tablesaw/api/TableTest.java
@@ -279,6 +279,16 @@ public class TableTest {
         });
   }
 
+  @Test
+  void appendSmallerColumnToEmptyTable() {
+    assertThrows(
+        IllegalArgumentException.class,
+        () -> {
+          table.doubleColumn("f1").append(23).append(42);
+          table.addColumns(StringColumn.create("test", 1));
+        });
+  }
+
   @Test
   void testCountBy() {
     assertEquals(3, bush.countBy("who", "date").columnCount());
@@ -286,8 +296,7 @@ public class TableTest {
 
   @Test
   void appendEmptyColumnToPopulatedTable() {
-    assertThrows(
-        IllegalArgumentException.class,
+    assertDoesNotThrow(
         () -> {
           table.doubleColumn("f1").append(23);
           table.addColumns(StringColumn.create("test"));