房价预测B

import pandas as pd     # 工具
import numpy as np
import joblib
import os

from sklearn.preprocessing import PowerTransformer, PolynomialFeatures, StandardScaler      # 预处理
from sklearn.ensemble import RandomForestRegressor      # 离群点和错误值筛选
from sklearn.ensemble import IsolationForest
from sklearn.feature_selection import variance_threshold    # 特征选择
from sklearn.decomposition import PCA
from sklearn.model_selection import train_test_split, cross_val_score, KFold    # 切分数据集
from sklearn.linear_model import Lasso, Ridge, LinearRegression     # 算法
from xgboost import XGBRegressor
from lightgbm import LGBMRegressor
from mlxtend import regressor
from sklearn.metrics import mean_absolute_error, r2_score, mean_squared_error   # 评分
from hyperopt import Trials, tpe, pyll, fmin, hp    # 动态调参
import functools
import time
import matplotlib.pyplot as plt


pd.set_option("display.max_columns", 500)
pd.set_option("display.width", 1000)
pd.set_option("display.max_rows", 500)
# pd.set_option("display.height", 1000)  # 测试时不能进行行高设置

TIME_DISPLAY_FLAG = True    # 是否显示函数运行时间

'''
注:
    房价预测A是后来写的,限于时间关系没有严格调参,没有进行主观特征合并(假设自己为房价小白)训练集和测试集的R2都为91.4%。
    房价预测B是自学以来第一次写机器学习项目,中间有些数据处理的逻辑不对,导致准确率较低,
    好在都是分块的,并不影响其他部分的正确性。
    房价预测B虽然准确率低,但是考虑了模块化,自动化,和自动调参,所以放上来。
    
创新点(由于准确率较低,并不能证明有效或无效,写出来仅供发散思维):
    1.构造data_of_stats对数据进行归纳,用来自动化条件判定,对数据的预处理包含了对表格的完善。
    2.部分避免了引入random_state的特殊性。如:Base_m.rf_choose()函数
    3.对孤立森林选择离群点的优化:见Base.iso_check()函数
    4.对PCA的部分推测:1)对非相关特征数据A进行PCA后得到B,对A、B同时去除相等数量的相关系数较小的特征,
                          相关系数越小,信息损失越小,得到的预处理数据会越好。(我的验证中是成立的)
                       2)对相关特征数据A进行PCA后得到B,对A、B同时去除相等数量的相关系数较小的特征,
                          结果是否会有规律,如果没有规律,有没有办法转为1)。(未测试)
'''

'''random_state 引入的特殊性。
    random_state 本身是随机的, 但是本身的随机会引入特殊因素。所以train_test_split才会有分层筛选(但只是对标签的分层)。
    类似于:九个中国人一个日本人,random_state 选到 日本人的话,如果固定用日本人来做训练集,会造成很大的误差。
    10个数据的数据集,如果按照7:3划分数据集,有C(7, 10)=120种排序。换个角度,就是有120个人,random_state代表选择其中一个人。
    如果固定random_state的话要获得高预测准确率,则必须对data进行更大幅度的数据预处理,且必定会引入特殊性。
    我的想法是在小项目中不固定 random_state, 将 random_state 和其他参数视作相同地位的参数,筛选出最普遍的人。
'''

# 数据划分示意
# [data: [train:[x_train, y_train], test: [x_test, y_test]]]
# [data: [train: [train_num, train_str], test: [test_num, test_str]]]
# [data: [train: [train_num, train_one_hot], test: [test_num, test_one_hot]]]


# 测量时间的装饰器
def time_metric(func):
    @functools.wraps(func)
    def wrapper(*args, **kw):
        __time_start = time.time()
        __f = func(*args, **kw)
        __time_close = time.time()
        __time_spent = __time_close - __time_start
        if TIME_DISPLAY_FLAG:
            print('%s cost time: %f' % (func.__name__, __time_spent))
        return __f
    return wrapper


