Repository: feature-engine/feature_engine
Pull request #405 (2022-03-31 22:29:29)

extends functionality of DatetimeFeatures to extract features from the dataframe index
Here's a draft to implement the functionality. I might have forgotten about some test cases and there might be better ways to refactor the logic overall

I tried to keep the logic as consistent as possible with the rest of the cases (e.g. find_or_check_dt_variables dumps "index" into ["index"] as it would do with any other string etc) although the fact that we need to access .index rather than a (set of) column(s) introduces some "boilerplate" code

I did think of having _check_contains_na check for nans in the index but I found out some check estimator tests would fail then, so I just added that logic under an if statement in the datetime transformer (more boilerplate, ugh)

Also, this is probably a trivial note, but as things stand there's no coverage for the case where an actual variable (not the index) is named "index". I suppose we might just leave that as it is as it sounds like a very unrealistic name for a column anyway

Let me know how we can improve it

dodoarg: I think I've tackled pretty much all issues you raised @solegalli ? 

- I removed the "index_" prefix for feature columns extracted from the index

- The part where the DatetimeIndex is dumped into a pd.Series was necessary because the way you access to datetime functions on an index object looks like `df.index.year` i.e. it "bypasses" the call to the dt module. Since our logic calls a set of lambda functions (FEATURES_FUNCTIONS) that all look like x.dt.year I thought it would add less boilerplate code to put the index data into a Series so as to access those functions the same way rather than rewrite all them without the dt part just for this specific case
dodoarg: Should be better now. Notice that when setting self.variables_ as None in the index case it occurred to me that I had to adapt get_feature_names_out to the index case
solegalli: Thank you so much @dodoarg !!!! This will do!

Merging on Monday.

Have a nice weekend.
dodoarg: Should have made quick work of that in my break
dodoarg: is this ready to merge @solegalli ?

Patch:
diff --git a/docs/user_guide/datetime/DatetimeFeatures.rst b/docs/user_guide/datetime/DatetimeFeatures.rst
index c548d1c..139968f 100644
--- a/docs/user_guide/datetime/DatetimeFeatures.rst
+++ b/docs/user_guide/datetime/DatetimeFeatures.rst
@@ -5,11 +5,18 @@
 DatetimeFeatures
 ================
 
-:class:`DatetimeFeatures()` extracts several datetime features from datetime
+:class:`DatetimeFeatures()` extracts several date and time features from datetime
 variables. It works with variables whose original dtype is datetime, and also with
 object-like and categorical variables, provided that they can be parsed into datetime
 format. It *cannot* extract features from numerical variables.
 
+In time series data, the index of the dataframe often contains date and time
+information. :class:`DatetimeFeatures()` can also extract date and time variables from
+the dataframe index.
+
+Tabular data
+------------
+
 Oftentimes, datasets contain information related to dates and/or times at which an event
 occurred. In pandas dataframes, these datetime variables can be cast as datetime or,
 more generically, as object.
@@ -17,18 +24,16 @@ more generically, as object.
 Datetime variables, in their raw format, are generally not suitable to train machine
 learning models. Yet, an enormous amount of information can be extracted from them.
 
-:class:`DatetimeFeatures()` is able to extract many numerical and binary date and
-time features from these datetime variables. Among these features we can find the month
-in which an event occurred, the day of the week, or whether that day was a weekend day.
+:class:`DatetimeFeatures()` extracts many numerical and binary date and time features
+from these datetime variables. Among these features, we find the month in which an
+event occurred, the day of the week, or whether that day was a weekend day.
 
-With :class:`DatetimeFeatures()` you can choose which date and time features
-to extract from your datetime variables. You can also extract date and time features
-from a subset of datetime variables in your data.
+With :class:`DatetimeFeatures()` we can choose which date and time features
+to extract from the datetime variables. We can also extract date and time features
+from a subset of datetime variables in the data.
 
-Examples
---------
 Through the following examples we highlight the functionality and versatility of
