code: def _handle_missing_data(
      df: pd.DataFrame,
      freq: Optional[str],
      n_lags: int,
      n_forecasts: int,
      config_missing,
      config_regressors: Optional[ConfigFutureRegressors],
      config_lagged_regressors: Optional[ConfigLaggedRegressors],
      config_events: Optional[ConfigEvents],
      config_seasonality: Optional[ConfigSeasonality],
      predicting: bool = False,
  ) -> pd.DataFrame:
      """
      Checks and normalizes new data, auto-imputing missing data if `config_missing` allows it.
  
      Parameters
      ----------
      df : pd.DataFrame
          Dataframe containing columns 'ds', 'y', and optionally 'ID' with all data.
      freq : str
              data step sizes. Frequency of data recording,
  
              Note
              ----
              Any valid frequency for pd.date_range, such as ``5min``, ``D``, ``MS`` or ``auto`` (default) to
              automatically set frequency.
      n_lags : int
          Previous time series steps to include in auto-regression. Aka AR-order
      n_forecasts : int
          Number of steps ahead of prediction time step to forecast.
      config_missing :
          Configuration options for handling missing data.
      config_regressors : Optional[ConfigFutureRegressors]
          Configuration options for adding future regressors to the model.
      config_lagged_regressors : Optional[ConfigLaggedRegressors]
          Configuration options for adding lagged external regressors to the model.
      config_events : Optional[ConfigEvents]
          Configuration options for adding events to the model.
      config_seasonality : Optional[ConfigSeasonality]
          Configuration options for adding seasonal components to the model.
      predicting : bool, default False
          If True, allows missing values in the 'y' column for the forecast period, or missing completely.
  
      Returns
      -------
      pd.DataFrame
          The pre-processed DataFrame, including imputed missing data, if applicable.
  
      """
      df, _, _, _ = df_utils.prep_or_copy_df(df)
  
      if n_lags == 0 and not predicting:
          # drop rows with NaNs in y and count them
          df_na_dropped = df.dropna(subset=["y"])
          n_dropped = len(df) - len(df_na_dropped)
          if n_dropped > 0:
              df = df_na_dropped
              log.info(
                  f"Dropped {n_dropped} rows with NaNs in 'y' column."
              )
  
      if n_lags > 0:
          # add missig dates to df
          df_grouped = (
              df.groupby("ID")
              .apply(
                  lambda x: x.set_index("ds").resample(freq).asfreq()
              )
              .drop(columns=["ID"])
          )
          n_missing_dates = len(df_grouped) - len(df)
          if n_missing_dates > 0:
              df = df_grouped.reset_index()
              log.info(f"Added {n_missing_dates} missing dates.")
  
      if config_regressors is not None:
          # drop complete row for future regressors that are NaN at the end
          last_valid_index = df.groupby("ID")[
              list(config_regressors.keys())
          ].apply(lambda x: x.last_valid_index())
          df_dropped = df.groupby("ID", group_keys=False).apply(
              lambda x: x.loc[: last_valid_index[x.name]]
          )
          n_dropped = len(df) - len(df_dropped)
          if n_dropped > 0:
              df = df_dropped
              log.info(
                  f"Dropped {n_dropped} rows at the end with NaNs in future regressors."
              )
  
      dropped_trailing_y = False
      if df["y"].isna().any():
          # drop complete row if y of ID ends with nan
          last_valid_index = df.groupby("ID")["y"].apply(
              lambda x: x.last_valid_index()
          )
          df_dropped = df.groupby("ID", group_keys=False).apply(
              lambda x: x.loc[: last_valid_index[x.name]]
          )
          n_dropped = len(df) - len(df_dropped)
          if n_dropped > 0:
              dropped_trailing_y = True
              # save dropped rows for later
              df_to_add = df.groupby("ID", group_keys=False).apply(
                  lambda x: x.loc[last_valid_index[x.name] + 1 :]
              )
              df = df_dropped
              log.info(
                  f"Dropped {n_dropped} rows at the end with NaNs in 'y' column."
              )
  
      if config_missing.impute_missing:
          # impute missing values
          data_columns = []
          if n_lags > 0:
              data_columns.append("y")
          if config_lagged_regressors is not None:
              data_columns.extend(config_lagged_regressors.keys())
          if config_regressors is not None:
              data_columns.extend(config_regressors.keys())
          if config_events is not None:
              data_columns.extend(config_events.keys())
          conditional_cols = []
          if config_seasonality is not None:
              conditional_cols = list(
                  set(
                      [
                          value.condition_name
                          for key, value in config_seasonality.periods.items()
                          if value.condition_name is not None
                      ]
                  )
              )
              data_columns.extend(conditional_cols)
          for column in data_columns:
              sum_na = df[column].isna().sum()
              if sum_na > 0:
                  log.warning(
                      f"{sum_na} missing values in column {column} were detected in total. "
                  )
                  # use 0 substitution for holidays and events missing values
                  if (
                      config_events is not None
                      and column in config_events.keys()
                  ):
                      df[column].fillna(0, inplace=True)
                      remaining_na = 0
                  else:
                      (
                          df.loc[:, column],
                          remaining_na,
                      ) = df_utils.fill_linear_then_rolling_avg(
                          df[column],
                          limit_linear=config_missing.impute_linear,
                          rolling=config_missing.impute_rolling,
                      )
                  log.info(
                      f"{sum_na - remaining_na} NaN values in column {column} were auto-imputed."
                  )
                  if remaining_na > 0:
                      log.warning(
                          f"More than {2 * config_missing.impute_linear + config_missing.impute_rolling} consecutive \
                              missing values encountered in column {column}. "
                          f"{remaining_na} NA remain after auto-imputation. "
                      )
      if dropped_trailing_y and predicting:
          # add trailing y values again if in predict mode
          df = pd.concat([df, df_to_add])
          if (
              config_seasonality is not None
              and len(conditional_cols) > 0
          ):
              df[conditional_cols] = df[conditional_cols].ffill()  # type: ignore
      return df
  
file_path: neural_prophet/neuralprophet/data/process.py
imports: import logging
  from typing import List, Optional
  import numpy
  import pandas
  from neuralprophet import df_utils, time_dataset
  from neuralprophet.configure import ConfigCountryHolidays, ConfigEvents, ConfigFutureRegressors, ConfigLaggedRegressors, ConfigSeasonality
  from neuralprophet.np_types import Components
label_desc: Use image node_id 6 for calling a global function with example usage: _handle_missing_data(df, freq, n_lags, n_forecasts, config_missing, config_regressors, config_lagged_regressors, config_events, config_seasonality, predicting) and returns: df