# 用"_m"后缀建立所有Class, 方便以后更改。所有改良的函数也用"_m"后缀注明,方便以后更改改良方法。
class Base_m(object):
    @staticmethod
    def isDataframe(data=None):
        assert isinstance(data, pd.core.frame.DataFrame), "请输入pandas.Dataframe格式数据"

    @staticmethod
    def isndarray(data=None):
        assert isinstance(data, np.ndarray), "请输入np.ndarray格式数据"

    @staticmethod
    def isarrayorDataFrame(data=None):
        assert isinstance(data, pd.core.frame.DataFrame) or isinstance(data, np.ndarray), "请输入Dataframe或者ndarray格式数据"

    @staticmethod
    def change_train_shape_0(drop_list=None):
        global TRAIN_SHAPE_0    # 全局变量 train 个数
        for i in drop_list:
            del_num = TRAIN_SHAPE_0
            if i < del_num:
                TRAIN_SHAPE_0 -= 1

    @staticmethod
    @time_metric
    def modify(data, modify_list=None, number=2020):
        """
        针对年份相关的修改,将变化幅度过小的数据进行重新整理,在有意义的条件下尽量增加其变化幅度
        Parameters
            ----------
            data: pd.core.DataFrame
            modify_list: list, data中需要修改的列的列表
            number: int, 年份。
        比如 2010年,改为与2020年差10,会增大标准差。易于观察变化。
        """
        if modify_list is None:
            modify_list = ["YearBuilt", "YearRemodAdd", "GarageYrBlt", "YrSold"]
        for i in modify_list:
            data[i] = number - data[i]
        return data

    @time_metric
    def data_of_stats(self, data=None):
        """
        对数据进行统计
        想法: 将图像表格化,可以根据表格统计进行自动化,对房价的预测的数据预处理其实就是对 data_stats 的完善。
        Parameters
            ----------
            data: pd.core.DataFrame
        Returns
            -------
            data_stats: pd.core.DataFrame, 数据统计表。size为 len(data.columns) * 7,
                        "name" 为 data对应列名称,
                        "mean_std" 为data对应列平均值与标准差比值。
                        "dtypes" 为data对应列中的数据类型。
                        "value_counts" 为data对应列中的不同数据的数值个数。
                        "nan_counts" 为data对应列中的未识别数据个数。
                        "max_ratio" 为data对应列中的数据中占比最大的数据所占的百分比。
                        "corr" 为data对应列与目标列的相似度。
        """
        self.isarrayorDataFrame(data)
        if type(data) == np.ndarray:
            data = pd.DataFrame(data)
        all_col = data.columns
        dtypes_list = []
        value_counts_list = []
        nan_counts_list = []
        max_ratio_list = []
        mean_std_list = []
        for i in all_col:
            dtypes_list.append(data.loc[:, i].dtypes.name)
            value_counts_list.append(len(data.loc[:, i].value_counts()))
            nan_counts_list.append(data.loc[:, i].isnull().sum())
            max_ratio_list.append(data.loc[:, i].value_counts().max() / data.shape[0])
            if data.loc[:, i].dtype.name != 'object':
                mean_std_list.append(data.loc[:, i].mean() / data.loc[:, i].std())
            else:
                mean_std_list.append(0)
        data_stats = pd.DataFrame()
        data_stats["name"] = all_col
        data_stats["mean_std"] = mean_std_list
        data_stats["dtypes"] = dtypes_list
        data_stats["value_counts"] = value_counts_list
        data_stats["nan_counts"] = nan_counts_list
        data_stats["max_ratio"] = max_ratio_list
        data_stats["corr"] = np.nan
        data_stats.loc[data_stats.loc[:, 'dtypes'] != 'object', 'corr'] = data.corr().iloc[:, -1].values
        return data_stats

    @time_metric
    def iso_check(self, data, iso_num=10, iso_length=50, iso_estimators=500):
        """
        进行多次孤立森林再重新筛选出离群点。
        原因:孤立森林每次都会得到很多不一样的离群点。解决方法一:设定同一random_state, 这并没有根本性改变其偶然性。
            解决方法二:多次求取孤立森林再根据结果取舍,保证最终离群点稳定,可以增加范性。
        另外:非关键性特征和关键性特征的偏离效果应该不同,构造孤立森林时赋予不同特征权重(LASSO和随机森林获取),权重大的划分范围更细,权重小的划分范围更大。
        Parameters
            ----------
            data: pd.core.DataFrame
            iso_num: 进行孤立森林的次数,越大范性越大,时间越长。
            iso_length: 假定的离群点数量。
            iso_estimators:每次孤立森林的树数量, 越大范性越大,时间越长。
        Returns
            -------
            iso_stats: pd.core.DataFrame, 离群点统计表,可以根据此表进行离群点判定。
        """
        self.isDataframe(data)
        if data.shape[0] < iso_length:
            iso_length = data.shape[0]
        iso_frame = pd.DataFrame(index=range(iso_length), columns=range(iso_num))
        num = 0
        for i in range(iso_num):
            iso = IsolationForest(iso_estimators, n_jobs=-1)
            iso_a = iso.fit_predict(data.iloc[:, 0:-1], data.iloc[:, -1])
            frame_index = 0
            for index, value in enumerate(iso_a):
                if value != 1:
                    iso_frame.iloc[frame_index, num] = index
                    frame_index += 1
            num += 1
        iso_frame.dropna(axis=0, how="all", inplace=True)
        iso_stats = pd.DataFrame(iso_frame.values.reshape(-1, 1)).iloc[:, 0].value_counts() / iso_num
        return iso_stats

    @time_metric
    def rf_choose(self, data, rf_num):
        """
        采用随机森林回归对 data 筛选合适的column
        Parameters
            ----------
            data: pd.core.DataFrame
            rf_num: int, 需要保留的columns数量

        Returns
            -------
            data: pd.core.DataFrame
            imp: pd.core.DataFrame,权重表格, size为 rf_num * 3。 第一列 "str" 为 data 列的名字,
                 第二列 "int" 为 data 列 的下标, 第三列 "order"为 data 列的重要性排序。
        """
        self.isDataframe(data)
        imp = pd.DataFrame()
        imp["str"] = data.columns
        imp.drop([len(imp) - 1], axis=0, inplace=True)
        rfr = RandomForestRegressor(500, n_jobs=-1, random_state=42)
        rfr.fit(data.iloc[:, 0:-1], data.iloc[:, -1])
        imp["int"] = rfr.feature_importances_
        imp.sort_values("int", inplace=True, ascending=False)
        imp["order"] = imp.index
        # 删除imp较小的列
        imp.drop(imp["order"][rf_num:imp.shape[0]], axis=0, inplace=True)
        imp.sort_index(inplace=True)
        data = data.loc[:, imp["str"].values.tolist() + ["SalePrice"]]
        return data, imp

    @staticmethod
    def one_hot_of_stats(train_one_hot, target):
        """
        Parameters
            ----------
            train_one_hot: DataFrame, 训练集字符型one_hot编码后的数据
            target: DataFrame, 预测值列
        对data字符型列编码成的 one_hot 进行统计。
        可以通过人工统计重组 train_one_hot,也可以用 self.regroup() 自动重组 train_one_hot。
        """
        one_hot_stats = pd.DataFrame()
        name_list = []
        one_hot_mean = []
        one_hot_std = []
        one_hot_group = []
        zero_one_list = []
        zero_percnt_list = []
        for i in train_one_hot.columns:
            name_list.append(i)
            one_hot_mean.append(float(np.mean(target[train_one_hot.loc[:, i] == 1])))
            one_hot_std.append(float(np.std(target[train_one_hot.loc[:, i] == 1])))
            one_hot_group.append(i.split('_')[0])
            a = train_one_hot.loc[:, i].value_counts()
            zero_one_list.append(a.index[0])
            zero_percnt_list.append(a[0] / (train_one_hot.shape[0]))
        one_hot_stats['name'] = name_list
        one_hot_stats['zero_one'] = zero_one_list
        one_hot_stats['zero_percnt'] = zero_percnt_list
        one_hot_stats['group'] = one_hot_group
        one_hot_stats['mean_price'] = one_hot_mean
        one_hot_stats['std_price'] = one_hot_std
        one_hot_stats = one_hot_stats.sort_values(['group', 'mean_price'], ascending=[True, True])
        # 添加['determine']
        one_hot_stats['sml'] = 0
        one_hot_stats['big'] = 0
        sml_list = []
        big_list = []
        for _, j in one_hot_stats.groupby('group'):
            mean_col_ = j.columns.to_list().index('mean_price')
            for index in range(j.shape[0]):
                if index == 0:
                    if j.shape[0] == 1:
                        sml_list.append(0)
                        big_list.append(1)
                    else:
                        sml_list.append(0)
                        big_list.append((j.iloc[index + 1, mean_col_] - j.iloc[index, mean_col_]) /
                                        j.iloc[index, mean_col_])
                elif index == (j.shape[0] - 1):
                    big_list.append(1)
                    sml_list.append((j.iloc[index, mean_col_] - j.iloc[index - 1, mean_col_]) /
                                    j.iloc[index - 1, mean_col_])
                else:
                    big_list.append((j.iloc[index + 1, mean_col_] - j.iloc[index, mean_col_]) /
                                    j.iloc[index, mean_col_])
                    sml_list.append((j.iloc[index, mean_col_] - j.iloc[index - 1, mean_col_]) /
                                    j.iloc[index - 1, mean_col_])
        one_hot_stats.loc[:, 'sml'] = sml_list
        one_hot_stats.loc[:, 'big'] = big_list
        return one_hot_stats

    @staticmethod
    @time_metric
    def concat_col(one_hot, old_col_list, index_name=None):
        """
        建议看作私有函数, 对data中的字符型列编码成的 one_hot 进行操作。
        Parameters
            ----------
            one_hot: 数据集字符型one_hot编码后的数据
            old_col_list: 数据集未进行one_hot前的column的列表。
            index_name: 右半部分名称。
            例如:初始列名为 people, 变换后为 people_1, people_2.
        """
        # global change_list
        # change_list = []
        col_list = one_hot.iloc[:, old_col_list].columns
        tol = 0
        n = 0
        for i in col_list:
            index_1, index_2 = i.split('_')
            index_1_name = index_1
            if index_name is not None:
                index_2_name = str(index_name)
                break
            try:
                n += 1
                index_2 = int(index_2)
                tol += index_2
                index_2_name = str(int((tol / n)))
            except (ValueError, TypeError):
                index_2_name = str(np.random.randint(100))
                break
        new_col = index_1_name + '_' + index_2_name
        one_hot[new_col] = one_hot.iloc[:, old_col_list].apply(lambda x: x.sum(), axis=1)
        # change_list = change_list + list(col_list)
        return None

    def regroup(self, train_one_hot, target, df_size=100, max_percnt=0.8, scope_percnt=0.5):
        """
        将data中的字符型列编码成 one_hot, 对 one_hot 进行重新分组。
        原因: 如果直接删除的话可能会少很多信息.
        分类的原则: 如果离散的数值 zero_percnt 特征大于 max_percnt,则不进行合并。
                    如果离散的数值大小范围在 scope_percnt 范围内则进行合并,范围外则不合并。
                    分组后应自己删除残余的离群点(默认小于5%应删除)
        Parameters
            ----------
            train_one_hot: DataFrame, 训练集字符型one_hot编码后的数据
            target: DataFrame, 预测值列
            df_size: 预定义DataFrame大小。
            max_percnt:float, 看示例
            scope_percnt: float, 看示例
            实例:room 进行 one_hot 分为 room_0, room_1, room_2三个属性。
                  如果room_x中的占比大于max_percnt,该列就不再重组;
                  如果room_0和room_1代指数量0和1,若(1-0)/1 < scope_percnt则合并该两列,否则不合并。
        """
        one_hot_stats = self.one_hot_of_stats(train_one_hot, target)
        group_df = pd.DataFrame(index=range(df_size))
        for group, one_hot in one_hot_stats.groupby('group'):
            times = 0
            index = 0
            one_list = []
            for i in list(one_hot.index):
                if one_hot.loc[i, 'zero_percnt'] >= max_percnt:
                    if times == 0:
                        one_list.append(i)
                        times += 1
                    elif times == 1:
                        if one_hot.loc[i, 'sml'] >= scope_percnt:
                            one_list = []
                            times = 0
                        elif one_hot.loc[i, 'sml'] < scope_percnt:
                            one_list.append(i)
                            times += 1
                    elif times >= 2:
                        if one_hot.loc[i, 'sml'] >= scope_percnt:
                            self.concat_col(train_one_hot, one_list, index)
                            group_df[group + '_' + str(index)] = pd.Series(one_list)
                            one_list = []
                            times = 0
                            index += 1
                        elif one_hot.loc[i, 'sml'] < scope_percnt:
                            one_list.append(i)
                            times += 1
                elif one_hot.loc[i, 'zero_percnt'] <= max_percnt:
                    if times < 2:
                        one_list = []
                        times = 0
                    elif times >= 2:
                        self.concat_col(train_one_hot, one_list, index)
                        group_df[group + '_' + str(index)] = pd.Series(one_list)
                        one_list = []
                        times = 0
                        index += 1
        group_df.dropna(axis=0, how='all', inplace=True)
        group_df.dropna(axis=1, how='all', inplace=True)
        return group_df


