Repository: alteryx/woodwork (python)

Issue:
Cleanup dtype usage in Accessor
In order to remove the `dtype` field from the Schema dictionary (#653), we need to make sure we have consistent dtype usage across woodwork. There are several changes that should be made :
- replace `pandas_dtype` with `primary_dtype` since it also gets used for Dask
- Confirm we compare strings when looking at dtype equality (at Schema validation and at in _make_dataframe_call)
  - add tests that catch the bugs that happen when strings aren't being compared (see #668)
- pass `type(series)` to `_get_valid_dtype` so that it's clear we never look at the data when determine a logical types valid dtype
- make sure we never do the logic to get the backup dtype directly and either call `_get_valid_dtype` or look at the series' dtype

Pull request description:
Cleanup dtype usage
- cleans up how we use dtypes across Woodwork
- Closes #681 

Patch:
diff --git a/docs/source/release_notes.rst b/docs/source/release_notes.rst
index a4f1e7c..6559086 100644
--- a/docs/source/release_notes.rst
+++ b/docs/source/release_notes.rst
@@ -50,6 +50,7 @@ Release Notes
         * Bump min Koalas version to 1.4.0 (:pr:`638`)
         * Preserve pandas underlying index when not creating a Woodwork index (:pr:`664`)
         * Restrict Koalas version to ``<1.7.0`` due to breaking changes (:pr:`674`)
+        * Clean up dtype usage across Woodwork (:pr:`682`)
     * Documentation Changes
         * Update docstrings and API Reference page (:pr:`660`)
     * Testing Changes
diff --git a/woodwork/accessor_utils.py b/woodwork/accessor_utils.py
index 34756f6..4ec8241 100644
--- a/woodwork/accessor_utils.py
+++ b/woodwork/accessor_utils.py
@@ -65,11 +65,8 @@ def _update_column_dtype(series, logical_type):
             series = ks.from_pandas(formatted_series)
         else:
             series = series.apply(_reformat_to_latlong)
-    if logical_type.pandas_dtype != str(series.dtype):
-        if ks and isinstance(series, ks.Series) and logical_type.backup_dtype:
-            new_dtype = logical_type.backup_dtype
-        else:
-            new_dtype = logical_type.pandas_dtype
+    if logical_type.primary_dtype != str(series.dtype):
+        new_dtype = _get_valid_dtype(type(series), logical_type)
         # Update the underlying series
         try:
             if _get_ltype_class(logical_type) == Datetime:
@@ -113,13 +110,13 @@ def _is_dataframe(data):
     return False
 
 
-def _get_valid_dtype(series, logical_type):
+def _get_valid_dtype(series_type, logical_type):
     """Return the dtype that is considered valid for a series
     with the given logical_type"""
     backup_dtype = logical_type.backup_dtype
-    if ks and isinstance(series, ks.Series) and backup_dtype:
+    if ks and series_type == ks.Series and backup_dtype:
         valid_dtype = backup_dtype
     else:
-        valid_dtype = logical_type.pandas_dtype
+        valid_dtype = logical_type.primary_dtype
 
     return valid_dtype
diff --git a/woodwork/column_accessor.py b/woodwork/column_accessor.py
index aff1993..17a437d 100644
--- a/woodwork/column_accessor.py
+++ b/woodwork/column_accessor.py
@@ -192,8 +192,8 @@ class WoodworkColumnAccessor:
 
                 # Try to initialize Woodwork with the existing Schema
                 if _is_series(result):
-                    valid_dtype = _get_valid_dtype(result, self._schema['logical_type'])
-                    if result.dtype == valid_dtype:
+                    valid_dtype = _get_valid_dtype(type(result), self._schema['logical_type'])
+                    if str(result.dtype) == valid_dtype:
                         schema = copy.deepcopy(self._schema)
                         # We don't need to pass dtype from the schema to init
                         del schema['dtype']
@@ -214,7 +214,7 @@ class WoodworkColumnAccessor:
     def _validate_logical_type(self, logical_type):
         """Validates that a logical type is consistent with the series dtype. Performs additional type
         specific validation, as required."""
-        valid_dtype = _get_valid_dtype(self._series, logical_type)
+        valid_dtype = _get_valid_dtype(type(self._series), logical_type)
         if valid_dtype != str(self._series.dtype):
             raise ValueError(f"Cannot initialize Woodwork. Series dtype '{self._series.dtype}' is "
                              f"incompatible with {logical_type} dtype. Try converting series "
diff --git a/woodwork/datacolumn.py b/woodwork/datacolumn.py
index 514310a..b701c34 100644
--- a/woodwork/datacolumn.py
+++ b/woodwork/datacolumn.py
@@ -114,7 +114,7 @@ class DataColumn(object):
             else:
                 self._series = self._series.apply(_reformat_to_latlong)
 
-        if self.logical_type.pandas_dtype != str(self._series.dtype):
+        if self.logical_type.primary_dtype != str(self._series.dtype):
             # Update the underlying series
             try:
                 if _get_ltype_class(self.logical_type) == Datetime:
@@ -132,11 +132,11 @@ class DataColumn(object):
                     if ks and isinstance(self._series, ks.Series) and self.logical_type.backup_dtype:
                         new_dtype = self.logical_type.backup_dtype
                     else:
-                        new_dtype = self.logical_type.pandas_dtype
+                        new_dtype = self.logical_type.primary_dtype
                     self._series = self._series.astype(new_dtype)
             except (TypeError, ValueError):
                 error_msg = f'Error converting datatype for column {self.name} from type {str(self._series.dtype)} ' \
-                    f'to type {self.logical_type.pandas_dtype}. Please confirm the underlying data is consistent with ' \
+                    f'to type {self.logical_type.primary_dtype}. Please confirm the underlying data is consistent with ' \
                     f'logical type {self.logical_type}.'
                 raise TypeError(error_msg)
 
diff --git a/woodwork/logical_types.py b/woodwork/logical_types.py
index 0788b1d..e29bb41 100644
--- a/woodwork/logical_types.py
+++ b/woodwork/logical_types.py
@@ -20,7 +20,7 @@ class LogicalTypeMetaClass(type):
 class LogicalType(object, metaclass=LogicalTypeMetaClass):
     """Base class for all other Logical Types"""
     type_string = ClassNameDescriptor()
-    pandas_dtype = 'string'
+    primary_dtype = 'string'
     backup_dtype = None
     standard_tags = set()
 
@@ -40,7 +40,7 @@ class Boolean(LogicalType):
             [True, False, True]
             [0, 1, 1]
     """
-    pandas_dtype = 'boolean'
+    primary_dtype = 'boolean'
 
 
 class Categorical(LogicalType):
@@ -54,7 +54,7 @@ class Categorical(LogicalType):
             ["produce", "dairy", "bakery"]
             [3, 1, 2]
     """
-    pandas_dtype = 'category'
+    primary_dtype = 'category'
     backup_dtype = 'string'
     standard_tags = {'category'}
 
@@ -74,7 +74,7 @@ class CountryCode(LogicalType):
             ["AUS", "USA", "UKR"]
             ["GB", "NZ", "DE"]
     """
-    pandas_dtype = 'category'
+    primary_dtype = 'category'
     backup_dtype = 'string'
     standard_tags = {'category'}
 
@@ -92,7 +92,7 @@ class Datetime(LogicalType):
              "2020-01-10 00:00:00",
              "01/01/2000 08:30"]
     """
-    pandas_dtype = 'datetime64[ns]'
+    primary_dtype = 'datetime64[ns]'
     datetime_format = None
 
     def __init__(self, datetime_format=None):
@@ -110,7 +110,7 @@ class Double(LogicalType):
             [1.2, 100.4, 3.5]
             [-15.34, 100, 58.3]
     """
-    pandas_dtype = 'float64'
+    primary_dtype = 'float64'
     standard_tags = {'numeric'}
 
 
@@ -125,7 +125,7 @@ class Integer(LogicalType):
             [100, 35, 0]
             [-54, 73, 11]
     """
-    pandas_dtype = 'Int64'
+    primary_dtype = 'Int64'
     standard_tags = {'numeric'}
 
 
@@ -139,7 +139,7 @@ class EmailAddress(LogicalType):
              "support@example.com",
              "team@example.com"]
     """
-    pandas_dtype = 'string'
+    primary_dtype = 'string'
 
 
 class Filepath(LogicalType):
@@ -153,7 +153,7 @@ class Filepath(LogicalType):
              "/Users/john.smith/dev/index.html",
              "/tmp"]
     """
-    pandas_dtype = 'string'
+    primary_dtype = 'string'
 
 
 class FullName(LogicalType):
@@ -167,7 +167,7 @@ class FullName(LogicalType):
              "Doe, Mrs. Jane",
              "James Brown"]
     """
-    pandas_dtype = 'string'
+    primary_dtype = 'string'
 
 
 class IPAddress(LogicalType):
@@ -181,7 +181,7 @@ class IPAddress(LogicalType):
              "192.0.0.0",
              "2001:0db8:0000:0000:0000:ff00:0042:8329"]
     """
