def fit(self, X_actual, y_actual, X_expected, y_expected):
        """Fit monitoring with actual and expected data.

        Parameters
        ----------
        X_actual : pandas.DataFrame
            New/actual/test data input samples.

        y_actual : array-like of shape (n_samples,)
            Target vector relative to X actual.

        X_expected : pandas.DataFrame
            Trainning data used for fitting the scorecard.

        y_expected : array-like of shape (n_samples,)
            Target vector relative to X expected.

        Returns
        -------
        self : ScorecardMonitoring
            Fitted monitoring.
        """
        time_init = time.perf_counter()

        if self.verbose:
            logger.info("Monitoring started.")
            logger.info("Options: check parameters.")

        # Check parameters
        _check_parameters(**self.get_params(deep=False))

        # Check if scorecard is fitted
        self.scorecard._check_is_fitted()

        target_dtype = type_of_target(y_actual)
        target_dtype_e = type_of_target(y_expected)

        if target_dtype not in ("binary", "continuous"):
            raise ValueError("Target type (actual) {} is not supported."
                             .format(target_dtype))

        if target_dtype_e not in ("binary", "continuous"):
            raise ValueError("Target type (expected) {} is not supported."
                             .format(target_dtype_e))

        if target_dtype != target_dtype_e:
            raise ValueError("Target types must coincide; {} != {}."
                             .format(target_dtype, target_dtype_e))

        self._target_dtype = target_dtype

        # Check variable names
        if list(X_actual.columns) != list(X_expected.columns):
            raise ValueError("Dataframes X_actual and X_expected must "
                             "have the same columns.")

        # Statistics at system level
        if self.verbose:
            logger.info("System stability analysis started.")

        time_system = time.perf_counter()
        self._fit_system(X_actual, y_actual, X_expected, y_expected)
        self._time_system = time.perf_counter() - time_system

        if self.verbose:
            logger.info("System stability analysis terminated. Time: {:.4f}s"
                        .format(self._time_system))

        # Statistics at variable level
        if self.verbose:
            logger.info("Variable analysis started.")

        time_variable = time.perf_counter()
        self._fit_variables(X_actual, X_expected)
        self._time_variable = time.perf_counter() - time_variable

        if self.verbose:
            logger.info("Variable analysis terminated. Time: {:.4f}s"
                        .format(self._time_variable))

        self._time_total = time.perf_counter() - time_init

        if self.verbose:
            logger.info("Monitoring terminated. Time: {:.4f}s"
                        .format(self._time_total))

        # Completed successfully
        self._is_fitted = True

        return self
Fit monitoring with actual and expected data.

        Parameters
        ----------
        X_actual : pandas.DataFrame
            New/actual/test data input samples.

        y_actual : array-like of shape (n_samples,)
            Target vector relative to X actual.

        X_expected : pandas.DataFrame
            Trainning data used for fitting the scorecard.

        y_expected : array-like of shape (n_samples,)
            Target vector relative to X expected.

        Returns
        -------
        self : ScorecardMonitoring
            Fitted monitoring.
        
optbinning/scorecard/monitoring.py