# 原则:尽量先保持数据原有信息。然后增加数据信息,最后减少数据信息。
# 离群点和错误值筛选


# 预处理。先去除错误信息和无效信息。
class Preprocesing_m(Base_m):
    """
    对 data 进行修改。
    有两个原则:
    1.在保证准确率的情况下能自动则优先自动。
    2.在自动准确率不高的情况下人工观察,应做到只需向 Preprocessing_m 的函数 提供人工观察得到的参数,函数就能自动调节。
    3.函数中的参数如果以 observe_ 命名的,则代表它需要经过观察分析确定。求取方法会在注释中声明。
    4.带有_trimming字样的函数需进入函数内部进行调试,函数注释中会有调试方法,
    5.带有 _num(eg:_1, _2)的函数代表其默认顺序,请谨慎调整位置。
    """

    @time_metric
    def change_small_1(self, data, observe_modify_list=None):
        # # 第一步将变化太小数据的进行变换。(mean/std 越大说明变化越小)
        # data_stats = self.data_of_stats(data)
        # modify_list = data_stats.loc[data_stats['mean_std'] > 10, :].loc[:, "name"].values
        data = self.modify(data, observe_modify_list)
        return data

    @time_metric
    def float2object_2(self, data, observe_str_col_list=None):
        # # 第二步,将那些本应是“字符型”,却用“数字”表示的列进行正确转换,我会在 data_stats 中观察
        # data_stats = self.data_of_stats(data)
        # # 观察value值较小的data_description.txt文件,确定其类型
        # data_stats[data_stats.loc[:, "dtypes"] != "object"].sort_values("value_counts")
        # str_col_list = ["MSSubClass", "OverallCond", "YrSold", "MoSold", "OverallQual"]
        for col in observe_str_col_list:
            data.loc[:, col] = data.loc[:, col].astype(str)
        return data

    @time_metric
    def missing_check_3_trimming(self, data, observe_data_unmodify):
        # 第三步,将缺失值补全,缺失值有两个原因,一个是本身为空,另一个是因为 pandas 读取了“NA” 的字符串。
        # 对于本身为空的,应该使用0或均值或众数补全;对于本身是“NA”字符串的,应该观察每列具体意义。
        # 为了区分这两类,我是按以下操作做的:
        # 先用 pandas 读取了原始数据,用 .info() 观察存在了很多空值。
        # 然后在 sublime 中修改所有“NA”字符为“AN”,再次用 .info() 观察空值。
        # 如果不同,则文档本身存在了“NA”字符串, 如果相同,则内容本身为空。
        data_stats = self.data_of_stats(data)
        """ data_unmodify:
        train_unmodify = pd.read_csv("data_unmodify/train.csv")
        x_test_unmodify = pd.read_csv("data_unmodify/test.csv")
        data_unmodify = pd.concat([train_unmodify, pd.concat([x_test_unmodify, y_test.iloc[:, [1]]], axis=1)], axis=0)
        """
        data_stats["data_unmodify_nan_counts"] = self.data_of_stats(observe_data_unmodify).loc[:, "nan_counts"]
        # 观察发现都是“NA”字符串
        # 且本次预测中数字型中的“NA”字符串代表0,将用0代替, 字符型中的“NA”字符串代表无,我将会用“AN”代替他
        int_col = data_stats[data_stats.loc[:, "dtypes"] != "object"].loc[:, "name"].values
        data.loc[:, set(int_col) - {'SalePrice'}] = data.loc[:, set(int_col) - {'SalePrice'}].fillna(0)
        str_col = set(data.columns) - set(int_col)
        data.loc[:, str_col] = data.loc[:, str_col].fillna("AN")
        return data

    @time_metric
    def delete_radio_big_or_small_4(self, data, observe_drop_list=None):
        # # 第四步,再次求 data_stats, 观察其 max_ratio 项,过大则说明数据较单一,过小则很可能类似“ID”,都要进行观察筛选
        # data_stats = self.data_of_stats(data)
        # data_stats.iloc[0:-1, :].sort_values(by="max_ratio")
        # drop_list = ['Id', 'PoolQC', 'Street', 'PoolArea']       # ,  14, 69, 45, 22, 39
        data.drop(observe_drop_list, axis=1, inplace=True)
        return data

    @time_metric
    def delete_corr_small_5(self, data, observe_corr_drop_list=None):
        # # 第五步,除去corr太小的列。
        # train = data.iloc[0:train_shape_0, :]
        # data_stats = pre.data_of_stats(train)
        # data_stats.sort_values(by='corr')
        # # 训练集corr值小于0.1的列表(测试集SalePrice未知,所以应该用train观察,data最终改变)
        # corr_drop_list = data_stats.loc[abs(data_stats.loc[:, 'corr']) < 0.1, 'name'].values.tolist()
        data.drop(observe_corr_drop_list, axis=1, inplace=True)
        return data

    @time_metric
    def rebuilt_data_6_trimming(self, data):
        data_stats = self.data_of_stats(data)
        # 第六步,对剩余的数据进行处理,数值型减小偏度, 字符型 one_hot 编码,共同构建 data_new
        data_one_hot = pd.get_dummies(data.iloc[:, data_stats.loc[data_stats.loc[:, "dtypes"] == 'object', :].index],
                                      prefix=data_stats.loc[data_stats.loc[:, "dtypes"] == 'object', :].index)

        data_num = data.iloc[:, data_stats.loc[data_stats.loc[:, "dtypes"] != 'object', :].index.tolist()]
        # 调整偏度。
        # pt = PowerTransformer(standardize=True)
        # data_new_num = data_num.copy()
        # for i in data_num.columns:
        #     print(i)
        #     data_new_num.loc[:, i] = pt.fit_transform(data_num.loc[:, [i]])
        #     # RuntimeWarning: dividebyzeroencountered in loglike = -n_samples / 2 * np.log(x_trans.var())
        #     # LotArea     1stFlrSF        GrLivArea

        # for i in ["1stFlrSF", "LotArea", "GrLivArea"]:
        #     counts = data.loc[:, i].value_counts().sort_index()
        #     plt.plot(counts.index.to_list(), counts.values, "ro")
        #     plt.title(i)
        #     plt.show()

        # del data_new_num
        drop_col = set(list(data_num.loc[data_num.loc[:, "1stFlrSF"] > 3500, :].index)
                       + list(data_num.loc[data_num.loc[:, "LotArea"] > 100000, :].index)
                       + list(data_num.loc[data_num.loc[:, "GrLivArea"] > 4000, :].index))
        self.change_train_shape_0(drop_col)
        data_one_hot.drop(drop_col, axis=0, inplace=True)
        data_one_hot.reset_index(drop=True, inplace=True)

        data_one_hot.drop(data_one_hot.loc[:, data_one_hot.std() == 0].columns, axis=1, inplace=True)

        data_num = data_num.drop(drop_col, axis=0, inplace=False)
        data_num.reset_index(drop=True, inplace=True)
        x_num_data = data_num.iloc[:, 0:-1]
        y_num_train = data_num.iloc[0:TRAIN_SHAPE_0, [-1]]
        y_num_test = data_num.iloc[TRAIN_SHAPE_0::, [-1]]
        pt_x = PowerTransformer(standardize=True)
        x_num_data = pd.DataFrame(pt_x.fit_transform(x_num_data), columns=x_num_data.columns, index=x_num_data.index)
        pt_y = PowerTransformer(standardize=False)
        y_num_train = pd.DataFrame(pt_y.fit_transform(y_num_train), columns=y_num_train.columns, index=y_num_train.index)
        y_num_test = pd.DataFrame(pt_y.transform(y_num_test), columns=y_num_test.columns, index=y_num_test.index)
        data = pd.concat([data_one_hot,
                          pd.concat([x_num_data,
                                     pd.concat([y_num_train, y_num_test], axis=0)], axis=1)], axis=1)
        data_one_hot_columns = data_one_hot.columns
        x_num_data_columns = x_num_data.columns
        return data, data_one_hot_columns, x_num_data_columns

    @time_metric
    def findoutlier_7_trimming(self, data, x_num_data_columns=None):    # 六七不分开
        # # 第七步,用IsolationForest对数据进行离群值进行检测(只对数值型数据操作)
        # 其实还有一种新的设想。因为IsolationForest分两个步骤:(由定义可知,其只对'连续性数据'有意义)
        # 1.随机选出训练数据,随机选择特征,随机选择划分点。
        # 2.遍历测试数据,求出测试数据在每棵树的路径长度,再求出均值。
        # 缺点:建树过程是对特征随机选择,但是忽视了各个特征对训练集的权重不同,如果用Lasso或者随机森林特征选择赋予权重,
        #      则能更好的求解多维数据求离群点。(涉于时间关系,本次并没有重做)
        #
        # self.isDataframe(data), 'Please input DataFrame'
        # if x_num_data_columns is None:
        #     iso_stats = self.iso_check(data, iso_length=200)
        # else:
        #     iso_stats = self.iso_check(data.loc[:, x_num_data_columns], iso_length=200)
        # iso_drop = iso_stats[iso_stats >= 0.7].index  # 0.7为观测值,对同一数据多次测定,iso_drop不变说明异常点提取稳定。
        # 0.8, [2207, 631, 1826, 1585, 632, 529, 88, 700, 1828]
        iso_drop = [2207, 631, 1826, 1585, 632, 529, 88, 700, 1828]
        self.change_train_shape_0(iso_drop)
        data.drop(iso_drop, axis=0, inplace=True)
        data.reset_index(drop=True, inplace=True)
        return data