-    pandas_dtype = 'string'
+    primary_dtype = 'string'
 
 
 class LatLong(LogicalType):
@@ -203,7 +203,7 @@ class LatLong(LogicalType):
              (40.423599, -86.921162),
              (-45.031705, nan)]
     """
-    pandas_dtype = 'object'
+    primary_dtype = 'object'
 
 
 class NaturalLanguage(LogicalType):
@@ -217,7 +217,7 @@ class NaturalLanguage(LogicalType):
              "I like to eat pizza!",
              "When will humans go to mars?"]
     """
-    pandas_dtype = 'string'
+    primary_dtype = 'string'
 
 
 class Ordinal(LogicalType):
@@ -235,7 +235,7 @@ class Ordinal(LogicalType):
             ["first", "second", "third"]
             ["bronze", "silver", "gold"]
     """
-    pandas_dtype = 'category'
+    primary_dtype = 'category'
     backup_dtype = 'string'
     standard_tags = {'category'}
 
@@ -268,7 +268,7 @@ class PhoneNumber(LogicalType):
              "+1-555-123-5495",
              "5551235495"]
     """
-    pandas_dtype = 'string'
+    primary_dtype = 'string'
 
 
 class SubRegionCode(LogicalType):
@@ -281,7 +281,7 @@ class SubRegionCode(LogicalType):
             ["US-CO", "US-MA", "US-CA"]
             ["AU-NSW", "AU-TAS", "AU-QLD"]
     """
-    pandas_dtype = 'category'
+    primary_dtype = 'category'
     backup_dtype = 'string'
     standard_tags = {'category'}
 
@@ -296,7 +296,7 @@ class Timedelta(LogicalType):
              pd.Timedelta('-1 days +23:40:00'),
              pd.Timedelta('4 days 12:00:00')]
     """
-    pandas_dtype = 'timedelta64[ns]'
+    primary_dtype = 'timedelta64[ns]'
 
 
 class URL(LogicalType):
