Repository: alteryx/woodwork (python)

Issue:
Add WoodworkTableAccessor.pop
- Use pop to remove a series from a DataFrame with Woodwork initialized inplace, returning the series with Woodwork initialized
- Handle the case where Woodwork isn't initialized and return the series without woodwork initialized


Pull request description:
Add pop to WoodworkTableAccessor
- Removes column from dataframe and returns the series with woodwork typing information initialized
- Closes #614 

Patch:
diff --git a/docs/source/api_reference.rst b/docs/source/api_reference.rst
index b58f70f..d5b9119 100644
--- a/docs/source/api_reference.rst
+++ b/docs/source/api_reference.rst
@@ -17,6 +17,7 @@ WoodworkTableAccessor
     WoodworkTableAccessor.loc
     WoodworkTableAccessor.mutual_information_dict
     WoodworkTableAccessor.mutual_information
+    WoodworkTableAccessor.pop
     WoodworkTableAccessor.select
     WoodworkTableAccessor.set_index
     WoodworkTableAccessor.set_types
diff --git a/docs/source/release_notes.rst b/docs/source/release_notes.rst
index 0aadc04..8c5e713 100644
--- a/docs/source/release_notes.rst
+++ b/docs/source/release_notes.rst
@@ -32,6 +32,7 @@ Release Notes
         * Add DaskColumnAccessor (:pr:`625`)
         * Allow deserialization from csv, pickle, and parquet to Woodwork table (:pr:`626`)
         * Add ``value_counts`` to WoodworkTableAccessor (:pr:`632`)
+        * Add ``pop`` to WoodworkTableAccessor (:pr:`636`)
     * Fixes
         * Create new Schema object when performing pandas operation on Accessors (:pr:`595`)
     * Changes
diff --git a/woodwork/table_accessor.py b/woodwork/table_accessor.py
index 560842a..a5f9fe5 100644
--- a/woodwork/table_accessor.py
+++ b/woodwork/table_accessor.py
@@ -433,6 +433,30 @@ class WoodworkTableAccessor:
 
         return new_df
 
+    def pop(self, column_name):
+        """Return a Series with Woodwork typing information and remove it from the DataFrame.
+
+        Args:
+            column (str): Name of the column to pop.
+
+        Returns:
+            (pd.Series): Popped series with Woodwork initialized
+        """
+        if column_name not in self._dataframe.columns:
+            raise LookupError(f'Column with name {column_name} not found in DataFrame')
+
+        series = self._dataframe.pop(column_name)
+
+        # Initialize Woodwork typing info for series
+        col_schema = self._schema.columns[column_name]
+        del col_schema['dtype']
+        series.ww.init(**col_schema, use_standard_tags=self.use_standard_tags)
+
+        # Update schema to not include popped column
+        del self._schema.columns[column_name]
+
+        return series
+
     def mutual_information_dict(self, num_bins=10, nrows=None):
         """
         Calculates mutual information between all pairs of columns in the DataFrame that


Test patch:
diff --git a/woodwork/tests/accessor/test_table_accessor.py b/woodwork/tests/accessor/test_table_accessor.py
index 0353203..7948061 100644
--- a/woodwork/tests/accessor/test_table_accessor.py
+++ b/woodwork/tests/accessor/test_table_accessor.py
@@ -824,18 +824,20 @@ def test_accessor_with_falsy_column_names(falsy_names_df):
     schema_df.ww.set_time_index(None)
     assert schema_df.ww.time_index is None
 
-    # --> add back in when series accessor is implemented
-    # for col_name in falsy_names_df.columns:
-    #     dc = dt[col_name]
-    #     assert dc.name == col_name
-    #     assert dc._series.name == col_name
-
-    # --> add back in once pop and rename are implemented on the accessor
-    # popped_col = dt.pop('')
+    schema_df.ww.set_time_index('')
+    assert schema_df.ww.time_index == ''
+
+    schema_df.ww.pop('')
+    assert '' not in schema_df
+    assert '' not in schema_df.ww.columns
+    assert schema_df.ww.time_index is None
+
+    # --> add back in if setitem is implemented
     # dt[''] = popped_col
     # assert dt[''].name == ''
     # assert dt['']._series.name == ''
 
+    # --> add back in once rename is implemented on the accessor
     # dt = dt.rename({'': 'col_with_name'})
     # assert '' not in dt.columns
     # assert 'col_with_name' in dt.columns
@@ -976,14 +978,6 @@ def test_dataframe_methods_on_accessor_returning_series(sample_df):
     assert schema_df.ww.name == 'test_schema'
     pd.testing.assert_series_equal(memory, schema_df.memory_usage())
 
-    warning = "Operation performed by pop has invalidated the Woodwork typing information:\n "
-    "The following columns in the typing information were missing from the DataFrame: {'id'}.\n "
-    "Please initialize Woodwork with DataFrame.ww.init"
-    with pytest.warns(TypingInfoMismatchWarning, match=warning):
-        schema_df.ww.pop('id')
-    assert 'id' not in schema_df.columns
-    assert schema_df.ww.schema is None
-
 
 def test_dataframe_methods_on_accessor_other_returns(sample_df):
     xfail_dask_and_koalas(sample_df)
@@ -1397,3 +1391,60 @@ def test_set_types_errors(sample_df):
                       "use DataFrame.ww.set_index() instead.")
     with pytest.raises(ValueError, match=error):
         sample_df.ww.set_types(semantic_tags={'email': 'index'})
+
+
+def test_pop(sample_df):
+    xfail_dask_and_koalas(sample_df)
+
+    schema_df = sample_df.copy()
+    schema_df.ww.init(semantic_tags={'age': 'custom_tag'})
+    original_schema = schema_df.ww.schema
+
+    popped_series = schema_df.ww.pop('age')
+
+    assert isinstance(popped_series, pd.Series)
+    assert popped_series.ww.semantic_tags == {'custom_tag', 'numeric'}
+    assert all(to_pandas(popped_series).values == [33, 25, 33, 57])
+    assert popped_series.ww.logical_type == Integer
+
+    assert 'age' not in schema_df.columns
+    assert 'age' not in schema_df.ww.columns
+
+    assert 'age' not in schema_df.ww.logical_types.keys()
+    assert 'age' not in schema_df.ww.semantic_tags.keys()
+
+    assert schema_df.ww.schema == original_schema._get_subset_schema(list(schema_df.columns))
+
+    schema_df = sample_df.copy()
+    schema_df.ww.init(
+        name='table',
+        logical_types={'age': Integer},
+        semantic_tags={'age': 'custom_tag'},
+        use_standard_tags=False)
+
+    popped_series = schema_df.ww.pop('age')
+
+    assert popped_series.ww.semantic_tags == {'custom_tag'}
+
+
+def test_pop_index(sample_df):
+    xfail_dask_and_koalas(sample_df)
+
+    sample_df.ww.init(index='id', name='dt_name')
+    assert sample_df.ww.index == 'id'
+    id_col = sample_df.ww.pop('id')
+    assert sample_df.ww.index is None
+    assert 'index' in id_col.ww.semantic_tags
+
+
+def test_pop_error(sample_df):
+    xfail_dask_and_koalas(sample_df)
+
+    sample_df.ww.init(
+        name='table',
+        logical_types={'age': Integer},
+        semantic_tags={'age': 'custom_tag'},
+        use_standard_tags=True)
+
+    with pytest.raises(LookupError, match="Column with name missing not found in DataFrame"):
+        sample_df.ww.pop("missing")