# 特征选择,增加信息,再减去信息。
class Feature_selection_m(Base_m):
    """
    对标准化后的 train 进行 pca 提取信息,至于很多人担心的PCA后每列具体的意义已经不确定,对我来说是毫无压力的,
    每个人收集的资料本身就是主观片面的,列的意义也只是针对这部分人有效。我认为,只需要知道这些数据投影到某一位空间,
    或者以另一个角度看会更有效,这就可以了。提取的信息的过程中做了进一步的优化。类似于膨胀和腐蚀,先膨胀再腐蚀去除杂乱点。
    """
    def __init__(self, data, corr_default=0.1):
        """
            corr_default: int, 如果为空,则利用全部数据; 如果不为空,则选取数据中corr大于corr_default的列。
        """
        self.data = data
        self.train = data.iloc[0:TRAIN_SHAPE_0, :]      # 提取 train

        self.data = self.data.loc[:, self.train.std() != 0]
        self.train = self.train.loc[:, self.train.std() != 0]   # 去除train当中std为0的列。one_hot时用的是data, train可能数值都相同。

        if corr_default is None:
            pass
        else:
            self.data = self.data.loc[:, self.train.corr().iloc[:, -1] > corr_default]
            self.train = self.train.loc[:, self.train.corr().iloc[:, -1] > corr_default]
        self.isDataframe(self.train)
        self.pca = PCA()
        self.std = StandardScaler()

        self.base_list = list(abs(self.train.corr().iloc[:, -1]))[0:-1]
        self.sort_list = sorted(self.base_list, reverse=True)
        self.index_list = []
        for i in self.sort_list:
            self.index_list.append(self.base_list.index(i))

    # 筛选corr较大的列
    def one(self):
        # 第一种方法,构造一个方形Dataframe,行列相同,都是index_list,表格内每个数字表示该两列数据pca后corr变化趋势。
        pass

    # 筛选corr较大的列
    @time_metric
    def two(self, flag_num_corr_change=True, flag_times=2, corr_default=0.3):  # flag_num_corr_change和 flag_times双重循环控制标识符FLAG,
        """
        :param flag_num_corr_change:
        :param flag_times:
        :param corr_default: 如果将要新增列的corr()小于corr_default,则停止增加该列。
        :return:
        """
        while flag_num_corr_change and flag_times:             # 一个自动调节结束次数,一个根据默认次数结束循环。
            flag_num_corr_change = False
            # 第二种方法,对index_list按顺序两两pca,如果corr值比原来大,则用pca产生的新列替换掉原来的列。
            cal_times = 0
            for index_i, i in enumerate(self.index_list):
                # print("outer", index_i, i)
                for index_j, j in enumerate(self.index_list):
                    # print("inter", index_j, j)
                    if index_i >= index_j:
                        continue
                    # print(index_i, i)
                    # print(index_j, j)
                    try:
                        __pca_train = pd.DataFrame(self.pca.fit_transform(self.train.iloc[:, [i, j]]))
                        __pca_train = pd.DataFrame(self.std.fit_transform(__pca_train))

                        __pca_data = pd.DataFrame(self.pca.fit_transform(self.data.iloc[:, [i, j]]))
                        __pca_data = pd.DataFrame(self.std.fit_transform(__pca_data))

                        a_last = (abs(pd.concat([__pca_train, self.train.iloc[:, [-1]]], axis=1).corr().iloc[:, -1])
                                  - self.sort_list[index_i]).to_list()  # 增长或者减少。
                        if max(a_last[0], a_last[1], 0) == 0:
                            pass
                        if max(a_last[0], a_last[1], 0) == a_last[0]:           # a_last[0]包含了原先绝大部分信息。
                            flag_num_corr_change = True
                            # self.train.iloc[:, i] = __pca_train.iloc[:, 0]     # 所以直接用a_last[0] 替换 掉原有列。
                            self.data.iloc[:, i] = __pca_data.iloc[:, 0]
                        if max(a_last[0], a_last[1], 0) == a_last[1] and a_last[1] > corr_default:  # a_last[1]仅包含了原先的少量信息。
                            flag_num_corr_change = True            # 所以在原有列基础上 追加 a_last[1]
                            # self.train["corr" + str(self.train.shape[1])] = __pca_train.iloc[:, 1]
                            # self.train = self.train.iloc[:, list(range(self.train.shape[1]-2))+[-1, -2]]

                            self.data["corr" + str(self.data.shape[1])] = __pca_data.iloc[:, 1]
                            self.data = self.data.iloc[:, list(range(self.data.shape[1] - 2)) + [-1, -2]]
                        cal_times += 1
                        if cal_times % 1000 == 0:
                            print(self.data.shape)
                            print('round {} times'.format(cal_times))
                    except IndexError:
                        print(i, j, index_i, index_j)
            flag_times -= 1
            # self.two(flag_num_corr_change, flag_times)


