Repository: alteryx/woodwork (python)

Issue:
Need better error if user assigns new column to dataframe outside of Woodwork
If a user assigns a new column to a DataFrame, but does so outside of the Woodwork accessor, and subsequently attempts to access the Woodwork information (which should now be invalid), a somewhat confusing `KeyError` is raised. Catching this and raising a better error would be helpful.

There might be other similar operations on the DataFrame that could also cause this type of error.

#### Code Sample, a copy-pastable example to reproduce your bug.

```python
import pandas as pd
import woodwork as ww

df = pd.DataFrame({
    'id': [0, 1, 2],
    'val': [10, 20, 30]
})
df.ww.init()
new = pd.Series([True, False, True], dtype='boolean')
df['new'] = new
df.ww
```

```
KeyError: 'new'

table_accessor.py:586: TypingInfoMismatchWarning: Operation performed by _repr_latex_ has invalidated the Woodwork typing information:
 The following columns in the DataFrame were missing from the typing information: {'new'}.
 Please initialize Woodwork with DataFrame.ww.init
  warnings.warn(TypingInfoMismatchWarning().get_warning_message(attr, invalid_schema_message, 'DataFrame'),
```


Pull request description:
Update to use better error message
Fixes #724 

Verified that it works locally. We use `repr` in the test in order to see the error and allow pytest to catch
<img width="1263" alt="image" src="https://user-images.githubusercontent.com/22552445/160899795-33fb20ef-83a7-4503-ad80-eb8a21a1770f.png">


Patch:
diff --git a/docs/source/release_notes.rst b/docs/source/release_notes.rst
index c5ca93f..0f0ddc2 100644
--- a/docs/source/release_notes.rst
+++ b/docs/source/release_notes.rst
@@ -6,6 +6,7 @@ Release Notes
 Future Release
 ==============
     * Enhancements
+        * Added a better error message when dataframe and schema have different columns (:pr:`1366`)
         * Added type inference for phone numbers (:pr:`1357`)
     * Fixes
     * Changes
@@ -17,7 +18,7 @@ Future Release
     * Testing Changes
 
     Thanks to the following people for contributing to this release:
-    :user:`gsheni`, :user:`rwedge`, :user:`ParthivNaresh`, :user:`thehomebrewnerd`
+    :user:`gsheni`, :user:`rwedge`, :user:`ParthivNaresh`, :user:`thehomebrewnerd`, :user:`bchen1116`
 
 v0.15.0 Mar 24, 2022
 ====================
diff --git a/woodwork/accessor_utils.py b/woodwork/accessor_utils.py
index e265f8b..09561b3 100644
--- a/woodwork/accessor_utils.py
+++ b/woodwork/accessor_utils.py
@@ -3,7 +3,7 @@ from functools import wraps
 import numpy as np
 import pandas as pd
 
-from woodwork.exceptions import WoodworkNotInitError
+from woodwork.exceptions import ColumnNotPresentInSchemaError, WoodworkNotInitError
 from woodwork.utils import _get_column_logical_type, import_or_none
 
 dd = import_or_none("dask.dataframe")
@@ -206,6 +206,11 @@ def _check_table_schema(method):
                 "calling DataFrame.ww.init"
             )
             raise WoodworkNotInitError(msg)
+        diff_cols = set(self._dataframe.columns).difference(
+            set(self._schema.columns.keys())
+        )
+        if diff_cols:
+            raise ColumnNotPresentInSchemaError(sorted(list(diff_cols)))
         return method(self, *args, **kwargs)
 
     return wrapper
diff --git a/woodwork/exceptions.py b/woodwork/exceptions.py
index a4b214f..cc9271d 100644
--- a/woodwork/exceptions.py
+++ b/woodwork/exceptions.py
@@ -68,6 +68,13 @@ class ColumnNotPresentError(KeyError):
             return super().__init__(f"Column(s) '{column}' not found in DataFrame")
 
 
+class ColumnNotPresentInSchemaError(KeyError):
+    def __init__(self, column):
+        return super().__init__(
+            f"Column(s) {column} not found in Woodwork schema. Please initialize with DataFrame.ww.init"
+        )
+
+
 class WoodworkNotInitError(AttributeError):
     pass
 


Test patch:
diff --git a/woodwork/tests/accessor/test_table_accessor.py b/woodwork/tests/accessor/test_table_accessor.py
index d81b6f3..931fe41 100644
--- a/woodwork/tests/accessor/test_table_accessor.py
+++ b/woodwork/tests/accessor/test_table_accessor.py
@@ -16,6 +16,7 @@ from woodwork.accessor_utils import (
 )
 from woodwork.exceptions import (
     ColumnNotPresentError,
+    ColumnNotPresentInSchemaError,
     IndexTagRemovedWarning,
     ParametersIgnoredWarning,
     TypeConversionError,
@@ -2651,8 +2652,9 @@ def test_maintain_column_order_disordered_schema(sample_df):
     column_order = list(sample_df.columns)
 
     scramble_df = sample_df.ww.copy()
-    id_col = scramble_df.ww.columns.pop("id")
-    scramble_df.ww.columns["id"] = id_col
+    table_schema = scramble_df.ww.columns
+    id_col = table_schema.pop("id")
+    table_schema["id"] = id_col
     assert list(scramble_df.ww.columns.keys()) != column_order
 
     assert scramble_df.ww.schema == sample_df.ww.schema
@@ -3060,3 +3062,30 @@ def test_validate_logical_types_call(sample_df):
             assert not validate_method.called
             assert sample_df.ww.validate_logical_types() is None
             assert validate_method.called
+
+
+@pytest.mark.parametrize(
+    "dict_info,expected_str",
+    [
+        ({"new": pd.Series([True, False, True], dtype="boolean")}, "['new']"),
+        (
+            {
+                "new": pd.Series([True, False, True], dtype="boolean"),
+                "another": pd.Series(["no", "maybe", "yes"]),
+            },
+            "['another', 'new']",
+        ),
+    ],
+)
+def test_column_initialized_outside_woodwork_error(dict_info, expected_str):
+    df = pd.DataFrame({"id": [0, 1, 2], "val": [10, 20, 30]})
+    df.ww.init()
+    for col_name, series in dict_info.items():
+        df[col_name] = series
+    message = re.escape(
+        "Column(s) "
+        + expected_str
+        + " not found in Woodwork schema. Please initialize with DataFrame.ww.init"
+    )
+    with pytest.raises(ColumnNotPresentInSchemaError, match=message):
+        repr(df.ww)