Repository: alteryx/featuretools

Issue:
Move set_secondary_time_index method from Entity to EntitySet
For Woodwork integration the `set_secondary_time_index` method needs to be moved from `Entity` to `EntitySet` as this method will not be present on a `ww.DataTable`.

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/alteryx/featuretools/pull/1280?src=pr&el=h1) Report
> Merging [#1280](https://codecov.io/gh/alteryx/featuretools/pull/1280?src=pr&el=desc) (17eb463) into [woodwork-integration](https://codecov.io/gh/alteryx/featuretools/commit/653a179971212ffbbeba815ab89dca6f5e8d13ad?el=desc) (653a179) will **decrease** coverage by `0.00%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/alteryx/featuretools/pull/1280/graphs/tree.svg?width=650&height=150&src=pr&token=PqDLD96eXJ)](https://codecov.io/gh/alteryx/featuretools/pull/1280?src=pr&el=tree)

```diff
@@                   Coverage Diff                    @@
##           woodwork-integration    #1280      +/-   ##
========================================================
- Coverage                 98.44%   98.44%   -0.01%     
========================================================
  Files                       134      134              
  Lines                     14466    14465       -1     
========================================================
- Hits                      14241    14240       -1     
  Misses                      225      225              
```


| [Impacted Files](https://codecov.io/gh/alteryx/featuretools/pull/1280?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [featuretools/entityset/entity.py](https://codecov.io/gh/alteryx/featuretools/pull/1280/diff?src=pr&el=tree#diff-ZmVhdHVyZXRvb2xzL2VudGl0eXNldC9lbnRpdHkucHk=) | `97.48% <100.00%> (-0.17%)` | :arrow_down: |
| [featuretools/entityset/entityset.py](https://codecov.io/gh/alteryx/featuretools/pull/1280/diff?src=pr&el=tree#diff-ZmVhdHVyZXRvb2xzL2VudGl0eXNldC9lbnRpdHlzZXQucHk=) | `98.12% <100.00%> (+0.04%)` | :arrow_up: |
| [featuretools/tests/entityset\_tests/test\_es.py](https://codecov.io/gh/alteryx/featuretools/pull/1280/diff?src=pr&el=tree#diff-ZmVhdHVyZXRvb2xzL3Rlc3RzL2VudGl0eXNldF90ZXN0cy90ZXN0X2VzLnB5) | `98.86% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/alteryx/featuretools/pull/1280?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/alteryx/featuretools/pull/1280?src=pr&el=footer). Last update [653a179...d54ef69](https://codecov.io/gh/alteryx/featuretools/pull/1280?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/docs/source/release_notes.rst b/docs/source/release_notes.rst
index f3f9df1f..d702f61a 100644
--- a/docs/source/release_notes.rst
+++ b/docs/source/release_notes.rst
@@ -11,6 +11,7 @@ Release Notes
         * Move ``query_by_values`` method from ``Entity`` to ``EntitySet`` (:pr:`1251`)
         * Remove ``add_interesting_values`` from ``Entity`` (:pr:`1269`)
         * Move ``_handle_time`` method from ``Entity`` to ``EntitySet`` (:pr:`1276`)
+        * Move ``set_secondary_time_index`` method from ``Entity`` to ``EntitySet`` (:pr:`1280`)
     * Documentation Changes
         * Fix installation command for Add-ons (:pr:`1279`)
     * Testing Changes
@@ -27,6 +28,8 @@ Release Notes
 * ``Entity.add_interesting_values`` has been removed. To add interesting values for a single
     entity, call ``EntitySet.add_interesting_values`` and pass the id of the entity for
     which to add interesting values in the ``entity_id`` parameter. 
+* ``Entity.set_secondary_time_index`` has been removed and replaced by ``EntitySet.set_secondary_time_index``
+    with an added ``entity`` parameter to specify the entity on which to set the secondary time index.
 
 **v0.22.0 Nov 30, 2020**
     * Enhancements
diff --git a/featuretools/entityset/entity.py b/featuretools/entityset/entity.py
index fc6a9f4d..5c5032c8 100644
--- a/featuretools/entityset/entity.py
+++ b/featuretools/entityset/entity.py
@@ -1,7 +1,6 @@
 import warnings
 
 import dask.dataframe as dd
-import numpy as np
 import pandas as pd
 
 from featuretools import variable_types as vtypes
@@ -76,7 +75,7 @@ class Entity(object):
         if time_index:
             self.set_time_index(time_index, already_sorted=already_sorted)
 
-        self.set_secondary_time_index(secondary_time_index)
+        entityset.set_secondary_time_index(self, secondary_time_index)
 
     def __repr__(self):
         repr_out = u"Entity: {}\n".format(self.id)
@@ -281,7 +280,7 @@ class Entity(object):
         if self.time_index is not None:
             self.set_time_index(self.time_index, already_sorted=already_sorted)
 
-        self.set_secondary_time_index(self.secondary_time_index)
+        self.entityset.set_secondary_time_index(self, self.secondary_time_index)
         if recalculate_last_time_indexes and self.last_time_index is not None:
             self.entityset.add_last_time_indexes(updated_entities=[self.id])
         self.entityset.reset_data_description()
@@ -360,25 +359,6 @@ class Entity(object):
         self.convert_variable_type(variable_id, vtypes.Index, convert_data=False)
         self.index = variable_id
 
-    def set_secondary_time_index(self, secondary_time_index):
-        for time_index, columns in secondary_time_index.items():
-            if is_instance(self.df, (dd, ks), 'DataFrame') or self.df.empty:
-                time_to_check = vtypes.DEFAULT_DTYPE_VALUES[self[time_index]._default_pandas_dtype]
-            else:
-                time_to_check = self.df[time_index].head(1).iloc[0]
-            time_type = _check_time_type(time_to_check)
-            if time_type is None:
-                raise TypeError("%s time index not recognized as numeric or"
-                                " datetime" % (self.id))
-            if self.entityset.time_type != time_type:
-                raise TypeError("%s time index is %s type which differs from"
-                                " other entityset time indexes" %
-                                (self.id, time_type))
-            if time_index not in columns:
-                columns.append(time_index)
-
-        self.secondary_time_index = secondary_time_index
-
 
 def _create_index(index, make_index, df):
     '''Handles index creation logic base on user input'''
diff --git a/featuretools/entityset/entityset.py b/featuretools/entityset/entityset.py
index 80d29eac..9e902b2c 100644
--- a/featuretools/entityset/entityset.py
+++ b/featuretools/entityset/entityset.py
@@ -19,7 +19,7 @@ from featuretools.utils.plot_utils import (
     get_graphviz_format,
     save_graph
 )
-from featuretools.utils.wrangle import _check_timedelta
+from featuretools.utils.wrangle import _check_time_type, _check_timedelta
 
 ks = import_or_none('databricks.koalas')
 
@@ -291,6 +291,26 @@ class EntitySet(object):
         self.reset_data_description()
         return self
 
+    def set_secondary_time_index(self, entity, secondary_time_index):
+        for time_index, columns in secondary_time_index.items():
+            if is_instance(entity.df, (dd, ks), 'DataFrame') or entity.df.empty:
+                variable_dtype = entity[time_index]._default_pandas_dtype
+                time_to_check = vtypes.DEFAULT_DTYPE_VALUES[variable_dtype]
+            else:
+                time_to_check = entity.df[time_index].head(1).iloc[0]
+            time_type = _check_time_type(time_to_check)
+            if time_type is None:
+                raise TypeError("%s time index not recognized as numeric or"
+                                " datetime" % (entity.id))
+            if self.time_type != time_type:
+                raise TypeError("%s time index is %s type which differs from"
+                                " other entityset time indexes" %
+                                (entity.id, time_type))
+            if time_index not in columns:
+                columns.append(time_index)
+
+        entity.secondary_time_index = secondary_time_index
+
     ###########################################################################
     #   Relationship access/helper methods  ###################################
     ###########################################################################


Test patch:
diff --git a/featuretools/tests/entityset_tests/test_es.py b/featuretools/tests/entityset_tests/test_es.py
index 38263db3..74669744 100644
--- a/featuretools/tests/entityset_tests/test_es.py
+++ b/featuretools/tests/entityset_tests/test_es.py
@@ -971,27 +971,27 @@ def test_checks_time_type_setting_secondary_time_index(es):
     # add secondary index that is timestamp type
     new_2nd_ti = {'upgrade_date': ['upgrade_date', 'favorite_quote'],
                   'cancel_date': ['cancel_date', 'cancel_reason']}
-    es["customers"].set_secondary_time_index(new_2nd_ti)
+    es.set_secondary_time_index(es["customers"], new_2nd_ti)
     assert es.time_type == variable_types.DatetimeTimeIndex
     # add secondary index that is numeric type
     new_2nd_ti = {'age': ['age', 'loves_ice_cream']}
 
     error_text = "customers time index is <class 'featuretools.variable_types.variable.NumericTimeIndex'> type which differs from other entityset time indexes"
     with pytest.raises(TypeError, match=error_text):
-        es["customers"].set_secondary_time_index(new_2nd_ti)
+        es.set_secondary_time_index(es["customers"], new_2nd_ti)
     # add secondary index that is non-time type
     new_2nd_ti = {'favorite_quote': ['favorite_quote', 'loves_ice_cream']}
 
     error_text = r"data type (\"|')(All members of the working classes must seize the means of production.|test)(\"|') not understood"
     with pytest.raises(TypeError, match=error_text):
-        es["customers"].set_secondary_time_index(new_2nd_ti)
+        es.set_secondary_time_index(es["customers"], new_2nd_ti)
     # add mismatched pair of secondary time indexes
     new_2nd_ti = {'upgrade_date': ['upgrade_date', 'favorite_quote'],
                   'age': ['age', 'loves_ice_cream']}
 
     error_text = "customers time index is <class 'featuretools.variable_types.variable.NumericTimeIndex'> type which differs from other entityset time indexes"
     with pytest.raises(TypeError, match=error_text):
-        es["customers"].set_secondary_time_index(new_2nd_ti)
+        es.set_secondary_time_index(es["customers"], new_2nd_ti)
 
     # create entityset with numeric time type
     cards_df = pd.DataFrame({"id": [1, 2, 3, 4, 5]})
@@ -1013,30 +1013,30 @@ def test_checks_time_type_setting_secondary_time_index(es):
     assert card_es.time_type == variable_types.NumericTimeIndex
     # add secondary index that is numeric time type
     new_2nd_ti = {'fraud_decision_time': ['fraud_decision_time', 'fraud']}
-    card_es['transactions'].set_secondary_time_index(new_2nd_ti)
+    card_es.set_secondary_time_index(card_es['transactions'], new_2nd_ti)
     assert card_es.time_type == variable_types.NumericTimeIndex
     # add secondary index that is timestamp type
     new_2nd_ti = {'transaction_date': ['transaction_date', 'fraud']}
 
     error_text = "transactions time index is <class 'featuretools.variable_types.variable.DatetimeTimeIndex'> type which differs from other entityset time indexes"
     with pytest.raises(TypeError, match=error_text):
-        card_es['transactions'].set_secondary_time_index(new_2nd_ti)
+        card_es.set_secondary_time_index(card_es['transactions'], new_2nd_ti)
     # add secondary index that is non-time type
     new_2nd_ti = {'transaction_city': ['transaction_city', 'fraud']}
 
     error_text = r"data type ('|\")City A('|\") not understood"
     with pytest.raises(TypeError, match=error_text):
-        card_es['transactions'].set_secondary_time_index(new_2nd_ti)
+        card_es.set_secondary_time_index(card_es['transactions'], new_2nd_ti)
     # add mixed secondary time indexes
     new_2nd_ti = {'transaction_city': ['transaction_city', 'fraud'],
                   'fraud_decision_time': ['fraud_decision_time', 'fraud']}
     with pytest.raises(TypeError, match=error_text):
-        card_es['transactions'].set_secondary_time_index(new_2nd_ti)
+        card_es.set_secondary_time_index(card_es['transactions'], new_2nd_ti)
 
     # add bool secondary time index
     error_text = 'transactions time index not recognized as numeric or datetime'
     with pytest.raises(TypeError, match=error_text):
-        card_es['transactions'].set_secondary_time_index({'fraud': ['fraud']})
+        card_es.set_secondary_time_index(card_es['transactions'], {'fraud': ['fraud']})
 
 
 def test_normalize_entity(es):