# 切分数据集
class Train_test_split_m(Base_m):
    def split(self, data=None, random_state=50):    # 调参时可随机选取random_state,获取更大范性
        self.isarrayorDataFrame(data)
        train_x, test_x, train_y, test_y = train_test_split(data.iloc[:, 0:-1], data.iloc[:, -1],
                                                            test_size=0.2, random_state=random_state, shuffle=False)
        return train_x, test_x, train_y, test_y


# 评分
class Score_m(Base_m):
    def __init__(self, train_x, train_y, test_x, test_y):
        self.train_x = train_x
        self.train_y = train_y
        self.test_x = test_x
        self.test_y = test_y
        self.best_score = 0

        self.stack_r2_list = []
        self.stack_square_list = []
        self.stack_abs_list = []
        self.stack_score = 0
        self.stack_file_index = os.listdir("dump/stack").__len__()

        self.ls_r2_list = []
        self.ls_square_list = []
        self.ls_abs_list = []
        self.ls_score = 0
        self.ls_file_index = os.listdir("dump/ls").__len__()

        self.rg_r2_list = []
        self.rg_square_list = []
        self.rg_abs_list = []
        self.rg_score = 0
        self.rg_file_index = os.listdir("dump/rg").__len__()

        self.lgbm_r2_list = []
        self.lgbm_square_list = []
        self.lgbm_abs_list = []
        self.lgbm_score = 0
        self.lgbm_file_index = os.listdir("dump/lgbm").__len__()

        self.xgb_r2_list = []
        self.xgb_square_list = []
        self.xgb_abs_list = []
        self.xgb_score = 0
        self.xgb_file_index = os.listdir("dump/xgb").__len__()

    def my_ls(self, params):
        ls = Lasso(max_iter=100000, **params["ls_1"])
        ls.fit(self.train_x, self.train_y)
        self.ls_score = np.sqrt(-cross_val_score(ls, self.train_x.values, self.train_y.values,
                                                scoring='neg_mean_squared_error', cv=5, ).mean())
        if self.ls_score > self.best_score:
            self.best_score = self.ls_score
            joblib.dump(ls, os.path.join('dump/ls', "model_" + str(self.ls_file_index) + ".pkl"))  # 保存模型

            self.ls_r2_list.append(r2_score(self.test_y, ls.predict(self.test_x)))
            self.ls_square_list.append(mean_squared_error(self.test_y, ls.predict(self.test_x)))
            self.ls_abs_list.append(mean_absolute_error(self.test_y, ls.predict(self.test_x)))
        return self.ls_score

    def my_rg(self, params):
        rg = Ridge(max_iter=100000, **params["rg_1"])
        rg.fit(self.train_x, self.train_y)
        self.rg_score = np.sqrt(-cross_val_score(rg, self.train_x.values, self.train_y.values,
                                        scoring='neg_mean_squared_error', cv=5, ).mean())
        if self.rg_score > self.best_score:
            self.best_score = self.rg_score
            joblib.dump(rg, os.path.join('dump/rg', "model_" + str(self.rg_file_index) + ".pkl"))  # 保存模型

            self.rg_r2_list.append(r2_score(self.test_y, rg.predict(self.test_x)))
            self.rg_square_list.append(mean_squared_error(self.test_y, rg.predict(self.test_x)))
            self.rg_abs_list.append(mean_absolute_error(self.test_y, rg.predict(self.test_x)))
        return self.rg_score

    def my_lgbm(self, params):
        lgbm = LGBMRegressor(**params["lgbm_1"])
        lgbm.fit(self.train_x, self.train_y)
        self.lgbm_score = np.sqrt(-cross_val_score(lgbm, self.train_x.values, self.train_y.values,
                                          scoring='neg_mean_squared_error', cv=5, ).mean())
        if self.lgbm_score > self.best_score:
            self.best_score = self.lgbm_score
            joblib.dump(lgbm, os.path.join('dump/lgbm', "model_" + str(self.lgbm_file_index) + ".pkl"))  # 保存模型

            self.lgbm_r2_list.append(r2_score(self.test_y, lgbm.predict(self.test_x)))
            self.lgbm_square_list.append(mean_squared_error(self.test_y, lgbm.predict(self.test_x)))
            self.lgbm_abs_list.append(mean_absolute_error(self.test_y, lgbm.predict(self.test_x)))
        return self.lgbm_score

    def my_xgb(self, params):
        xgb = XGBRegressor(**params["xgb_1"])
        xgb.fit(self.train_x, self.train_y)
        self.xgb_score = np.sqrt(-cross_val_score(xgb, self.train_x.values, self.train_y.values,
                                         scoring='neg_mean_squared_error', cv=5, ).mean())
        if self.xgb_score > self.best_score:
            self.best_score = self.xgb_score
            joblib.dump(xgb, os.path.join('dump/xgb', "model_" + str(self.xgb_file_index) + ".pkl"))  # 保存模型

            self.xgb_r2_list.append(r2_score(self.test_y, xgb.predict(self.test_x)))
            self.xgb_square_list.append(mean_squared_error(self.test_y, xgb.predict(self.test_x)))
            self.xgb_abs_list.append(mean_absolute_error(self.test_y, xgb.predict(self.test_x)))
        return self.xgb_score

    def my_stack(self, params):
        lr = LinearRegression()
        ls_1 = Lasso(max_iter=100000, **params["ls_1"])
        rg_1 = Ridge(**params["rg_1"])
        lgbm_1 = LGBMRegressor(**params["lgbm_1"])
        xgb_1 = XGBRegressor(**params["xgb_1"])
        stack = regressor.StackingRegressor(regressors=[ls_1, rg_1, xgb_1, lgbm_1], meta_regressor=lr, verbose=0)
        stack.fit(self.train_x, self.train_y)
        self.stack_score = np.sqrt(-cross_val_score(stack, self.train_x.values, self.train_y.values,
                                           scoring='neg_mean_squared_error', cv=5, ).mean())
        if self.stack_score > self.best_score:
            self.best_score = self.stack_score
            joblib.dump(stack, os.path.join('dump/stack', "model_" + str(self.stack_file_index) + ".pkl"))  # 保存模型

            self.stack_r2_list.append(r2_score(self.test_y, stack.predict(self.test_x)))
            self.stack_square_list.append(mean_squared_error(self.test_y, stack.predict(self.test_x)))
            self.stack_abs_list.append(mean_absolute_error(self.test_y, stack.predict(self.test_x)))
        return self.stack_score