-:class:`DatetimeFeatures()`.
+:class:`DatetimeFeatures()` for tabular data.
 
 Extract date features
 ~~~~~~~~~~~~~~~~~~~~~
@@ -62,7 +67,7 @@ second datetime variable in our dataset.
 
     df_transf = dtfs.fit_transform(toy_df)
 
-    print(df_transf)
+    df_transf
 
 .. code:: python
 
@@ -80,6 +85,29 @@ By default, :class:`DatetimeFeatures()` drops the variable from which the date a
 features were extracted, in this case, *var_date2*. To keep the variable, we just need
 to indicate `drop_original=False` when initializing the transformer.
 
+Finally, we can obtain the name of the variables in the returned data as follows:
+
+.. code:: python
+
+    dtfs.get_feature_names_out()
+
+.. code:: python
+
+    ['var_date1',
+     'var_date2_month',
+     'var_date2_month_end',
+     'var_date2_day_of_year']
+
+Or we can obtain the names of the features created from a certain variable as follows:
+
+.. code:: python
+
+    dtfs.get_feature_names_out(["var_date2"])
+
+.. code:: python
+
+    ['var_date2_month', 'var_date2_month_end', 'var_date2_day_of_year']
+
 Extract time features
 ~~~~~~~~~~~~~~~~~~~~~
 
@@ -109,7 +137,7 @@ don't need to specify them.
 
     df_transf = dfts.fit_transform(toy_df)
 
-    print(df_transf)
+    df_transf
 
 .. code:: python
 
@@ -138,6 +166,37 @@ Again, the original datetime variables are dropped from the data by default. If
 want to keep them, we just need to indicate `drop_original=False` when initializing
 the transformer.
 
+Finally, if we want to obtain the names of the variables in the output data, we can use:
+
+.. code:: python
+
+    dfts.get_feature_names_out()
+
+.. code:: python
+
+    ['not_a_dt', 'var_time1_minute', 'var_time2_minute']
+
+We can obtain the date and time features derived from both our datetime variables as
+follows:
+
+.. code:: python
+
+    dfts.get_feature_names_out(['var_time1', 'var_time2'])
+
+.. code:: python
+
+    ['var_time1_minute', 'var_time2_minute']
+
+Or, we can obtain the features derived from just one of the variables like this:
+
+.. code:: python
+
+    dfts.get_feature_names_out(['var_time1'])
+
+.. code:: python
+
+    ['var_time1_minute']
+
 Extract date and time features
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -187,8 +246,113 @@ the features.
 
 And that is it. The additional features are now added in the dataframe.
 
