[{'role': 'system', 'content': "You are an expert in machine learning and your task is to classify code snippets into one of the following categories: ['helper_functions', 'load_data', 'data_exploration', 'data_preprocessing', 'evaluation', 'modelling', 'prediction', 'result_visualization', 'save_results', 'comment_only']."}, {'role': 'user', 'content': 'Identify the single most appropriate category to which the provided code belongs. Provide your response as a single category label without any additional information, commentary, or personal opinions.\\nPython Code:class ClassifierModel(object):\n    """\n    A wrapper class for classification models.\n    It can be used for training and prediction.\n    Can plot feature importance and training progress (if relevant for model).\n\n    """\n\n    def __init__(self, columns: list = None, model_wrapper=None):\n        """\n\n        :param original_columns:\n        :param model_wrapper:\n        """\n        self.columns = columns\n        self.model_wrapper = model_wrapper\n        self.result_dict = {}\n        self.train_one_fold = False\n        self.preprocesser = None\n\n    def fit(self, X: pd.DataFrame, y,\n            X_holdout: pd.DataFrame = None, y_holdout=None,\n            folds=None,\n            params: dict = None,\n            eval_metric=\'auc\',\n            cols_to_drop: list = None,\n            preprocesser=None,\n            transformers: dict = None,\n            adversarial: bool = False,\n            plot: bool = True):\n        """\n        Training the model.\n\n        :param X: training data\n        :param y: training target\n        :param X_holdout: holdout data\n        :param y_holdout: holdout target\n        :param folds: folds to split the data. If not defined, then model will be trained on the whole X\n        :param params: training parameters\n        :param eval_metric: metric for validataion\n        :param cols_to_drop: list of columns to drop (for example ID)\n        :param preprocesser: preprocesser class\n        :param transformers: transformer to use on folds\n        :param adversarial\n        :return:\n        """\n        self.cols_to_drop = cols_to_drop\n\n        if folds is None:\n            folds = KFold(n_splits=3, random_state=42)\n            self.train_one_fold = True\n\n        self.columns = X.columns if self.columns is None else self.columns\n        self.feature_importances = pd.DataFrame(columns=[\'feature\', \'importance\'])\n        self.trained_transformers = {k: [] for k in transformers}\n        self.transformers = transformers\n        self.models = []\n        self.folds_dict = {}\n        self.eval_metric = eval_metric\n        n_target = 1 if len(set(y.values)) == 2 else len(set(y.values))\n        self.oof = np.zeros((len(X), n_target))\n        self.n_target = n_target\n\n        X = X[self.columns]\n        if X_holdout is not None:\n            X_holdout = X_holdout[self.columns]\n\n        if preprocesser is not None:\n            self.preprocesser = preprocesser\n            self.preprocesser.fit(X, y)\n            X = self.preprocesser.transform(X, y)\n            self.columns = X.columns.tolist()\n            if X_holdout is not None:\n                X_holdout = self.preprocesser.transform(X_holdout)\n            # y = X[\'accuracy_group\']\n\n        for fold_n, (train_index, valid_index) in enumerate(folds.split(X, y)):\n            if X_holdout is not None:\n                X_hold = X_holdout.copy()\n            else:\n                X_hold = None\n            self.folds_dict[fold_n] = {}\n            if params[\'verbose\']:\n                print(f\'Fold {fold_n + 1} started at {time.ctime()}\')\n            self.folds_dict[fold_n] = {}\n\n            X_train, X_valid = X.iloc[train_index], X.iloc[valid_index]\n            y_train, y_valid = y.iloc[train_index], y.iloc[valid_index]\n            if self.train_one_fold:\n                X_train = X[self.original_columns]\n                y_train = y\n                X_valid = None\n                y_valid = None\n\n            datasets = {\'X_train\': X_train, \'X_valid\': X_valid, \'X_holdout\': X_hold, \'y_train\': y_train}\n            X_train, X_valid, X_hold = self.transform_(datasets, cols_to_drop)\n\n            self.folds_dict[fold_n][\'columns\'] = X_train.columns.tolist()\n\n            model = copy.deepcopy(self.model_wrapper)\n\n            if adversarial:\n                X_new1 = X_train.copy()\n                if X_valid is not None:\n                    X_new2 = X_valid.copy()\n                elif X_holdout is not None:\n                    X_new2 = X_holdout.copy()\n                X_new = pd.concat([X_new1, X_new2], axis=0)\n                y_new = np.hstack((np.zeros((X_new1.shape[0])), np.ones((X_new2.shape[0]))))\n                X_train, X_valid, y_train, y_valid = train_test_split(X_new, y_new)\n\n            model.fit(X_train, y_train, X_valid, y_valid, X_hold, y_holdout, params=params)\n\n            self.folds_dict[fold_n][\'scores\'] = model.best_score_\n            if self.oof.shape[0] != len(X):\n                self.oof = np.zeros((X.shape[0], self.oof.shape[1]))\n            if not adversarial:\n                self.oof[valid_index] = model.predict_proba(X_valid).reshape(-1, n_target)\n\n            fold_importance = pd.DataFrame(list(zip(X_train.columns, model.feature_importances_)),\n                                           columns=[\'feature\', \'importance\'])\n            self.feature_importances = self.feature_importances.append(fold_importance)\n            self.models.append(model)\n\n        self.feature_importances[\'importance\'] = self.feature_importances[\'importance\'].astype(float)\n\n        # if params[\'verbose\']:\n        self.calc_scores_()\n\n        if plot:\n#             print(classification_report(y, self.oof.argmax(1)))\n            print(classification_report(y, (self.oof > 0.5) * 1))\n            fig, ax = plt.subplots(figsize=(16, 12))\n            plt.subplot(2, 2, 1)\n            self.plot_feature_importance(top_n=25)\n            plt.subplot(2, 2, 2)\n            self.plot_metric()\n            plt.subplot(2, 2, 3)\n            g = sns.heatmap(confusion_matrix(y, (self.oof > 0.5) * 1), annot=True, cmap=plt.cm.Blues,fmt="d")\n            g.set(ylim=(-0.5, 4), xlim=(-0.5, 4), title=\'Confusion matrix\')\n\n            plt.subplot(2, 2, 4)\n            plt.hist(self.oof)\n            plt.xticks(range(self.n_target), range(self.n_target))\n            plt.title(\'Distribution of oof predictions\');\n\n    def transform_(self, datasets, cols_to_drop):\n        for name, transformer in self.transformers.items():\n            transformer.fit(datasets[\'X_train\'], datasets[\'y_train\'])\n            datasets[\'X_train\'] = transformer.transform(datasets[\'X_train\'])\n            if datasets[\'X_valid\'] is not None:\n                datasets[\'X_valid\'] = transformer.transform(datasets[\'X_valid\'])\n            if datasets[\'X_holdout\'] is not None:\n                datasets[\'X_holdout\'] = transformer.transform(datasets[\'X_holdout\'])\n            self.trained_transformers[name].append(transformer)\n        if cols_to_drop is not None:\n            cols_to_drop = [col for col in cols_to_drop if col in datasets[\'X_train\'].columns]\n            self.cols_to_drop = cols_to_drop\n            datasets[\'X_train\'] = datasets[\'X_train\'].drop(cols_to_drop, axis=1)\n            if datasets[\'X_valid\'] is not None:\n                datasets[\'X_valid\'] = datasets[\'X_valid\'].drop(cols_to_drop, axis=1)\n            if datasets[\'X_holdout\'] is not None:\n                datasets[\'X_holdout\'] = datasets[\'X_holdout\'].drop(cols_to_drop, axis=1)\n\n        return datasets[\'X_train\'], datasets[\'X_valid\'], datasets[\'X_holdout\']\n\n    def calc_scores_(self):\n        print()\n        datasets = [k for k, v in [v[\'scores\'] for k, v in self.folds_dict.items()][0].items() if len(v) > 0]\n        self.scores = {}\n        for d in datasets:\n            scores = [v[\'scores\'][d][self.eval_metric] for k, v in self.folds_dict.items()]\n            print(f"CV mean score on {d}: {np.mean(scores):.4f} +/- {np.std(scores):.4f} std.")\n            self.scores[d] = np.mean(scores)\n\n    def predict(self, X_test, averaging: str = \'usual\'):\n        """\n        Make prediction\n\n        :param X_test:\n        :param averaging: method of averaging\n        :return:\n        """\n        full_prediction = np.zeros((X_test.shape[0], self.oof.shape[1]))\n        if self.preprocesser is not None:\n            X_test = self.preprocesser.transform(X_test)\n        for i in range(len(self.models)):\n            X_t = X_test.copy()\n            for name, transformers in self.trained_transformers.items():\n                X_t = transformers[i].transform(X_t)\n            if self.cols_to_drop:\n                cols_to_drop = [col for col in self.cols_to_drop if col in X_t.columns]\n                X_t = X_t.drop(cols_to_drop, axis=1)\n            y_pred = self.models[i].predict_proba(X_t[self.folds_dict[i][\'columns\']]).reshape(-1, full_prediction.shape[1])\n\n            # if case transformation changes the number of the rows\n            if full_prediction.shape[0] != len(y_pred):\n                full_prediction = np.zeros((y_pred.shape[0], self.oof.shape[1]))\n\n            if averaging == \'usual\':\n                full_prediction += y_pred\n            elif averaging == \'rank\':\n                full_prediction += pd.Series(y_pred).rank().values\n\n        return full_prediction / len(self.models)\n\n    def plot_feature_importance(self, drop_null_importance: bool = True, top_n: int = 10):\n        """\n        Plot default feature importance.\n\n        :param drop_null_importance: drop columns with null feature importance\n        :param top_n: show top n columns\n        :return:\n        """\n\n        top_feats = self.get_top_features(drop_null_importance, top_n)\n        feature_importances = self.feature_importances.loc[self.feature_importances[\'feature\'].isin(top_feats)]\n        feature_importances[\'feature\'] = feature_importances[\'feature\'].astype(str)\n        top_feats = [str(i) for i in top_feats]\n        sns.barplot(data=feature_importances, x=\'importance\', y=\'feature\', orient=\'h\', order=top_feats)\n        plt.title(\'Feature importances\')\n\n    def get_top_features(self, drop_null_importance: bool = True, top_n: int = 10):\n        """\n        Get top features by importance.\n\n        :param drop_null_importance:\n        :param top_n:\n        :return:\n        """\n        grouped_feats = self.feature_importances.groupby([\'feature\'])[\'importance\'].mean()\n        if drop_null_importance:\n            grouped_feats = grouped_feats[grouped_feats != 0]\n        return list(grouped_feats.sort_values(ascending=False).index)[:top_n]\n\n    def plot_metric(self):\n        """\n        Plot training progress.\n        Inspired by `plot_metric` from https://lightgbm.readthedocs.io/en/latest/_modules/lightgbm/plotting.html\n\n        :return:\n        """\n        full_evals_results = pd.DataFrame()\n        for model in self.models:\n            evals_result = pd.DataFrame()\n            for k in model.model.evals_result_.keys():\n                evals_result[k] = model.model.evals_result_[k][self.eval_metric]\n            evals_result = evals_result.reset_index().rename(columns={\'index\': \'iteration\'})\n            full_evals_results = full_evals_results.append(evals_result)\n\n        full_evals_results = full_evals_results.melt(id_vars=[\'iteration\']).rename(columns={\'value\': self.eval_metric,\n                                                                                            \'variable\': \'dataset\'})\n        full_evals_results[self.eval_metric] = np.abs(full_evals_results[self.eval_metric])\n        sns.lineplot(data=full_evals_results, x=\'iteration\', y=self.eval_metric, hue=\'dataset\')\n        plt.title(\'Training progress\')\''}, {'role': 'assistant', 'content': 'helper_functions'}]