# 调参
class Opt(object):
    def __init__(self, train_x, train_y, test_x, test_y):
        self.space_ls = {
            "ls_1": {
                'alpha': hp.uniform('ls_1_alpha', 0.001, 100)
            }
        }
        self.space_rg = {
            "rg_1": {
                'alpha': hp.uniform('rg_1_alpha', 0.001, 100),
            }
        }
        self.space_lgbm = {
            "lgbm_1": {
                "num_leaves": hp.choice('lgbm_1_nl', [i for i in range(12, 64)]),
                "learning_rate": hp.choice('lgbm_1_lr', [0.01, 0.03, 0.05, 0.07, 0.1, 0.13, 0.15, 0.17, 0.2]),
                "n_estimators": hp.choice('lgbm_1_estima', [100, 200, 300, 400, 500, 600, 700, 800, 900, 1000]),
                "subsample": hp.choice('lgbm_1_subsample', [0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6]),
            }
        }
        self.space_xgb = {
            "xgb_1": {
                "learning_rate": hp.choice("xgb_1_lr", [i / 10 for i in range(1, 5)]),
                "max_depth": hp.choice("xgb_1_depth", range(1, 7)),
                "n_estimators": hp.choice("xgb_1_estimators", [100, 300, 500, 700, 1000]),
                "subsample": hp.choice("xgb_1_subsample", [i / 10 for i in range(5, 10)]),
            }
        }
        self.space_stack = {
            'ls_1': {
                'alpha': hp.uniform('ls_1_alpha', 0.001, 100),
            },
            'rg_1': {
                'alpha': hp.uniform('rg_1_alpha', 0.001, 100),
            },
            'lgbm_1': {
                "num_leaves": hp.choice('lgbm_1_nl', [i for i in range(12, 64)]),
                "learning_rate": hp.choice('lgbm_1_lr', [0.01, 0.03, 0.05, 0.07, 0.1, 0.13, 0.15, 0.17, 0.2]),
                "n_estimators": hp.choice('lgbm_1_estima', [100, 200, 300, 400, 500, 600, 700, 800, 900, 1000]),
                "subsample": hp.choice('lgbm_1_subsample', [0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6]),
            },
            'xgb_1': {
                "learning_rate": hp.choice("xgb_1_lr", [i / 10 for i in range(1, 5)]),
                "max_depth": hp.choice("xgb_1_depth", range(1, 7)),
                "reg_lambda": hp.choice("xgb_1_lambda", [0, 1, 10, 100]),
                "reg_alpha": hp.choice("xgb_1_alpha", [0, 1, 10, 100]),
                "min_child_weight": hp.choice("xgb_1_weight", [i / 10 for i in range(5, 50, 1)]),
                "n_estimators": hp.choice("xgb_1_estimators", [100, 300, 500, 700, 1000]),
                "max_delta_step": hp.choice("xgb_1_step", [0, ]),
                "gamma": hp.choice("xgb_1_gamma", [0, 1, 2, 3, 10, 100]),
                "subsample": hp.choice("xgb_1_subsample", [i / 10 for i in range(5, 10)]),
                "colsample_bylevel": hp.choice("xgb_1_bylever", [i / 10 for i in range(5, 10)]),
                "colsample_bynode": hp.choice("xgb_1_bynode", [i / 10 for i in range(5, 10)]),
                "colsample_bytree": hp.choice("xgb_1_bytree", [i / 10 for i in range(5, 10)]),
            },
        }
        self.max_evals = None
        self.space_choose = None
        self.score_m = Score_m(train_x, train_y, test_x, test_y)

    # def cal_f(self, score_m, params):
    def cal_f(self, params):
        if self.space_choose == "ls":
            return self.score_m.my_ls(params)
        elif self.space_choose == "rg":
            return self.score_m.my_rg(params)
        elif self.space_choose == "xgb":
            return self.score_m.my_xgb(params)
        elif self.space_choose == "lgbm":
            return self.score_m.my_lgbm(params)
        elif self.space_choose == "stack":
            return self.score_m.my_stack(params)
        else:
            return 1

    def cal_best(self):
        for space_choose in [self.space_ls, self.space_lgbm, self.space_rg, self.space_stack, self.space_xgb]:
            trials = Trials()
            if space_choose == self.space_lgbm:
                self.max_evals = 100
                self.space_choose = "lgbm"
            elif space_choose == self.space_xgb:
                self.max_evals = 100
                self.space_choose = "xgb"
            elif space_choose == self.space_stack:
                self.max_evals = 100
                self.space_choose = "stack"
            elif space_choose == self.space_rg:
                self.max_evals = 2500
                self.space_choose = "rg"
            else:
                self.max_evals = 2500
                self.space_choose = "ls"
            best = fmin(fn=self.cal_f, space=space_choose, algo=tpe.suggest, max_evals=self.max_evals, trials=trials)