+Time series
+-----------
+
+Time series data consist of datapoints indexed in time order. The time is usually in
+the index of the dataframe. With :class:`DatetimeFeatures` we can also create date and
+time features from the dataframe index.
+
+Let's create a toy dataframe with datetime in the index.
+
+.. code:: python
+
+    import pandas as pd
+
+    X = {"ambient_temp": [31.31, 31.51, 32.15, 32.39, 32.62, 32.5, 32.52, 32.68],
+         "module_temp": [49.18, 49.84, 52.35, 50.63, 49.61, 47.01, 46.67, 47.52],
+         "irradiation": [0.51, 0.79, 0.65, 0.76, 0.42, 0.49, 0.57, 0.56],
+         "color": ["green"] * 4 + ["blue"] * 4,
+         }
+
+    X = pd.DataFrame(X)
+    X.index = pd.date_range("2020-05-15 12:00:00", periods=8, freq="15min")
+
+    X.head()
+
+Below we see the output of our toy dataframe:
+
+.. code:: python
+
+                         ambient_temp  module_temp  irradiation  color
+    2020-05-15 12:00:00         31.31        49.18         0.51  green
+    2020-05-15 12:15:00         31.51        49.84         0.79  green
+    2020-05-15 12:30:00         32.15        52.35         0.65  green
+    2020-05-15 12:45:00         32.39        50.63         0.76  green
+    2020-05-15 13:00:00         32.62        49.61         0.42   blue
+
+We can extract features from the index as follows:
+
+..  code:: python
+
+    from feature_engine.datetime import DatetimeFeatures
+
+    dtf = DatetimeFeatures(variables="index")
+
+    Xtr = dtf.fit_transform(X)
+
+    Xtr
+
+We can see that the transformer created the default time features and added them at
+the end of the dataframe.
+
+.. code:: python
+
+                         ambient_temp  module_temp  irradiation  color  month  \
+    2020-05-15 12:00:00         31.31        49.18         0.51  green      5
+    2020-05-15 12:15:00         31.51        49.84         0.79  green      5
+    2020-05-15 12:30:00         32.15        52.35         0.65  green      5
+    2020-05-15 12:45:00         32.39        50.63         0.76  green      5
+    2020-05-15 13:00:00         32.62        49.61         0.42   blue      5
+    2020-05-15 13:15:00         32.50        47.01         0.49   blue      5
+    2020-05-15 13:30:00         32.52        46.67         0.57   blue      5
+    2020-05-15 13:45:00         32.68        47.52         0.56   blue      5
+
+                         year  day_of_week  day_of_month  hour  minute  second
+    2020-05-15 12:00:00  2020            4            15    12       0       0
+    2020-05-15 12:15:00  2020            4            15    12      15       0
+    2020-05-15 12:30:00  2020            4            15    12      30       0
+    2020-05-15 12:45:00  2020            4            15    12      45       0
+    2020-05-15 13:00:00  2020            4            15    13       0       0
+    2020-05-15 13:15:00  2020            4            15    13      15       0
+    2020-05-15 13:30:00  2020            4            15    13      30       0
+    2020-05-15 13:45:00  2020            4            15    13      45       0
+
+We can obtain the name of all the variables in the output dataframe as follows:
+
+.. code:: python
+
+    dtf.get_feature_names_out()
+
+.. code:: python
+
+    ['ambient_temp',
+     'module_temp',
+     'irradiation',
+     'color',
+     'month',
+     'year',
+     'day_of_week',
+     'day_of_month',
+     'hour',
+     'minute',
+     'second']
+
+
+Alternatively, we can obtain the name of the features created from the index as
+follows:
+
+.. code:: python
+
+    dtf.get_feature_names_out("index")
+
+.. code:: python
+
+    ['month', 'year', 'day_of_week', 'day_of_month', 'hour', 'minute', 'second']
+
+
 Important
-~~~~~~~~~
+---------
 
 We highly recommend specifying the date and time features that you would like to extract
 from your datetime variables. If you have too many time variables, this might not be
@@ -271,7 +435,7 @@ And mistakenly extract the hour and the minute.
 The new features will contain the value 0.
 
 Automating feature extraction
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+-----------------------------
 
 We can indicate which features we want to extract from the datetime variables as we did
 in the previous examples, passing the feature names in lists. Alternatively,
@@ -418,7 +582,7 @@ We can find the group of features extracted by the transformer in its attribute.
      'second']
 
 Extract and select features automatically
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+-----------------------------------------
 
 If we have a dataframe with a date variables, time variables and date and time variables,
 we can extract all features, or the most common features from all the variables, and then
@@ -494,7 +658,7 @@ from the dataset.
 As you can see, we do not have the constant features in the transformed dataset.
 
 Extract features from time-aware variables
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+------------------------------------------
 
 Time-aware datetime variables can be particularly cumbersome to work with as far
 as the format goes. We will briefly show how :class:`DatetimeFeatures()` deals
@@ -606,8 +770,9 @@ is the default option.
 Note that the hour extracted from the variable differ in this dataframe respect to the
 one obtained in **Case 2**.
 
+
 More details