@@ -310,7 +310,7 @@ class URL(LogicalType):
              "https://example.com/index.html",
              "example.com"]
     """
-    pandas_dtype = 'string'
+    primary_dtype = 'string'
 
 
 class ZIPCode(LogicalType):
@@ -325,6 +325,6 @@ class ZIPCode(LogicalType):
              "60018-0123",
              "10021"]
     """
-    pandas_dtype = 'category'
+    primary_dtype = 'category'
     backup_dtype = 'string'
     standard_tags = {'category'}
diff --git a/woodwork/schema_column.py b/woodwork/schema_column.py
index c88dc53..c88d0a5 100644
--- a/woodwork/schema_column.py
+++ b/woodwork/schema_column.py
@@ -36,7 +36,7 @@ def _get_column_dict(name,
     semantic_tags = _get_column_tags(semantic_tags, logical_type, use_standard_tags, name)
 
     return {
-        'dtype': logical_type.pandas_dtype,
+        'dtype': logical_type.primary_dtype,
         'logical_type': logical_type,
         'semantic_tags': semantic_tags,
         'description': description,
diff --git a/woodwork/table_accessor.py b/woodwork/table_accessor.py
index 93da0ec..b299c49 100644
--- a/woodwork/table_accessor.py
+++ b/woodwork/table_accessor.py
@@ -805,8 +805,8 @@ def _get_invalid_schema_message(dataframe, schema):
             f'{schema_cols_not_in_df}'
     for name in dataframe.columns:
         df_dtype = dataframe[name].dtype
-        valid_dtype = _get_valid_dtype(dataframe[name], schema.logical_types[name])
-        if df_dtype != valid_dtype:
+        valid_dtype = _get_valid_dtype(type(dataframe[name]), schema.logical_types[name])
+        if str(df_dtype) != valid_dtype:
             return f'dtype mismatch for column {name} between DataFrame dtype, '\
                 f'{df_dtype}, and {schema.logical_types[name]} dtype, {valid_dtype}'
     if schema.index is not None and isinstance(dataframe, pd.DataFrame):
diff --git a/woodwork/type_sys/utils.py b/woodwork/type_sys/utils.py
index 210caa4..37237e3 100644
--- a/woodwork/type_sys/utils.py
+++ b/woodwork/type_sys/utils.py
@@ -74,7 +74,7 @@ def list_logical_types():
         [{'name': ltype.__name__,
           'type_string': ltype.type_string,
           'description': ltype.__doc__,
-          'physical_type': ltype.pandas_dtype,
+          'physical_type': ltype.primary_dtype,
           'standard_tags': ltype.standard_tags,
           'is_default_type': ltype in ww.type_system._default_inference_functions,
           'is_registered': ltype in ww.type_system.registered_types,


Test patch:
diff --git a/woodwork/tests/accessor/test_accessor_utils.py b/woodwork/tests/accessor/test_accessor_utils.py
index 5256570..aad9c83 100644
--- a/woodwork/tests/accessor/test_accessor_utils.py
+++ b/woodwork/tests/accessor/test_accessor_utils.py
@@ -22,14 +22,14 @@ ks = import_or_none('databricks.koalas')
 def test_init_series_valid_conversion_specified_ltype(sample_series):
     series = init_series(sample_series, logical_type='categorical')
     assert series is not sample_series
-    correct_dtype = _get_valid_dtype(sample_series, Categorical)
+    correct_dtype = _get_valid_dtype(type(sample_series), Categorical)
     assert series.dtype == correct_dtype
     assert series.ww.logical_type == Categorical
     assert series.ww.semantic_tags == {'category'}
 
     series = init_series(sample_series, logical_type='natural_language')
     assert series is not sample_series
-    correct_dtype = _get_valid_dtype(sample_series, NaturalLanguage)
+    correct_dtype = _get_valid_dtype(type(sample_series), NaturalLanguage)
     assert series.dtype == correct_dtype
     assert series.ww.logical_type == NaturalLanguage
     assert series.ww.semantic_tags == set()
@@ -38,7 +38,7 @@ def test_init_series_valid_conversion_specified_ltype(sample_series):
 def test_init_series_valid_conversion_inferred_ltype(sample_series):
     series = init_series(sample_series)
     assert series is not sample_series
-    correct_dtype = _get_valid_dtype(sample_series, Categorical)
+    correct_dtype = _get_valid_dtype(type(sample_series), Categorical)
     assert series.dtype == correct_dtype
     assert series.ww.logical_type == Categorical
     assert series.ww.semantic_tags == {'category'}
@@ -60,7 +60,7 @@ def test_init_series_all_parameters(sample_series):
                          description=description,
                          use_standard_tags=False)
     assert series is not sample_series
-    correct_dtype = _get_valid_dtype(sample_series, Categorical)
+    correct_dtype = _get_valid_dtype(type(sample_series), Categorical)
     assert series.dtype == correct_dtype
     assert series.ww.logical_type == Categorical
     assert series.ww.semantic_tags == {'custom_tag'}
@@ -91,11 +91,11 @@ def test_is_dataframe(sample_df):
 
 
 def test_get_valid_dtype(sample_series):
-    valid_dtype = _get_valid_dtype(sample_series, Categorical)
+    valid_dtype = _get_valid_dtype(type(sample_series), Categorical)
     if ks and isinstance(sample_series, ks.Series):
         assert valid_dtype == 'string'
     else:
         assert valid_dtype == 'category'
 
-    valid_dtype = _get_valid_dtype(sample_series, Boolean)
+    valid_dtype = _get_valid_dtype(type(sample_series), Boolean)
     assert valid_dtype == 'boolean'
diff --git a/woodwork/tests/accessor/test_column_accessor.py b/woodwork/tests/accessor/test_column_accessor.py
index b4b689c..5214b5b 100644
--- a/woodwork/tests/accessor/test_column_accessor.py
+++ b/woodwork/tests/accessor/test_column_accessor.py
@@ -196,7 +196,7 @@ def test_adds_numeric_standard_tag():
 
     logical_types = [Integer, Double]
     for logical_type in logical_types:
-        series = series.astype(logical_type.pandas_dtype)
+        series = series.astype(logical_type.primary_dtype)
         series.ww.init(logical_type=logical_type, semantic_tags=semantic_tags)
         assert series.ww.semantic_tags == {'custom_tag', 'numeric'}
 
@@ -401,6 +401,21 @@ def test_series_methods_on_accessor_returning_series_valid_schema(sample_series)
     pd.testing.assert_series_equal(to_pandas(replace_series), to_pandas(series.replace('a', 'd')))
 
 
+def test_series_methods_on_accessor_dtype_mismatch(sample_df):
+    ints_series = convert_series(sample_df['id'], Integer)
+    ints_series.ww.init()
+
+    assert ints_series.ww.logical_type == Integer
+    assert str(ints_series.dtype) == 'Int64'
+
+    warning = ("Operation performed by astype has invalidated the Woodwork typing information:\n "
+               "dtype mismatch between original dtype, Int64, and returned dtype, int64.\n "
+               "Please initialize Woodwork with Series.ww.init")
+    with pytest.warns(TypingInfoMismatchWarning, match=warning):
+        series = ints_series.ww.astype('int64')
+    assert series.ww._schema is None
+
+
 def test_series_methods_on_accessor_inplace(sample_series):
     # TODO: Try to find a supported inplace method for Dask, if one exists
     if dd and isinstance(sample_series, dd.Series):
diff --git a/woodwork/tests/accessor/test_statistics.py b/woodwork/tests/accessor/test_statistics.py
index 4b0bcbf..c6d2132 100644
--- a/woodwork/tests/accessor/test_statistics.py
+++ b/woodwork/tests/accessor/test_statistics.py
@@ -296,7 +296,7 @@ def test_describe_accessor_method(describe_df):
     datetime_data = describe_df[['datetime_col']]
     for ltype in datetime_ltypes:
         expected_vals = pd.Series({
-            'physical_type': ltype.pandas_dtype,
+            'physical_type': ltype.primary_dtype,
             'logical_type': ltype,
             'semantic_tags': {'custom_tag'},
             'count': 7,
@@ -325,7 +325,7 @@ def test_describe_accessor_method(describe_df):
                                                 '2020-02-01',
                                                 '2020-01-02'])
         expected_vals = pd.Series({
-            'physical_type': ltype.pandas_dtype,
+            'physical_type': ltype.primary_dtype,
             'logical_type': ltype,
             'semantic_tags': {'custom_tag'},
             'count': 7,
@@ -349,7 +349,7 @@ def test_describe_accessor_method(describe_df):
         timedelta_data = describe_df['timedelta_col']
         for ltype in timedelta_ltypes:
             expected_vals = pd.Series({
-                'physical_type': ltype.pandas_dtype,
+                'physical_type': ltype.primary_dtype,
                 'logical_type': ltype,
                 'semantic_tags': {'custom_tag'},
                 'count': 7,
@@ -367,7 +367,7 @@ def test_describe_accessor_method(describe_df):
     numeric_data = describe_df[['numeric_col']]
     for ltype in numeric_ltypes:
         expected_vals = pd.Series({
-            'physical_type': ltype.pandas_dtype,
+            'physical_type': ltype.primary_dtype,
             'logical_type': ltype,
             'semantic_tags': {'numeric', 'custom_tag'},
             'count': 7,
diff --git a/woodwork/tests/accessor/test_table_accessor.py b/woodwork/tests/accessor/test_table_accessor.py
index 9e7002e..03f50bf 100644
--- a/woodwork/tests/accessor/test_table_accessor.py
+++ b/woodwork/tests/accessor/test_table_accessor.py
@@ -514,8 +514,8 @@ def test_sets_category_dtype_on_init():
             df = pd.DataFrame(series)
             df.ww.init(logical_types=ltypes)
             assert df.ww.columns[column_name]['logical_type'] == logical_type
-            assert df.ww.columns[column_name]['dtype'] == logical_type.pandas_dtype
-            assert df[column_name].dtype == logical_type.pandas_dtype
+            assert df.ww.columns[column_name]['dtype'] == logical_type.primary_dtype
+            assert df[column_name].dtype == logical_type.primary_dtype
 
 
 def test_sets_object_dtype_on_init(latlong_df):
@@ -526,8 +526,8 @@ def test_sets_object_dtype_on_init(latlong_df):
         df = latlong_df.loc[:, [column_name]]
         df.ww.init(logical_types=ltypes)
         assert df.ww.columns[column_name]['logical_type'] == LatLong
-        assert df.ww.columns[column_name]['dtype'] == LatLong.pandas_dtype
-        assert df[column_name].dtype == LatLong.pandas_dtype
+        assert df.ww.columns[column_name]['dtype'] == LatLong.primary_dtype
+        assert df[column_name].dtype == LatLong.primary_dtype
         df_pandas = to_pandas(df[column_name])
         expected_val = (3, 4)
         if ks and isinstance(latlong_df, ks.DataFrame):
@@ -562,8 +562,8 @@ def test_sets_string_dtype_on_init():
             df = pd.DataFrame(series)
             df.ww.init(logical_types=ltypes)
             assert df.ww.columns[column_name]['logical_type'] == logical_type
-            assert df.ww.columns[column_name]['dtype'] == logical_type.pandas_dtype
-            assert df[column_name].dtype == logical_type.pandas_dtype
+            assert df.ww.columns[column_name]['dtype'] == logical_type.primary_dtype
+            assert df[column_name].dtype == logical_type.primary_dtype
 
 
 def test_sets_boolean_dtype_on_init():
@@ -584,8 +584,8 @@ def test_sets_boolean_dtype_on_init():
         df = pd.DataFrame(series)
         df.ww.init(logical_types=ltypes)
         assert df.ww.columns[column_name]['logical_type'] == logical_type
-        assert df.ww.columns[column_name]['dtype'] == logical_type.pandas_dtype
-        assert df[column_name].dtype == logical_type.pandas_dtype
+        assert df.ww.columns[column_name]['dtype'] == logical_type.primary_dtype
+        assert df[column_name].dtype == logical_type.primary_dtype
 
 
 def test_sets_int64_dtype_on_init():
@@ -607,8 +607,8 @@ def test_sets_int64_dtype_on_init():
             df = pd.DataFrame(series)
             df.ww.init(logical_types=ltypes)
             assert df.ww.columns[column_name]['logical_type'] == logical_type
-            assert df.ww.columns[column_name]['dtype'] == logical_type.pandas_dtype
-            assert df[column_name].dtype == logical_type.pandas_dtype
+            assert df.ww.columns[column_name]['dtype'] == logical_type.primary_dtype
+            assert df[column_name].dtype == logical_type.primary_dtype
 
 
 def test_sets_float64_dtype_on_init():
@@ -628,8 +628,8 @@ def test_sets_float64_dtype_on_init():
         df = pd.DataFrame(series)
         df.ww.init(logical_types=ltypes)
         assert df.ww.columns[column_name]['logical_type'] == logical_type
-        assert df.ww.columns[column_name]['dtype'] == logical_type.pandas_dtype
-        assert df[column_name].dtype == logical_type.pandas_dtype
+        assert df.ww.columns[column_name]['dtype'] == logical_type.primary_dtype
+        assert df[column_name].dtype == logical_type.primary_dtype
 
 
 def test_sets_datetime64_dtype_on_init():
@@ -651,8 +651,8 @@ def test_sets_datetime64_dtype_on_init():
         df = pd.DataFrame(series)
         df.ww.init(logical_types=ltypes)
         assert df.ww.columns[column_name]['logical_type'] == logical_type
-        assert df.ww.columns[column_name]['dtype'] == logical_type.pandas_dtype
-        assert df[column_name].dtype == logical_type.pandas_dtype
+        assert df.ww.columns[column_name]['dtype'] == logical_type.primary_dtype
+        assert df[column_name].dtype == logical_type.primary_dtype
 
 
 def test_invalid_dtype_casting():
@@ -997,6 +997,28 @@ def test_get_invalid_schema_message(sample_df):
             "The following columns in the DataFrame were missing from the typing information: {'new_col'}")
 
 
+def test_get_invalid_schema_message_dtype_mismatch(sample_df):
+    schema_df = sample_df.copy()
+    schema_df.ww.init(logical_types={'age': 'Categorical'})
+    schema = schema_df.ww.schema
+
+    incorrect_int_dtype_df = schema_df.ww.astype({'id': 'int64'})
+    incorrect_bool_dtype_df = schema_df.ww.astype({'is_registered': 'bool'})
+    incorrect_str_dtype_df = schema_df.ww.astype({'full_name': 'object'})  # wont work for koalas
+    incorrect_categorical_dtype_df = schema_df.ww.astype({'age': 'string'})  # wont work for koalas
+
+    assert (_get_invalid_schema_message(incorrect_int_dtype_df, schema) ==
+            'dtype mismatch for column id between DataFrame dtype, int64, and Integer dtype, Int64')
+    assert (_get_invalid_schema_message(incorrect_bool_dtype_df, schema) ==
+            'dtype mismatch for column is_registered between DataFrame dtype, bool, and Boolean dtype, boolean')
+    # Koalas backup dtypes make these checks not relevant
+    if ks and not isinstance(sample_df, ks.DataFrame):
+        assert (_get_invalid_schema_message(incorrect_str_dtype_df, schema) ==
+                'dtype mismatch for column full_name between DataFrame dtype, object, and NaturalLanguage dtype, string')
+        assert (_get_invalid_schema_message(incorrect_categorical_dtype_df, schema) ==
+                'dtype mismatch for column age between DataFrame dtype, string, and Categorical dtype, category')
+
+
 def test_get_invalid_schema_message_index_checks(sample_df):
     if not isinstance(sample_df, pd.DataFrame):
         pytest.xfail('Index validation not performed for Dask or Koalas DataFrames')
diff --git a/woodwork/tests/datatable/test_datatable.py b/woodwork/tests/datatable/test_datatable.py
index 48079ac..446ba24 100644
--- a/woodwork/tests/datatable/test_datatable.py
+++ b/woodwork/tests/datatable/test_datatable.py
@@ -631,7 +631,7 @@ def test_datatable_describe_method(describe_df):
     datetime_data = describe_df[['datetime_col']]
     for ltype in datetime_ltypes:
         expected_vals = pd.Series({
-            'physical_type': ltype.pandas_dtype,
+            'physical_type': ltype.primary_dtype,
             'logical_type': ltype,
             'semantic_tags': {'custom_tag'},
             'count': 7,
@@ -660,7 +660,7 @@ def test_datatable_describe_method(describe_df):
                                                 '2020-02-01',
                                                 '2020-01-02'])
         expected_vals = pd.Series({
-            'physical_type': ltype.pandas_dtype,
+            'physical_type': ltype.primary_dtype,
             'logical_type': ltype,
             'semantic_tags': {'custom_tag'},
             'count': 7,
@@ -684,7 +684,7 @@ def test_datatable_describe_method(describe_df):
         timedelta_data = describe_df['timedelta_col']
         for ltype in timedelta_ltypes:
             expected_vals = pd.Series({
-                'physical_type': ltype.pandas_dtype,
+                'physical_type': ltype.primary_dtype,
                 'logical_type': ltype,
                 'semantic_tags': {'custom_tag'},
                 'count': 7,
@@ -702,7 +702,7 @@ def test_datatable_describe_method(describe_df):
     numeric_data = describe_df[['numeric_col']]
     for ltype in numeric_ltypes:
         expected_vals = pd.Series({
-            'physical_type': ltype.pandas_dtype,
+            'physical_type': ltype.primary_dtype,
             'logical_type': ltype,
             'semantic_tags': {'numeric', 'custom_tag'},
             'count': 7,
diff --git a/woodwork/tests/datatable/test_datatable_dtypes.py b/woodwork/tests/datatable/test_datatable_dtypes.py
index 55f04e0..d1177db 100644
--- a/woodwork/tests/datatable/test_datatable_dtypes.py
+++ b/woodwork/tests/datatable/test_datatable_dtypes.py
@@ -60,8 +60,8 @@ def test_sets_category_dtype_on_init():
             }
             dt = DataTable(pd.DataFrame(series), logical_types=ltypes)
             assert dt.columns[column_name].logical_type == logical_type
-            assert dt.columns[column_name].dtype == logical_type.pandas_dtype
-            assert dt.to_dataframe()[column_name].dtype == logical_type.pandas_dtype
+            assert dt.columns[column_name].dtype == logical_type.primary_dtype
+            assert dt.to_dataframe()[column_name].dtype == logical_type.primary_dtype
 
 
 def test_sets_category_dtype_on_update():
@@ -83,8 +83,8 @@ def test_sets_category_dtype_on_update():
         dt = DataTable(pd.DataFrame(series), logical_types=ltypes)
         dt = dt.set_types(logical_types={column_name: logical_type})
         assert dt.columns[column_name].logical_type == logical_type
-        assert dt.columns[column_name].dtype == logical_type.pandas_dtype
-        assert dt.to_dataframe()[column_name].dtype == logical_type.pandas_dtype
+        assert dt.columns[column_name].dtype == logical_type.primary_dtype
+        assert dt.to_dataframe()[column_name].dtype == logical_type.primary_dtype
 
 
 def test_sets_object_dtype_on_init(latlong_df):
@@ -94,8 +94,8 @@ def test_sets_object_dtype_on_init(latlong_df):
         }
         dt = DataTable(latlong_df.loc[:, [column_name]], logical_types=ltypes)
         assert dt.columns[column_name].logical_type == LatLong
-        assert dt.columns[column_name].dtype == LatLong.pandas_dtype
-        assert dt.to_dataframe()[column_name].dtype == LatLong.pandas_dtype
+        assert dt.columns[column_name].dtype == LatLong.primary_dtype
+        assert dt.to_dataframe()[column_name].dtype == LatLong.primary_dtype
 
 
 def test_sets_object_dtype_on_update(latlong_df):
@@ -106,8 +106,8 @@ def test_sets_object_dtype_on_update(latlong_df):
         dt = DataTable(latlong_df.loc[:, [column_name]], logical_types=ltypes)
         dt = dt.set_types(logical_types={column_name: LatLong})
         assert dt.columns[column_name].logical_type == LatLong
-        assert dt.columns[column_name].dtype == LatLong.pandas_dtype
-        assert dt.to_dataframe()[column_name].dtype == LatLong.pandas_dtype
+        assert dt.columns[column_name].dtype == LatLong.primary_dtype
+        assert dt.to_dataframe()[column_name].dtype == LatLong.primary_dtype
 
 
 def test_sets_string_dtype_on_init():
@@ -136,8 +136,8 @@ def test_sets_string_dtype_on_init():
             }
             dt = DataTable(pd.DataFrame(series), logical_types=ltypes)
             assert dt.columns[column_name].logical_type == logical_type
-            assert dt.columns[column_name].dtype == logical_type.pandas_dtype
-            assert dt.to_dataframe()[column_name].dtype == logical_type.pandas_dtype
+            assert dt.columns[column_name].dtype == logical_type.primary_dtype
+            assert dt.to_dataframe()[column_name].dtype == logical_type.primary_dtype
 
 
 def test_sets_string_dtype_on_update():
@@ -160,8 +160,8 @@ def test_sets_string_dtype_on_update():
         dt = DataTable(pd.DataFrame(series), logical_types=ltypes)
         dt = dt.set_types(logical_types={column_name: logical_type})
         assert dt.columns[column_name].logical_type == logical_type
-        assert dt.columns[column_name].dtype == logical_type.pandas_dtype
-        assert dt.to_dataframe()[column_name].dtype == logical_type.pandas_dtype
+        assert dt.columns[column_name].dtype == logical_type.primary_dtype
+        assert dt.to_dataframe()[column_name].dtype == logical_type.primary_dtype
 
 
 def test_sets_boolean_dtype_on_init():
@@ -181,8 +181,8 @@ def test_sets_boolean_dtype_on_init():
         }
         dt = DataTable(pd.DataFrame(series), logical_types=ltypes)
         assert dt.columns[column_name].logical_type == logical_type
-        assert dt.columns[column_name].dtype == logical_type.pandas_dtype
-        assert dt.to_dataframe()[column_name].dtype == logical_type.pandas_dtype
+        assert dt.columns[column_name].dtype == logical_type.primary_dtype
+        assert dt.to_dataframe()[column_name].dtype == logical_type.primary_dtype
 
 
 def test_sets_boolean_dtype_on_update():
@@ -195,8 +195,8 @@ def test_sets_boolean_dtype_on_update():
     dt = DataTable(pd.DataFrame(series), logical_types=ltypes)
     dt = dt.set_types(logical_types={column_name: Boolean})
     assert dt.columns[column_name].logical_type == Boolean
-    assert dt.columns[column_name].dtype == Boolean.pandas_dtype
-    assert dt.to_dataframe()[column_name].dtype == Boolean.pandas_dtype
+    assert dt.columns[column_name].dtype == Boolean.primary_dtype
+    assert dt.to_dataframe()[column_name].dtype == Boolean.primary_dtype
 
 
 def test_sets_int64_dtype_on_init():
@@ -217,8 +217,8 @@ def test_sets_int64_dtype_on_init():
             }
             dt = DataTable(pd.DataFrame(series), logical_types=ltypes)
         assert dt.columns[column_name].logical_type == logical_type
-        assert dt.columns[column_name].dtype == logical_type.pandas_dtype
-        assert dt.to_dataframe()[column_name].dtype == logical_type.pandas_dtype
+        assert dt.columns[column_name].dtype == logical_type.primary_dtype
+        assert dt.to_dataframe()[column_name].dtype == logical_type.primary_dtype
 
 
 def test_sets_int64_dtype_on_update():
@@ -234,8 +234,8 @@ def test_sets_int64_dtype_on_update():
         dt = DataTable(pd.DataFrame(series), logical_types=ltypes)
         dt = dt.set_types(logical_types={column_name: logical_type})
         assert dt.columns[column_name].logical_type == logical_type
-        assert dt.columns[column_name].dtype == logical_type.pandas_dtype
-        assert dt.to_dataframe()[column_name].dtype == logical_type.pandas_dtype
+        assert dt.columns[column_name].dtype == logical_type.primary_dtype
+        assert dt.to_dataframe()[column_name].dtype == logical_type.primary_dtype
 
 
 def test_sets_float64_dtype_on_init():
@@ -254,8 +254,8 @@ def test_sets_float64_dtype_on_init():
         }
         dt = DataTable(pd.DataFrame(series), logical_types=ltypes)
         assert dt.columns[column_name].logical_type == logical_type
-        assert dt.columns[column_name].dtype == logical_type.pandas_dtype
-        assert dt.to_dataframe()[column_name].dtype == logical_type.pandas_dtype
+        assert dt.columns[column_name].dtype == logical_type.primary_dtype
+        assert dt.to_dataframe()[column_name].dtype == logical_type.primary_dtype
 
 
 def test_sets_float64_dtype_on_update():
@@ -268,8 +268,8 @@ def test_sets_float64_dtype_on_update():
     dt = DataTable(pd.DataFrame(series), logical_types=ltypes)
     dt = dt.set_types(logical_types={column_name: Double})
     assert dt.columns[column_name].logical_type == Double
-    assert dt.columns[column_name].dtype == Double.pandas_dtype
-    assert dt.to_dataframe()[column_name].dtype == Double.pandas_dtype
+    assert dt.columns[column_name].dtype == Double.primary_dtype
+    assert dt.to_dataframe()[column_name].dtype == Double.primary_dtype
 
 
 def test_sets_datetime64_dtype_on_init():
@@ -290,8 +290,8 @@ def test_sets_datetime64_dtype_on_init():
         }
         dt = DataTable(pd.DataFrame(series), logical_types=ltypes)
         assert dt.columns[column_name].logical_type == logical_type
-        assert dt.columns[column_name].dtype == logical_type.pandas_dtype
-        assert dt.to_dataframe()[column_name].dtype == logical_type.pandas_dtype
+        assert dt.columns[column_name].dtype == logical_type.primary_dtype
+        assert dt.to_dataframe()[column_name].dtype == logical_type.primary_dtype
 
 
 def test_sets_datetime_dtype_on_update():
@@ -304,8 +304,8 @@ def test_sets_datetime_dtype_on_update():
     dt = DataTable(pd.DataFrame(series), logical_types=ltypes)
     dt = dt.set_types(logical_types={column_name: Datetime})
     assert dt.columns[column_name].logical_type == Datetime
-    assert dt.columns[column_name].dtype == Datetime.pandas_dtype
-    assert dt.to_dataframe()[column_name].dtype == Datetime.pandas_dtype
+    assert dt.columns[column_name].dtype == Datetime.primary_dtype
+    assert dt.to_dataframe()[column_name].dtype == Datetime.primary_dtype
 
 
 def test_invalid_dtype_casting():
diff --git a/woodwork/tests/schema/test_schema.py b/woodwork/tests/schema/test_schema.py
index cb60d23..df650a6 100644
--- a/woodwork/tests/schema/test_schema.py
+++ b/woodwork/tests/schema/test_schema.py
@@ -25,7 +25,7 @@ def test_schema_physical_types(sample_column_names, sample_inferred_logical_type
     assert isinstance(schema.physical_types, dict)
     assert set(schema.physical_types.keys()) == set(sample_column_names)
     for k, v in schema.physical_types.items():
-        assert v == schema.columns[k]['logical_type'].pandas_dtype
+        assert v == schema.columns[k]['logical_type'].primary_dtype
 
 
 def test_schema_logical_types(sample_column_names, sample_inferred_logical_types):
diff --git a/woodwork/tests/test_utils.py b/woodwork/tests/test_utils.py
index 6e4f465..b226ec8 100644
--- a/woodwork/tests/test_utils.py
+++ b/woodwork/tests/test_utils.py
@@ -102,10 +102,10 @@ def test_list_logical_types_customized_type_system():
     ww.type_system.remove_type('URL')
 
     class CustomRegistered(ww.logical_types.LogicalType):
-        pandas_dtype = 'int64'
+        primary_dtype = 'int64'
 
     class CustomNotRegistered(ww.logical_types.LogicalType):
-        pandas_dtype = 'int64'
+        primary_dtype = 'int64'
 
     ww.type_system.add_type(CustomRegistered)
     all_ltypes = ww.logical_types.LogicalType.__subclasses__()
diff --git a/woodwork/tests/testing_utils/column_utils.py b/woodwork/tests/testing_utils/column_utils.py
index efa3cc7..b9a91b1 100644
--- a/woodwork/tests/testing_utils/column_utils.py
+++ b/woodwork/tests/testing_utils/column_utils.py
@@ -4,9 +4,9 @@ ks = import_or_none('databricks.koalas')
 
 
 def convert_series(series, logical_type):
-    """Converts a series to match the pandas_dtype of the provided logical type
+    """Converts a series to match the primary_dtype of the provided logical type
     for pandas/Dask, or converts to the backup_dtype for Koalas if one is defined"""
     if ks and isinstance(series, ks.Series) and logical_type.backup_dtype:
         return series.astype(logical_type.backup_dtype)
     else:
-        return series.astype(logical_type.pandas_dtype)
+        return series.astype(logical_type.primary_dtype)
diff --git a/woodwork/tests/type_system/test_custom_types.py b/woodwork/tests/type_system/test_custom_types.py
index 092840b..9d11f8c 100644
--- a/woodwork/tests/type_system/test_custom_types.py
+++ b/woodwork/tests/type_system/test_custom_types.py
@@ -7,7 +7,7 @@ from woodwork.logical_types import Categorical, Double, LogicalType
 
 def test_register_custom_logical_type(type_sys):
     class CustomLogicalType(LogicalType):
-        pandas_dtype = 'string'
+        primary_dtype = 'string'
 
     def custom_func(series):
         return True
@@ -23,7 +23,7 @@ def test_register_custom_logical_type(type_sys):
 
 def test_custom_type_with_datatable(sample_df):
     class AgesAbove20(LogicalType):
-        pandas_dtype = 'float64'
+        primary_dtype = 'float64'
         standard_tags = {'age', 'numeric'}
 
     def ages_func(series):
diff --git a/woodwork/tests/type_system/test_ltype_inference.py b/woodwork/tests/type_system/test_ltype_inference.py
index d746db7..30433b2 100644
--- a/woodwork/tests/type_system/test_ltype_inference.py
+++ b/woodwork/tests/type_system/test_ltype_inference.py
@@ -164,7 +164,7 @@ def test_updated_ltype_inference(integers, type_sys):
     type_sys.remove_type(ww.logical_types.Integer)
 
     class Integer(LogicalType):
-        pandas_dtype = 'string'
+        primary_dtype = 'string'
 
     type_sys.add_type(Integer, inference_function=inference_fn)
 
@@ -176,4 +176,4 @@ def test_updated_ltype_inference(integers, type_sys):
         for dtype in dtypes:
             inferred_type = type_sys.infer_logical_type(series.astype(dtype))
             assert inferred_type == Integer
-            assert inferred_type.pandas_dtype == 'string'
+            assert inferred_type.primary_dtype == 'string'
diff --git a/woodwork/tests/type_system/test_type_system.py b/woodwork/tests/type_system/test_type_system.py
index cb3391b..a02abb7 100644
--- a/woodwork/tests/type_system/test_type_system.py
+++ b/woodwork/tests/type_system/test_type_system.py
@@ -142,7 +142,7 @@ def test_add_duplicate_ltype(type_sys):
     assert ww.type_system.str_to_logical_type('Integer') == ww.logical_types.Integer
 
     class Integer(LogicalType):
-        pandas_dtype = 'string'
+        primary_dtype = 'string'
 
     error_msg = 'Logical Type with name Integer already present in the Type System. Please rename the LogicalType or remove existing one.'
     with pytest.raises(ValueError, match=error_msg):
@@ -152,12 +152,12 @@ def test_add_duplicate_ltype(type_sys):
     type_sys.add_type(Integer, inference_function=inference_fn)
 
     ltype = type_sys.str_to_logical_type('Integer')
-    assert ltype.pandas_dtype == 'string'
+    assert ltype.primary_dtype == 'string'
     assert ltype == Integer
 
     type_sys.reset_defaults()
     ltype = type_sys.str_to_logical_type('Integer')
-    assert ltype.pandas_dtype == 'Int64'
+    assert ltype.primary_dtype == 'Int64'
     assert ltype == ww.logical_types.Integer