if __name__ == "__main__":
    # data
    train = pd.read_csv("data/train.csv")
    x_test = pd.read_csv("data/test.csv")
    y_test = pd.read_csv('data/sample_submission.csv')
    # 合并了 y_test,对 data 操作的话,注意避免 运用 y_test 数据。
    data = pd.concat([train, pd.concat([x_test, y_test.iloc[:, [1]]], axis=1)], axis=0)
    data.index = range(data.shape[0])
    TRAIN_SHAPE_0 = train.shape[0]
    # # preprocessing
    pre = Preprocesing_m()

    # data_stats = pre.data_of_stats(data)  #
    # data_stats = pre.data_of_stats(data)
    # observe_modify_list = data_stats.loc[data_stats['mean_std'] > 10, :].loc[:, "name"].values
    observe_modify_list = ['YearBuilt', 'YearRemodAdd', 'GarageYrBlt', 'YrSold']
    data = pre.change_small_1(data, observe_modify_list)

    # data_stats = pre.data_of_stats(data)  #
    # data_stats[data_stats.loc[:, "dtypes"] != "object"].sort_values("value_counts")   #
    observe_str_col_list = ["MSSubClass", "OverallCond", "YrSold", "MoSold", "OverallQual"]
    data = pre.float2object_2(data, observe_str_col_list)

    train_unmodify = pd.read_csv("data_unmodify/train.csv")
    x_test_unmodify = pd.read_csv("data_unmodify/test.csv")
    data_unmodify = pd.concat([train_unmodify, pd.concat([x_test_unmodify, y_test.iloc[:, [1]]], axis=1)], axis=0)
    data = pre.missing_check_3_trimming(data, observe_data_unmodify=data_unmodify)

    # data_stats = pre.data_of_stats(data)
    # data_stats.iloc[0:-1, :].sort_values(by="max_ratio")
    observe_drop_list = ['Id', 'PoolQC', 'Street', 'PoolArea']
    data = pre.delete_radio_big_or_small_4(data, observe_drop_list)

    # train = data.iloc[0: train_shape_0, :]
    # data_stats = pre.data_of_stats(train)
    # data_stats.sort_values(by='corr')
    # # 训练集corr值小于0.1的列表(测试集SalePrice未知,所以应该用train观察,data最终改变)
    # corr_drop_list = data_stats.loc[abs(data_stats.loc[:, 'corr']) < 0.1, 'name'].values.tolist()
    observe_corr_drop_list = ['BsmtFinSF2', 'LowQualFinSF', 'BsmtHalfBath', '3SsnPorch', 'MiscVal']
    data = pre.delete_corr_small_5(data, observe_corr_drop_list)

    data, data_one_hot_columns, x_num_data_columns = pre.rebuilt_data_6_trimming(data)

    # train_one_hot = data.iloc[0: train_shape_0, :].loc[:, data_one_hot_columns]
    # train_one_hot.drop(train_one_hot.loc[:, train_one_hot.std() == 0].columns, axis=1, inplace=True)
    # pre.regroup(train_one_hot, target=data.iloc[0: train_shape_0, [-1]])

    data = pre.findoutlier_7_trimming(data, x_num_data_columns)

    fts = Feature_selection_m(data)
    fts.two()
    data = fts.data

    # 用随机森林获取权重较大的列 rand_col, 用 corr() 获取相关性高的列 corr_col
    # set(rand_col) - set(corr_col) 可以获得非相关性特征small_corr_col。
    # 然后将 corr_col 进行 pca 整合为 corr 更高的特征 new_corr_col。
    # 最后set(new_corr_col) + set(corr_col) 就可以获取得到最后的特征。

    # pre.rf_choose(data=data, rf_num=20)
    data.to_csv('data/mydata.csv', index=0)
    data = pd.read_csv('data/mydata.csv')
    train_x = data.iloc[0: TRAIN_SHAPE_0, 0:-1]
    train_y = data.iloc[0: TRAIN_SHAPE_0, -1]
    test_x = data.iloc[TRAIN_SHAPE_0::, 0:-1]
    test_y = data.iloc[TRAIN_SHAPE_0::, -1]
    # test_x.reset_index(drop=True, inplace=True)
    # test_y.reset_index(drop=True, inplace=True)

    op = Opt(train_x, train_y, test_x, test_y)
    op.cal_best()

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值