-^^^^^^^^^^^^
+~~~~~~~~~~~~
 
 You can find additional examples with a real dataset on how to use
 :class:`DatetimeFeatures()` in the following Jupyter notebook.
diff --git a/feature_engine/datetime/_datetime_constants.py b/feature_engine/datetime/_datetime_constants.py
index 273247f..f7ef69a 100644
--- a/feature_engine/datetime/_datetime_constants.py
+++ b/feature_engine/datetime/_datetime_constants.py
@@ -23,6 +23,16 @@ FEATURES_SUPPORTED = [
     "second",
 ]
 
+FEATURES_DEFAULT = [
+    "month",
+    "year",
+    "day_of_week",
+    "day_of_month",
+    "hour",
+    "minute",
+    "second",
+]
+
 FEATURES_SUFFIXES = {
     "month": "_month",
     "quarter": "_quarter",
@@ -68,13 +78,3 @@ FEATURES_FUNCTIONS = {
     "minute": lambda x: x.dt.minute,
     "second": lambda x: x.dt.second,
 }
-
-FEATURES_DEFAULT = [
-    "month",
-    "year",
-    "day_of_week",
-    "day_of_month",
-    "hour",
-    "minute",
-    "second",
-]
diff --git a/feature_engine/datetime/datetime.py b/feature_engine/datetime/datetime.py
index eafa8d0..c57808a 100644
--- a/feature_engine/datetime/datetime.py
+++ b/feature_engine/datetime/datetime.py
@@ -4,6 +4,7 @@ from typing import List, Optional, Union
 
 import pandas as pd
 from pandas.api.types import is_datetime64_any_dtype as is_datetime
+from pandas.api.types import is_numeric_dtype as is_numeric
 from sklearn.base import BaseEstimator, TransformerMixin
 from sklearn.utils.validation import check_is_fitted
 
@@ -28,6 +29,7 @@ from feature_engine.docstrings import (
 from feature_engine.variable_manipulation import (
     _check_input_parameter_variables,
     _find_or_check_datetime_variables,
+    _is_categorical_and_is_datetime,
 )
 
 
@@ -40,10 +42,10 @@ from feature_engine.variable_manipulation import (
 class DatetimeFeatures(BaseEstimator, TransformerMixin):
     """
     DatetimeFeatures extracts date and time features from datetime variables, adding
-    new columns to the dataset. DatetimeFeatures is able to extract datetime
-    information from existing datetime or object-like variables.
+    new columns to the dataset. DatetimeFeatures can extract datetime information from
+    existing datetime or object-like variables or from the dataframe index.
 
-    DatetimeFeatures uses pandas.to_datetime to convert object variables to datetime
+    DatetimeFeatures uses `pandas.to_datetime` to convert object variables to datetime
     and pandas.dt to extract the features from datetime.
 
     The transformer supports the extraction of the following features:
@@ -73,17 +75,19 @@ class DatetimeFeatures(BaseEstimator, TransformerMixin):
 
     Parameters
     ----------
-    variables: list, default=None
-        The list of variables to extract date and time features from.
+    variables: str, list, default=None
+        List with the variables from which date and time information will be extracted.
         If None, the transformer will find and select all datetime variables,
         including variables of type object that can be converted to datetime.
+        If "index", the transformer will extract datetime features from the
+        index of the dataframe.
 
     features_to_extract: list, default=None
         The list of date features to extract. If None, the following features will be
         extracted: "month", "year", "day_of_week", "day_of_month", "hour",
         "minute" and "second". If "all", all supported features will be extracted.
-        Alternatively, you can pass a list with the names of the supported features
-        you want to extract.
+        Alternatively, you can pass a list with the names of the features you want to
+        extract.
 
     drop_original: bool, default="True"
         If True, the original datetime variables will be dropped from the dataframe.
@@ -92,29 +96,37 @@ class DatetimeFeatures(BaseEstimator, TransformerMixin):
         Indicates if missing values should be ignored or raised. If 'raise' the
         transformer will return an error if the the datasets to `fit` or `transform`
         contain missing values. If 'ignore', missing data will be ignored when
-        performing the feature extraction.
+        performing the feature extraction. Missing data is only evaluated in the
+        variables that will be used to derive the date and time features. If features
+        are derived from the dataframe index, missing data will be checked in the
+        index.
 
     dayfirst: bool, default="False"
-        Specify a date parse order if arg is str or its list-likes. If True, parses
-        dates with the day first, eg 10/11/12 is parsed as 2012-11-10.
+        Specify a date parse order if arg is str or is list-like. If True, parses
+        dates with the day first, e.g. 10/11/12 is parsed as 2012-11-10. Same as in
+        `pandas.to_datetime`.
 
     yearfirst: bool, default="False"
-        Specify a date parse order if arg is str or its list-likes.
+        Specify a date parse order if arg is str or is list-like.
+        Same as in `pandas.to_datetime`.
 
-        - If True parses dates with the year first, eg 10/11/12 is parsed as 2010-11-12.
+        - If True parses dates with the year first, e.g. 10/11/12 is parsed as
+          2010-11-12.
         - If both dayfirst and yearfirst are True, yearfirst is preceded.
 
     utc: bool, default=None
         Return UTC DatetimeIndex if True (converting any tz-aware datetime.datetime
-        objects as well).
+        objects as well). Same as in `pandas.to_datetime`.
 
     Attributes
     ----------
     variables_:
-        List of variables from which date and time features will be extracted.
+        List of variables from which date and time features will be extracted. If None,
+        features will be extracted from the dataframe index.
 
     features_to_extract_:
-        The date and time features that will be extracted from each variable.
+        The date and time features that will be extracted from each variable or the
+        index.
 
     {feature_names_in_}
 
@@ -204,12 +216,29 @@ class DatetimeFeatures(BaseEstimator, TransformerMixin):
         # check input dataframe
         X = _is_dataframe(X)
 
-        # find or check for datetime variables
-        self.variables_ = _find_or_check_datetime_variables(X, self.variables)
+        # special case index
+        if self.variables == "index":
 
-        # check if datetime variables contains na
-        if self.missing_values == "raise":
-            _check_contains_na(X, self.variables_)
+            if not (
+                is_datetime(X.index)
+                or (
+                    not is_numeric(X.index) and _is_categorical_and_is_datetime(X.index)
+                )
+            ):
+                raise TypeError("The dataframe index is not datetime.")
+
+            if self.missing_values == "raise":
+                self._check_index_contains_na(X.index)
+
+            self.variables_ = None
+
+        else:
+            # find or check for datetime variables
+            self.variables_ = _find_or_check_datetime_variables(X, self.variables)
+
+            # check if datetime variables contains na
+            if self.missing_values == "raise":
+                _check_contains_na(X, self.variables_)
 
         if self.features_to_extract is None:
             self.features_to_extract_ = FEATURES_DEFAULT
@@ -250,57 +279,83 @@ class DatetimeFeatures(BaseEstimator, TransformerMixin):
         # Check if input data contains same number of columns as dataframe used to fit.
         _check_input_matches_training_df(X, self.n_features_in_)
 
-        # check if dataset contains na
-        if self.missing_values == "raise":
-            _check_contains_na(X, self.variables_)
-
         # reorder variables to match train set
         X = X[self.feature_names_in_]
 
-        # convert datetime variables
-        datetime_df = pd.concat(
-            [
+        # special case index
+        if self.variables_ is None:
+            # check if dataset contains na
+            if self.missing_values == "raise":
+                self._check_index_contains_na(X.index)
+
+            # convert index to a datetime series
+            idx_datetime = pd.Series(
                 pd.to_datetime(
-                    X[variable],
+                    X.index,
                     dayfirst=self.dayfirst,
                     yearfirst=self.yearfirst,
                     utc=self.utc,
-                )
-                for variable in self.variables_
-            ],
-            axis=1,
-        )
-
-        non_dt_columns = datetime_df.columns[~datetime_df.apply(is_datetime)].tolist()
-        if non_dt_columns:
-            raise ValueError(
-                "ValueError: variable(s) "
-                + (len(non_dt_columns) * "{} ").format(*non_dt_columns)
-                + "could not be converted to datetime. Try setting utc=True"
+                ),
+                index=X.index,
             )
 
-        # create new features
-        for var in self.variables_:
+            # create new features
             for feat in self.features_to_extract_:
-                X[str(var) + FEATURES_SUFFIXES[feat]] = FEATURES_FUNCTIONS[feat](
-                    datetime_df[var]
+                X[FEATURES_SUFFIXES[feat][1:]] = FEATURES_FUNCTIONS[feat](idx_datetime)
+
+        else:
+            # check if dataset contains na
+            if self.missing_values == "raise":
+                _check_contains_na(X, self.variables_)
+
+            # convert datetime variables
+            datetime_df = pd.concat(
+                [
+                    pd.to_datetime(
+                        X[variable],
+                        dayfirst=self.dayfirst,
+                        yearfirst=self.yearfirst,
+                        utc=self.utc,
+                    )
+                    for variable in self.variables_
+                ],
+                axis=1,
+            )
+
+            non_dt_columns = datetime_df.columns[
+                ~datetime_df.apply(is_datetime)
+            ].tolist()
+            if non_dt_columns:
+                raise ValueError(
+                    "ValueError: variable(s) "
+                    + (len(non_dt_columns) * "{} ").format(*non_dt_columns)
+                    + "could not be converted to datetime. Try setting utc=True"
                 )
 
-        if self.drop_original:
-            X.drop(self.variables_, axis=1, inplace=True)
+            # create new features
+            for var in self.variables_:
+                for feat in self.features_to_extract_:
+                    X[str(var) + FEATURES_SUFFIXES[feat]] = FEATURES_FUNCTIONS[feat](
+                        datetime_df[var]
+                    )
+            if self.drop_original:
+                X.drop(self.variables_, axis=1, inplace=True)
 
         return X
 
-    def get_feature_names_out(self, input_features: Optional[List] = None) -> List:
+    def get_feature_names_out(
+        self, input_features: Optional[Union[List, str]] = None
+    ) -> List:
         """Get output feature names for transformation.
 
         Parameters
         ----------
-        input_features: list, default=None
-            Input features. If `input_features` is `None`, then the names of all the
-            variables in the transformed dataset (original + new variables) is returned.
-            Alternatively, only the names for the datetime features derived from
-            input_features will be returned.
+        input_features: str, list, default=None
+            Input features. If `None`, then the names of all the variables in the
+            transformed dataset (original + new variables) is returned. If 'index',
+            then the name of the features created from the index is returned. If list
+            with feature names, the names for the datetime features derived from
+            the variables in the list will be returned."
 
         Returns
         -------
@@ -309,44 +364,80 @@ class DatetimeFeatures(BaseEstimator, TransformerMixin):
         """
         check_is_fitted(self)
 
-        # Create names for all features or just the indicated ones.
-        if input_features is None:
-            # Create all features.
-            input_features_ = self.variables_
-        else:
-            if not isinstance(input_features, list):
-                raise ValueError(
-                    f"input_features must be a list. Got {input_features} instead."
+        # special case index
+        if self.variables_ is None:
+
+            if input_features is None:
+                feature_names = (
+                    self.feature_names_in_ + self._get_datetime_feature_names("index")
                 )
-            if any([f for f in input_features if f not in self.variables_]):
+
+            elif input_features == "index":
+                feature_names = self._get_datetime_feature_names("index")
+
+            else:
                 raise ValueError(
-                    "Some features in input_features were not used to extract new "
-                    "variables. You can only get the names of the extracted features "
-                    "with this function."
+                    "The dataframe index was used to extract date and time variables."
+                    "Thus, input_features can only be None or 'index'. "
+                    f"Got {input_features} instead."
                 )
-            # Create just indicated features.
-            input_features_ = input_features
-
-        # create the names for the lag features
-        feature_names = [
-            str(var) + FEATURES_SUFFIXES[feat]
-            for var in input_features_
-            for feat in self.features_to_extract_
-        ]
-
-        # Return names of all variables if input_features is None.
-        if input_features is None:
-            if self.drop_original is True:
-                # Remove names of variables to drop.
-                original = [
-                    f for f in self.feature_names_in_ if f not in self.variables_
-                ]
-                feature_names = original + feature_names
+
+        else:
+            # Return all features in transformed output.
+            if input_features is None:
+                # All datetime features.
+                feature_names = self._get_datetime_feature_names(self.variables_)
+
+                if self.drop_original is True:
+                    # Remove names of variables to drop.
+                    original = [
+                        f for f in self.feature_names_in_ if f not in self.variables_
+                    ]
+                    feature_names = original + feature_names
+                else:
+                    feature_names = self.feature_names_in_ + feature_names
+
             else:
-                feature_names = self.feature_names_in_ + feature_names
+                # Return features requested by user.
+                if not isinstance(input_features, list):
+                    raise ValueError(
+                        f"input_features must be a list. Got {input_features} instead."
+                    )
+                if any([f for f in input_features if f not in self.variables_]):
+                    raise ValueError(
+                        "Some features in input_features were not used to extract new "
+                        "variables. Pass either None, or a list with the features that "
+                        "were used to create date and time features."
+                    )
+                # Create just indicated features.
+                feature_names = self._get_datetime_feature_names(input_features)
 
         return feature_names
 
+    def _get_datetime_feature_names(self, input_features: Union[List, str]):
+        """create the names for the datetime features."""
+
+        if input_features == "index":
+            feature_names = [
+                FEATURES_SUFFIXES[feat][1:] for feat in self.features_to_extract_
+            ]
+        else:
+            feature_names = [
+                str(var) + FEATURES_SUFFIXES[feat]
+                for var in input_features
+                for feat in self.features_to_extract_
+            ]
+
+        return feature_names
+
+    def _check_index_contains_na(self, index: pd.Index):
+        if index.isnull().any():
+            raise ValueError(
+                "The dataframe index contains missing data. "
+                "Check and remove those before using this transformer "
+                "or set missing_values to False."
+            )
+
     def _more_tags(self):
         tags_dict = {"variables": "datetime"}
         return tags_dict


Test patch:
diff --git a/tests/test_datetime/test_datetime_features.py b/tests/test_datetime/test_datetime_features.py
index e040889..a7a4f4d 100644
--- a/tests/test_datetime/test_datetime_features.py
+++ b/tests/test_datetime/test_datetime_features.py
@@ -14,6 +14,13 @@ vars_dt = ["datetime_range", "date_obj1", "date_obj2", "time_obj"]
 vars_non_dt = ["Name", "Age"]
 feat_names_default = [FEATURES_SUFFIXES[feat] for feat in FEATURES_DEFAULT]
 dates_nan = pd.DataFrame({"dates_na": ["Feb-2010", np.nan, "Jun-1922", np.nan]})
+dates_idx_nan = pd.DataFrame(
+    [1, 2, 3, 4], index=["Feb-2010", np.nan, "Jun-1922", np.nan]
+)
+dates_idx_dt = pd.DataFrame(
+    [4, 3, 2, 1],
+    index=pd.date_range("2003-02-27", periods=4, freq="D"),
+)
 
 
 _false_input_params = [
@@ -91,6 +98,8 @@ def test_raises_error_when_variables_not_datetime(df_datetime):
         DatetimeFeatures(variables=["Age"]).fit(df_datetime)
     with pytest.raises(TypeError):
         DatetimeFeatures(variables=["Name", "Age", "date_obj1"]).fit(df_datetime)
+    with pytest.raises(TypeError):
+        DatetimeFeatures(variables="index").fit(df_datetime)
     # passing a df that contains no datetime variables
     with pytest.raises(ValueError):
         DatetimeFeatures().fit(df_datetime[["Name", "Age"]])
@@ -100,6 +109,8 @@ def test_raises_error_when_df_has_nan():
     # dataset containing nans
     with pytest.raises(ValueError):
         DatetimeFeatures().fit(dates_nan)
+    with pytest.raises(ValueError):
+        DatetimeFeatures(variables="index").fit(dates_idx_nan)
 
 
 def test_attributes_upon_fitting(df_datetime):
@@ -148,7 +159,11 @@ def test_raises_error_when_nan_in_transform_df(df_datetime):
     transformer.fit(df_datetime)
     # dataset containing nans
     with pytest.raises(ValueError):
-        DatetimeFeatures().transform(dates_nan)
+        transformer.transform(dates_nan)
+    transformer = DatetimeFeatures(variables="index")
+    transformer.fit(dates_idx_dt)
+    with pytest.raises(ValueError):
+        transformer.transform(dates_idx_nan)
 
 
 def test_raises_non_fitted_error(df_datetime):
@@ -218,6 +233,25 @@ def test_extract_datetime_features_from_specified_variables(
         ],
     )
 
+    # datetime variable is index
+    X = DatetimeFeatures(
+        variables="index", features_to_extract=["month", "day_of_month"]
+    ).fit_transform(dates_idx_dt)
+    pd.testing.assert_frame_equal(
+        X,
+        pd.concat(
+            [
+                dates_idx_dt,
+                pd.DataFrame(
+                    [[2, 27], [2, 28], [3, 1], [3, 2]],
+                    index=dates_idx_dt.index,
+                    columns=["month", "day_of_month"],
+                ),
+            ],
+            axis=1,
+        ),
+    )
+
 
 def test_extract_all_datetime_features(df_datetime, df_datetime_transformed):
     X = DatetimeFeatures(features_to_extract="all").fit_transform(df_datetime)
@@ -375,6 +409,22 @@ def test_extract_features_from_variables_containing_nans():
         X,
         pd.DataFrame({"dates_na_year": [2010, np.nan, 1922, np.nan]}),
     )
+    # dt variable is index
+    X = DatetimeFeatures(
+        variables="index", features_to_extract=["month"], missing_values="ignore"
+    ).fit_transform(dates_idx_nan)
+    pd.testing.assert_frame_equal(
+        X,
+        pd.concat(
+            [
+                dates_idx_nan,
+                pd.DataFrame(
+                    {"month": [2, np.nan, 6, np.nan]}, index=dates_idx_nan.index
+                ),
+            ],
+            axis=1,
+        ),
+    )
 
 
 def test_extract_features_with_different_datetime_parsing_options(df_datetime):
@@ -438,6 +488,23 @@ def test_get_feature_names_out(df_datetime, df_datetime_transformed):
         "date_obj1_" + feat for feat in ["semester", "week"]
     ]
 
+    # features were extracted from index
+    transformer = DatetimeFeatures(
+        variables="index", features_to_extract=["semester", "week"]
+    )
+    X = transformer.fit_transform(dates_idx_dt)
+    # user passes something else than index as input_features
+    with pytest.raises(ValueError):
+        transformer.get_feature_names_out(input_features="not_index")
+    with pytest.raises(ValueError):
+        transformer.get_feature_names_out(input_features=["still", "not", "index"])
+    # input_features is None
+    assert list(X.columns) == transformer.get_feature_names_out()
+    # input_features is index
+    assert ["semester", "week"] == transformer.get_feature_names_out(
+        input_features="index"
+    )
+
     # when drop original is False
     transformer = DatetimeFeatures(drop_original=False)
     X = transformer.fit_transform(df_datetime)